diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 83ca72addb1..e753689fcb1 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -656,6 +656,13 @@ export namespace MessageV2 { return result } + const isOpenAiErrorRetryable = (e: APICallError) => { + const status = e.statusCode + if (!status) return e.isRetryable + // openai sometimes returns 404 for models that are actually available + return status === 404 || e.isRetryable + } + export function fromError(e: unknown, ctx: { providerID: string }) { switch (true) { case e instanceof DOMException && e.name === "AbortError": @@ -724,7 +731,7 @@ export namespace MessageV2 { { message, statusCode: e.statusCode, - isRetryable: e.isRetryable, + isRetryable: ctx.providerID.startsWith("openai") ? isOpenAiErrorRetryable(e) : e.isRetryable, responseHeaders: e.responseHeaders, responseBody: e.responseBody, metadata, diff --git a/packages/opencode/test/session/retry.test.ts b/packages/opencode/test/session/retry.test.ts index b130e927e4a..10137ed988c 100644 --- a/packages/opencode/test/session/retry.test.ts +++ b/packages/opencode/test/session/retry.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from "bun:test" +import { APICallError } from "ai" import { SessionRetry } from "../../src/session/retry" import { MessageV2 } from "../../src/session/message-v2" @@ -128,4 +129,18 @@ describe("session.message-v2.fromError", () => { expect(retryable).toBeDefined() expect(retryable).toBe("Connection reset by server") }) + + test("marks OpenAI 404 status codes as retryable", () => { + const error = new APICallError({ + message: "boom", + url: "https://api.openai.com/v1/chat/completions", + requestBodyValues: {}, + statusCode: 404, + responseHeaders: { "content-type": "application/json" }, + responseBody: "{\"error\":\"boom\"}", + isRetryable: false, + }) + const result = MessageV2.fromError(error, { providerID: "openai" }) as MessageV2.APIError + expect(result.data.isRetryable).toBe(true) + }) })