Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(usage): apply cached-token discounts to all OpenAI-compatible providers#439

Merged
SantiagoDePolonia merged 1 commit into
mainfrom
fix/costs-calculation
Jun 29, 2026
Merged

fix(usage): apply cached-token discounts to all OpenAI-compatible providers#439
SantiagoDePolonia merged 1 commit into
mainfrom
fix/costs-calculation

Conversation

@SantiagoDePolonia

@SantiagoDePolonia SantiagoDePolonia commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #435. Cost calculation only applied per-token-type pricing (cached input, reasoning, audio) for the six provider types listed in providerMappings (openai, openrouter, anthropic, gemini, groq, xai). Every other OpenAI-compatible provider got no mapping at all — so their cached prompt tokens were billed at the full input rate instead of the much cheaper cached-input rate, over-charging cache-heavy workloads.

Affected provider types: xiaomi, deepseek, zai, minimax, bailian, oracle, azure, vllm, ollama, opencode_go.

Why this caused the reported ~4×

Xiaomi MiMo prices cache-hit input at ~1/50th of cache-miss input (e.g. MiMo-V2.5: ¥0.02 vs ¥1.0 /Mtok). The gateway already captured the cached count (prompt_tokens_details.cached_tokensprompt_cached_tokens) but, because xiaomi wasn't in providerMappings, charged every input token at the full rate — and even flagged the cached field as an "unmapped token field" caveat. For a cache-heavy workload that lands around 4× the real bill, matching the issue report (~$9.4 vs ~$2.2).

Changes

  • Default unmapped providers to the OpenAI-compatible token mappings. They all speak the OpenAI usage schema, so cached/reasoning/audio breakdowns must be priced the same way. Only providers whose usage schema differs (anthropic, gemini) or that report extra token types (xai) keep an explicit entry.
  • Map DeepSeek's non-standard top-level cache fields. DeepSeek doesn't use prompt_tokens_details.cached_tokens; it reports top-level prompt_cache_hit_tokens / prompt_cache_miss_tokens where prompt_tokens = hit + miss. Added prompt_cache_hit_tokens as a cached-input alias (includedInBase) and prompt_cache_miss_tokens as an informational field (no separate rate, no spurious caveat).

The dedup-by-pricing-field guard already in place means a provider emitting several cache aliases can't double-count.

Provider field-name coverage (verified against official docs)

Provider Cache field reported Handled by
Xiaomi, Z.ai/GLM, MiniMax, Bailian/Qwen, vLLM prompt_tokens_details.cached_tokens OpenAI-compatible default
Moonshot/Kimi top-level cached_tokens (also nested) OpenAI-compatible default
DeepSeek top-level prompt_cache_hit_tokens / prompt_cache_miss_tokens DeepSeek aliases (new)
Oracle none documented n/a

