Repository navigation
Add per-session cost readout for API billing - #127
Merged
Merged
Conversation
The footer knew the model and how full the context window was, but never what the session cost. This adds that readout beside the context ring — off by default, and absent rather than zeroed when it is off, because on a subscription plan the number is noise and nobody on one should have to read a running total that does not apply to them. The setting lives in the backend rather than the page's localStorage: it gates the work, not just the paint. Off, no transcript is summed and no price is ever fetched. Three pieces sit behind it: - internal/pricing bills token counts against a table baked into the binary, which refreshes itself from LiteLLM's published one the first time a session runs a model this build never heard of, and caches that under the config dir. Keyed by model id and nothing else, so a second provider's reader prices off the same table. - The transcript read gains output_tokens and a second traversal. The context gauge answers "how full is the window now" from the tail; cost is a sum over every assistant line, sub-agents included — they are billed to the same account — resumed from a stored byte offset so a turn re-reads only what was appended. - session_costs keeps one ledger per (session, transcript), so a /clear starts a new conversation without losing what the old one cost, the total survives a restart, and a session parked with its worktree keeps it across the reopen. A model no table can price stops the count at that line instead of skipping it, and the readout goes absent until a price for it lands: a total that silently misses a turn is worse than no total, and pricing an unknown model at zero is the one answer this must never give. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NW5SGwooH8xCCvnrc3PhZY
Both sides added an entry to CHANGELOG's [Unreleased] and a bullet to Known Ceilings, in the same spots. Kept both, main's first. The code merged cleanly and stays orthogonal: main's LICH_WORKTREE_PORT rides in sessionEnv, this branch's cost readout in the Store interface and the usage event. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NW5SGwooH8xCCvnrc3PhZY
This was referenced Jul 29, 2026
Merged
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.
Adds an optional cost readout to the footer showing what the current session has spent in USD. The feature is off by default (subscription plans don't need it) and only appears when enabled in settings and the provider's pricing is known.
Summary
This implements a complete cost-tracking pipeline from provider transcripts through to the UI:
internal/pricingpackage maintains a two-layer price table (baked Claude models + remote refresh for newer releases). Fetches from LiteLLM's public table once per unknown model per run, caches the result under the config dir.internal/store/cost.gotracks per-transcript accounting (byte offset, last message ID, cumulative cost) so rescans only process appended lines.internal/terminal/usage_cost.gowalks provider transcripts, sums token costs, deduplicates repeated messages, and stops cleanly when a model has no price yet (rather than guessing zero).usageEventwith optionalCostUSDfield; newsessionCost()method aggregates all conversations in a session.cost-readout-storemanages the on/off setting (stored in backend, loaded once per page). NewformatCost()utility renders the number with appropriate precision (cents for dollars, three decimals below). Footer displays cost alongside context usage when enabled.Key implementation details
complete=false) when it hits an unpriced model, never skipping it. This prevents silent undercounting. The miss schedules a price refresh that may unblock it on the next turn.emitUsageruns on every non-idle hook.<synthetic>(provider error entries) are skipped without blocking the count.-20251101) to resolve to the undated family entry, so a model released after this build still prices correctly once the remote table is fetched.Testing
internal/pricing/pricing_test.go: Baked table, remote refresh, caching, deduplication, zero-priced entries.internal/terminal/usage_cost_test.go: Transcript scanning, resumption, partial lines, message dedup across boundaries, unpriced models, synthetic lines, sidechain billing.internal/store/cost_test.go: Ledger round-trip, session aggregation, session deletion, race conditions.frontend/src/lib/cost-readout-store.test.ts: Store load, toggle persistence, error handling.frontend/src/lib/session/session-cost.test.ts: Cost formatting (cents vs. decimals, sub-cent floor).frontend/src/lib/session/session-usage-store.test.ts: Cost field in usage events.All tests follow the contract-first pattern: they name the invariant being tested and assert the behavior, never the implementation.
CHANGELOG
Added entry under
[Unreleased]documenting the feature for users.https://claude.ai/code/session_01NW5SGwooH8xCCvnrc3PhZY