Repository navigation
fix(opencode): session stability, free-tier routing, and CLI defaults - #10571
diegosouzapw merged 2 commits into
Conversation
- 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
|
The underlying problems (random session IDs breaking prompt-cache hits, header-synthesis being opt-in) look real, but this PR flips OPENCODE_SYNTHESIZE_CLI_HEADERS's default from off to on — a decision the existing test file documents as deliberate and owner-requested to stay off pending live validation — and breaks 2 of that file's assertions (I ran them: |
…rage PR cheaper-inference#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 cheaper-inference#5997 regression test and no coverage for the new session-fingerprinting, free-tier routing, or noAuth echoModel logic (Hard Rule cheaper-inference#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 cheaper-inference#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 cheaper-inference#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>
9222528
into
cheaper-inference:release/v3.8.50
…e only filler Review follow-up (PR cheaper-inference#12719): under the default configuration applyCliDefaults (step 4, cheaper-inference#10571) backfills x-opencode-session, so the step-3 gap this PR fixes is only observable when an operator sets OPENCODE_SYNTHESIZE_CLI_HEADERS=false — step 4 never runs there and step 3 is the only filler left. The new test isolates exactly that scenario and fails on the unpatched tip (verified: 10/11 pass there, only this test red), proving it pins the fix.
…cheaper-inference#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 cheaper-inference#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 cheaper-inference#5997 regression test and no coverage for the new session-fingerprinting, free-tier routing, or noAuth echoModel logic (Hard Rule cheaper-inference#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 cheaper-inference#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 cheaper-inference#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>
…y synthesis
The keyless OpenCode free tier (opencode.ai/zen/v1) refuses any request that
does not match the OpenCode client contract with 403 FreeTierError
("OpenCode's free tier can only be used from within OpenCode"). Port the
upstream stack (release/v3.8.52) onto the fork:
- opencodeFreeTierContract.ts: gate the keyless models, force the streaming
flag, declare an observed tool list, assemble the CLI identity defaults.
- opencodeRequestShape.ts / opencodeToolObservation.ts: request shaping for
the gate (streaming shape, tool names learned from compliant callers).
- opencodeSessionIdentity.ts / opencodeCliVersion.ts: canonical ses_/msg_
identity and opencode/<version> UA (>= 1.17, default 1.18.31).
- opencodeHeaders.ts: CLI header synthesis is ON by default (cheaper-inference#10571); a
client value wins and non-canonical sessions are translated one-to-one.
- base.ts: buildHeaders now receives the request body (gating needs it).
- executorClientHeaders.ts + chatCore.ts: preserve native conversation
identity before body translation, strip lease-control headers (cheaper-inference#14390).
- opencodeFreeTierRetry.ts + opencodeRequestContext.ts: retry a refusal once
with the observed tool list.
- opencode.ts: integrate the contract, send Accept: text/event-stream for
gated models, clean stream_options on non-streaming shapes (cheaper-inference#13819),
retry refusals on the fast path and during account rotation.
Ported from upstream PRs cheaper-inference#14013, cheaper-inference#10571, cheaper-inference#14390, cheaper-inference#13819.
OPENCODE_SYNTHESIZE_CLI_HEADERS is now ON by default (cheaper-inference#10571) and the keyless request contract can be tuned per install. Document the new defaults (OPENCODE_USER_AGENT=opencode/1.18.31, OPENCODE_CLIENT=desktop, OPENCODE_PROJECT=global) plus OPENCODE_FREE_TIER_REQUEST_CONTRACT and OPENCODE_FREE_TIER_PLACEHOLDER_TOOLS in .env.example and docs/reference/ENVIRONMENT.md.
Summary
Fix OpenCode provider defaults: stable session IDs for prompt caching, correct free-tier model routing, and CLI header synthesis enabled by default.
Problem
Three issues affecting OpenCode provider reliability on VPS deployments:
x-opencode-sessionwas set torandomUUID()per request, so opencode.ai's upstream prompt cache never hit across a conversationbig-pickleand*-freecould be incorrectly routed to non-opencode providers when the live catalog was temporarily unreachableFix
generateSessionId()fromsessionManager.tsintoopencodeHeaders.ts— session ID is now a deterministic SHA-256 fingerprint of model + system prompt + first user message + toolsbuildHeaders()in base executor andOpencodeExecutoroverride so the fingerprint has access to conversation contextOPENCODE_SYNTHESIZE_CLI_HEADERS=false), with production-proven defaults (opencode/desktop/global)response.modelmatches/v1/modelslistingResults
big-pickle,*-free) correctly route to opencode when availableAI Assisted changes notice
This change was developed with AI assistance (ZCode agent).