diff --git a/src/agents/embedded-agent-runner/result-fallback-classifier.test.ts b/src/agents/embedded-agent-runner/result-fallback-classifier.test.ts index d601ffd7df5..e0c51525fad 100644 --- a/src/agents/embedded-agent-runner/result-fallback-classifier.test.ts +++ b/src/agents/embedded-agent-runner/result-fallback-classifier.test.ts @@ -70,6 +70,31 @@ describe("classifyEmbeddedAgentRunResultForModelFallback", () => { expect(result).toBeNull(); }); + it("uses provider-scoped failover matching for business-denial payloads", () => { + const result = classifyEmbeddedAgentRunResultForModelFallback({ + provider: "openrouter", + model: "claude-3.5-sonnet", + result: { + payloads: [ + { + isError: true, + text: "Key limit exceeded", + }, + ], + meta: { + durationMs: 42, + }, + }, + }); + + expect(result).toEqual({ + message: "openrouter/claude-3.5-sonnet ended with a provider error: Key limit exceeded", + reason: "billing", + code: "embedded_error_payload", + rawError: "Key limit exceeded", + }); + }); + it("does not retry unclassified non-GPT error payloads", () => { const result = classifyEmbeddedAgentRunResultForModelFallback({ provider: "custom", diff --git a/src/agents/embedded-agent-runner/result-fallback-classifier.ts b/src/agents/embedded-agent-runner/result-fallback-classifier.ts index 560097b668b..6fc29cf1964 100644 --- a/src/agents/embedded-agent-runner/result-fallback-classifier.ts +++ b/src/agents/embedded-agent-runner/result-fallback-classifier.ts @@ -1,9 +1,5 @@ import { isSilentReplyPayloadText } from "../../auto-reply/tokens.js"; -import { - isAuthErrorMessage, - isAuthPermanentErrorMessage, - isBillingErrorMessage, -} from "../embedded-agent-helpers/failover-matches.js"; +import { classifyFailoverReason } from "../embedded-agent-helpers/errors.js"; import type { FailoverReason } from "../embedded-agent-helpers/types.js"; import { isGpt5ModelId } from "../gpt5-prompt-overlay.js"; import type { ModelFallbackResultClassification } from "../model-fallback.js"; @@ -63,20 +59,20 @@ function classifyHarnessResult(params: { function classifyBusinessDenialErrorPayloadReason( errorText: string, + provider: string, ): Extract | null { if (!errorText.trim()) { return null; } - if (isBillingErrorMessage(errorText)) { - return "billing"; + const failoverReason = classifyFailoverReason(errorText, { provider }); + switch (failoverReason) { + case "auth": + case "auth_permanent": + case "billing": + return failoverReason; + default: + return null; } - if (isAuthPermanentErrorMessage(errorText)) { - return "auth_permanent"; - } - if (isAuthErrorMessage(errorText)) { - return "auth"; - } - return null; } export function classifyEmbeddedAgentRunResultForModelFallback(params: { @@ -128,7 +124,7 @@ export function classifyEmbeddedAgentRunResultForModelFallback(params: { code: "incomplete_result", }; } - const failoverReason = classifyBusinessDenialErrorPayloadReason(errorText); + const failoverReason = classifyBusinessDenialErrorPayloadReason(errorText, params.provider); if (failoverReason) { return { message: `${params.provider}/${params.model} ended with a provider error: ${errorText}`,