Skip to content

Commit 509346d

Browse files
committed
fix(providers): add totalCost calculation using user-configured pricing
1 parent 58d2be8 commit 509346d

11 files changed

Lines changed: 174 additions & 40 deletions

File tree

src/api/providers/anthropic-vertex.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
} from "@roo-code/types"
1313

1414
import { ApiHandlerOptions } from "../../shared/api"
15+
import { calculateApiCostAnthropic } from "../../shared/cost"
1516

1617
import { ApiStream } from "../transform/stream"
1718
import { addCacheBreakpoints } from "../transform/caching/vertex"
@@ -120,18 +121,31 @@ export class AnthropicVertexHandler extends BaseProvider implements SingleComple
120121
for await (const chunk of stream) {
121122
switch (chunk.type) {
122123
case "message_start": {
123-
const usage = chunk.message!.usage
124-
125-
yield {
126-
type: "usage",
127-
inputTokens: usage.input_tokens || 0,
128-
outputTokens: usage.output_tokens || 0,
129-
cacheWriteTokens: usage.cache_creation_input_tokens || undefined,
130-
cacheReadTokens: usage.cache_read_input_tokens || undefined,
124+
const usage = chunk.message!.usage
125+
126+
const inputTokens = usage.input_tokens || 0
127+
const outputTokens = usage.output_tokens || 0
128+
const cacheWriteTokens = usage.cache_creation_input_tokens || 0
129+
const cacheReadTokens = usage.cache_read_input_tokens || 0
130+
131+
// Compute cost using user-configured pricing from model info.
132+
// Anthropic semantics: inputTokens does NOT include cached tokens.
133+
const modelInfo = this.getModel().info
134+
const { totalCost } = modelInfo
135+
? calculateApiCostAnthropic(modelInfo, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens)
136+
: { totalCost: 0 }
137+
138+
yield {
139+
type: "usage",
140+
inputTokens,
141+
outputTokens,
142+
cacheWriteTokens: cacheWriteTokens || undefined,
143+
cacheReadTokens: cacheReadTokens || undefined,
144+
totalCost,
145+
}
146+
147+
break
131148
}
132-
133-
break
134-
}
135149
case "message_delta": {
136150
yield {
137151
type: "usage",

src/api/providers/bedrock.ts

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import { ApiStream } from "../transform/stream"
3838
import { BaseProvider } from "./base-provider"
3939
import { logger } from "../../utils/logging"
4040
import { Package } from "../../shared/package"
41+
import { calculateApiCostAnthropic } from "../../shared/cost"
4142
import { MultiPointStrategy } from "../transform/cache-strategy/multi-point-strategy"
4243
import { ModelInfo as CacheModelInfo } from "../transform/cache-strategy/types"
4344
import { convertToBedrockConverseMessages as sharedConverter } from "../transform/bedrock-converse-format"
@@ -575,14 +576,24 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH
575576
// Check both field naming conventions for cache tokens
576577
const cacheReadTokens = usage.cacheReadInputTokens || usage.cacheReadInputTokenCount || 0
577578
const cacheWriteTokens = usage.cacheWriteInputTokens || usage.cacheWriteInputTokenCount || 0
579+
const inputTokens = usage.inputTokens || 0
580+
const outputTokens = usage.outputTokens || 0
581+
582+
// Compute cost using user-configured pricing from model info.
583+
// Bedrock's inputTokens does NOT include cached tokens (Anthropic semantics).
584+
const costModelInfo = this.costModelConfig?.info
585+
const { totalCost } = costModelInfo
586+
? calculateApiCostAnthropic(costModelInfo, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens)
587+
: { totalCost: 0 }
578588

579589
// Always include all available token information
580590
yield {
581591
type: "usage",
582-
inputTokens: usage.inputTokens || 0,
583-
outputTokens: usage.outputTokens || 0,
584-
cacheReadTokens: cacheReadTokens,
585-
cacheWriteTokens: cacheWriteTokens,
592+
inputTokens,
593+
outputTokens,
594+
cacheReadTokens,
595+
cacheWriteTokens,
596+
totalCost,
586597
}
587598
continue
588599
}
@@ -612,13 +623,22 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH
612623
routerUsage.cacheReadTokens || routerUsage.cacheReadInputTokenCount || 0
613624
const cacheWriteTokens =
614625
routerUsage.cacheWriteTokens || routerUsage.cacheWriteInputTokenCount || 0
626+
const inputTokens = routerUsage.inputTokens || 0
627+
const outputTokens = routerUsage.outputTokens || 0
628+
629+
// Compute cost using user-configured pricing from model info.
630+
const costModelInfo = this.costModelConfig?.info
631+
const { totalCost } = costModelInfo
632+
? calculateApiCostAnthropic(costModelInfo, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens)
633+
: { totalCost: 0 }
615634

616635
yield {
617636
type: "usage",
618-
inputTokens: routerUsage.inputTokens || 0,
619-
outputTokens: routerUsage.outputTokens || 0,
620-
cacheReadTokens: cacheReadTokens,
621-
cacheWriteTokens: cacheWriteTokens,
637+
inputTokens,
638+
outputTokens,
639+
cacheReadTokens,
640+
cacheWriteTokens,
641+
totalCost,
622642
}
623643
}
624644
} catch (error) {

src/api/providers/deepseek.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from "@roo-code/types"
1111

1212
import type { ApiHandlerOptions } from "../../shared/api"
13+
import { calculateApiCostOpenAI } from "../../shared/cost"
1314

1415
import { ApiStream, ApiStreamUsageChunk } from "../transform/stream"
1516
import { getModelParams } from "../transform/model-params"
@@ -186,12 +187,23 @@ export class DeepSeekHandler extends OpenAiHandler {
186187

187188
// Override to handle DeepSeek's usage metrics, including caching.
188189
protected override processUsageMetrics(usage: any, _modelInfo?: any): ApiStreamUsageChunk {
190+
const inputTokens = usage?.prompt_tokens || 0
191+
const outputTokens = usage?.completion_tokens || 0
192+
const cacheWriteTokens = usage?.prompt_tokens_details?.cache_miss_tokens || 0
193+
const cacheReadTokens = usage?.prompt_tokens_details?.cached_tokens || 0
194+
195+
const modelInfo = _modelInfo ?? this.getModel().info
196+
const { totalCost } = modelInfo
197+
? calculateApiCostOpenAI(modelInfo, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens)
198+
: { totalCost: 0 }
199+
189200
return {
190201
type: "usage",
191-
inputTokens: usage?.prompt_tokens || 0,
192-
outputTokens: usage?.completion_tokens || 0,
193-
cacheWriteTokens: usage?.prompt_tokens_details?.cache_miss_tokens,
194-
cacheReadTokens: usage?.prompt_tokens_details?.cached_tokens,
202+
inputTokens,
203+
outputTokens,
204+
cacheWriteTokens: cacheWriteTokens || undefined,
205+
cacheReadTokens: cacheReadTokens || undefined,
206+
totalCost,
195207
}
196208
}
197209
}

src/api/providers/kenari.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from "@roo-code/types"
1010

1111
import { ApiHandlerOptions } from "../../shared/api"
12+
import { calculateApiCostOpenAI } from "../../shared/cost"
1213

1314
import { ApiStream } from "../transform/stream"
1415
import { convertToOpenAiMessages } from "../transform/openai-format"
@@ -106,11 +107,22 @@ export class KenariHandler extends RouterProvider implements SingleCompletionHan
106107
}
107108

108109
if (chunk.usage) {
110+
const inputTokens = chunk.usage.prompt_tokens || 0
111+
const outputTokens = chunk.usage.completion_tokens || 0
112+
const cacheReadTokens = chunk.usage.prompt_tokens_details?.cached_tokens || 0
113+
114+
// Compute cost using user-configured pricing from model info.
115+
const modelInfo = info
116+
const { totalCost } = modelInfo
117+
? calculateApiCostOpenAI(modelInfo, inputTokens, outputTokens, 0, cacheReadTokens)
118+
: { totalCost: 0 }
119+
109120
yield {
110121
type: "usage",
111-
inputTokens: chunk.usage.prompt_tokens || 0,
112-
outputTokens: chunk.usage.completion_tokens || 0,
113-
cacheReadTokens: chunk.usage.prompt_tokens_details?.cached_tokens || undefined,
122+
inputTokens,
123+
outputTokens,
124+
cacheReadTokens: cacheReadTokens || undefined,
125+
totalCost,
114126
}
115127
}
116128
}

