From 6a79c42e620c7b7e6a203f80ffaba128c0b87985 Mon Sep 17 00:00:00 2001 From: waw4303 Date: Thu, 20 Aug 2026 02:08:57 +0300 Subject: [PATCH] fix(providers): add official opencode client User-Agent to opencode-free OpenCode's Zen gateway requires the official client User-Agent prefix ('opencode') alongside the client marker, answering 429/403 to generic fetch User-Agents. Keep the desktop tier identity and add the canonical User-Agent header. --- src/providers/registry.ts | 1 + tests/management-provider-validation.test.ts | 6 +++--- tests/opencode-free-provider.test.ts | 3 +++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/providers/registry.ts b/src/providers/registry.ts index 53922e75ad..af849984d2 100644 --- a/src/providers/registry.ts +++ b/src/providers/registry.ts @@ -2435,6 +2435,7 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [ note: "No key needed — public desktop tier. OpenCode currently advertises about 200 Big Pickle/free-model requests per 5 hours. The same Zen gateway can also short-window rate-limit free models at roughly 15-20 requests/minute, and may return generic 429s without Retry-After (opencodex synthesizes backoff only when that header is omitted). Free models are discovered live from Zen. Data use: per OpenCode's Zen docs (https://opencode.ai/docs/zen/), prompts sent to free models may be retained and used for training/improvement — do not send confidential material through this provider.", dashboardUrl: "https://opencode.ai", staticHeaders: { + "User-Agent": "opencode", "x-opencode-client": "desktop", }, modelReasoningEfforts: Object.fromEntries(OPENCODE_FREE_DEEPSEEK_MODELS.map(id => [id, deepseekThinkingEffortsFor(id)])), diff --git a/tests/management-provider-validation.test.ts b/tests/management-provider-validation.test.ts index fd79c750e9..8bb0830a3a 100644 --- a/tests/management-provider-validation.test.ts +++ b/tests/management-provider-validation.test.ts @@ -2888,11 +2888,11 @@ describe("provider management validation", () => { }; // Clearing user-managed headers must not delete the registry-owned static - // metadata (opencode-free's x-opencode-client marker) the transport relies on. + // metadata (opencode-free's User-Agent and x-opencode-client markers) the transport relies on. expect((await patch("opencode-free", { headers: null }))?.status).toBe(200); - expect(liveConfig.providers["opencode-free"].headers).toEqual({ "x-opencode-client": "desktop" }); + expect(liveConfig.providers["opencode-free"].headers).toEqual({ "User-Agent": "opencode", "x-opencode-client": "desktop" }); const saved = JSON.parse(readFileSync(join(TEST_DIR, "config.json"), "utf8")) as OcxConfig; - expect(saved.providers["opencode-free"]?.headers).toEqual({ "x-opencode-client": "desktop" }); + expect(saved.providers["opencode-free"]?.headers).toEqual({ "User-Agent": "opencode", "x-opencode-client": "desktop" }); }); test("concurrent provider PATCHes merge different headers", async () => { if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true }); diff --git a/tests/opencode-free-provider.test.ts b/tests/opencode-free-provider.test.ts index 097fd98ef9..23af38626b 100644 --- a/tests/opencode-free-provider.test.ts +++ b/tests/opencode-free-provider.test.ts @@ -29,12 +29,14 @@ describe("opencode-free provider", () => { test("static headers include only the public client marker", () => { expect(entry?.staticHeaders?.["Authorization"]).toBeUndefined(); + expect(entry?.staticHeaders?.["User-Agent"]).toBe("opencode"); expect(entry?.staticHeaders?.["x-opencode-client"]).toBe("desktop"); }); test("providerConfigSeed propagates static headers", () => { const seed = providerConfigSeed(entry!); expect(seed.headers?.["Authorization"]).toBeUndefined(); + expect(seed.headers?.["User-Agent"]).toBe("opencode"); expect(seed.headers?.["x-opencode-client"]).toBe("desktop"); expect(seed.keyOptional).toBe(true); expect(seed.liveModels).toBe(true); @@ -55,6 +57,7 @@ describe("opencode-free provider", () => { const req = adapter.buildRequest(minimalRequest()); const headers = req.headers as Record; expect(headers["Authorization"]).toBeUndefined(); + expect(headers["User-Agent"]).toBe("opencode"); expect(headers["x-opencode-client"]).toBe("desktop"); expect(req.url).toBe("https://opencode.ai/zen/v1/chat/completions"); });