fix(usage): correct cache cost semantics and silence pricing warn storm

- Split CostCalculator into per-app cache semantics: Anthropic's
  input_tokens is already fresh input, while Codex/Gemini include
  cached tokens in their prompt count. The old shared formula
  double-subtracted cache_read for Claude, under-billing input cost.
- Backfill now reads cost_multiplier from the per-log snapshot column
  instead of re-querying providers.meta, so historical rows are no
  longer rewritten with the current multiplier.
- Move the "pricing not found" warn out of find_model_pricing_row;
  emit it only when a brand new log is written, and skip placeholder
  models (unknown / empty / null / none) entirely.
- Broaden model id normalization: strip namespace prefixes
  (anthropic./openai./global./bedrock.), bedrock-style -vN suffixes,
  reasoning effort suffixes (-low/-medium/-high/-xhigh/-minimal),
  Claude Desktop's claude-<non-anthropic> wrapper, dot-to-dash for
  Claude, and try a LIKE prefix match for Claude short route ids
  (e.g. claude-haiku-4-5 -> claude-haiku-4-5-20251001).
- Fall back to request_model when the stored model is missing, so
  early Codex session rows with model=unknown can still be priced.
This commit is contained in:
Jason
2026-05-13 17:51:35 +08:00
parent 4b57f7e113
commit aa5e58d060
5 changed files with 505 additions and 102 deletions
@@ -480,7 +480,7 @@ fn insert_codex_session_entry(
let (input_cost, output_cost, cache_read_cost, cache_creation_cost, total_cost) = match pricing
{
Some(p) => {
let cost = CostCalculator::calculate(&usage, &p, multiplier);
let cost = CostCalculator::calculate_for_app("codex", &usage, &p, multiplier);
(
cost.input_cost.to_string(),
cost.output_cost.to_string(),