Skip to content

feat: track per-message LLM cost in chat metrics - #6102

Open
angelplusultra wants to merge 13 commits into
masterfrom
feat/cost-metric-field
Open

feat: track per-message LLM cost in chat metrics#6102
angelplusultra wants to merge 13 commits into
masterfrom
feat/cost-metric-field

Conversation

@angelplusultra

@angelplusultra angelplusultra commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Pull Request Type

  • ✨ feat (New feature)
  • 🐛 fix (Bug fix)
  • ♻️ refactor (Code refactoring without changing behavior)
  • 💄 style (UI style changes)
  • 🔨 chore (Build, CI, maintenance)
  • 📝 docs (Documentation updates)

Translations #6103

Description

Adds per-message USD cost tracking to chat and agent metrics. This is plumbing for upcoming cost features — costs are recorded in the metrics of every chat and agent run, but intentionally not displayed anywhere in the UI yet, since individual chat/run costs are usually too small to be useful on their own.

Server

  • New ModelPricing helper (server/utils/helpers/modelPricing/) that sources pricing from models.dev, cached on disk for 3 days with ETag revalidation. Falls back to the pricing snapshot bundled in @opencode-ai/models when offline/air-gapped.
  • Cost (inputCost/outputCost/totalCost) is added to metrics in all chat flows (workspace stream, developer API, embed, OpenAI-compatible endpoint, Telegram bot) and agent runs. Agent runs price each completion call individually, so totals stay correct even if the model changes mid-run.
  • Local/self-hosted providers (Ollama, LM Studio, etc.) report $0. Unknown providers/models omit the cost fields entirely — pricing degrades to "unknown", never a wrong number.
  • Works with the model router: cost is calculated against the resolved delegate provider/model, not the router itself.
  • New display_currency system setting (admin/manager editable, validated against supported currencies) and a GET /system/exchange-rates endpoint. Exchange rates come from Frankfurter, cached on disk server-side for 30 days with a 10s fetch timeout — air-gapped installs degrade to USD.

Display currency (built, not yet wired in)

The display currency setting and cost conversion pipeline are fully built and ready to go — they just need to be wired in once we have a feature we want to build around cost:

  • CurrencyPreference dropdown (Settings → Interface) is complete but commented out of the page with a note.
  • useCurrency hook fetches the instance currency + rates and formats stored USD costs for display; currently unused, documented as the entry point for future cost surfaces.

Includes #6085 (cumulative agent token usage), which this builds on — agent cost is derived from the accumulated per-call usage that PR introduces.

Visuals (if applicable)

N/A — no user-facing changes. Cost lives only in stored metrics for now.

Additional Information

Pricing data adds no hard runtime dependency: if models.dev is unreachable and no cache exists, the bundled snapshot is used; if that also fails, chats simply record no cost.

Developer Validations

  • I ran yarn lint from the root of the repo & committed changes
  • Relevant documentation has been updated (if applicable)
  • I have tested my code functionality
  • Docker build succeeds locally

@angelplusultra
angelplusultra marked this pull request as ready for review August 7, 2026 19:53

@shatfield4 shatfield4 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small nit here with deduping 2 functions with the same logic in the quick review that I did.

The only other thing that I would investigate is if adding the new fields to the metrics/usage object causes any side effects (specifically in the openai compat responses). I'm not exactly sure if this can cause errors across other providers but lets be sure to test this.

Comment on lines +120 to +124
static #toSafeMetric(value) {
const number = Number(value);
if (!Number.isFinite(number) || number < 0) return 0;
return number;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See previous comment on #toSafeMetric to dedupe and form into a util

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const number = Number(value);
if (!Number.isFinite(number) || number < 0) return 0;
return number;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like the same logic as the #toSafeMetric function in ai-provider.js. Let's move this to a util and reuse it in both place.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@angelplusultra angelplusultra changed the title feat: track and display per-message LLM cost in chat metrics feat: track per-message LLM cost in chat metrics Aug 10, 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