[needs-vps] fix(providers): synthesize OpenCode CLI identity headers by default - #10357
saeedhosseiinii wants to merge 4 commits into
Conversation
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, <PROVIDER>_USER_AGENT) - Update regression tests for the new default
2f960b3 to
b2373e9
Compare
|
Hi Saeed, thanks for digging into the opencode-go/opencode-zen 429 issue and for updating the test suite alongside the change — the code itself is clean and the 8 updated/added tests in Before this can merge, I want to flag one thing that needs to be resolved rather than fixed-in-place: the PR's premise is that sending the CLI identity headers by default turns the Two smaller things worth fixing alongside that:
Happy to take another look once there's a live confirmation and the docs/changelog are in sync — the mechanism itself (opt-out via |
… 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 diegosouzapw#10357)
|
@diegosouzapw — thanks for the thorough review! I've addressed all three points and added live before/after evidence. 1. Live 429 → 200 validation on a real VPS (same egress IP)I ran a fresh trace from this box (datacenter VPS, fixed IP). After a few rapid requests the free-tier quota hit its cap and headerless requests returned 429 2.
|
…ouzapw#10357) Raw before/after on a single datacenter egress IP (45.39.60.14): generic client UA 429s (FreeUsageLimitError) while the synthesized OpenCode CLI identity headers return 200 on the same IP.
|
@diegosouzapw — full raw trace attached. Same egress IP end-to-end on a fixed datacenter VPS. Same IP end-to-end: generic UA 429s ( Full raw trace (headers + response bodies, timestamped) |
fa9d15f to
50ecfb4
Compare
… 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.
d236021 to
4f8453e
Compare
|
Hi Saeed, the live before/after trace you published on this PR is now recorded as the required environment evidence: the same datacenter egress produced 429 without the synthesized identity headers and repeated 200 responses with them, with the generic request returning to 429 afterward. I am keeping this PR in the release drain with |
|
The PR is now parked in the release drain with |
|
The header-precedence and session-id-stability logic here is clean and well covered (10/10 unit tests pass locally, no new lint issues). The part that can't be validated from a unit test is exactly the part this PR changes: flipping OPENCODE_SYNTHESIZE_CLI_HEADERS to on-by-default reverses a deliberate prior decision (see the comment you removed, tied to the #5720 regression where a wrong default header value got requests rejected upstream). The changelog entry says 'live-verified' but there's no attached evidence — could you attach the actual VPS command/output that confirms the new defaults (opencode/latest/1.18.18/cli / desktop / /opencode) turn the free-tier 429 into a 200 on a real datacenter egress IP? That's the mandatory smoke test before this can merge (tagged needs-vps, matches your own PR title). Also flagging: PR #10571 (fix/opencode-defaults) independently flips this exact same default (with a different project value, plus additional session-id-fingerprint and free-tier-routing fixes). Worth reconciling with that PR so we don't land two different literal defaults for the same knob back to back. |
…rage PR diegosouzapw#10571 flipped OPENCODE_SYNTHESIZE_CLI_HEADERS to on-by-default and changed the synthesized UA/client/project default values, but shipped with 2 broken assertions in the existing diegosouzapw#5997 regression test and no coverage for the new session-fingerprinting, free-tier routing, or noAuth echoModel logic (Hard Rule diegosouzapw#18). - Update tests/unit/opencode-cli-headers-synthesis-5997.test.ts to match the new on-by-default behavior and new default values; add an explicit opt-out coverage test so the forward-only path is still guarded. - Fix 20 further test failures in tests/unit/opencode-executor.test.ts and tests/unit/refactor-buildHeaders-opencode.test.ts caused by the same default flip (pin OPENCODE_SYNTHESIZE_CLI_HEADERS=false for the characterization suites that predate diegosouzapw#10571; use a genuinely CLI-looking UA where the preserved-UA test requires one). - Fix a real bug found via TDD while adding the mandated free-tier routing regression test: the big-pickle/*-free short-circuit in open-sse/services/model.ts checked activeProviders?.has("opencode") literally, but getActiveProviderSet() canonicalizes every connection's provider id through resolveProviderAlias(), which rewrites "opencode" to "opencode-zen" via a manual override — so an active no-auth opencode connection could never satisfy the check. Now checks both opencode-family candidate ids. Proven with a test that fails on the original code and passes with the fix (both connections active with a stale synced catalog omitting big-pickle). - Extract the noAuth-provider echoModel aliasing in chatCore.ts into a pure, directly-testable helper (open-sse/handlers/chatCore/noAuthEchoModel.ts), matching the existing chatCore god-file decomposition pattern. - Add regression tests for generateSessionId()-based x-opencode-session fingerprinting (stable within a conversation, changes on model/message changes), the free-tier routing short-circuit, and the noAuth echoModel aliasing. - Add the changelog.d/ fragment and sync docs/reference/ENVIRONMENT.md's OPENCODE_SYNTHESIZE_CLI_HEADERS/OPENCODE_USER_AGENT/OPENCODE_CLIENT/ OPENCODE_PROJECT rows to the new defaults. Does NOT resolve whether flipping OPENCODE_SYNTHESIZE_CLI_HEADERS's default was the right call, and does NOT touch the separate open PR diegosouzapw#10357 which flips the same flag with a different literal default value - that decision is left to the maintainer at merge time. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
…nCode 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.
|
@diegosouzapw — smoke test done; raw evidence is now attached to this PR. New commit Live trace — same egress IP end-to-end (
The interleaving is the point: on the same datacenter egress, headerless requests 429 (IP-quota per #9611) while the synthesized CLI identity headers — the exact new literals this PR ships — return 200, and the generic request reverts to 429 immediately after. Cloudflare Re #10571 (
If it's cleaner, I'm happy to rebase ours onto #10571 once it's merged (or fold the session approach into theirs) — just say which way you want the reconciliation to go. |
…#10571) * fix(opencode): session stability, free-tier routing, and CLI defaults - Wire generateSessionId() into opencodeHeaders so x-opencode-session is a deterministic fingerprint instead of randomUUID() per request, enabling upstream prompt caching across a conversation - Thread request body through buildHeaders() so session fingerprint has access to model, system, messages, and tools - Default CLI header synthesis to ON (opt-out via false), align values with 9router proven defaults (opencode/desktop/global) - Auto-echo listing-valid model names for noAuth providers so response.model matches /v1/models listing - Short-circuit free-tier model resolution to opencode provider first to prevent prefix inference misrouting when catalog is unreachable * fix(opencode): make free-tier default flip self-consistent + add coverage PR #10571 flipped OPENCODE_SYNTHESIZE_CLI_HEADERS to on-by-default and changed the synthesized UA/client/project default values, but shipped with 2 broken assertions in the existing #5997 regression test and no coverage for the new session-fingerprinting, free-tier routing, or noAuth echoModel logic (Hard Rule #18). - Update tests/unit/opencode-cli-headers-synthesis-5997.test.ts to match the new on-by-default behavior and new default values; add an explicit opt-out coverage test so the forward-only path is still guarded. - Fix 20 further test failures in tests/unit/opencode-executor.test.ts and tests/unit/refactor-buildHeaders-opencode.test.ts caused by the same default flip (pin OPENCODE_SYNTHESIZE_CLI_HEADERS=false for the characterization suites that predate #10571; use a genuinely CLI-looking UA where the preserved-UA test requires one). - Fix a real bug found via TDD while adding the mandated free-tier routing regression test: the big-pickle/*-free short-circuit in open-sse/services/model.ts checked activeProviders?.has("opencode") literally, but getActiveProviderSet() canonicalizes every connection's provider id through resolveProviderAlias(), which rewrites "opencode" to "opencode-zen" via a manual override — so an active no-auth opencode connection could never satisfy the check. Now checks both opencode-family candidate ids. Proven with a test that fails on the original code and passes with the fix (both connections active with a stale synced catalog omitting big-pickle). - Extract the noAuth-provider echoModel aliasing in chatCore.ts into a pure, directly-testable helper (open-sse/handlers/chatCore/noAuthEchoModel.ts), matching the existing chatCore god-file decomposition pattern. - Add regression tests for generateSessionId()-based x-opencode-session fingerprinting (stable within a conversation, changes on model/message changes), the free-tier routing short-circuit, and the noAuth echoModel aliasing. - Add the changelog.d/ fragment and sync docs/reference/ENVIRONMENT.md's OPENCODE_SYNTHESIZE_CLI_HEADERS/OPENCODE_USER_AGENT/OPENCODE_CLIENT/ OPENCODE_PROJECT rows to the new defaults. Does NOT resolve whether flipping OPENCODE_SYNTHESIZE_CLI_HEADERS's default was the right call, and does NOT touch the separate open PR #10357 which flips the same flag with a different literal default value - that decision is left to the maintainer at merge time. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
…diegosouzapw#10571) * fix(opencode): session stability, free-tier routing, and CLI defaults - Wire generateSessionId() into opencodeHeaders so x-opencode-session is a deterministic fingerprint instead of randomUUID() per request, enabling upstream prompt caching across a conversation - Thread request body through buildHeaders() so session fingerprint has access to model, system, messages, and tools - Default CLI header synthesis to ON (opt-out via false), align values with 9router proven defaults (opencode/desktop/global) - Auto-echo listing-valid model names for noAuth providers so response.model matches /v1/models listing - Short-circuit free-tier model resolution to opencode provider first to prevent prefix inference misrouting when catalog is unreachable * fix(opencode): make free-tier default flip self-consistent + add coverage PR diegosouzapw#10571 flipped OPENCODE_SYNTHESIZE_CLI_HEADERS to on-by-default and changed the synthesized UA/client/project default values, but shipped with 2 broken assertions in the existing diegosouzapw#5997 regression test and no coverage for the new session-fingerprinting, free-tier routing, or noAuth echoModel logic (Hard Rule diegosouzapw#18). - Update tests/unit/opencode-cli-headers-synthesis-5997.test.ts to match the new on-by-default behavior and new default values; add an explicit opt-out coverage test so the forward-only path is still guarded. - Fix 20 further test failures in tests/unit/opencode-executor.test.ts and tests/unit/refactor-buildHeaders-opencode.test.ts caused by the same default flip (pin OPENCODE_SYNTHESIZE_CLI_HEADERS=false for the characterization suites that predate diegosouzapw#10571; use a genuinely CLI-looking UA where the preserved-UA test requires one). - Fix a real bug found via TDD while adding the mandated free-tier routing regression test: the big-pickle/*-free short-circuit in open-sse/services/model.ts checked activeProviders?.has("opencode") literally, but getActiveProviderSet() canonicalizes every connection's provider id through resolveProviderAlias(), which rewrites "opencode" to "opencode-zen" via a manual override — so an active no-auth opencode connection could never satisfy the check. Now checks both opencode-family candidate ids. Proven with a test that fails on the original code and passes with the fix (both connections active with a stale synced catalog omitting big-pickle). - Extract the noAuth-provider echoModel aliasing in chatCore.ts into a pure, directly-testable helper (open-sse/handlers/chatCore/noAuthEchoModel.ts), matching the existing chatCore god-file decomposition pattern. - Add regression tests for generateSessionId()-based x-opencode-session fingerprinting (stable within a conversation, changes on model/message changes), the free-tier routing short-circuit, and the noAuth echoModel aliasing. - Add the changelog.d/ fragment and sync docs/reference/ENVIRONMENT.md's OPENCODE_SYNTHESIZE_CLI_HEADERS/OPENCODE_USER_AGENT/OPENCODE_CLIENT/ OPENCODE_PROJECT rows to the new defaults. Does NOT resolve whether flipping OPENCODE_SYNTHESIZE_CLI_HEADERS's default was the right call, and does NOT touch the separate open PR diegosouzapw#10357 which flips the same flag with a different literal default value - that decision is left to the maintainer at merge time. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
…diegosouzapw#10571) * fix(opencode): session stability, free-tier routing, and CLI defaults - Wire generateSessionId() into opencodeHeaders so x-opencode-session is a deterministic fingerprint instead of randomUUID() per request, enabling upstream prompt caching across a conversation - Thread request body through buildHeaders() so session fingerprint has access to model, system, messages, and tools - Default CLI header synthesis to ON (opt-out via false), align values with 9router proven defaults (opencode/desktop/global) - Auto-echo listing-valid model names for noAuth providers so response.model matches /v1/models listing - Short-circuit free-tier model resolution to opencode provider first to prevent prefix inference misrouting when catalog is unreachable * fix(opencode): make free-tier default flip self-consistent + add coverage PR diegosouzapw#10571 flipped OPENCODE_SYNTHESIZE_CLI_HEADERS to on-by-default and changed the synthesized UA/client/project default values, but shipped with 2 broken assertions in the existing diegosouzapw#5997 regression test and no coverage for the new session-fingerprinting, free-tier routing, or noAuth echoModel logic (Hard Rule diegosouzapw#18). - Update tests/unit/opencode-cli-headers-synthesis-5997.test.ts to match the new on-by-default behavior and new default values; add an explicit opt-out coverage test so the forward-only path is still guarded. - Fix 20 further test failures in tests/unit/opencode-executor.test.ts and tests/unit/refactor-buildHeaders-opencode.test.ts caused by the same default flip (pin OPENCODE_SYNTHESIZE_CLI_HEADERS=false for the characterization suites that predate diegosouzapw#10571; use a genuinely CLI-looking UA where the preserved-UA test requires one). - Fix a real bug found via TDD while adding the mandated free-tier routing regression test: the big-pickle/*-free short-circuit in open-sse/services/model.ts checked activeProviders?.has("opencode") literally, but getActiveProviderSet() canonicalizes every connection's provider id through resolveProviderAlias(), which rewrites "opencode" to "opencode-zen" via a manual override — so an active no-auth opencode connection could never satisfy the check. Now checks both opencode-family candidate ids. Proven with a test that fails on the original code and passes with the fix (both connections active with a stale synced catalog omitting big-pickle). - Extract the noAuth-provider echoModel aliasing in chatCore.ts into a pure, directly-testable helper (open-sse/handlers/chatCore/noAuthEchoModel.ts), matching the existing chatCore god-file decomposition pattern. - Add regression tests for generateSessionId()-based x-opencode-session fingerprinting (stable within a conversation, changes on model/message changes), the free-tier routing short-circuit, and the noAuth echoModel aliasing. - Add the changelog.d/ fragment and sync docs/reference/ENVIRONMENT.md's OPENCODE_SYNTHESIZE_CLI_HEADERS/OPENCODE_USER_AGENT/OPENCODE_CLIENT/ OPENCODE_PROJECT rows to the new defaults. Does NOT resolve whether flipping OPENCODE_SYNTHESIZE_CLI_HEADERS's default was the right call, and does NOT touch the separate open PR diegosouzapw#10357 which flips the same flag with a different literal default value - that decision is left to the maintainer at merge time. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
|
Obrigado pela contribuição! Ao revisar, encontrei que este PR está subsumido pelo #10571 ("fix(opencode): session stability, free-tier routing, and CLI defaults"), já mergeado em Evidência — o Isso resolve exatamente o mesmo problema (#5997) que este PR ataca. A única diferença remanescente é cosmética: os valores padrão diferem ( |
…diegosouzapw#10571) * fix(opencode): session stability, free-tier routing, and CLI defaults - Wire generateSessionId() into opencodeHeaders so x-opencode-session is a deterministic fingerprint instead of randomUUID() per request, enabling upstream prompt caching across a conversation - Thread request body through buildHeaders() so session fingerprint has access to model, system, messages, and tools - Default CLI header synthesis to ON (opt-out via false), align values with 9router proven defaults (opencode/desktop/global) - Auto-echo listing-valid model names for noAuth providers so response.model matches /v1/models listing - Short-circuit free-tier model resolution to opencode provider first to prevent prefix inference misrouting when catalog is unreachable * fix(opencode): make free-tier default flip self-consistent + add coverage PR diegosouzapw#10571 flipped OPENCODE_SYNTHESIZE_CLI_HEADERS to on-by-default and changed the synthesized UA/client/project default values, but shipped with 2 broken assertions in the existing diegosouzapw#5997 regression test and no coverage for the new session-fingerprinting, free-tier routing, or noAuth echoModel logic (Hard Rule diegosouzapw#18). - Update tests/unit/opencode-cli-headers-synthesis-5997.test.ts to match the new on-by-default behavior and new default values; add an explicit opt-out coverage test so the forward-only path is still guarded. - Fix 20 further test failures in tests/unit/opencode-executor.test.ts and tests/unit/refactor-buildHeaders-opencode.test.ts caused by the same default flip (pin OPENCODE_SYNTHESIZE_CLI_HEADERS=false for the characterization suites that predate diegosouzapw#10571; use a genuinely CLI-looking UA where the preserved-UA test requires one). - Fix a real bug found via TDD while adding the mandated free-tier routing regression test: the big-pickle/*-free short-circuit in open-sse/services/model.ts checked activeProviders?.has("opencode") literally, but getActiveProviderSet() canonicalizes every connection's provider id through resolveProviderAlias(), which rewrites "opencode" to "opencode-zen" via a manual override — so an active no-auth opencode connection could never satisfy the check. Now checks both opencode-family candidate ids. Proven with a test that fails on the original code and passes with the fix (both connections active with a stale synced catalog omitting big-pickle). - Extract the noAuth-provider echoModel aliasing in chatCore.ts into a pure, directly-testable helper (open-sse/handlers/chatCore/noAuthEchoModel.ts), matching the existing chatCore god-file decomposition pattern. - Add regression tests for generateSessionId()-based x-opencode-session fingerprinting (stable within a conversation, changes on model/message changes), the free-tier routing short-circuit, and the noAuth echoModel aliasing. - Add the changelog.d/ fragment and sync docs/reference/ENVIRONMENT.md's OPENCODE_SYNTHESIZE_CLI_HEADERS/OPENCODE_USER_AGENT/OPENCODE_CLIENT/ OPENCODE_PROJECT rows to the new defaults. Does NOT resolve whether flipping OPENCODE_SYNTHESIZE_CLI_HEADERS's default was the right call, and does NOT touch the separate open PR diegosouzapw#10357 which flips the same flag with a different literal default value - that decision is left to the maintainer at merge time. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Problem
On a datacenter VPS, opencode.ai free-tier requests (opencode-go/opencode-zen) return
FreeUsageLimitError(429) when they lack OpenCode CLI identity headers. The synthesis existed but was opt-in viaOPENCODE_SYNTHESIZE_CLI_HEADERS=true, so the default behavior still 429s.Fix
Synthesis is now ON by default; sending the official CLI fingerprint headers (dynamic session/request ids, project, CLI User-Agent) turns the 429 into a 200.
OPENCODE_SYNTHESIZE_CLI_HEADERS=falseUser-Agent: opencode/latest/1.18.18/cli,x-opencode-client: desktop,x-opencode-project: /opencodeOPENCODE_USER_AGENT/OPENCODE_CLIENT/OPENCODE_PROJECT, or<PROVIDER>_USER_AGENT)Tests
tests/unit/opencode-cli-headers-synthesis-5997.test.tsupdated: default-unset now expects synthesis; added a case for=falsedisabling it