feat: price the token volume at API list rates - #26
Merged
Conversation
Tokens need translating and dollars do not, so the tokens report now prices what it counted: the subagent volume as it ran, the same volume had every subagent inherited its session model, the difference, and the main-session volume beside them for scale. The arithmetic is per token TYPE, not per token, and that is the whole correctness story. Base input, cache reads at 0.1x, five-minute cache writes at 1.25x, one-hour writes at 2x and output at its own rate are priced an order of magnitude apart, and cache reads dominate real transcripts - a flat volume multiply would overstate the bill several times over. Transcripts carry the cache write breakdown by TTL, so the split is exact rather than averaged; a line carrying only a total is charged at the cheaper five-minute rate. Prices are transcribed from the Anthropic pricing page and enumerated per model rather than per family, for the reason the effort table already is: families span price changes, and Opus 4.1 bills at three times Opus 4.5, so a loose pattern would silently misprice a retired model. A model absent from the table is excluded from every figure and declared on its own line, never counted as free. Sonnet 5 is the one model whose rate changes on a calendar date rather than with a new id, so a window is priced at the rates in effect at its end. The framing is the part that keeps this honest. It is a counterfactual, not a bill: on a subscription none of it is paid, and the difference inherits the same assumption the volume chart in the README already flags as an upper bound. Against that, two documented facts bias it downward - the tokenizer from Opus 4.7 and Sonnet 5 onward produces about 30% more tokens for the same text than earlier models, so re-pricing cheap-model counts at expensive-model rates understates the counterfactual, and untyped cache writes take the cheaper rate. Conservative in both directions it can be, which is the direction to err in. 64 tests, five of them pricing: every token type at its documented multiplier with an exact dollar assertion, the untyped-write fallback, unpriced volume excluded and declared, the section absent when nothing can be priced, and the date stamp reaching the output rather than sitting in a comment.
…ache buckets Two reviewers went at the cost feature. The sharpest finding was against my own tests: the headline fixture passed the same 1e6 for every token type, which makes the total invariant under any permutation of the caching multipliers. Swapping 0.1 and 1.25 still printed $9.35. The counts are now 1M/2M/4M/8M so each rate is separably pinned, and the one-hour multiplier is identified rather than merely consistent with the five-minute one. The TTL split took the known buckets whenever either was present and only fell back to the flat total when both were absent, so a bucket added upstream would vanish from volume AND cost - worse than the flat field it replaced. It now charges the remainder, whatever it is, at the cheapest rate: known buckets at their own rate, everything unaccounted for at 1.25x rather than disappearing. The section was gated on the subagent figure alone, so a window with priced main sessions and entirely unpriced subagents printed neither the main-session row nor the unpriced declaration the README promises is always made. It now prints whatever it has, and a window with nothing priceable says so rather than going silent - silence would read as "this cost nothing". A negative difference, which is what deliberate above-tier work produces, printed as "$-37.40" and lost its thousands separators. The sign now sits outside the dollar mark. From the pricing-page check: the Haiku 3.5 row never matched anything, because pre-4.x ids put the generation first (claude-3-5-haiku-...). The row one line above already carried that trick for claude-opus-4-20250514, so the trap was known and not applied. Both orders now match. Also disclosed rather than silently ignored: fast mode and US-only inference are invisible in a usage line and both raise real cost, and the rates are first-party only - Bedrock and Google Cloud bill separately. The label column width is derived from the labels instead of a hand-counted constant, and PRICES now has the same consistency test the effort table has, since it is the third ordered regex over the model string. 68 tests pass.
Round-2 leftovers from both reviewers, all non-blocking, both of whom reached a mergeable verdict. The label width was derived from a second copy of the labels, so the comment claiming a rename could not break the column was false: renaming at the call site alone made padEnd a no-op, which is the hand-counted-constant failure moved one indirection away. Labels and amounts are now one list, and the width comes from the rows actually printed. Two wording fixes where the output claimed more than the figures on screen. The counterfactual caveats printed even in a window with no difference row to qualify, and the tokenizer caveat was attributed to "these figures" when it biases only the inherited side and the difference - it does not touch what actually ran. And the unpriced declaration said "excluded from every figure above" whether or not any figure was above it. The two table-consistency tests were near-identical; one helper takes the table name, and the effort test keeps its extra assertion about levels belonging to the ladder. 68 tests pass.
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.
Tokens need translating; dollars do not.
tokensnow prices the volume it already counts.Why this is not the dollar fiction the file warns about
The old note said "no dollar fiction", and the objection it aimed at was never the translation - it was the saving. A saving needs a baseline nobody measured, which is why the README already calls the volume chart an upper bound rather than a measurement. So there are two figures and a difference, all three labelled, and the difference inherits that same caveat verbatim rather than being presented as money recovered.
It is also a counterfactual by construction: on a subscription none of this is paid. The wording says so on its own line.
The correctness story is per token type
Base input, cache reads at 0.1x, five-minute cache writes at 1.25x, one-hour writes at 2x, output at its own rate. These are an order of magnitude apart and cache reads dominate real transcripts, so a flat volume multiply would overstate the bill several times over. Transcripts carry
cache_creation.ephemeral_5m_input_tokensandephemeral_1h_input_tokens, so the write split is exact; a line with only the flat total takes the cheaper five-minute rate rather than guessing upward.Prices are enumerated per model rather than per family, for the reason
EFFORT_SUPPORTalready is: families span price changes, Opus 4.1 bills at three times Opus 4.5, and a loose pattern would quietly misprice a retired model. Anything absent from the table is excluded from every figure and declared on its own line - never counted as free. Sonnet 5 is the one model whose rate changes on a calendar date rather than with a new id, so a window is priced at the rates in effect at its end, not at today.What biases the number, and which way
Downward, both of them, which is the direction to err in:
Tests
64 pass. Five are new: every token type at its documented multiplier asserted to the cent ($9.35 on haiku, $46.75 for the same counts on opus-5), the untyped-write fallback, unpriced volume excluded and declared, the section absent when nothing in the window can be priced, and the price stamp reaching the output instead of sitting in a comment where nobody checks it.