src/api/providers/mistral.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
import { TelemetryService } from "@roo-code/telemetry"
1313

1414
import { ApiHandlerOptions } from "../../shared/api"
15+
import { calculateApiCostOpenAI } from "../../shared/cost"
1516

1617
import { convertToMistralMessages } from "../transform/mistral-format"
1718
import { ApiStream } from "../transform/stream"
@@ -155,10 +156,20 @@ export class MistralHandler extends BaseProvider implements SingleCompletionHand
155156
}
156157

157158
if (event.data.usage) {
159+
const inputTokens = event.data.usage.promptTokens || 0
160+
const outputTokens = event.data.usage.completionTokens || 0
161+
162+
// Compute cost using user-configured pricing from model info.
163+
const modelInfo = info
164+
const { totalCost } = modelInfo
165+
? calculateApiCostOpenAI(modelInfo, inputTokens, outputTokens, 0, 0)
166+
: { totalCost: 0 }
167+
158168
yield {
159169
type: "usage",
160-
inputTokens: event.data.usage.promptTokens || 0,
161-
outputTokens: event.data.usage.completionTokens || 0,
170+
inputTokens,
171+
outputTokens,
172+
totalCost,
162173
}
163174
}
164175
}

src/api/providers/moonshot.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { moonshotModels, moonshotDefaultModelId, type ModelInfo } from "@roo-code/types"
22