Not changed (deliberate)

  • CachedOutputPerMtok is intentionally not added. Verified against OpenAI / Anthropic / Gemini / DeepSeek / xAI / GLM docs: prompt caching is input-only everywhere; there is no cached-output billing concept. The real cache dimensions are already modeled (CachedInputPerMtok, plus Anthropic's CacheWritePerMtok).
  • Anthropic-native routing under a non-anthropic provider type (e.g. opencode_go models in OPENCODE_GO_MESSAGES_MODELS, MiniMax's /messages surface) reports cache_read_input_tokens / cache_creation_input_tokens, which the OpenAI default doesn't map. Narrow case; the clean fix is endpoint/shape-aware mapping selection, left out to keep this change focused.

Follow-up (data, separate repo)

The discount only applies when the model's registry pricing carries cached_input_per_mtok. 303/777 ai-model-list models have it (incl. DeepSeek-V3.x, GLM), but mimo-v2.5 does not (only mimo-v2.5-pro). A companion ENTERPILOT/ai-model-list update is needed to add native Xiaomi cache-hit pricing so mimo-v2.5 users are fully corrected.

Tests

  • TestCalculateGranularCost_OpenAICompatibleProvidersDefaultMappings — all 10 providers now match the openai baseline cost for a cache-heavy response, no caveat.
  • TestCalculateGranularCost_DeepSeekTopLevelCacheFields — DeepSeek top-level hit/miss fields priced correctly (cache-hit at cached rate, miss informational), no caveat.
  • Existing cost tests and the usage/gateway/budget/admin suites pass; make test-race and make lint green via pre-commit.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Corrected token cost handling for OpenAI-compatible providers so cached input is priced appropriately instead of at full input rate.
    • Improved support for DeepSeek-style cache token reporting, preventing unmapped token warnings for cache miss fields and pricing cache hits correctly.
  • Tests
    • Added regression coverage for OpenAI-compatible provider cost calculations.
    • Added regression coverage for DeepSeek cache token fields.

…viders

Cost calculation only applied per-token-type pricing (cached input,
reasoning, audio) for six provider types listed in providerMappings
(openai, openrouter, anthropic, gemini, groq, xai). Every other
OpenAI-compatible provider — xiaomi, deepseek, zai, minimax, bailian,
oracle, azure, vllm, ollama, opencode_go — got no mapping, so their
cached prompt tokens were billed at the full input rate instead of the
much cheaper cached-input rate, over-charging cache-heavy workloads.

This is the cause of issue #435: Xiaomi MiMo bills cache-hit input at
~1/50th of cache-miss input, but the gateway charged every input token
at the full rate, reporting roughly 4x the real cost.

Default any provider not in providerMappings to the OpenAI-compatible
token mappings, since they all speak the OpenAI usage schema. Also map
DeepSeek's non-standard top-level cache fields: prompt_cache_hit_tokens
(cached-input alias; the hit count is already inside prompt_tokens) and
prompt_cache_miss_tokens (informational remainder, no separate rate).

Note: the discount still requires the model's registry pricing to carry
cached_input_per_mtok; models without it (e.g. mimo-v2.5) need a
companion ai-model-list data update.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Extends openAICompatibleTokenCostMappings to include prompt_cache_hit_tokens (DeepSeek cache alias) and marks prompt_cache_miss_tokens as informational. Adds tokenCostMappingsForProvider to fall back to OpenAI-compatible mappings for providers not explicitly registered, and refactors CalculateGranularCost to use it. Two regression tests cover both behaviors.

Changes

Provider Token Cost Mapping Fixes

Layer / File(s) Summary
Token mappings and provider fallback helper
internal/usage/cost.go
Adds prompt_cache_hit_tokens to openAICompatibleTokenCostMappings, marks prompt_cache_miss_tokens as informational, and introduces tokenCostMappingsForProvider returning explicit provider mappings or falling back to OpenAI-compatible defaults.
CalculateGranularCost loop refactor
internal/usage/cost.go
Replaces direct providerMappings indexing with the new tokenCostMappingsForProvider helper in the token-mapping loop, preserving de-duplication and base-rate adjustment logic.
Regression tests
internal/usage/cost_test.go
TestCalculateGranularCost_OpenAICompatibleProvidersDefaultMappings validates cached-input discounting for unregistered providers matches an OpenAI baseline; TestCalculateGranularCost_DeepSeekTopLevelCacheFields validates DeepSeek hit/miss token pricing produces no caveat.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • ENTERPILOT/GoModel#288: Modifies the same openAICompatibleTokenCostMappings and cached-input token pricing logic in internal/usage/cost.go.

Poem

🐇 A rabbit checks the token bill,
"These cache hits cost too much, they will!"
DeepSeek's fields now map just right,
Unknown providers priced alight,
No more four-times overspend—
Accurate costs, from start to end! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: extending cached-token discounts across OpenAI-compatible providers.
Linked Issues check ✅ Passed The code fixes cached-token pricing for unmapped OpenAI-compatible providers and DeepSeek cache fields, addressing the cost accuracy issue in #435.
Out of Scope Changes check ✅ Passed The changes stay focused on usage cost calculation and regression tests, with no obvious unrelated scope added.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/costs-calculation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/usage/cost_test.go`:
- Around line 732-744: The current test in CalculateGranularCost only compares
fallback providers against the openai result, so it does not explicitly verify
the discounted baseline. Update the test to assert fixed expected InputCost,
OutputCost, and TotalCost values for the openai baseline before iterating
through the provider list, using CalculateGranularCost and the existing
pricing/rawData setup to lock in the cached-token discount behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d64e0f65-bbd2-4c88-b979-683072e14b82

📥 Commits

Reviewing files that changed from the base of the PR and between 2318d7d and 0e7b1ea.

📒 Files selected for processing (2)
  • internal/usage/cost.go
  • internal/usage/cost_test.go

Comment on lines +732 to +744
want := CalculateGranularCost(1_000_000, 200_000, rawData, "openai", pricing)
if want.TotalCost == nil {
t.Fatal("expected a total cost for the openai baseline")
}

for _, provider := range []string{
"xiaomi", "deepseek", "zai", "minimax", "bailian",
"oracle", "azure", "vllm", "ollama", "opencode_go",
} {
got := CalculateGranularCost(1_000_000, 200_000, rawData, provider, pricing)
assertCostNear(t, provider+" InputCost", got.InputCost, *want.InputCost)
assertCostNear(t, provider+" OutputCost", got.OutputCost, *want.OutputCost)
assertCostNear(t, provider+" TotalCost", got.TotalCost, *want.TotalCost)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the discounted baseline explicitly.

This only proves fallback providers match "openai", not that cached tokens are actually discounted. If openAICompatibleTokenCostMappings regresses for both paths, this test still passes. Add fixed expected costs for the "openai" baseline before the provider loop.

Suggested change
 	want := CalculateGranularCost(1_000_000, 200_000, rawData, "openai", pricing)
-	if want.TotalCost == nil {
-		t.Fatal("expected a total cost for the openai baseline")
-	}
+	assertCostNear(t, "openai InputCost", want.InputCost, 0.04674)
+	assertCostNear(t, "openai OutputCost", want.OutputCost, 0.174)
+	assertCostNear(t, "openai TotalCost", want.TotalCost, 0.22074)

 	for _, provider := range []string{
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
want := CalculateGranularCost(1_000_000, 200_000, rawData, "openai", pricing)
if want.TotalCost == nil {
t.Fatal("expected a total cost for the openai baseline")
}
for _, provider := range []string{
"xiaomi", "deepseek", "zai", "minimax", "bailian",
"oracle", "azure", "vllm", "ollama", "opencode_go",
} {
got := CalculateGranularCost(1_000_000, 200_000, rawData, provider, pricing)
assertCostNear(t, provider+" InputCost", got.InputCost, *want.InputCost)
assertCostNear(t, provider+" OutputCost", got.OutputCost, *want.OutputCost)
assertCostNear(t, provider+" TotalCost", got.TotalCost, *want.TotalCost)
want := CalculateGranularCost(1_000_000, 200_000, rawData, "openai", pricing)
assertCostNear(t, "openai InputCost", want.InputCost, 0.04674)
assertCostNear(t, "openai OutputCost", want.OutputCost, 0.174)
assertCostNear(t, "openai TotalCost", want.TotalCost, 0.22074)
for _, provider := range []string{
"xiaomi", "deepseek", "zai", "minimax", "bailian",
"oracle", "azure", "vllm", "ollama", "opencode_go",
} {
got := CalculateGranularCost(1_000_000, 200_000, rawData, provider, pricing)
assertCostNear(t, provider+" InputCost", got.InputCost, *want.InputCost)
assertCostNear(t, provider+" OutputCost", got.OutputCost, *want.OutputCost)
assertCostNear(t, provider+" TotalCost", got.TotalCost, *want.TotalCost)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/usage/cost_test.go` around lines 732 - 744, The current test in
CalculateGranularCost only compares fallback providers against the openai
result, so it does not explicitly verify the discounted baseline. Update the
test to assert fixed expected InputCost, OutputCost, and TotalCost values for
the openai baseline before iterating through the provider list, using
CalculateGranularCost and the existing pricing/rawData setup to lock in the
cached-token discount behavior.

Source: Coding guidelines

@greptile-apps

greptile-apps Bot commented Jun 29, 2026

Copy link
Copy Markdown

Confidence Score: 5/5

The changes are narrowly scoped to usage cost mapping behavior and add targeted tests for the corrected provider cases.

No correctness issues were identified in the changed usage cost logic, and the added tests cover the intended OpenAI-compatible fallback and DeepSeek top-level cache fields.

T-Rex T-Rex Logs

What T-Rex did

  • Normalized pricing so that head OpenAI and all listed compatible providers share identical input, output, and total prices, removing the unmapped token caveat.
  • Applied a cached discount to the top-level pricing, discounting 900,000 hit tokens while miss tokens remain base-priced, producing the expected total with no caveats.

View all artifacts

T-Rex Ran code and verified through T-Rex

Reviews (1): Last reviewed commit: "fix(usage): apply cached-token discounts..." | Re-trigger Greptile

@SantiagoDePolonia SantiagoDePolonia merged commit 236c2f0 into main Jun 29, 2026
19 checks passed
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.

Cost estimation's accuracy

2 participants