diff --git a/src/agents/failover-error.test.ts b/src/agents/failover-error.test.ts index ec7e7a195ee..6add16356a7 100644 --- a/src/agents/failover-error.test.ts +++ b/src/agents/failover-error.test.ts @@ -39,6 +39,9 @@ describe("failover-error", () => { expect(resolveFailoverReasonFromError({ message: "Unhandled stop reason: abort" })).toBe( "timeout", ); + expect(resolveFailoverReasonFromError({ message: "Unhandled stop reason: error" })).toBe( + "timeout", + ); expect(resolveFailoverReasonFromError({ message: "stop reason: abort" })).toBe("timeout"); expect(resolveFailoverReasonFromError({ message: "reason: abort" })).toBe("timeout"); }); diff --git a/src/agents/failover-error.ts b/src/agents/failover-error.ts index 7b9f5194fd3..7a9d5ca354c 100644 --- a/src/agents/failover-error.ts +++ b/src/agents/failover-error.ts @@ -6,7 +6,7 @@ import { } from "./pi-embedded-helpers.js"; const TIMEOUT_HINT_RE = - /timeout|timed out|deadline exceeded|context deadline exceeded|stop reason:\s*abort|reason:\s*abort|unhandled stop reason:\s*abort/i; + /timeout|timed out|deadline exceeded|context deadline exceeded|stop reason:\s*abort|reason:\s*abort|unhandled stop reason:\s*(?:abort|error)/i; const ABORT_TIMEOUT_RE = /request was aborted|request aborted/i; export class FailoverError extends Error { diff --git a/src/agents/model-fallback.test.ts b/src/agents/model-fallback.test.ts index 0b527392ef1..c40e3649eb4 100644 --- a/src/agents/model-fallback.test.ts +++ b/src/agents/model-fallback.test.ts @@ -743,6 +743,14 @@ describe("runWithModelFallback", () => { }); }); + it("falls back on unhandled stop reason error responses", async () => { + await expectFallsBackToHaiku({ + provider: "openai", + model: "gpt-4.1-mini", + firstError: new Error("Unhandled stop reason: error"), + }); + }); + it("falls back when message says aborted but error is a timeout", async () => { await expectFallsBackToHaiku({ provider: "openai", diff --git a/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts b/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts index 21751d15dc5..0cf9df4d7f7 100644 --- a/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts +++ b/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts @@ -423,7 +423,12 @@ describe("isFailoverErrorMessage", () => { }); it("matches abort stop-reason timeout variants", () => { - const samples = ["Unhandled stop reason: abort", "stop reason: abort", "reason: abort"]; + const samples = [ + "Unhandled stop reason: abort", + "Unhandled stop reason: error", + "stop reason: abort", + "reason: abort", + ]; for (const sample of samples) { expect(isTimeoutErrorMessage(sample)).toBe(true); expect(classifyFailoverReason(sample)).toBe("timeout"); diff --git a/src/agents/pi-embedded-helpers/errors.ts b/src/agents/pi-embedded-helpers/errors.ts index aa64450df6b..852ba227d5e 100644 --- a/src/agents/pi-embedded-helpers/errors.ts +++ b/src/agents/pi-embedded-helpers/errors.ts @@ -643,7 +643,7 @@ const ERROR_PATTERNS = { /without sending (?:any )?chunks?/i, /\bstop reason:\s*abort\b/i, /\breason:\s*abort\b/i, - /\bunhandled stop reason:\s*abort\b/i, + /\bunhandled stop reason:\s*(?:abort|error)\b/i, ], billing: [ /["']?(?:status|code)["']?\s*[:=]\s*402\b|\bhttp\s*402\b|\berror(?:\s+code)?\s*[:=]?\s*402\b|\b(?:got|returned|received)\s+(?:a\s+)?402\b|^\s*402\s+payment/i,