diff --git a/packages/opencode/src/provider/error.ts b/packages/opencode/src/provider/error.ts index b13a3375a483..52d6f08ee62e 100644 --- a/packages/opencode/src/provider/error.ts +++ b/packages/opencode/src/provider/error.ts @@ -99,7 +99,7 @@ export type ParsedStreamError = responseBody: string } -// OpenRouter relays upstream overflow as a typeless `{ code: 502, message }` chunk. +// OpenRouter relays upstream failures as typeless `{ code: 5xx, message }` chunks. function isOverflow(body: { error?: { code?: string; message?: string }; message?: string }) { return body.error?.code === "context_length_exceeded" || isContextOverflow(body.error?.message ?? body.message ?? "") } @@ -117,6 +117,14 @@ export function parseStreamError(input: unknown): ParsedStreamError | undefined responseBody, } } + if (typeof body.code === "number" && body.code >= 500) { + return { + type: "api_error", + message: typeof body.message === "string" ? body.message : "Server error.", + isRetryable: true, + responseBody, + } + } if (body.type !== "error") return switch (body?.error?.code) { diff --git a/packages/opencode/test/session/message-v2.test.ts b/packages/opencode/test/session/message-v2.test.ts index 4dbddcc09b43..cbc89d254b3f 100644 --- a/packages/opencode/test/session/message-v2.test.ts +++ b/packages/opencode/test/session/message-v2.test.ts @@ -1399,6 +1399,24 @@ describe("session.message-v2.fromError", () => { }) }) + test("serializes OpenRouter typeless 5xx chunks as retryable APIError", () => { + const input = { + code: 502, + message: "Stream ended before a terminal response event", + metadata: { error_type: "provider_unavailable" }, + } + const result = MessageV2.fromError(input, { providerID }) + + expect(result).toStrictEqual({ + name: "APIError", + data: { + message: input.message, + isRetryable: true, + responseBody: JSON.stringify(input), + }, + }) + }) + test("serializes response error codes", () => { const cases = [ {