diff --git a/src/agents/failover-error.test.ts b/src/agents/failover-error.test.ts index 6add16356..33ffe2d2d 100644 --- a/src/agents/failover-error.test.ts +++ b/src/agents/failover-error.test.ts @@ -35,7 +35,7 @@ describe("failover-error", () => { expect(resolveFailoverReasonFromError({ code: "ECONNRESET" })).toBe("timeout"); }); - it("infers timeout from abort stop-reason messages", () => { + it("infers timeout from abort/error stop-reason messages", () => { expect(resolveFailoverReasonFromError({ message: "Unhandled stop reason: abort" })).toBe( "timeout", ); @@ -43,7 +43,9 @@ describe("failover-error", () => { "timeout", ); expect(resolveFailoverReasonFromError({ message: "stop reason: abort" })).toBe("timeout"); + expect(resolveFailoverReasonFromError({ message: "stop reason: error" })).toBe("timeout"); expect(resolveFailoverReasonFromError({ message: "reason: abort" })).toBe("timeout"); + expect(resolveFailoverReasonFromError({ message: "reason: error" })).toBe("timeout"); }); it("treats AbortError reason=abort as timeout", () => { diff --git a/src/agents/failover-error.ts b/src/agents/failover-error.ts index 7a9d5ca35..63e5c26c7 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|error)/i; + /timeout|timed out|deadline exceeded|context deadline exceeded|stop reason:\s*(?:abort|error)|reason:\s*(?:abort|error)|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 c40e3649e..6f6fdd8b7 100644 --- a/src/agents/model-fallback.test.ts +++ b/src/agents/model-fallback.test.ts @@ -751,6 +751,17 @@ describe("runWithModelFallback", () => { }); }); + it("falls back on abort errors with reason: error", async () => { + await expectFallsBackToHaiku({ + provider: "openai", + model: "gpt-4.1-mini", + firstError: Object.assign(new Error("aborted"), { + name: "AbortError", + reason: "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 0cf9df4d7..11b29abad 100644 --- a/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts +++ b/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts @@ -427,7 +427,9 @@ describe("isFailoverErrorMessage", () => { "Unhandled stop reason: abort", "Unhandled stop reason: error", "stop reason: abort", + "stop reason: error", "reason: abort", + "reason: error", ]; for (const sample of samples) { expect(isTimeoutErrorMessage(sample)).toBe(true); diff --git a/src/agents/pi-embedded-helpers/errors.ts b/src/agents/pi-embedded-helpers/errors.ts index 852ba227d..754bd03ba 100644 --- a/src/agents/pi-embedded-helpers/errors.ts +++ b/src/agents/pi-embedded-helpers/errors.ts @@ -641,8 +641,8 @@ const ERROR_PATTERNS = { "deadline exceeded", "context deadline exceeded", /without sending (?:any )?chunks?/i, - /\bstop reason:\s*abort\b/i, - /\breason:\s*abort\b/i, + /\bstop reason:\s*(?:abort|error)\b/i, + /\breason:\s*(?:abort|error)\b/i, /\bunhandled stop reason:\s*(?:abort|error)\b/i, ], billing: [