Uh oh!
There was an error while loading. Please reload this page.
feat(opencode): capture cache-diagnostics and usage cache accounting per request - #159
feat(opencode): capture cache-diagnostics and usage cache accounting per request#159iceteaSA wants to merge 1 commit into
Conversation
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
All reported issues were addressed across 5 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
All reported issues were addressed
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
All reported issues were addressed across 3 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Uh oh!
There was an error while loading. Please reload this page.
iceteaSA
commented
Aug 20, 2026
Triaged all bot findings against the code; Fixed in
Refuted, with the code path:
Declined:
|
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
All reported issues were addressed across 5 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
iceteaSA
commented
Aug 20, 2026
Second round was right on both counts —
|
7b45b0f to
fc2073bCompareUh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
All reported issues were addressed across 3 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
All reported issues were addressed across 3 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
3b9f126 to
57171fbCompareiceteaSA
commented
Aug 20, 2026
Greptile's summary was right — All four per-chunk consumers now share identical drain-before-cap semantics. If the repetition bothers anyone at merge time, the structural follow-up is one shared bounded-drain helper — happy to do that as a separate PR rather than grow this one. |
Uh oh!
There was an error while loading. Please reload this page.
57171fb to
a8eea4bCompareUh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
a8eea4b to
5f3d05eCompareUh oh!
There was an error while loading. Please reload this page.
5f3d05e to
c1b7101Compare…synthetic turn Adds an OpenCode-only /claude-start command: queue one synthetic one-token turn through the current session's normal model, agent, variant, quota, routing, cache, relay, signing, and response pipeline, so a resumed session's prompt cache is re-warmed and its TTL clock refreshed without an operator turn. The synthetic prompt ([lane start] - automated cache warm; no response needed.) is injected via the session SDK with synthetic:true and the session's context resolved from message history; OpenCode assembles the request exactly as a real turn, so the warm is byte-exact by construction. The fetch layer correlates the request by its synthetic message id (exact-match, one-shot, session-scoped, bounded) and shapes it fail-closed: max_tokens 1, thinking stripped, streaming kept. The terminal max_tokens stop is rewritten to end_turn for the correlated turn only, so the session records a clean micro-turn. CacheKeep adopts the session afterward; dumps tag start requests -start- on direct and relay paths; diagnostics records carry source:start with synthetic:true. API-key routes are unreachable by all of this. /claude-start off is persisted (and logged to the command audit trail); automatic is reserved and replies honestly that it is not yet wired. Verified live on a ~460K-token session: start read=459,602 write=419; next real turn read=460,021 = start read + write exactly - shaping does not fork the cache key. ~$0.48 per start vs ~$9.26 for a cold rewrite. Stacked on the cache-diagnostics capture branch (cortexkit#159).
…per request The plugin has sent cache-diagnosis-2026-04-07 in its beta list on every request without enabling the feature: the beta requires a request-body opt-in (diagnostics.previous_message_id), and nothing sent it or read the response. Turn the dormant channel on, measure-only. Request side: eligible OAuth requests gain the diagnostics opt-in inside the fail-closed rewriteRequestBody pipeline. The id sent is only ever one captured from a prior Anthropic response (bounded per-session tracker) - opencode mints its own msg_01-shaped ids for every provider, and a foreign id fails silently as previous_message_not_found. Response side: the existing SSE wrapper exposes message_start.message through a typed callback; per valid eligible response one MC-CACHE-DIAG single-line JSON record (schema v:2) is emitted via the logger with verbatim usage, TTL-bucket accounting, diag_state (absent|server_null|pending|populated, always a string), populated cache_miss_reason.type, and attribution fields: source (open set) + synthetic (closed machinery/traffic split with published mapping), account_id (opaque persisted identifier; consumer timelines key on (account_id, prefix) because sticky routing migrates sessions across account-scoped caches), betas_hash (xxh64 of the sorted sent beta list, resolved by a once-per-hash MC-CACHE-DIAG-BETAS side-channel line), and requested_model present only on request/served divergence. Dumps: responses gain artifacts (status/id/model/usage/diagnostics, never content); CacheKeep prewarms are dumped tagged -prewarm-cachekeep- and emit records through the same chain, since any write resets the server TTL clock. Canary: short-gap previous_message_not_found with a genuinely-sent id logs a warn - the id capture broke, not a fingerprint expiry. Every per-chunk stateful consumer in the response wrapper is bounded at 8 MiB with drain-before-cap semantics; complete frames are processed before overflow passthrough engages. Verified live: null(write=24831) -> null(read=24831, hit) -> system_changed(cache_missed_input_tokens=23963) on a forced system change against a warm prefix. Comparison engages only on cacheable requests; consumers classify hit-first (documented in README, which is the record contract). Closescortexkit#157
c1b7101 to
b5d77c3Compare…synthetic turn Adds an OpenCode-only /claude-start command: queue one synthetic one-token turn through the current session's normal model, agent, variant, quota, routing, cache, relay, signing, and response pipeline, so a resumed session's prompt cache is re-warmed and its TTL clock refreshed without an operator turn. The synthetic prompt ([lane start] - automated cache warm; no response needed.) is injected via the session SDK with synthetic:true and the session's context resolved from message history; OpenCode assembles the request exactly as a real turn, so the warm is byte-exact by construction. The fetch layer correlates the request by its synthetic message id (exact-match, one-shot, session-scoped, bounded) and shapes it fail-closed: max_tokens 1, thinking stripped, streaming kept. The terminal max_tokens stop is rewritten to end_turn for the correlated turn only, so the session records a clean micro-turn. CacheKeep adopts the session afterward; dumps tag start requests -start- on direct and relay paths; diagnostics records carry source:start with synthetic:true. API-key routes are unreachable by all of this. /claude-start off is persisted (and logged to the command audit trail); automatic is reserved and replies honestly that it is not yet wired. Verified live on a ~460K-token session: start read=459,602 write=419; next real turn read=460,021 = start read + write exactly - shaping does not fork the cache key. ~$0.48 per start vs ~$9.26 for a cold rewrite. Stacked on the cache-diagnostics capture branch (cortexkit#159).
Closes#157.
The plugin has sent
cache-diagnosis-2026-04-07in its beta list on every request without ever enabling the feature: the beta requires a request-body opt-in (diagnostics.previous_message_id), and nothing sent it or read the response. This PR turns the dormant channel on, measure-only.What it does
Request side: eligible requests (OAuth routes whose beta set already includes the diagnosis beta) gain
"diagnostics": {"previous_message_id": <captured id | null>}inside the existing fail-closedrewriteRequestBodypipeline. API-key fallback routes are untouched. The id sent is only ever one captured from a prior Anthropic response — a bounded per-session tracker holds it. That distinction matters: opencode mints its ownmsg_01…-shaped message ids, and sending one of those fails silently asprevious_message_not_found, which the API also uses for benign fingerprint expiry. The tracker asserts identity (send-what-was-captured), never format.Response side: the existing SSE wrapper exposes
message_start.messagethrough a new typed callback (same pattern as the current fallback-outcome callback; emitted bytes untouched). Per valid eligible response, one machine-parseable record is emitted through the existing logger:Record schema
v:2(the README table is the contract, settled with its first external consumer):diag_state∈absent | server_null | pending | populated(always a string, never JSON null),miss_reason/cache_missed_input_tokensonly when populated. No derived hit/miss flags, no normalization.source(open set; known valuesturn,prewarm_cachekeep) withsyntheticas the closed-form machinery/traffic split (published mapping, synthetic wins on conflict, mismatch logs a warn),account_id(opaque persisted identifier — cache entries are account-scoped and sticky routing migrates sessions, so consumer timelines key on(account_id, prefix)),betas_hash(xxh64 of the sorted sent beta list —unavailablefires on beta-set changes), andrequested_modelpresent only when the request-body model differs from the served model (labels recovery/fallback switches).betas_hashper process emits oneMC-CACHE-DIAG-BETAS {"hash","betas":[…]}resolution line. The trailing space in theMC-CACHE-DIAGdelimiter is load-bearing (it is what keeps the two line types distinct);grep -c "MC-CACHE-DIAG"without it over-counts.Dumps: when dumping is enabled, responses get an artifact (status, id, model, usage, diagnostics — never content blocks), and CacheKeep prewarm requests are dumped too, tagged
-prewarm-cachekeep-so traffic censuses can exclude keepalive noise. Prewarms also send the opt-in and emit records (source: "prewarm_cachekeep") — the server's TTL clock runs from the last write by any caller, so an unrecorded prewarm would be an invisible clock reset.Canary:
previous_message_not_foundon a short-gap turn where an id was actually sent is logged at warn — at short gaps the fingerprint can't plausibly have expired, so it means the id capture broke. This makes the one silent failure mode of the feature self-detecting.Bounded buffering: every per-chunk stateful consumer in the response wrapper is bounded at 8 MiB with drain-before-cap semantics — complete SSE frames (including
message_startand fallback boundaries) are always processed before overflow passthrough engages, so the cap can't drop an id capture or leak an internalfallbackblock.Verified against the live API
Chained smoke on a real cacheable prefix (~24.8K tokens):
Two behaviors worth knowing that the docs understate: the comparison only engages on cacheable requests (
nullmeans "not compared" unless cache activity is nonzero), and on multi-turn agent traffic a populated*_changedroutinely coexists with a full prefix hit — consumers must classify hit-first (cache_readis a fact;miss_reasonis an interpretation). Both documented in the README.Gates
bun run test1084 pass / 0 fail ·bun run typecheckclean ·bun run lintclean · e2e 26/27 — the one failure is the pre-existingtool-prefix.test.tsstale-Opus-bridge case, which fails identically on a cleanb1d8f8cworktree (verified side-by-side; unrelated to this diff).Pi behavior is unchanged (all new core params optional; call sites read to confirm, not just typechecked).
Relation to #155
Same file (
cachekeep.ts), no overlap: this branch deliberately carries none of #155's error-containment scaffolding, so the two merge independently in either order.Tests were written red-first throughout; the concurrency tests bind different predecessors across interleaved in-flight sessions specifically so a mutable-current-request regression cannot pass.
Greptile Summary
This PR enables per-request Anthropic cache diagnostics and cache-usage accounting while preserving response bytes and bounding response-processing buffers.
Confidence Score: 5/5
The PR appears safe to merge.
No blocking failure remains.
Important Files Changed
Sequence Diagram
Reviews (17): Last reviewed commit: "feat(opencode): capture cache-diagnostic..." | Re-trigger Greptile