analytics: resolve model pricing from the provider catalog - #138
Merged
Conversation
`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>
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.
Follow-up to #85, which attached
prompt_tokens,completion_tokensandcost_usdtomessage_sent. The token fields arrive.cost_usdnever has.The problem
resolveModelPricingcallsresolveModel(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 touserModelsand then toDEFAULT_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.
estimateCostreturns 0 on absent pricing, the field is omitted rather than sent, and no install has reported acost_usdsince #85 landed.The prices were there the whole time.
OPENROUTER_MODELS_CATALOGandAIMLAPI_MODELS_CATALOGboth carrypricingon every entry, and the OpenRouter live fetch readspricing.prompt/pricing.completionoff the API. Nothing reached the analytics seam.What this changes
catalogForProvidermaps a provider entry to its bundled catalog, and the call site passes it.Only the two aggregators have one.
llama-serverruns local weights with no list price;openai-compatibleandgeminipoint 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.
resolveModelalready ordersuserModelsabove 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_usdrather than reporting a misleading0.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.
refreshOpenRouterChatCatalogFromApialready 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
tscclean.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.main(TUI, sidecar, timing-sensitive integration tests). None introduced here; the delta is the known flaky set.🤖 Generated with Claude Code