fix: bill cached input at cache prices, estimate missing usage, strip carried signatures - #40
Merged
Merged
Conversation
… carried signatures Cached input was billed at the full input price. The prompt count put cache reads and writes together with plain input, so an Anthropic turn that read 10k tokens from its cache cost as much as sending them fresh, and weighed as much against rate limits and budgets. Models get three optional weights against the input baseline: cache read, cache write, and 1-hour cache write. Unset, they follow the input weight at Anthropic's ratios (0.1x, 1.25x, 2x). Cost and weighted tokens now price each bucket on its own weight. The audit row keeps the whole input in `input_tokens` and gains the cache split in its detail. The admin API and the model editor carry the new weights. A request the upstream reported no usage for was billed as zero tokens. #34 made Chat streams ask for usage, but usage can still be missing: the upstream ignores `stream_options`, or the caller leaves mid-stream and takes the final usage chunk with it. The count is now estimated at about four bytes a token from the request and from the answer that arrived, and the row says `usage_estimated`. A stream cut short keeps the upstream's input count and bills the larger of its running output count and the estimate. No answer at all still bills nothing. A request forwarded as sent now drops the `tw1.` reasoning signatures an earlier conversion wrote, via `tw_dialect::convert::strip_carried`, as the desktop gateway does. Anthropic refuses a whole request over them. Co-Authored-By: Claude Opus 5.5 <[email protected]>
fylorn
force-pushed
the
fix/passthrough-usage-cache-pricing
branch
from
September 24, 2026 06:47
7daab45 to
b87acfc
Compare
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
Three billing and passthrough fixes on the generation pipeline.
Cached input was billed at the full input price.
tokens()folded cache reads and writes into the prompt count, andCostTrackerand the weighted-token debit priced all of it as plain input. An Anthropic turn that read 10k tokens from its cache cost, and weighed against rate limits and budgets, as much as sending them fresh.modelsgetscache_read_weight,cache_write_weightandcache_write_1h_weight. Each is nullable and priced against the input baseline, likeinput_weight. Declared in the table, plus theALTER … ADD COLUMN IF NOT EXISTSlinesschema.sqluses for columns added after first boot.input_weightat Anthropic's ratios: read 0.1×, write 1.25×, 1-hour write 2×. This is documented inschema.sql, inlimits::weight(CACHE_*_RATIO) and in the editor hint. OpenAI's older models discount cache reads less (0.5×, 0.25×), so a model served there should set its own.TokenCounts,Weights::resolve).input_tokenson the audit row is still the whole input. The detail gainscache_read_tokens,cache_write_tokensandcache_write_1h.nullmeaning "derive again", and list returns them. The model editor has three optional fields; their placeholders show the derived value. The detail sheet shows each weight, marked when it is derived.Missing usage was billed as zero. #34 made Chat streams ask for usage. Usage can still be missing: the upstream ignores
stream_options, or the caller leaves mid-stream and takes the final usage chunk with it.usage_estimatemodule, restoring what 407ae5f had before feat!: forward what can be forwarded, convert only what must be #26:usage_estimated: truein its detail, and thegateway_usage_estimated_totalcounter goes up.Passthrough forwarded carried reasoning signatures. A request forwarded as sent now goes through
tw_dialect::convert::strip_carried, as the desktop gateway does. It removes thetw1.signatures an earlier conversion wrote, which Anthropic rejects. A native signature is left alone.Test plan
a_stream_without_usage_is_billed_on_an_estimate: 800 bytes in and 400 out are recorded as 200/100 tokens, cost > 0,usage_estimated;a_stream_the_caller_leaves_is_billed_on_an_estimate: a raw upstream sends one chunk and hangs; the client drops the stream; the row is 499, billed 200/100, estimated;cached_input_is_billed_at_the_cache_prices: Anthropic usage of 100 fresh, 10k read, 2k written and 50 out costs0.0076at the default baseline, the budget counter is debited 3650, andcache_read_weight = 0.5changes the cost to0.0156;a_passthrough_request_leaves_the_gateways_own_signatures_behind: thetw1.thinking block is gone and the native one is kept;model_cache_weights_round_trip: admin create, PATCH,nullclear, negative rejected, list.--all-targets -D warningsand--lib, workspace unit tests.check:i18n, lint, test, build.🤖 Generated with Claude Code