Skip to content

analytics: resolve model pricing from the provider catalog - #138

Merged
sosidudku1 merged 1 commit into
mainfrom
analytics/catalog-pricing
Aug 18, 2026
Merged

analytics: resolve model pricing from the provider catalog#138
sosidudku1 merged 1 commit into
mainfrom
analytics/catalog-pricing

Conversation

@sosidudku1

Copy link
Copy Markdown
Collaborator

Follow-up to #85, which attached prompt_tokens, completion_tokens and cost_usd to message_sent. The token fields arrive. cost_usd never has.

The problem

resolveModelPricing calls resolveModel(entry, modelId) and stops there. The third parameter is the model catalog, and it defaults to an empty map, so the lookup skips its catalog branch and falls through to userModels and then to DEFAULT_CHAT, which carries no pricing.

The effect is that a cloud model resolves unpriced unless the operator wrote a price into their config by hand. estimateCost returns 0 on absent pricing, the field is omitted rather than sent, and no install has reported a cost_usd since #85 landed.

The prices were there the whole time. OPENROUTER_MODELS_CATALOG and AIMLAPI_MODELS_CATALOG both carry pricing on every entry, and the OpenRouter live fetch reads pricing.prompt / pricing.completion off the API. Nothing reached the analytics seam.

What this changes

catalogForProvider maps a provider entry to its bundled catalog, and the call site passes it.

Only the two aggregators have one. llama-server runs local weights with no list price; openai-compatible and gemini point at whatever endpoint the operator configured. An empty map is the honest answer for those, and it leaves them on exactly the path they take today.

Precedence is unchanged. resolveModel already orders userModels above the catalog, so a hand-configured price still wins.

Absent stays absent

The rule #85 set holds. Local runners still resolve to no pricing and still omit cost_usd rather than reporting a misleading 0.00. What changes is that a priced cloud model now reports what it actually cost.

Scope note

Pricing comes from the static catalogs, which are a May 2026 snapshot and will drift. refreshOpenRouterChatCatalogFromApi already fetches live rows with current prices and caches them for an hour, but only the TUI model picker holds that cache today. Wiring the live catalog into this path is a separate change.

Testing

  • tsc clean.
  • 7 new tests in catalog-for-provider.test.ts, including the regression: the same model resolves unpriced through the old no-catalog call shape and priced through the new one.
  • Provider and analytics suites pass: 164 tests.
  • Full suite shows 9 failures against 10 on main (TUI, sidecar, timing-sensitive integration tests). None introduced here; the delta is the known flaky set.

🤖 Generated with Claude Code

`resolveModelPricing` called `resolveModel` without its catalog
argument, so the lookup fell through to `userModels` and defaults.
Cloud models that ship with published prices resolved unpriced, and
`cost_usd` was omitted from every `message_sent` that was not hand
configured.

Add `catalogForProvider`, which maps a provider entry to its bundled
catalog, and pass it at the call site. Precedence is unchanged:
`userModels` still wins over the catalog.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sosidudku1
sosidudku1 merged commit 98b4059 into main Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants