From c3a1c3eebf61e91b0095f2b2d1e931caa615ea95 Mon Sep 17 00:00:00 2001 From: CreatorGhost Date: Sun, 12 Jul 2026 00:56:44 +0530 Subject: [PATCH] fix(provider): extract OpenAI-shaped error messages Ported from upstream anomalyco/opencode#36411. --- packages/opencode/src/provider/error.ts | 10 ++-- packages/opencode/test/provider/error.test.ts | 49 +++++++++++++++++++ 2 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 packages/opencode/test/provider/error.test.ts diff --git a/packages/opencode/src/provider/error.ts b/packages/opencode/src/provider/error.ts index aa5a41c2042e..f745a1899da4 100644 --- a/packages/opencode/src/provider/error.ts +++ b/packages/opencode/src/provider/error.ts @@ -47,9 +47,13 @@ function message(providerID: ProviderV2.ID, e: APICallError) { try { const body = JSON.parse(e.responseBody) - // try to extract common error message fields - const errMsg = body.message || body.error || body.error?.message - if (errMsg && typeof errMsg === "string") { + // Prefer string fields. OpenAI-shaped bodies use { error: { message } }, + // so reading body.error first would grab the object and skip the message. + const errMsg = + (typeof body.message === "string" ? body.message : undefined) || + (typeof body.error === "string" ? body.error : undefined) || + (typeof body.error?.message === "string" ? body.error.message : undefined) + if (errMsg) { return `${msg}: ${errMsg}` } } catch {} diff --git a/packages/opencode/test/provider/error.test.ts b/packages/opencode/test/provider/error.test.ts new file mode 100644 index 000000000000..4a3bc4ade53c --- /dev/null +++ b/packages/opencode/test/provider/error.test.ts @@ -0,0 +1,49 @@ +import { expect, test } from "bun:test" +import { APICallError } from "ai" +import { ProviderV2 } from "@opencode-ai/core/provider" +import { ProviderError } from "@/provider/error" + +test("parseAPICallError extracts OpenAI-shaped error.message", () => { + const error = new APICallError({ + message: "Too Many Requests", + statusCode: 429, + url: "https://api.openai.com/v1/chat/completions", + requestBodyValues: {}, + responseBody: JSON.stringify({ + error: { + message: "Rate limit exceeded", + type: "rate_limit_error", + }, + }), + isRetryable: true, + }) + + const parsed = ProviderError.parseAPICallError({ + providerID: ProviderV2.ID.make("openai"), + error, + }) + + expect(parsed.type).toBe("api_error") + expect(parsed.message).toBe("Too Many Requests: Rate limit exceeded") +}) + +test("parseAPICallError still extracts string body.error", () => { + const error = new APICallError({ + message: "Bad Request", + statusCode: 400, + url: "https://example.com/v1/chat", + requestBodyValues: {}, + responseBody: JSON.stringify({ + error: "model not found", + }), + isRetryable: false, + }) + + const parsed = ProviderError.parseAPICallError({ + providerID: ProviderV2.ID.make("openai"), + error, + }) + + expect(parsed.type).toBe("api_error") + expect(parsed.message).toBe("Bad Request: model not found") +})