Skip to content

Commit 3329fca

Browse files
committed
fix(cost): make pricing tier selection per-request and avoid auto-selecting named service tiers
1 parent dbd835d commit 3329fca

File tree

1 file changed

+58
-6
lines changed

1 file changed

+58
-6
lines changed

src/shared/cost.ts

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,66 @@
11
import type { ModelInfo } from "@roo-code/types"
22

3+
/**
4+
* Determine effective per‑million prices for this request based on model tiers.
5+
* If tiers are defined, pick the first tier whose contextWindow >= tierBasisTokens.
6+
* Fallback to the last tier when all tiers are below the observed tokens.
7+
*/
8+
function selectTierPrices(
9+
modelInfo: ModelInfo,
10+
tierBasisTokens: number,
11+
): {
12+
inputPrice: number
13+
outputPrice: number
14+
cacheReadsPrice: number
15+
} {
16+
let inputPrice = modelInfo.inputPrice ?? 0
17+
let outputPrice = modelInfo.outputPrice ?? 0
18+
let cacheReadsPrice = modelInfo.cacheReadsPrice ?? 0
19+
20+
const tiers = (modelInfo as ModelInfo).tiers
21+
if (Array.isArray(tiers) && tiers.length > 0) {
22+
// If tiers are "service tiers" (e.g., OpenAI flex/priority), they will have a name.
23+
// Do NOT auto-select by tokens in that case. Pricing is chosen explicitly by the provider path.
24+
const hasNamedTiers = (tiers as any[]).some(
25+
(t) => typeof (t as any).name === "string" && (t as any).name.length > 0,
26+
)
27+
28+
if (!hasNamedTiers) {
29+
// Choose the smallest tier that can accommodate the request's input size
30+
let chosen =
31+
tiers.find(
32+
(t) =>
33+
tierBasisTokens <=
34+
(t.contextWindow === Infinity ? Number.POSITIVE_INFINITY : (t.contextWindow as number)),
35+
) || tiers[tiers.length - 1]!
36+
37+
inputPrice = chosen.inputPrice ?? inputPrice
38+
outputPrice = chosen.outputPrice ?? outputPrice
39+
cacheReadsPrice = chosen.cacheReadsPrice ?? cacheReadsPrice
40+
}
41+
}
42+
43+
return { inputPrice, outputPrice, cacheReadsPrice }
44+
}
45+
346
function calculateApiCostInternal(
447
modelInfo: ModelInfo,
548
inputTokens: number,
649
outputTokens: number,
750
cacheCreationInputTokens: number,
851
cacheReadInputTokens: number,
52+
// Use total input tokens (before cache deductions) to determine tier selection
53+
tierBasisTokens: number,
954
): number {
10-
const cacheWritesCost = ((modelInfo.cacheWritesPrice || 0) / 1_000_000) * cacheCreationInputTokens
11-
const cacheReadsCost = ((modelInfo.cacheReadsPrice || 0) / 1_000_000) * cacheReadInputTokens
12-
const baseInputCost = ((modelInfo.inputPrice || 0) / 1_000_000) * inputTokens
13-
const outputCost = ((modelInfo.outputPrice || 0) / 1_000_000) * outputTokens
14-
const totalCost = cacheWritesCost + cacheReadsCost + baseInputCost + outputCost
15-
return totalCost
55+
const { inputPrice, outputPrice, cacheReadsPrice } = selectTierPrices(modelInfo, tierBasisTokens)
56+
57+
const cacheWritesPrice = modelInfo.cacheWritesPrice || 0
58+
const cacheWritesCost = (cacheWritesPrice / 1_000_000) * cacheCreationInputTokens
59+
const cacheReadsCost = (cacheReadsPrice / 1_000_000) * cacheReadInputTokens
60+
const baseInputCost = (inputPrice / 1_000_000) * inputTokens
61+
const outputCost = (outputPrice / 1_000_000) * outputTokens
62+
63+
return cacheWritesCost + cacheReadsCost + baseInputCost + outputCost
1664
}
1765

1866
// For Anthropic compliant usage, the input tokens count does NOT include the
@@ -30,6 +78,8 @@ export function calculateApiCostAnthropic(
3078
outputTokens,
3179
cacheCreationInputTokens || 0,
3280
cacheReadInputTokens || 0,
81+
// Tier basis for Anthropic protocol = actual input tokens (no cache included)
82+
inputTokens,
3383
)
3484
}
3585

@@ -51,6 +101,8 @@ export function calculateApiCostOpenAI(
51101
outputTokens,
52102
cacheCreationInputTokensNum,
53103
cacheReadInputTokensNum,
104+
// Tier basis for OpenAI protocol = total input tokens before subtracting cache
105+
inputTokens,
54106
)
55107
}
56108

0 commit comments

Comments
 (0)