From 3887a915d7fb4a2b5b43c3c1733818d2b3be48c0 Mon Sep 17 00:00:00 2001 From: marcusquinn <6428977+marcusquinn@users.noreply.github.com> Date: Mon, 4 May 2026 17:23:58 +0100 Subject: [PATCH] fix: retry overloaded OpenAI stream errors --- packages/opencode/src/provider/error.ts | 19 +++++++++++++++- packages/opencode/test/session/retry.test.ts | 23 ++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/provider/error.ts b/packages/opencode/src/provider/error.ts index 3877dcb7f304..d0511a1d5ded 100644 --- a/packages/opencode/src/provider/error.ts +++ b/packages/opencode/src/provider/error.ts @@ -123,7 +123,10 @@ export function parseStreamError(input: unknown): ParsedStreamError | undefined const responseBody = JSON.stringify(body) if (body.type !== "error") return - switch (body?.error?.code) { + const errorCode = typeof body?.error?.code === "string" ? body.error.code : "" + const errorType = typeof body?.error?.type === "string" ? body.error.type : "" + + switch (errorCode) { case "context_length_exceeded": return { type: "context_overflow", @@ -159,6 +162,20 @@ export function parseStreamError(input: unknown): ParsedStreamError | undefined responseBody, } } + + if ( + errorType === "service_unavailable_error" || + errorCode === "server_is_overloaded" || + errorCode.includes("unavailable") || + errorCode.includes("exhausted") + ) { + return { + type: "api_error", + message: typeof body?.error?.message === "string" ? body.error.message : "Provider is overloaded", + isRetryable: true, + responseBody, + } + } } export type ParsedAPICallError = diff --git a/packages/opencode/test/session/retry.test.ts b/packages/opencode/test/session/retry.test.ts index 105c772d9735..bbfaf83d594c 100644 --- a/packages/opencode/test/session/retry.test.ts +++ b/packages/opencode/test/session/retry.test.ts @@ -323,4 +323,27 @@ describe("session.message-v2.fromError", () => { expect(result.data.isRetryable).toBe(true) expect(SessionRetry.retryable(result)).toBe("An error occurred while processing your request.") }) + + test("converts OpenAI overloaded stream chunks to retryable APIError", () => { + const result = MessageV2.fromError( + { + message: JSON.stringify({ + type: "error", + sequence_number: 2, + error: { + type: "service_unavailable_error", + code: "server_is_overloaded", + message: "Our servers are currently overloaded. Please try again later.", + param: null, + }, + }), + }, + { providerID: ProviderID.make("openai") }, + ) + + expect(MessageV2.APIError.isInstance(result)).toBe(true) + if (!MessageV2.APIError.isInstance(result)) throw new Error("expected APIError") + expect(result.data.isRetryable).toBe(true) + expect(SessionRetry.retryable(result)).toBe("Our servers are currently overloaded. Please try again later.") + }) })