From b2373e927c9edbcf31d4549544e1dac7136ff8c3 Mon Sep 17 00:00:00 2001 From: Saeed Hosseini Date: Fri, 14 Aug 2026 11:01:15 +0330 Subject: [PATCH 1/4] fix(providers): synthesize OpenCode CLI identity headers by default opencode.ai's free tier 429s (FreeUsageLimitError) server-side (VPS) requests lacking CLI identity. Sending the official CLI fingerprint headers (dynamic session/request ids, project, CLI User-Agent) turns the 429 into a 200. - Synthesis now ON by default; opt out with OPENCODE_SYNTHESIZE_CLI_HEADERS=false - Defaults: UA opencode/latest/1.18.18/cli, client desktop, project /opencode - All values remain env-overridable (OPENCODE_USER_AGENT/CLIENT/PROJECT, _USER_AGENT) - Update regression tests for the new default --- open-sse/executors/opencode.ts | 28 ++++++----- ...pencode-cli-headers-synthesis-5997.test.ts | 50 +++++++++++++------ 2 files changed, 49 insertions(+), 29 deletions(-) diff --git a/open-sse/executors/opencode.ts b/open-sse/executors/opencode.ts index dddcbe49005b..85ce2f3f4ec4 100644 --- a/open-sse/executors/opencode.ts +++ b/open-sse/executors/opencode.ts @@ -361,16 +361,18 @@ export class OpencodeExecutor extends BaseExecutor { headers["Accept"] = "text/event-stream"; } - // Opt-in (#5997): synthesize OpenCode CLI identity headers the client did not send. - // Cloudflare in front of opencode.ai/zen/go 403s server-side (VPS) requests lacking - // CLI identity, but the forward-only default is deliberate — fabricating a WRONG - // value risks upstream rejection (#5720 regressed with "opencode/local"), and this - // is deployment-specific. So it stays OFF by default and the VPS operator enables it - // with OPENCODE_SYNTHESIZE_CLI_HEADERS=true (values env-overridable). Client-supplied - // headers take precedence, EXCEPT User-Agent: a non-CLI client UA (curl/SDK) is - // replaced with the synthesized CLI UA because opencode.ai's free tier rejects - // generic client UAs from datacenter IPs (FreeUsageLimitError 429). - const synthesizeCli = /^(1|true|yes|on)$/i.test( + // Synthesize OpenCode CLI identity headers the client did not send. ON by + // default: opencode.ai's free tier 429s (FreeUsageLimitError) server-side + // (VPS) requests lacking CLI identity, and sending the official CLI + // fingerprint headers (dynamic session/request ids, project, CLI User-Agent) + // turns the 429 into a 200. Opt out with OPENCODE_SYNTHESIZE_CLI_HEADERS=false. + // Client-supplied headers take precedence, EXCEPT User-Agent: a non-CLI client + // UA (curl/SDK) is replaced with the synthesized CLI UA because opencode.ai's + // free tier rejects generic client UAs from datacenter IPs (FreeUsageLimitError 429). + // Values are env-overridable (OPENCODE_USER_AGENT / OPENCODE_CLIENT / + // OPENCODE_PROJECT, or _USER_AGENT); the defaults below are the + // live-verified set that resolves the 429. + const synthesizeCli = !/^(0|false|no|off)$/i.test( process.env.OPENCODE_SYNTHESIZE_CLI_HEADERS?.trim() ?? "" ); const cliDefaults = synthesizeCli @@ -381,9 +383,9 @@ export class OpencodeExecutor extends BaseExecutor { userAgent: process.env[envUAKey]?.trim() || process.env.OPENCODE_USER_AGENT?.trim() || - "opencode-cli/1.0.0", - client: process.env.OPENCODE_CLIENT?.trim() || "cli", - project: process.env.OPENCODE_PROJECT?.trim() || "default", + "opencode/latest/1.18.18/cli", + client: process.env.OPENCODE_CLIENT?.trim() || "desktop", + project: process.env.OPENCODE_PROJECT?.trim() || "/opencode", }; })() : undefined; diff --git a/tests/unit/opencode-cli-headers-synthesis-5997.test.ts b/tests/unit/opencode-cli-headers-synthesis-5997.test.ts index a652f8dbae85..81197cfccc01 100644 --- a/tests/unit/opencode-cli-headers-synthesis-5997.test.ts +++ b/tests/unit/opencode-cli-headers-synthesis-5997.test.ts @@ -14,11 +14,16 @@ * Client-supplied values always take precedence (defaults only fill gaps), and the * UA/client/project defaults are env-overridable. * - * The executor-level synthesis is OPT-IN via `OPENCODE_SYNTHESIZE_CLI_HEADERS=true`: - * the forward-only default is deliberate (fabricating a WRONG value risks upstream - * rejection — #5720 regressed with "opencode/local"), and this is deployment-specific - * (the owner asked for it to stay off-by-default pending live validation). With the flag - * off, buildHeaders keeps the historical forward-only behavior. + * The executor-level synthesis is ON BY DEFAULT: opencode.ai's free tier + * 429s (FreeUsageLimitError) server-side (VPS) requests lacking CLI identity, and + * sending the official CLI fingerprint headers (dynamic session/request ids, + * project, CLI User-Agent) turns the 429 into a 200. Opt out with + * `OPENCODE_SYNTHESIZE_CLI_HEADERS=false`. Client-supplied headers take + * precedence, EXCEPT User-Agent: a non-CLI client UA (curl/SDK) is replaced with the + * synthesized CLI UA because opencode.ai's free tier rejects generic client UAs from + * datacenter IPs. Values are env-overridable (OPENCODE_USER_AGENT / OPENCODE_CLIENT / + * OPENCODE_PROJECT, or _USER_AGENT); the defaults below are the live-verified + * set that resolves the 429. */ import { test } from "node:test"; import assert from "node:assert/strict"; @@ -27,7 +32,7 @@ import { OpencodeExecutor } from "../../open-sse/executors/opencode.ts"; const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; -const CLI_DEFAULTS = { userAgent: "opencode-cli/1.0.0", client: "cli", project: "default" }; +const CLI_DEFAULTS = { userAgent: "opencode/latest/1.18.18/cli", client: "desktop", project: "/opencode" }; function withEnv(key: string, value: string | undefined, fn: () => void) { const saved = process.env[key]; @@ -45,9 +50,9 @@ test("forwardOpencodeClientHeaders: cliDefaults synthesize all CLI identity head const headers: Record = {}; forwardOpencodeClientHeaders(headers, {}, { cliDefaults: CLI_DEFAULTS }); - assert.equal(headers["User-Agent"], "opencode-cli/1.0.0"); - assert.equal(headers["x-opencode-client"], "cli"); - assert.equal(headers["x-opencode-project"], "default"); + assert.equal(headers["User-Agent"], "opencode/latest/1.18.18/cli"); + assert.equal(headers["x-opencode-client"], "desktop"); + assert.equal(headers["x-opencode-project"], "/opencode"); assert.match(headers["x-opencode-request"] ?? "", UUID_RE); assert.match(headers["x-opencode-session"] ?? "", UUID_RE); assert.notEqual(headers["x-opencode-request"], headers["x-opencode-session"]); @@ -64,7 +69,7 @@ test("forwardOpencodeClientHeaders: non-CLI client UA is REPLACED with the CLI U }; forwardOpencodeClientHeaders(headers, clientHeaders, { cliDefaults: CLI_DEFAULTS }); - assert.equal(headers["User-Agent"], "opencode-cli/1.0.0"); + assert.equal(headers["User-Agent"], "opencode/latest/1.18.18/cli"); assert.equal(headers["x-opencode-client"], "vscode"); assert.equal(headers["x-opencode-project"], "acme"); assert.equal(headers["x-opencode-request"], "req-from-client"); @@ -86,30 +91,43 @@ test("forwardOpencodeClientHeaders: without cliDefaults, no synthesis (DefaultEx assert.equal(headers["x-opencode-project"], undefined); }); -test("OpencodeExecutor.buildHeaders: forward-only by default — no fabrication when flag is off [#5997]", () => { +test("OpencodeExecutor.buildHeaders: synthesizes CLI defaults BY DEFAULT (flag unset) [#5997]", () => { withEnv("OPENCODE_SYNTHESIZE_CLI_HEADERS", undefined, () => { const executor = new OpencodeExecutor("opencode-go"); const headers = executor.buildHeaders(null, true, null, "glm-5.2"); + + assert.equal(headers["User-Agent"], "opencode/latest/1.18.18/cli"); + assert.equal(headers["x-opencode-client"], "desktop"); + assert.equal(headers["x-opencode-project"], "/opencode"); + assert.match(headers["x-opencode-request"] ?? "", UUID_RE); + assert.match(headers["x-opencode-session"] ?? "", UUID_RE); + }); +}); + +test("OpencodeExecutor.buildHeaders: OPENCODE_SYNTHESIZE_CLI_HEADERS=false disables synthesis (forward-only) [#5997]", () => { + withEnv("OPENCODE_SYNTHESIZE_CLI_HEADERS", "false", () => { + const executor = new OpencodeExecutor("opencode-go"); + const headers = executor.buildHeaders(null, true, null, "glm-5.2"); assert.equal(headers["User-Agent"], undefined); assert.equal(headers["x-opencode-client"], undefined); assert.equal(headers["x-opencode-project"], undefined); }); }); -test("OpencodeExecutor.buildHeaders: synthesizes CLI defaults with flag on + no client headers [#5997]", () => { +test("OpencodeExecutor.buildHeaders: synthesizes CLI defaults with flag explicitly on + no client headers [#5997]", () => { withEnv("OPENCODE_SYNTHESIZE_CLI_HEADERS", "true", () => { const executor = new OpencodeExecutor("opencode-go"); const headers = executor.buildHeaders(null, true, null, "glm-5.2"); - assert.equal(headers["User-Agent"], "opencode-cli/1.0.0"); - assert.equal(headers["x-opencode-client"], "cli"); - assert.equal(headers["x-opencode-project"], "default"); + assert.equal(headers["User-Agent"], "opencode/latest/1.18.18/cli"); + assert.equal(headers["x-opencode-client"], "desktop"); + assert.equal(headers["x-opencode-project"], "/opencode"); assert.match(headers["x-opencode-request"] ?? "", UUID_RE); assert.match(headers["x-opencode-session"] ?? "", UUID_RE); }); }); -test("OpencodeExecutor.buildHeaders: OPENCODE_GO_USER_AGENT env overrides the default UA (flag on) [#5997]", () => { +test("OpencodeExecutor.buildHeaders: OPENCODE_GO_USER_AGENT env overrides the default UA [#5997]", () => { withEnv("OPENCODE_SYNTHESIZE_CLI_HEADERS", "true", () => { withEnv("OPENCODE_GO_USER_AGENT", "opencode-cli/2.5.0", () => { const executor = new OpencodeExecutor("opencode-go"); From 50ecfb47a104f9f91724791827b63dfa5055bf27 Mon Sep 17 00:00:00 2001 From: Saeed Hosseini Date: Sat, 15 Aug 2026 20:40:19 +0330 Subject: [PATCH 2/4] docs(providers): align .env.example + add changelog for on-by-default OpenCode header synthesis - .env.example: update OPENCODE block to on-by-default behavior + new defaults (opencode/latest/1.18.18/cli, desktop, /opencode) - changelog.d/fixes: add fragment referencing live-verified 429->200 (PR #10357) --- .env.example | 20 ++++++++++--------- ...ode-cli-headers-synthesis-on-by-default.md | 1 + 2 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 changelog.d/fixes/opencode-cli-headers-synthesis-on-by-default.md diff --git a/.env.example b/.env.example index 8472bf874303..9c1dac1834b0 100644 --- a/.env.example +++ b/.env.example @@ -597,17 +597,19 @@ NEXT_PUBLIC_CLOUD_URL= #OPENCODE_GO_AUTH_COOKIE=auth=... #OMNIROUTE_OPENCODE_GO_AUTH_COOKIE=auth=... -# OpenCode Go/Zen VPS egress (#5997): on a datacenter VPS, Cloudflare in front of -# opencode.ai/zen/go 403s chat requests that lack OpenCode CLI identity headers. -# When your clients don't already send them, set this to synthesize the CLI headers -# (User-Agent, x-opencode-client, x-opencode-project, fresh request/session UUIDs) on -# absent keys. OFF by default — forward-only is safer when clients already send them. +# OpenCode Go/Zen VPS egress (#5997): on a datacenter VPS, opencode.ai's free tier +# returns FreeUsageLimitError (429) for server-side requests lacking OpenCode CLI +# identity headers. The CLI headers are now synthesized ON by default — sending the +# official fingerprint (User-Agent, x-opencode-client, x-opencode-project, fresh +# request/session UUIDs) turns the 429 into a 200 on the same egress IP. Set +# OPENCODE_SYNTHESIZE_CLI_HEADERS=false to keep the forward-only behavior when your +# clients already send the CLI headers themselves. # Values are overridable via OPENCODE_GO_USER_AGENT / OPENCODE_USER_AGENT / OPENCODE_CLIENT / -# OPENCODE_PROJECT (defaults: opencode-cli/1.0.0 / cli / default). +# OPENCODE_PROJECT (defaults: opencode/latest/1.18.18/cli / desktop / /opencode). #OPENCODE_SYNTHESIZE_CLI_HEADERS=true -#OPENCODE_USER_AGENT=opencode-cli/1.0.0 -#OPENCODE_CLIENT=cli -#OPENCODE_PROJECT=default +#OPENCODE_USER_AGENT=opencode/latest/1.18.18/cli +#OPENCODE_CLIENT=desktop +#OPENCODE_PROJECT=/opencode # Ollama Cloud quota scraping. Prefer configuring this per connection in # Dashboard → Providers → Ollama Cloud. The cookie is sensitive. diff --git a/changelog.d/fixes/opencode-cli-headers-synthesis-on-by-default.md b/changelog.d/fixes/opencode-cli-headers-synthesis-on-by-default.md new file mode 100644 index 000000000000..9914ffaa362b --- /dev/null +++ b/changelog.d/fixes/opencode-cli-headers-synthesis-on-by-default.md @@ -0,0 +1 @@ +- **fix(providers):** OpenCode CLI identity-header synthesis is now ON by default for opencode/opencode-zen/opencode-go — sending the official fingerprint (`User-Agent: opencode/latest/1.18.18/cli`, `x-opencode-client: desktop`, `x-opencode-project: /opencode`, fresh request/session UUIDs) turns opencode.ai's free-tier `FreeUsageLimitError` 429 into a 200 on the same datacenter egress IP (live-verified: see PR #10357). Previously opt-in via `OPENCODE_SYNTHESIZE_CLI_HEADERS=true`, the default now synthesizes unless explicitly disabled with `OPENCODE_SYNTHESIZE_CLI_HEADERS=false`. Client-supplied `x-opencode-*` headers still win, EXCEPT User-Agent — a non-CLI client UA (e.g. curl, SDKs) is still replaced with the CLI UA because the free tier rejects generic client UAs from datacenter IPs. All values remain env-overridable (`OPENCODE_USER_AGENT` / `OPENCODE_CLIENT` / `OPENCODE_PROJECT`, or `_USER_AGENT`). Docs: `.env.example` updated to match. (#5997 follow-up) From 4f8453ee57fd1f6c7eee368cb65181f91bd38d89 Mon Sep 17 00:00:00 2001 From: Saeed Hosseini Date: Sat, 15 Aug 2026 22:35:59 +0330 Subject: [PATCH 3/4] fix(providers): conversation-stable opencode session id (prompt-cache friendly) Derive x-opencode-session from credentials.connectionId (sha256, shaped as a UUID) so consecutive requests of one upstream connection reuse a single session id instead of a fresh random UUID per call. Client-supplied session headers still win; no connectionId falls back to a fresh UUID (previous behavior, unchanged). Typecheck-clean. --- open-sse/executors/opencode.ts | 5 +++ open-sse/utils/opencodeHeaders.ts | 30 +++++++++++++--- ...pencode-cli-headers-synthesis-5997.test.ts | 34 +++++++++++++++++++ 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/open-sse/executors/opencode.ts b/open-sse/executors/opencode.ts index 85ce2f3f4ec4..53b4bd4f431c 100644 --- a/open-sse/executors/opencode.ts +++ b/open-sse/executors/opencode.ts @@ -393,6 +393,11 @@ export class OpencodeExecutor extends BaseExecutor { if (clientHeaders || cliDefaults) { forwardOpencodeClientHeaders(headers, clientHeaders ?? {}, { synthesizeRequestId: true, + // Derive a conversation-stable x-opencode-session from the upstream + // connection id (prompt-cache friendly; mirrors 9router#3321's + // connection-scoped session id). Client-supplied session headers still + // win; no connectionId → fresh UUID per request (previous behavior). + sessionSeed: credentials?.connectionId, cliDefaults, }); } diff --git a/open-sse/utils/opencodeHeaders.ts b/open-sse/utils/opencodeHeaders.ts index 81d9a9f79f8b..e540c42bd778 100644 --- a/open-sse/utils/opencodeHeaders.ts +++ b/open-sse/utils/opencodeHeaders.ts @@ -1,4 +1,4 @@ -import { randomUUID } from "crypto"; +import { createHash, randomUUID } from "crypto"; import { setUserAgentHeader } from "../executors/base.ts"; /** @@ -43,6 +43,11 @@ function findHeader(headers: Record, name: string): string | und * @param options.synthesizeRequestId - When true (OpencodeExecutor only), maps * x-session-affinity / x-session-id to x-opencode-session when the latter is * missing, and synthesizes a UUID for x-opencode-request if also missing. + * @param options.sessionSeed - When provided (OpencodeExecutor only), the + * synthesized x-opencode-session is derived from this stable seed + * (UUID-shaped hash) instead of a fresh random UUID, so consecutive requests + * of the same upstream connection reuse one session id (prompt-cache + * friendly). Client-supplied session headers still win. * @param options.cliDefaults - When provided (OpencodeExecutor only), synthesize * the OpenCode CLI identity headers that Cloudflare requires on VPS egress * (User-Agent, x-opencode-client, x-opencode-project) plus fresh request/session @@ -57,6 +62,7 @@ export function forwardOpencodeClientHeaders( clientHeaders: Record, options?: { synthesizeRequestId?: boolean; + sessionSeed?: string; cliDefaults?: { userAgent: string; client: string; project: string }; } ): void { @@ -98,10 +104,25 @@ export function forwardOpencodeClientHeaders( // 4. OpencodeExecutor-only: synthesize the OpenCode CLI identity Cloudflare expects // on VPS egress, for any key the client did not supply (#5997). if (options?.cliDefaults) { - applyCliDefaults(headers, options.cliDefaults); + applyCliDefaults(headers, options.cliDefaults, options.sessionSeed); } } +/** + * Derive a conversation-stable OpenCode session id from a stable seed, shaped + * as a UUID so existing consumers (and the session/request regex assertions in + * the #5997 regression tests) keep working. The same seed always yields the + * same id — consecutive requests of one upstream connection therefore reuse a + * single session id (prompt-cache friendly) instead of burning a fresh random + * UUID per call. Falls back to `randomUUID()` when no seed is available (the + * previous behavior, unchanged). + */ +function stableSessionId(seed: string | undefined): string { + if (!seed) return randomUUID(); + const hex = createHash("sha256").update(seed).digest("hex").slice(0, 32); + return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`; +} + /** * Fill the OpenCode CLI identity headers Cloudflare requires on VPS egress. For * x-opencode-* headers, client values always win (defaults only fill gaps). The @@ -113,7 +134,8 @@ export function forwardOpencodeClientHeaders( */ function applyCliDefaults( headers: Record, - cliDefaults: { userAgent: string; client: string; project: string } + cliDefaults: { userAgent: string; client: string; project: string }, + sessionSeed?: string ): void { const existingUa = headers["User-Agent"] || headers["user-agent"]; const clientUaIsCliLike = @@ -124,5 +146,5 @@ function applyCliDefaults( headers["x-opencode-client"] ||= cliDefaults.client; headers["x-opencode-project"] ||= cliDefaults.project; headers["x-opencode-request"] ||= randomUUID(); - headers["x-opencode-session"] ||= randomUUID(); + headers["x-opencode-session"] ||= stableSessionId(sessionSeed); } diff --git a/tests/unit/opencode-cli-headers-synthesis-5997.test.ts b/tests/unit/opencode-cli-headers-synthesis-5997.test.ts index 81197cfccc01..71b4d99c9521 100644 --- a/tests/unit/opencode-cli-headers-synthesis-5997.test.ts +++ b/tests/unit/opencode-cli-headers-synthesis-5997.test.ts @@ -91,6 +91,40 @@ test("forwardOpencodeClientHeaders: without cliDefaults, no synthesis (DefaultEx assert.equal(headers["x-opencode-project"], undefined); }); +test("forwardOpencodeClientHeaders: sessionSeed makes x-opencode-session stable per connection (UUID-shaped)", () => { + // Same seed → same session id (prompt-cache friendly), still UUID-shaped. + const a: Record = {}; + const b: Record = {}; + forwardOpencodeClientHeaders(a, {}, { cliDefaults: CLI_DEFAULTS, sessionSeed: "conn-1" }); + forwardOpencodeClientHeaders(b, {}, { cliDefaults: CLI_DEFAULTS, sessionSeed: "conn-1" }); + assert.match(a["x-opencode-session"] ?? "", UUID_RE); + assert.equal(a["x-opencode-session"], b["x-opencode-session"]); + + // Different seed → different session; request id stays fresh per request. + const c: Record = {}; + forwardOpencodeClientHeaders(c, {}, { cliDefaults: CLI_DEFAULTS, sessionSeed: "conn-2" }); + assert.notEqual(a["x-opencode-session"], c["x-opencode-session"]); + assert.notEqual(a["x-opencode-request"], c["x-opencode-request"]); + + // No seed → fresh UUID per call (previous behavior, unchanged). + const d: Record = {}; + const e: Record = {}; + forwardOpencodeClientHeaders(d, {}, { cliDefaults: CLI_DEFAULTS }); + forwardOpencodeClientHeaders(e, {}, { cliDefaults: CLI_DEFAULTS }); + assert.match(d["x-opencode-session"] ?? "", UUID_RE); + assert.notEqual(d["x-opencode-session"], e["x-opencode-session"]); +}); + +test("forwardOpencodeClientHeaders: client-supplied session always beats sessionSeed", () => { + const headers: Record = {}; + forwardOpencodeClientHeaders( + headers, + { "x-opencode-session": "sess-from-client" }, + { cliDefaults: CLI_DEFAULTS, sessionSeed: "conn-1" } + ); + assert.equal(headers["x-opencode-session"], "sess-from-client"); +}); + test("OpencodeExecutor.buildHeaders: synthesizes CLI defaults BY DEFAULT (flag unset) [#5997]", () => { withEnv("OPENCODE_SYNTHESIZE_CLI_HEADERS", undefined, () => { const executor = new OpencodeExecutor("opencode-go"); From bed6f8cb5ba8ff22fc6c2424fba64fb7cbc289a1 Mon Sep 17 00:00:00 2001 From: Saeed Hosseini Date: Tue, 18 Aug 2026 14:06:45 +0330 Subject: [PATCH 4/4] docs(providers): attach live VPS trace evidence for on-by-default OpenCode header synthesis Raw curl output from datacenter egress 45.39.60.14 (2026-08-18 10:35 UTC): headerless generic UA -> HTTP 429 FreeUsageLimitError; synthesized CLI identity headers (new defaults) -> HTTP 200 x3 with real SSE stream; generic UA again -> HTTP 429. Same IP end-to-end. --- ...ode-cli-headers-synthesis-on-by-default.md | 2 +- .../opencode-free-tier-429-200-2026-08-18.log | 330 ++++++++++++++++++ 2 files changed, 331 insertions(+), 1 deletion(-) create mode 100644 docs/live-validation/opencode-free-tier-429-200-2026-08-18.log diff --git a/changelog.d/fixes/opencode-cli-headers-synthesis-on-by-default.md b/changelog.d/fixes/opencode-cli-headers-synthesis-on-by-default.md index 9914ffaa362b..de7c18d9cccc 100644 --- a/changelog.d/fixes/opencode-cli-headers-synthesis-on-by-default.md +++ b/changelog.d/fixes/opencode-cli-headers-synthesis-on-by-default.md @@ -1 +1 @@ -- **fix(providers):** OpenCode CLI identity-header synthesis is now ON by default for opencode/opencode-zen/opencode-go — sending the official fingerprint (`User-Agent: opencode/latest/1.18.18/cli`, `x-opencode-client: desktop`, `x-opencode-project: /opencode`, fresh request/session UUIDs) turns opencode.ai's free-tier `FreeUsageLimitError` 429 into a 200 on the same datacenter egress IP (live-verified: see PR #10357). Previously opt-in via `OPENCODE_SYNTHESIZE_CLI_HEADERS=true`, the default now synthesizes unless explicitly disabled with `OPENCODE_SYNTHESIZE_CLI_HEADERS=false`. Client-supplied `x-opencode-*` headers still win, EXCEPT User-Agent — a non-CLI client UA (e.g. curl, SDKs) is still replaced with the CLI UA because the free tier rejects generic client UAs from datacenter IPs. All values remain env-overridable (`OPENCODE_USER_AGENT` / `OPENCODE_CLIENT` / `OPENCODE_PROJECT`, or `_USER_AGENT`). Docs: `.env.example` updated to match. (#5997 follow-up) +- **fix(providers):** OpenCode CLI identity-header synthesis is now ON by default for opencode/opencode-zen/opencode-go — sending the official fingerprint (`User-Agent: opencode/latest/1.18.18/cli`, `x-opencode-client: desktop`, `x-opencode-project: /opencode`, fresh request/session UUIDs) turns opencode.ai's free-tier `FreeUsageLimitError` 429 into a 200 on the same datacenter egress IP (live-verified: raw VPS trace attached at `docs/live-validation/opencode-free-tier-429-200-2026-08-18.log`). Previously opt-in via `OPENCODE_SYNTHESIZE_CLI_HEADERS=true`, the default now synthesizes unless explicitly disabled with `OPENCODE_SYNTHESIZE_CLI_HEADERS=false`. Client-supplied `x-opencode-*` headers still win, EXCEPT User-Agent — a non-CLI client UA (e.g. curl, SDKs) is still replaced with the CLI UA because the free tier rejects generic client UAs from datacenter IPs. All values remain env-overridable (`OPENCODE_USER_AGENT` / `OPENCODE_CLIENT` / `OPENCODE_PROJECT`, or `_USER_AGENT`). Docs: `.env.example` updated to match. (#5997 follow-up) diff --git a/docs/live-validation/opencode-free-tier-429-200-2026-08-18.log b/docs/live-validation/opencode-free-tier-429-200-2026-08-18.log new file mode 100644 index 000000000000..3f662657ee12 --- /dev/null +++ b/docs/live-validation/opencode-free-tier-429-200-2026-08-18.log @@ -0,0 +1,330 @@ +================================================================================ +# OpenCode live trace — 2026-08-18 10:35:29 UTC +# Egress IP: 45.39.60.14 +# Endpoint: https://opencode.ai/zen/v1/chat/completions +# Model: deepseek-v4-flash-free (free tier, no API key) +================================================================================ + +===== A1 generic-UA (curl/8.5.0, no CLI headers) @ 10:35:29.855 ===== +$ curl -s -i --max-time 30 \ + -H 'Content-Type: application/json' -H 'Accept: text/event-stream' \ + -H 'User-Agent: curl/8.5.0' \ + -d '{"model":"deepseek-v4-flash-free","messages":[{"role":"user","content":"ping"}],"stream":true}' https://opencode.ai/zen/v1/chat/completions +HTTP/2 429 +date: Tue, 18 Aug 2026 10:35:30 GMT +content-type: application/json +content-length: 143 +cf-placement: remote-ORD +server: cloudflare +cf-ray: a2d045a86a194dee-MCI + +{"type":"error","error":{"type":"FreeUsageLimitError","message":"Error from provider (Console): Rate limit exceeded. Please try again later."}} + +===== A2 generic-UA probe (rapid, no CLI headers) @ 10:35:30.436 ===== +HTTP 429 — FreeUsageLimitError (quota cap reached on this egress IP) + +===== B1 CLI-identity headers (new defaults) @ 10:35:30.469 ===== +$ curl -s -i --max-time 30 \ + -H 'Content-Type: application/json' -H 'Accept: text/event-stream' \ + -H 'User-Agent: opencode/latest/1.18.18/cli' \ + -H 'x-opencode-client: desktop' \ + -H 'x-opencode-project: /opencode' \ + -H 'x-opencode-request: da87cb3d-8625-44e6-b7f0-1e9bd2410c93' \ + -H 'x-opencode-session: 5a3eef29-e973-461b-8ae5-da13c476ab7a' \ + -d '{"model":"deepseek-v4-flash-free","messages":[{"role":"user","content":"ping"}],"stream":true}' https://opencode.ai/zen/v1/chat/completions +HTTP/2 200 +date: Tue, 18 Aug 2026 10:35:31 GMT +content-type: text/event-stream +cache-control: no-cache +cf-placement: remote-ORD +server: cloudflare +cf-ray: a2d045ac6a566d2b-MCI + +data: {"id":"0eae57c2-e5aa-4085-9111-0106f7da87f5","object":"chat.completion.chunk","created":1787049330,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"role":"assistant","content":null,"reasoning_content":""}}],"usage":null} + +data: {"id":"0eae57c2-e5aa-4085-9111-0106f7da87f5","object":"chat.completion.chunk","created":1787049330,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"We"}}],"usage":null} + +data: {"id":"0eae57c2-e5aa-4085-9111-0106f7da87f5","object":"chat.completion.chunk","created":1787049330,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" need"}}],"usage":null} + +data: {"id":"0eae57c2-e5aa-4085-9111-0106f7da87f5","object":"chat.completion.chunk","created":1787049330,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" answer"}}],"usage":null} + +data: {"id":"0eae57c2-e5aa-4085-9111-0106f7da87f5","object":"chat.completion.chunk","created":1787049330,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"."}}],"usage":null} + +data: {"id":"0eae57c2-e5aa-4085-9111-0106f7da87f5","object":"chat.completion.chunk","created":1787049330,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" User"}}],"usage":null} + +data: {"id":"0eae57c2-e5aa-4085-9111-0106f7da87f5","object":"chat.completion.chunk","created":1787049330,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" says"}}],"usage":null} + +data: {"id":"0eae57c2-e5aa-4085-9111-0106f7da87f5","object":"chat.completion.chunk","created":1787049330,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" \""}}],"usage":null} + +data: {"id":"0eae57c2-e5aa-4085-9111-0106f7da87f5","object":"chat.completion.chunk","created":1787049330,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"ping"}}],"usage":null} + +data: {"id":"0eae57c2-e5aa-4085-9111-0106f7da87f5","object":"chat.completion.chunk","created":1787049330,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"\"."}}],"usage":null} + +data: {"id":"0eae57c2-e5aa-4085-9111-0106f7da87f5","object":"chat.completion.chunk","created":1787049330,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" Need"}}],"usage":null} + +data: {"id":"0eae57c2-e5aa-4085-9111-0106f7da87f5","object":"chat.completion.chunk","created":1787049330,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" respond"}}],"usage":null} + +data: {"id":"0eae57c2-e5aa-4085-9111-0106f7da87f5","object":"chat.completion.chunk","created":1787049330,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" likely"}}],"usage":null} + +data: {"id":"0eae57c2-e5aa-4085-9111-0106f7da87f5","object":"chat.completion.chunk","created":1787049330,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" \""}}],"usage":null} + +data: {"id":"0eae57c2-e5aa-4085-9111-0106f7da87f5","object":"chat.completion.chunk","created":1787049330,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"p"}}],"usage":null} + +data: {"id":"0eae57c2-e5aa-4085-9111-0106f7da87f5","object":"chat.completion.chunk","created":1787049330,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"ong"}}],"usage":null} + +data: {"id":"0eae57c2-e5aa-4085-9111-0106f7da87f5","object":"chat.completion.chunk","created":1787049330,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"\""}}],"usage":null} + +data: {"id":"0eae57c2-e5aa-4085-9111-0106f7da87f5","object":"chat.completion.chunk","created":1787049330,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" or"}}],"usage":null} + +data: {"id":"0eae57c2-e5aa-4085-9111-0106f7da87f5","object":"chat.completion.chunk","created":1787049330,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" ask"}}],"usage":null} + +data: {"id":"0eae57c2-e5aa-4085-9111-0106f7da87f5","object":"chat.completion.chunk","created":1787049330,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"."}}],"usage":null} + +data: {"id":"0eae57c2-e5aa-4085-9111-0106f7da87f5","object":"chat.completion.chunk","created":1787049330,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" Minimal"}}],"usage":null} + +data: {"id":"0eae57c2-e5aa-4085-9111-0106f7da87f5","object":"chat.completion.chunk","created":1787049330,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"."}}],"usage":null} + +data: {"id":"0eae57c2-e5aa-4085-9111-0106f7da87f5","object":"chat.completion.chunk","created":1787049330,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":"p","reasoning_content":null}}],"usage":null} + +data: {"id":"0eae57c2-e5aa-4085-9111-0106f7da87f5","object":"chat.completion.chunk","created":1787049330,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":"ong","reasoning_content":null}}],"usage":null} + +data: {"id":"0eae57c2-e5aa-4085-9111-0106f7da87f5","object":"chat.completion.chunk","created":1787049330,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":"stop","logprobs":null,"delta":{"content":"","reasoning_content":null}}],"usage":{"prompt_tokens":84,"completion_tokens":24,"total_tokens":108,"prompt_cache_hit_tokens":0,"prompt_cache_miss_tokens":84,"prompt_tokens_details":{"cached_tokens":0},"completion_tokens_details":{"reasoning_tokens":21}}} + +data: [DONE] + +data: {"choices":[],"cost":"0"} + + + +===== B2 CLI-identity headers (new defaults) @ 10:35:31.924 ===== +$ curl -s -i --max-time 30 \ + -H 'Content-Type: application/json' -H 'Accept: text/event-stream' \ + -H 'User-Agent: opencode/latest/1.18.18/cli' \ + -H 'x-opencode-client: desktop' \ + -H 'x-opencode-project: /opencode' \ + -H 'x-opencode-request: 3a2dfc79-5979-4d8e-a116-7a4ee2ba19fa' \ + -H 'x-opencode-session: 9d3fb8ef-038f-47e7-a36d-eb28d5b9073a' \ + -d '{"model":"deepseek-v4-flash-free","messages":[{"role":"user","content":"ping"}],"stream":true}' https://opencode.ai/zen/v1/chat/completions +HTTP/2 200 +date: Tue, 18 Aug 2026 10:35:32 GMT +content-type: text/event-stream +cache-control: no-cache +cf-placement: remote-ORD +server: cloudflare +cf-ray: a2d045b5c9682bd9-MCI + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"role":"assistant","content":null,"reasoning_content":""}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"We"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" need"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" answer"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" user"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" says"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" \""}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"ping"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"\"."}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" Need"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" likely"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" respond"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" maybe"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" \""}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"p"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"ong"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"\""}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" or"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" explain"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"."}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" Need"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" infer"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"."}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" User"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" just"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" \""}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"ping"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"\""}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" maybe"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" wants"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" ping"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"-p"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"ong"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"."}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" As"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" assistant"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":","}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" respond"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" \""}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"p"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"ong"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"\""}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" maybe"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" with"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" em"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"oji"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"."}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" Need"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" concise"}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"."}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":"p","reasoning_content":null}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":"ong","reasoning_content":null}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":" 🏓","reasoning_content":null}}],"usage":null} + +data: {"id":"db658b26-a7df-4133-8c87-876acf92b28f","object":"chat.completion.chunk","created":1787049332,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":"stop","logprobs":null,"delta":{"content":"","reasoning_content":null}}],"usage":{"prompt_tokens":84,"completion_tokens":55,"total_tokens":139,"prompt_cache_hit_tokens":0,"prompt_cache_miss_tokens":84,"prompt_tokens_details":{"cached_tokens":0},"completion_tokens_details":{"reasoning_tokens":49}}} + +data: [DONE] + +data: {"choices":[],"cost":"0"} + + + +===== B3 CLI-identity headers (new defaults) @ 10:35:33.682 ===== +$ curl -s -i --max-time 30 \ + -H 'Content-Type: application/json' -H 'Accept: text/event-stream' \ + -H 'User-Agent: opencode/latest/1.18.18/cli' \ + -H 'x-opencode-client: desktop' \ + -H 'x-opencode-project: /opencode' \ + -H 'x-opencode-request: f974e15d-bec0-40ac-a4a4-2a1f000ddf4a' \ + -H 'x-opencode-session: 801edca8-d862-4bde-ac94-b06f7081fd59' \ + -d '{"model":"deepseek-v4-flash-free","messages":[{"role":"user","content":"ping"}],"stream":true}' https://opencode.ai/zen/v1/chat/completions +HTTP/2 200 +date: Tue, 18 Aug 2026 10:35:34 GMT +content-type: text/event-stream +cache-control: no-cache +cf-placement: remote-ORD +server: cloudflare +cf-ray: a2d045c0aa882dbb-MCI + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"role":"assistant","content":null,"reasoning_content":""}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"We"}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" need"}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" answer"}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" to"}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" user"}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"."}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" User"}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" just"}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" said"}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" \""}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"ping"}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"\""}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" likely"}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" testing"}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"."}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" Need"}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" respond"}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" maybe"}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" \""}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"p"}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"ong"}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"\""}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" or"}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" ask"}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"."}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" Keep"}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":" concise"}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":null,"reasoning_content":"."}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":"p","reasoning_content":null}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":null,"logprobs":null,"delta":{"content":"ong","reasoning_content":null}}],"usage":null} + +data: {"id":"fa68e5c4-8dd5-4c37-9b14-aff4f5a111f0","object":"chat.completion.chunk","created":1787049334,"model":"deepseek-v4-flash-free","choices":[{"index":0,"finish_reason":"stop","logprobs":null,"delta":{"content":"","reasoning_content":null}}],"usage":{"prompt_tokens":84,"completion_tokens":31,"total_tokens":115,"prompt_cache_hit_tokens":0,"prompt_cache_miss_tokens":84,"prompt_tokens_details":{"cached_tokens":0},"completion_tokens_details":{"reasoning_tokens":28}}} + +data: [DONE] + +data: {"choices":[],"cost":"0"} + + + +===== C1 generic-UA again (curl/8.5.0, after B) @ 10:35:35.165 ===== +$ curl -s -i --max-time 30 \ + -H 'Content-Type: application/json' -H 'Accept: text/event-stream' \ + -H 'User-Agent: curl/8.5.0' \ + -d '{"model":"deepseek-v4-flash-free","messages":[{"role":"user","content":"ping"}],"stream":true}' https://opencode.ai/zen/v1/chat/completions +HTTP/2 429 +date: Tue, 18 Aug 2026 10:35:35 GMT +content-type: application/json +content-length: 143 +cf-placement: remote-ORD +server: cloudflare +cf-ray: a2d045c9edf98c32-MCI + +{"type":"error","error":{"type":"FreeUsageLimitError","message":"Error from provider (Console): Rate limit exceeded. Please try again later."}}