fix: report model run fallback metadata · openclaw/openclaw@ea1a027
steipete
·
2026-05-02
·
via Recent Commits to openclaw:main
| Original file line number | Diff line number | Diff line change |
|---|
@@ -49,6 +49,7 @@ Docs: https://docs.openclaw.ai
|
49 | 49 | - Web search: keep public provider requests on the strict SSRF guard and reserve private-network access for explicit self-hosted SearXNG/Firecrawl endpoints. Fixes #74357 and supersedes #74360. Thanks @fede-kamel. |
50 | 50 | - Firecrawl: reject private, loopback, metadata, and non-HTTP(S) `firecrawl_scrape` target URLs before forwarding them to Firecrawl. Supersedes #48133. Thanks @kn1ghtc. |
51 | 51 | - Web search/Firecrawl: allow self-hosted private/internal Firecrawl `baseUrl` endpoints, including HTTP for private targets, while keeping hosted Firecrawl on the strict official endpoint. Fixes #63877 and supersedes #59666, #63941, and #74013. Thanks @jhthompson12, @jzakirov, @Mlightsnow, and @shad0wca7. |
| 52 | +- CLI/models: report gateway model fallback attempts in `infer model run --json` and avoid double-prefixing provider-qualified defaults such as `openrouter/auto` in `models status`. Partially fixes #69527. Thanks @alexifra. |
52 | 53 | - Providers/OpenRouter: strip trailing assistant prefill turns from verified OpenRouter Anthropic model requests when reasoning is enabled, so Claude 4.6 routes no longer fail with Anthropic's prefill rejection through the OpenAI-compatible adapter. Fixes #75395. Thanks @sbmilburn. |
53 | 54 | - Feishu: preserve Feishu/Lark HTTP error bodies for message sends, media sends, and chat member lookups, so HTTP 400 failures include vendor code, message, log id, and troubleshooter details. Fixes #73860. Thanks @desksk. |
54 | 55 | - Agents/transcripts: avoid reopening large Pi transcript files through the synchronous session manager for maintenance rewrites, persisted tool-result truncation, manual compaction boundary hardening, and queued compaction rotation. Thanks @mariozechner. |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -864,6 +864,7 @@ describe("agentCommand – LiveSessionModelSwitchError retry", () => {
|
864 | 864 | { |
865 | 865 | provider: params.provider, |
866 | 866 | model: params.model, |
| 867 | +error: "empty result", |
867 | 868 | reason: "format", |
868 | 869 | code: "empty_result", |
869 | 870 | }, |
@@ -886,6 +887,21 @@ describe("agentCommand – LiveSessionModelSwitchError retry", () => {
|
886 | 887 | modelOverride: "gpt-5.4", |
887 | 888 | isFallbackRetry: true, |
888 | 889 | }); |
| 890 | +expect(state.deliverAgentCommandResultMock.mock.calls[0]?.[0]).toMatchObject({ |
| 891 | +result: { |
| 892 | +meta: { |
| 893 | +agentMeta: { |
| 894 | +fallbackAttempts: [ |
| 895 | +expect.objectContaining({ |
| 896 | +provider: "anthropic", |
| 897 | +model: "claude", |
| 898 | +reason: "format", |
| 899 | +}), |
| 900 | +], |
| 901 | +}, |
| 902 | +}, |
| 903 | +}, |
| 904 | +}); |
889 | 905 | }); |
890 | 906 | |
891 | 907 | it("updates hasSessionModelOverride for fallback resolution after switch", async () => { |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -1028,6 +1028,18 @@ async function agentCommandInternal(
|
1028 | 1028 | result = fallbackResult.result; |
1029 | 1029 | fallbackProvider = fallbackResult.provider; |
1030 | 1030 | fallbackModel = fallbackResult.model; |
| 1031 | +if (fallbackResult.attempts.length > 0 && result.meta.agentMeta) { |
| 1032 | +result = { |
| 1033 | + ...result, |
| 1034 | +meta: { |
| 1035 | + ...result.meta, |
| 1036 | +agentMeta: { |
| 1037 | + ...result.meta.agentMeta, |
| 1038 | +fallbackAttempts: fallbackResult.attempts, |
| 1039 | +}, |
| 1040 | +}, |
| 1041 | +}; |
| 1042 | +} |
1031 | 1043 | if (!lifecycleEnded) { |
1032 | 1044 | const stopReason = result.meta.stopReason; |
1033 | 1045 | if (stopReason && stopReason !== "end_turn") { |
|
| Original file line number | Diff line number | Diff line change |
|---|
|
1 | 1 | import type { HeartbeatToolResponse } from "../../auto-reply/heartbeat-tool-response.js"; |
2 | 2 | import type { CliSessionBinding, SessionSystemPromptReport } from "../../config/sessions/types.js"; |
3 | 3 | import type { DiagnosticTraceContext } from "../../infra/diagnostic-trace-context.js"; |
| 4 | +import type { FallbackAttempt } from "../model-fallback.types.js"; |
4 | 5 | import type { MessagingToolSend } from "../pi-embedded-messaging.types.js"; |
5 | 6 | |
6 | 7 | export type EmbeddedPiAgentMeta = { |
@@ -10,6 +11,7 @@ export type EmbeddedPiAgentMeta = {
|
10 | 11 | model: string; |
11 | 12 | contextTokens?: number; |
12 | 13 | agentHarnessId?: string; |
| 14 | +fallbackAttempts?: FallbackAttempt[]; |
13 | 15 | cliSessionBinding?: CliSessionBinding; |
14 | 16 | compactionCount?: number; |
15 | 17 | /** |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -585,6 +585,47 @@ describe("capability cli", () => {
|
585 | 585 | ); |
586 | 586 | }); |
587 | 587 | |
| 588 | +it("surfaces gateway model fallback attempts in model probe JSON", async () => { |
| 589 | +mocks.callGateway.mockResolvedValueOnce({ |
| 590 | +result: { |
| 591 | +payloads: [{ text: "gateway fallback reply" }], |
| 592 | +meta: { |
| 593 | +agentMeta: { |
| 594 | +provider: "openai", |
| 595 | +model: "gpt-4.1-mini", |
| 596 | +fallbackAttempts: [ |
| 597 | +{ |
| 598 | +provider: "openrouter", |
| 599 | +model: "openrouter/auto", |
| 600 | +error: "model unavailable", |
| 601 | +reason: "model_not_found", |
| 602 | +}, |
| 603 | +], |
| 604 | +}, |
| 605 | +}, |
| 606 | +}, |
| 607 | +} as never); |
| 608 | + |
| 609 | +await runRegisteredCli({ |
| 610 | +register: registerCapabilityCli as (program: Command) => void, |
| 611 | +argv: ["capability", "model", "run", "--prompt", "hello", "--gateway", "--json"], |
| 612 | +}); |
| 613 | + |
| 614 | +expect(mocks.runtime.writeJson).toHaveBeenCalledWith( |
| 615 | +expect.objectContaining({ |
| 616 | +provider: "openai", |
| 617 | +model: "gpt-4.1-mini", |
| 618 | +attempts: [ |
| 619 | +expect.objectContaining({ |
| 620 | +provider: "openrouter", |
| 621 | +model: "openrouter/auto", |
| 622 | +reason: "model_not_found", |
| 623 | +}), |
| 624 | +], |
| 625 | +}), |
| 626 | +); |
| 627 | +}); |
| 628 | + |
588 | 629 | it("requests admin scope for gateway model probes with provider/model overrides", async () => { |
589 | 630 | await runRegisteredCli({ |
590 | 631 | register: registerCapabilityCli as (program: Command) => void, |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -711,7 +711,13 @@ async function runModelRun(params: {
|
711 | 711 | const response: { |
712 | 712 | result?: { |
713 | 713 | payloads?: Array<{ text?: string; mediaUrl?: string | null; mediaUrls?: string[] }>; |
714 | | -meta?: { agentMeta?: { provider?: string; model?: string } }; |
| 714 | +meta?: { |
| 715 | +agentMeta?: { |
| 716 | +provider?: string; |
| 717 | +model?: string; |
| 718 | +fallbackAttempts?: Array<Record<string, unknown>>; |
| 719 | +}; |
| 720 | +}; |
715 | 721 | }; |
716 | 722 | } = await callGateway({ |
717 | 723 | method: "agent", |
@@ -746,7 +752,7 @@ async function runModelRun(params: {
|
746 | 752 | transport: "gateway" as const, |
747 | 753 | provider: response?.result?.meta?.agentMeta?.provider, |
748 | 754 | model: response?.result?.meta?.agentMeta?.model, |
749 | | -attempts: [], |
| 755 | +attempts: response?.result?.meta?.agentMeta?.fallbackAttempts ?? [], |
750 | 756 | outputs: (response?.result?.payloads ?? []).map((payload) => ({ |
751 | 757 | text: payload.text, |
752 | 758 | mediaUrl: payload.mediaUrl, |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -25,6 +25,7 @@ import { resolveEnvApiKey } from "../../agents/model-auth.js";
|
25 | 25 | import { |
26 | 26 | buildModelAliasIndex, |
27 | 27 | isCliProvider, |
| 28 | +modelKey, |
28 | 29 | normalizeProviderId, |
29 | 30 | parseModelRef, |
30 | 31 | resolveConfiguredModelRef, |
@@ -202,7 +203,7 @@ export async function modelsStatusCommand(
|
202 | 203 | |
203 | 204 | const rawDefaultsModel = resolveAgentModelPrimaryValue(cfg.agents?.defaults?.model) ?? ""; |
204 | 205 | const rawModel = agentModelPrimary ?? rawDefaultsModel; |
205 | | -const resolvedLabel = `${resolved.provider}/${resolved.model}`; |
| 206 | +const resolvedLabel = modelKey(resolved.provider, resolved.model); |
206 | 207 | const defaultLabel = rawModel || resolvedLabel; |
207 | 208 | const defaultsFallbacks = resolveAgentModelFallbackValues(cfg.agents?.defaults?.model); |
208 | 209 | const fallbacks = agentFallbacksOverride ?? defaultsFallbacks; |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -396,6 +396,33 @@ describe("modelsStatusCommand auth overview", () => {
|
396 | 396 | ); |
397 | 397 | }); |
398 | 398 | |
| 399 | +it("does not double-prefix provider-qualified resolved default models", async () => { |
| 400 | +const localRuntime = createRuntime(); |
| 401 | +const originalLoadConfig = mocks.loadConfig.getMockImplementation(); |
| 402 | +mocks.loadConfig.mockReturnValue({ |
| 403 | +agents: { |
| 404 | +defaults: { |
| 405 | +model: { primary: "openrouter/auto", fallbacks: [] }, |
| 406 | +models: { "openrouter/auto": {} }, |
| 407 | +}, |
| 408 | +}, |
| 409 | +models: { providers: {} }, |
| 410 | +env: { shellEnv: { enabled: true } }, |
| 411 | +}); |
| 412 | + |
| 413 | +try { |
| 414 | +await modelsStatusCommand({ json: true }, localRuntime as never); |
| 415 | +const payload = JSON.parse(String((localRuntime.log as Mock).mock.calls[0]?.[0])); |
| 416 | + |
| 417 | +expect(payload.defaultModel).toBe("openrouter/auto"); |
| 418 | +expect(payload.resolvedDefault).toBe("openrouter/auto"); |
| 419 | +} finally { |
| 420 | +if (originalLoadConfig) { |
| 421 | +mocks.loadConfig.mockImplementation(originalLoadConfig); |
| 422 | +} |
| 423 | +} |
| 424 | +}); |
| 425 | + |
399 | 426 | it("handles cli backend and aliased provider auth summaries", async () => { |
400 | 427 | const localRuntime = createRuntime(); |
401 | 428 | const originalLoadConfig = mocks.loadConfig.getMockImplementation(); |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。