33
import type { ApiHandlerOptions } from "../../shared/api"
4+
import { calculateApiCostOpenAI } from "../../shared/cost"
45

56
import type { ApiStreamUsageChunk } from "../transform/stream"
67
import { getModelParams } from "../transform/model-params"
@@ -55,12 +56,22 @@ export class MoonshotHandler extends OpenAICompatibleHandler {
5556
// Moonshot uses cached_tokens at the top level of raw usage data
5657
const rawUsage = usage.raw as { cached_tokens?: number } | undefined
5758

59+
const inputTokens = usage.inputTokens || 0
60+
const outputTokens = usage.outputTokens || 0
61+
const cacheReadTokens = rawUsage?.cached_tokens ?? usage.details?.cachedInputTokens ?? 0
62+
63+
const modelInfo = this.getModel().info
64+
const { totalCost } = modelInfo
65+
? calculateApiCostOpenAI(modelInfo, inputTokens, outputTokens, 0, cacheReadTokens)
66+
: { totalCost: 0 }
67+
5868
return {
5969
type: "usage",
60-
inputTokens: usage.inputTokens || 0,
61-
outputTokens: usage.outputTokens || 0,
70+
inputTokens,
71+
outputTokens,
6272
cacheWriteTokens: 0,
63-
cacheReadTokens: rawUsage?.cached_tokens ?? usage.details?.cachedInputTokens,
73+
cacheReadTokens: cacheReadTokens || undefined,
74+
totalCost,
6475
}
6576
}
6677

src/api/providers/openai-compatible.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { streamText, generateText, LanguageModel, ToolSet } from "ai"
1111
import type { ModelInfo } from "@roo-code/types"
1212

1313
import type { ApiHandlerOptions } from "../../shared/api"
14+
import { calculateApiCostOpenAI } from "../../shared/cost"
1415

1516
import { convertToAiSdkMessages, convertToolsForAiSdk, processAiSdkStreamPart } from "../transform/ai-sdk"
1617
import { ApiStream, ApiStreamUsageChunk } from "../transform/stream"
@@ -94,12 +95,22 @@ export abstract class OpenAICompatibleHandler extends BaseProvider implements Si
9495
}
9596
raw?: Record<string, unknown>
9697
}): ApiStreamUsageChunk {
98+
const inputTokens = usage.inputTokens || 0
99+
const outputTokens = usage.outputTokens || 0
100+
const cacheReadTokens = usage.details?.cachedInputTokens || 0
101+
102+
const modelInfo = this.getModel().info
103+
const { totalCost } = modelInfo
104+
? calculateApiCostOpenAI(modelInfo, inputTokens, outputTokens, 0, cacheReadTokens)
105+
: { totalCost: 0 }
106+
97107
return {
98108
type: "usage",
99-
inputTokens: usage.inputTokens || 0,
100-
outputTokens: usage.outputTokens || 0,
109+
inputTokens,
110+
outputTokens,
101111
cacheReadTokens: usage.details?.cachedInputTokens,
102112
reasoningTokens: usage.details?.reasoningTokens,
113+
totalCost,
103114
}
104115
}
105116

src/api/providers/openai.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from "@roo-code/types"
1212

