feat(cache): stable per-project cache key + deterministic prefix for provider cache hits - #212
Conversation
|
Hey @lecommander, thanks for this. I looked into the caching docs (OpenAI + Anthropic) and tested a few things locally before reviewing. The two-layer framing makes sense, and I verified your reasoning against the official specs: A few things before we merge: 1. In .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))Same for the 2. Cache-key parity with the CLI plugin The plugin (
Could you do one of these so the parity claim is verifiable for everyone, not just your setup?
And a line in the PR body about when parity holds (CLI run from the first workspace root) would help. 3. Minor
One question: do you know if the Zen gateway actually reads Happy to merge once 1 and 2 are in. Nice find on the responses envelope dropping |
|
Thanks for the thorough review — all points addressed in fixup 1. Replaced all 4 sites ( 2. Cache-key parity verifiability — done
3. Minor — done
Question on They mirror
|
|
Nice work on the fixup @lecommander, thanks for moving fast on all three points. The reference doc plus the pinned hash test is exactly what I was hoping for, that closes the verifiability gap. On the three headers: let's drop them. Your own run shows the honest answer, gateway accepted them but you have no hit-rate evidence they do anything beyond what On CI: the failing check isn't your code, the editorconfig step failed downloading its binary from GitHub Releases (HTTP 500 + ECONNRESET, transient infra flake). Everything real passed: ESLint, Markdown, Prettier, Shell, TypeScript, tests. I'll re-run it on my side. Once that's green and the headers are dropped, I'm good to merge. |
…provider cache hits - headers.ts: replicate CLI plugin cache-key resolution (env override -> sha256(user@host:workspaceDir)) so CLI and VS Code share the same affinity bucket. Sets x-session-id/conversation_id/session_id headers and logs to context-cache-vscode.log when OPENCODE_CONTEXT_CACHE_DEBUG=1. Normalizes drive letter c:->C: on Windows for hash parity. - openai.ts: sort tools by name before mapping (VS Code registration order varies across reloads) and include prompt_cache_key / promptCacheKey in chat-completions + responses bodies. - anthropic.ts / google.ts: same deterministic tool sort. - schema.ts: sort Object.entries keys for deterministic JSON prefix bytes. - responsesRequest.ts: add promptCacheKey field and passthrough to prompt_cache_key in envelope (was whitelisted away). - chatPrep.ts: log [prefix-hash] per request (first 3 messages + sorted tool names) to measure prefix stability directly. Verified: 356 requests single hash f3410589..d63 byte-identical to CLI plugin; prefix hash f94cacd57f2e stable across 10 turns with growing history.
…ebug-gated prefix hash - Replace localeCompare with plain code-unit comparison (a<b) in openai/anthropic/google tool mappers and schema key sort — spec- guaranteed deterministic, not locale/ICU-dependent. - Extract hashRawCacheKey + normalizeDirForCacheKey (backslash->slash + drive-letter upper-case) so C:\a\b and C:/a/b hash identically; add docs/references/opencode-context-cache-reference.md pinning the CLI plugin precedence and a pinned SHA256 regression test (src/test/headers.test.ts). - Gate [prefix-hash] on OPENCODE_CONTEXT_CACHE_DEBUG, use static crypto import, sort tool names deterministically, log failures via deps.log instead of swallowing. Parity holds when CLI is run from first workspace root; multi-root or CLI from subfolder will diverge (documented).
94d93cd to
80b4a3d
Compare
|
Rebased onto latest Dropped the three headers — done
Merge conflicts resolved
|
|
Checked the rebase, this looks clean. Header drop is exactly what we agreed, the conflict resolution kept the upstream fixes (#220 output channel, Nothing left from the review. Merging now with a merge commit so your branch history stays intact. Thanks for the patience through the two review rounds, and for pinning the hash test, that doc + test combo is the part I'd point other contributors to as a template. |
Summary
Fixes two layers of cache misses between the opencode CLI and the VS Code extension.
Problem
Routing misses: The CLI loads plugins from
~/.config/opencode/plugins/(e.g.opencode-context-cache.mjs) on everychat.paramscall. The VS Code extension implements its own transport toopencode.ai/zenand never runs the CLI, so no plugin ever loads in VS Code. CLI and VS Code sessions used different affinity keys.Prefix misses: Even with a stable routing key, the provider compares prefix bytes. Tool registration order and JSON key order were non-deterministic, invalidating the prefix cache on every turn.
Changes
Layer 1 — Cache key parity (routing):
src/request/headers.ts: Replicate CLI plugin resolution (OPENCODE_PROMPT_CACHE_KEY->OPENCODE_STICKY_SESSION_ID->sha256(user@host:workspaceDir)). Setsx-session-id/conversation_id/session_idheaders. Logs tocontext-cache-vscode.logwhenOPENCODE_CONTEXT_CACHE_DEBUG=1. Normalizes drive letterc:->C:and separators\->/on Windows for hash parity. Extracts purehashRawCacheKey(raw)for testability.src/request/openai.ts: Includeprompt_cache_key/promptCacheKeyin chat-completions + responses bodies.src/responsesRequest.ts: AddpromptCacheKeyfield and passthrough toprompt_cache_keyin envelope (was whitelisted away).Layer 2 — Prefix stabilization (provider cache):
src/request/openai.ts/anthropic.ts/google.ts: Sort tools bynamewith plain code-unit comparison(a<b?-1:a>b?1:0)before mapping (VS Code registration order varies across reloads;localeCompareis locale/ICU-dependent).src/request/schema.ts: SortObject.entrieskeys with plain comparison for deterministic JSON prefix bytes.src/provider/chatPrep.ts: Log[prefix-hash]per request (first 3 messages + sorted tool names), gated onOPENCODE_CONTEXT_CACHE_DEBUG, staticcryptoimport, failures viadeps.log.Verifiability (added in fixup 94d93cd):
docs/references/opencode-context-cache-reference.md: Pins CLI plugin precedence + normalization + pinned hash, so parity is verifiable without local plugin file.src/test/headers.test.ts: PinshashRawCacheKey("testuser@testhost:C:/project") == 4f77e704edc190c1872f5ac84f42320d082a4cf4c52bfdacd2634db07de24120.Verification
f3410589..d63, byte-identical to CLI plugin for same workspace (after separator normalization).f94cacd57f2estable across 10 turns with growing history (454->472 messages).npm run compile/npm test(446 pass) /npm run lintall pass.Parity scope
Parity holds when CLI is run from the first workspace root (
process.cwd() === workspaceFolders[0].fsPathafter normalization). Multi-root workspaces or CLI invoked from a subfolder will diverge — expected limitation, documented in reference doc.Notes
prompt_cache_keyonly onchat-completions/responses(invalid on Anthropic/Google — headers cover those).OPENCODE_CONTEXT_CACHE_DEBUG=1requires full VS Code quit+relaunch (main process must inherit it, not just Reload Window).x-session-id/conversation_id/session_idmirror CLI plugin headers; gateway primary isx-opencode-session->x-session-affinity. Happy to drop the three if you confirm they are no-ops — no blocker.