diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts index 4438688c22..48514e5865 100644 --- a/packages/console/app/src/routes/zen/util/handler.ts +++ b/packages/console/app/src/routes/zen/util/handler.ts @@ -48,6 +48,7 @@ import { localeFromRequest } from "~/lib/language" import { createModelTpmLimiter } from "./modelTpmLimiter" import { createModelTpsLimiter } from "./modelTpsLimiter" import { accumulateUsage, HOT_WORKSPACES } from "./usageBatcher" +import { parseProviderErrorBody } from "./providerError" type ZenData = Awaited> type RetryOptions = { @@ -248,7 +249,15 @@ export async function handler( // Handle non-streaming response if (!isStream || [400, 404, 429].includes(res.status)) { - const json = await res.json() + const json = await (async () => { + if (res.status === 200) return res.json() + const body = await res.text() + try { + const parsed = JSON.parse(body) + if (parsed && typeof parsed === "object") return parsed as Record + } catch {} + return parseProviderErrorBody(body, res.statusText) + })() await rateLimiter?.track() const usage = providerInfo.extractUsage(json) if (usage) { diff --git a/packages/console/app/src/routes/zen/util/providerError.ts b/packages/console/app/src/routes/zen/util/providerError.ts new file mode 100644 index 0000000000..f52f605252 --- /dev/null +++ b/packages/console/app/src/routes/zen/util/providerError.ts @@ -0,0 +1,25 @@ +export function parseProviderErrorBody(body: string, statusText: string) { + const text = body.trim() + const sseData = text + .split(/\r?\n/) + .map((line) => line.trim()) + .filter((line) => line.startsWith("data:")) + .map((line) => line.slice(5).trim()) + .filter((line) => line && line !== "[DONE]") + + const parsed = (() => { + for (const data of sseData) { + try { + const json = JSON.parse(data) + if (json && typeof json === "object") return json as Record + } catch {} + } + })() + if (parsed) return parsed as Record + + return { + error: { + message: sseData[0] || text || statusText, + }, + } +} diff --git a/packages/console/app/test/providerError.test.ts b/packages/console/app/test/providerError.test.ts new file mode 100644 index 0000000000..38d4f3c208 --- /dev/null +++ b/packages/console/app/test/providerError.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, test } from "bun:test" +import { parseProviderErrorBody } from "../src/routes/zen/util/providerError" + +describe("provider error parsing", () => { + test("parses SSE error bodies from upstream rate limits", () => { + expect( + parseProviderErrorBody( + 'event:error\ndata: {"error":{"type":"rate_limit_error","message":"Too many requests"}}\n\n', + "Too Many Requests", + ), + ).toEqual({ + error: { + type: "rate_limit_error", + message: "Too many requests", + }, + }) + }) + + test("wraps plain text errors in provider error shape", () => { + expect(parseProviderErrorBody("overloaded", "Too Many Requests")).toEqual({ + error: { + message: "overloaded", + }, + }) + }) +})