1313
import type { ApiHandlerOptions } from "../../shared/api"
14+
import { calculateApiCostOpenAI } from "../../shared/cost"
1415

1516
import { TagMatcher } from "../../utils/tag-matcher"
1617

@@ -274,12 +275,23 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
274275
}
275276

276277
protected processUsageMetrics(usage: any, _modelInfo?: ModelInfo): ApiStreamUsageChunk {
278+
const inputTokens = usage?.prompt_tokens || 0
279+
const outputTokens = usage?.completion_tokens || 0
280+
const cacheWriteTokens = usage?.cache_creation_input_tokens || 0
281+
const cacheReadTokens = usage?.cache_read_input_tokens || 0
282+
283+
const modelInfo = _modelInfo ?? this.getModel().info
284+
const { totalCost } = modelInfo
285+
? calculateApiCostOpenAI(modelInfo, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens)
286+
: { totalCost: 0 }
287+
277288
return {
278289
type: "usage",
279-
inputTokens: usage?.prompt_tokens || 0,
280-
outputTokens: usage?.completion_tokens || 0,
281-
cacheWriteTokens: usage?.cache_creation_input_tokens || undefined,
282-
cacheReadTokens: usage?.cache_read_input_tokens || undefined,
290+
inputTokens,
291+
outputTokens,
292+
cacheWriteTokens: cacheWriteTokens || undefined,
293+
cacheReadTokens: cacheReadTokens || undefined,
294+
totalCost,
283295
}
284296
}
285297

src/api/providers/poe.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import { TelemetryService } from "@roo-code/telemetry"
1414

1515
import { shouldUseReasoningBudget, shouldUseReasoningEffort, type ApiHandlerOptions } from "../../shared/api"
16+
import { calculateApiCostOpenAI } from "../../shared/cost"
1617

1718
import { convertToAiSdkMessages, convertToolsForAiSdk, processAiSdkStreamPart } from "../transform/ai-sdk"
1819
import { ApiStream } from "../transform/stream"
@@ -122,13 +123,27 @@ export class PoeHandler extends BaseProvider implements SingleCompletionHandler
122123
const usage = await result.usage
123124
if (usage) {
124125
const metrics = extractUsageMetrics(usage as any)
126+
127+
// Compute cost using user-configured pricing from model info.
128+
const modelInfo = info
129+
const { totalCost } = modelInfo
130+
? calculateApiCostOpenAI(
131+
modelInfo,
132+
metrics.inputTokens,
133+
metrics.outputTokens,
134+
metrics.cacheWriteTokens || 0,
135+
metrics.cacheReadTokens || 0,
136+
)
137+
: { totalCost: 0 }
138+
125139
yield {
126140
type: "usage" as const,
127141
inputTokens: metrics.inputTokens,
128142
outputTokens: metrics.outputTokens,
129143
cacheReadTokens: metrics.cacheReadTokens,
130144
cacheWriteTokens: metrics.cacheWriteTokens,
131145
reasoningTokens: metrics.reasoningTokens,
146+
totalCost,
132147
}
133148
}
134149
} catch (error) {

src/api/providers/qwen-code.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as path from "path"
77
import { type ModelInfo, type QwenCodeModelId, qwenCodeModels, qwenCodeDefaultModelId } from "@roo-code/types"
88

99
import type { ApiHandlerOptions } from "../../shared/api"
10+
import { calculateApiCostOpenAI } from "../../shared/cost"
1011

1112
import { NativeToolCallParser } from "../../core/assistant-message/NativeToolCallParser"
1213

@@ -312,10 +313,20 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan
312313
}
313314

314315
if (apiChunk.usage) {
316+
const inputTokens = apiChunk.usage.prompt_tokens || 0
317+
const outputTokens = apiChunk.usage.completion_tokens || 0
318+
319+
// Compute cost using user-configured pricing from model info.
320+
const modelInfo = model.info
321+
const { totalCost } = modelInfo
322+
? calculateApiCostOpenAI(modelInfo, inputTokens, outputTokens, 0, 0)
323+
: { totalCost: 0 }
324+
315325
yield {
316326
type: "usage",
317-
inputTokens: apiChunk.usage.prompt_tokens || 0,
318-
outputTokens: apiChunk.usage.completion_tokens || 0,
327+
inputTokens,
328+
outputTokens,
329+
totalCost,
319330
}
320331
}
321332
}

0 commit comments

Comments
 (0)