Repository navigation
feat(footer): show the last turn's prompt cache hit - #945
Merged
Merged
Conversation
…oltip The context window tooltip gains a line with the share of the last turn's input read back from the prompt cache, in green from 50% and amber below, over a bar splitting that input into cached, written and uncached. The session-usage event carries the split as api.SessionCache, read from the assistant line's usage on Claude Code and from last_token_usage on Codex. It is nil for every other provider and for a Claude conversation whose last event is a compaction. Tests: TestSessionUsageReportsTheTranscriptTail, TestSessionUsageReportsCodexRollout and TestScanCodexContextUsage expect the new Cache field because the contract changed.
cache_write_input_tokens is part of input_tokens: Codex fills it from the Responses API's input_tokens_details (its parses_cache_write_token_usage test has 40 cached + 60 written of 100 input). The cost priced it once at the cache-write rate and again inside the uncached input. Cost and the context tooltip's cache bar now share one split, codexCache. TestCodexTranscriptCostPricesTheRunningTotal asserted the double count, which Codex never promised: its uncached share is now 575, not 600.
…opencode Both record the last turn's cache split but no window, so the line rides on the cost tooltip instead of a context one. oh-my-pi's comes from the newest assistant line's usage (input, cacheRead, cacheWrite), opencode's from the newest finished assistant message row (tokens.input, tokens.cache.*); both already leave the cache out of input.
…oltip # Conflicts: # CHANGELOG.md # api/testdata/contract.golden
chromedp gives a launching Chrome 20s to print its DevTools URL. On a GitHub Ubuntu runner the suite's first launch passed that, failing TestChromeClearReachesTheFieldsListener with "websocket url timeout reached" on two runs; in the run that predates the sandbox sysctl the second launch took 6s and the third 1s to reach Chrome's sandbox check, so the time goes to loading the browser off a cold disk, not to lich.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The footer now says how much of the last turn's input came back from the prompt cache:
Cache: 98% hit, green from 50% and amber below, so a turn after the cache expired (0%, amber) stands out as the one billed at the full rate.It sits in the context window tooltip on Claude Code and Codex, and in the cost tooltip on oh-my-pi and opencode, which record the split but no window.
Also fixes the Codex cost billing a cache write twice:
cache_write_input_tokensis part ofinput_tokens(Codex fills it from the Responses API'sinput_tokens_details; its ownparses_cache_write_token_usagetest has 40 cached + 60 written of 100 input), but it was priced at the cache-write rate and again as uncached input. Cost and the cache bar now share one split,codexCache.How
api.SessionUsagegainsCache *SessionCache {read, write, uncached};contract.goldenmoved with it.cache_read_input_tokens,cache_creation_input_tokens,input_tokens). A compaction drops the split until the next assistant line, since the turn before it no longer describes the window.last_token_usage, both cache counters taken out ofinput_tokens.usage.input,cacheRead,cacheWrite(omp already takes the cache out ofinput,calculateOpenAIUsageAccounting).messagerow of the session,tokens.input,tokens.cache.read,tokens.cache.write; a sub-agent's own session is not read.footer_cache.godraws the line and the three-part bar, under the context token line or under the cost lines when there is no window; one new catalog key,shell.footerSession.cacheHit, in all seven locales.Provider gap (Invariant 5)
Crush folds its cache counts into its totals, Kiro files every token count as zero, and Cursor and Antigravity have no usage reader, so the four show no cache line. Written down in
docs/ceilings.md, the Provider support table of every README, anddocs/providers/.Tests changed
TestCodexTranscriptCostPricesTheRunningTotalasserted the double count, which Codex never promised: its uncached share is now 575 of 1000 input, not 600.TestSessionUsageReportsTheTranscriptTail,TestSessionUsageReportsCodexRollout,TestScanCodexContextUsage: the event contract gainedCache.Test plan
go test ./internal/terminal ./api: split per provider, compaction clears it, opencode skips an unfinished turn and a sub-agent's, a missingmessagetable reads as no line, the cost-only event carries it, Codex cost with a cache write.go test ./cmd/lich-native: decode keeps a valid split and drops a malformed or negative one;cacheHitrounding and the empty cases.native/): gofmt, vet, test.opencode.db.