diff --git a/open-sse/services/usage.ts b/open-sse/services/usage.ts index 5a442b4e6c..7dc9881f03 100644 --- a/open-sse/services/usage.ts +++ b/open-sse/services/usage.ts @@ -928,17 +928,25 @@ async function getOpenCodeGoUsage(apiKey: string) { }); if (!res.ok) { - if (res.status === 401 || res.status === 403) throw new Error("Invalid OpenCode Go API key"); - throw new Error(`OpenCode Go quota API error (${res.status})`); + if (res.status === 401 || res.status === 403) { + return { message: "OpenCode Go quota endpoint rejected this API key. Chat requests still work." }; + } + return { message: `OpenCode Go quota API error (${res.status})` }; } - const json = await res.json(); - const code = toNumber(json.code, 200); - if (code === 401 || code === 403 || json.success === false) { - throw new Error("Invalid OpenCode Go API key"); + let json: unknown; + try { + json = await res.json(); + } catch { + return { message: "OpenCode Go quota response parsing failed." }; } - const data = toRecord(json.data); + const code = toNumber((json as Record).code, 200); + if (code === 401 || code === 403 || (json as Record).success === false) { + return { message: "OpenCode Go quota endpoint rejected this API key. Chat requests still work." }; + } + + const data = toRecord((json as Record).data); const limits: unknown[] = Array.isArray(data.limits) ? data.limits : []; const quotas: Record = {}; diff --git a/tests/unit/opencode-go-usage.test.ts b/tests/unit/opencode-go-usage.test.ts index 63f6fefdd4..1cee60cd3d 100644 --- a/tests/unit/opencode-go-usage.test.ts +++ b/tests/unit/opencode-go-usage.test.ts @@ -136,20 +136,54 @@ test("getUsageForProvider exposes OpenCode Go 5h, weekly, and monthly quotas", a } }); -test("getUsageForProvider rejects invalid OpenCode Go API keys", async () => { +test("getUsageForProvider returns message for invalid OpenCode Go API keys", async () => { const originalFetch = globalThis.fetch; globalThis.fetch = async () => new Response("nope", { status: 401 }); try { - await assert.rejects( - () => - usage.getUsageForProvider({ - id: "opencode-go-401", - provider: "opencode-go", - apiKey: "bad-key", - }), - /Invalid OpenCode Go API key/ + const result = (await usage.getUsageForProvider({ + id: "opencode-go-401", + provider: "opencode-go", + apiKey: "bad-key", + })) as { message: string }; + assert.equal(result.message, "OpenCode Go quota endpoint rejected this API key. Chat requests still work."); + } finally { + globalThis.fetch = originalFetch; + } +}); + +test("getUsageForProvider returns message when OpenCode Go quota API returns 200 with auth error in body", async () => { + const originalFetch = globalThis.fetch; + globalThis.fetch = async () => + new Response( + JSON.stringify({ code: 401, msg: "token expired or incorrect", success: false }), + { status: 200, headers: { "content-type": "application/json" } } ); + + try { + const result = (await usage.getUsageForProvider({ + id: "opencode-go-body-401", + provider: "opencode-go", + apiKey: "sk-test-key", + })) as { message: string }; + assert.equal(result.message, "OpenCode Go quota endpoint rejected this API key. Chat requests still work."); + } finally { + globalThis.fetch = originalFetch; + } +}); + +test("getUsageForProvider returns message when OpenCode Go quota response is invalid JSON", async () => { + const originalFetch = globalThis.fetch; + globalThis.fetch = async () => + new Response("not json", { status: 200, headers: { "content-type": "text/html" } }); + + try { + const result = (await usage.getUsageForProvider({ + id: "opencode-go-bad-json", + provider: "opencode-go", + apiKey: "sk-test-key", + })) as { message: string }; + assert.equal(result.message, "OpenCode Go quota response parsing failed."); } finally { globalThis.fetch = originalFetch; }