diff --git a/package.json b/package.json index f4e8bbd5c9..178d6a9c7d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bitkyc08/opencodex", - "version": "2.24.2", + "version": "2.25.0", "description": "Universal provider proxy for OpenAI Codex & Claude Code — use any LLM with Codex CLI/App/SDK and Claude Code", "type": "module", "main": "./bin/package-main.mjs", diff --git a/src/providers/quota.ts b/src/providers/quota.ts index bb3bab2837..7ee2bb0e1c 100644 --- a/src/providers/quota.ts +++ b/src/providers/quota.ts @@ -2084,7 +2084,7 @@ async function maybeFetchProviderQuota( && isCanonicalCommandCodeBaseUrl(provider.baseUrl)) { return fetchCommandCodeQuota(name, provider); } - if ((provider.authMode ?? "key") === "key" && name === "opencode-go") { + if ((provider.authMode ?? "key") === "key" && isCanonicalOpenCodeGoBaseUrl(provider.baseUrl)) { return fetchOpenCodeGoQuota(name, provider); } if ((provider.authMode ?? "key") === "key" && isCanonicalA6apiBaseUrl(provider.baseUrl)) { diff --git a/tests/opencode-go-quota.test.ts b/tests/opencode-go-quota.test.ts index ece8d22b53..8a34056ff2 100644 --- a/tests/opencode-go-quota.test.ts +++ b/tests/opencode-go-quota.test.ts @@ -88,3 +88,63 @@ describe("OpenCode Go provider quota", () => { expect(result.reports).toEqual([]); }); }); + +describe("OpenCode Go provider quota (canonical base URL gating, #1924)", () => { + test("reports quota for every key-auth provider on the canonical Go base URL, not just the literal name", async () => { + const seen: Array<{ url: string; authorization?: string }> = []; + globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => { + const headers = init?.headers as Record | undefined; + seen.push({ url: String(input), authorization: headers?.Authorization }); + return new Response( + JSON.stringify({ + usage: { + rolling: { status: "ok", percent: 10, resetsAt: "2026-08-12T20:00:00.000Z" }, + weekly: { status: "ok", percent: 20, resetsAt: "2026-08-17T00:00:00.000Z" }, + monthly: { status: "ok", percent: 30, resetsAt: "2026-09-01T00:00:00.000Z" }, + }, + }), + { status: 200, headers: { "content-type": "application/json" } }, + ); + }) as typeof fetch; + + const config = openCodeGoConfig(); + config.providers = { + ...config.providers, + "opencode-go-2": { + adapter: "openai-chat", + authMode: "key", + baseUrl: "https://opencode.ai/zen/go/v1", + apiKey: "opencode-go-secret-2", + }, + }; + + const result = await fetchProviderQuotaReports(config, true); + + // Before #1924's fix only the provider literally named "opencode-go" + // produced a report; the sibling on the same canonical base URL was silent. + expect(result.reports.map((r: { provider: string }) => r.provider).sort()).toEqual([ + "opencode-go", + "opencode-go-2", + ]); + expect(seen).toHaveLength(2); + }); + + test("does not probe the Go usage endpoint for providers on other base URLs", async () => { + const seen: Array = []; + globalThis.fetch = (async (input: RequestInfo | URL) => { + seen.push(String(input)); + return new Response(JSON.stringify({ usage: {} }), { + status: 200, + headers: { "content-type": "application/json" }, + }); + }) as typeof fetch; + + const config = openCodeGoConfig("https://api.example.com/v1"); + const result = await fetchProviderQuotaReports(config, true); + + // The bearer key must never leave for a non-canonical host just because + // the provider is named opencode-go. + expect(result.reports).toHaveLength(0); + expect(seen.filter((u) => u.includes("opencode.ai"))).toHaveLength(0); + }); +});