From 05f9985a21dcda601d013db4782929cab666be45 Mon Sep 17 00:00:00 2001 From: MagMueller Date: Mon, 3 Aug 2026 15:51:23 -0700 Subject: [PATCH] fix(opencode): retry nested OpenAI errors --- packages/opencode/src/provider/error.ts | 17 +++++-- packages/opencode/test/session/retry.test.ts | 49 ++++++++++++++++++++ 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/provider/error.ts b/packages/opencode/src/provider/error.ts index 21149a2cf3..5214af43df 100644 --- a/packages/opencode/src/provider/error.ts +++ b/packages/opencode/src/provider/error.ts @@ -105,9 +105,16 @@ export function parseStreamError(input: unknown): ParsedStreamError | undefined if (!body) return const responseBody = JSON.stringify(body) - if (body.type !== "error") return - - switch (body?.error?.code) { + const error = + body.type === "error" + ? body.error + : body.type === "response.failed" && + (body.response?.error?.code === "server_is_overloaded" || body.response?.error?.code === "server_error") + ? body.response.error + : undefined + if (!error) return + + switch (error.code) { case "context_length_exceeded": return { type: "context_overflow", @@ -131,7 +138,7 @@ export function parseStreamError(input: unknown): ParsedStreamError | undefined case "invalid_prompt": return { type: "api_error", - message: typeof body?.error?.message === "string" ? body?.error?.message : "Invalid prompt.", + message: typeof error.message === "string" ? error.message : "Invalid prompt.", isRetryable: false, responseBody, } @@ -139,7 +146,7 @@ export function parseStreamError(input: unknown): ParsedStreamError | undefined case "server_error": return { type: "api_error", - message: typeof body?.error?.message === "string" ? body?.error?.message : "Server error.", + message: typeof error.message === "string" ? error.message : "Server error.", isRetryable: true, responseBody, } diff --git a/packages/opencode/test/session/retry.test.ts b/packages/opencode/test/session/retry.test.ts index 24d2dd728d..76de24cccf 100644 --- a/packages/opencode/test/session/retry.test.ts +++ b/packages/opencode/test/session/retry.test.ts @@ -485,4 +485,53 @@ describe("session.message-v2.fromError", () => { message: "An error occurred while processing your request.", }) }) + + test("converts nested OpenAI response.failed server errors to retryable APIError", () => { + const cases = ["server_error", "server_is_overloaded"] + + cases.forEach((code) => { + const body = { + type: "response.failed", + sequence_number: 2, + response: { + status: "failed", + error: { + code, + message: `OpenAI ${code}`, + }, + }, + } + const result = MessageV2.fromError( + { message: JSON.stringify(body) }, + { providerID: ProviderV2.ID.make("openai") }, + ) + + expect(SessionV1.APIError.isInstance(result)).toBe(true) + if (!SessionV1.APIError.isInstance(result)) throw new Error("expected APIError") + expect(result.data.isRetryable).toBe(true) + expect(result.data.responseBody).toBe(JSON.stringify(body)) + expect(SessionRetry.retryable(result, retryProvider)).toEqual({ message: `OpenAI ${code}` }) + }) + }) + + test("leaves other nested OpenAI response.failed errors terminal", () => { + const result = MessageV2.fromError( + { + message: JSON.stringify({ + type: "response.failed", + response: { + status: "failed", + error: { + code: "invalid_prompt", + message: "Invalid prompt from test", + }, + }, + }), + }, + { providerID: ProviderV2.ID.make("openai") }, + ) + + expect(SessionV1.APIError.isInstance(result)).toBe(false) + expect(SessionRetry.retryable(result, retryProvider)).toBeUndefined() + }) })