diff --git a/packages/core/src/session/runner/retry.ts b/packages/core/src/session/runner/retry.ts index a4526dc4f4..7b44aa892b 100644 --- a/packages/core/src/session/runner/retry.ts +++ b/packages/core/src/session/runner/retry.ts @@ -15,6 +15,9 @@ export class RetryableFailure extends Data.TaggedError("SessionRunner.RetryableF }> {} export function isRetryable(error: AIError) { + const override = "http" in error.reason ? error.reason.http?.response?.headers["x-should-retry"] : undefined + if (override === "true") return true + if (override === "false") return false switch (error.reason._tag) { case "RateLimit": case "ProviderInternal": diff --git a/packages/core/test/session-error.test.ts b/packages/core/test/session-error.test.ts index d79050745a..8f0b18aece 100644 --- a/packages/core/test/session-error.test.ts +++ b/packages/core/test/session-error.test.ts @@ -176,4 +176,29 @@ describe("toSessionError", () => { expect(retryable.map(SessionRunnerRetry.isRetryable)).toEqual([true, true]) expect(ineligible.map(SessionRunnerRetry.isRetryable)).toEqual([false, false, false]) }) + + test("honors provider retry header overrides", () => { + const http = (headers: Record) => + new HttpContext({ + request: new HttpRequestDetails({ method: "POST", url: "https://example.com", headers: {} }), + response: new HttpResponseDetails({ status: 500, headers }), + }) + + expect( + SessionRunnerRetry.isRetryable( + llm( + new ProviderInternalReason({ + message: "do not retry", + status: 500, + http: http({ "x-should-retry": "false" }), + }), + ), + ), + ).toBeFalse() + expect( + SessionRunnerRetry.isRetryable( + llm(new InvalidRequestReason({ message: "retry", http: http({ "x-should-retry": "true" }) })), + ), + ).toBeTrue() + }) })