Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/providers/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)])),
Expand Down
6 changes: 3 additions & 3 deletions tests/management-provider-validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down
3 changes: 3 additions & 0 deletions tests/opencode-free-provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -55,6 +57,7 @@ describe("opencode-free provider", () => {
const req = adapter.buildRequest(minimalRequest());
const headers = req.headers as Record<string, string>;
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");
});
Expand Down
Loading