@@ -157,7 +157,15 @@ describe("OpenRouterHandler", () => {
157157 // Verify stream chunks
158158 expect ( chunks ) . toHaveLength ( 2 ) // One text chunk and one usage chunk
159159 expect ( chunks [ 0 ] ) . toEqual ( { type : "text" , text : "test response" } )
160- expect ( chunks [ 1 ] ) . toEqual ( { type : "usage" , inputTokens : 10 , outputTokens : 20 , totalCost : 0.001 } )
160+ // Cost is now calculated locally: (3/1M * 10) + (15/1M * 20) = 0.00003 + 0.0003 = 0.00033
161+ expect ( chunks [ 1 ] ) . toEqual ( {
162+ type : "usage" ,
163+ inputTokens : 10 ,
164+ outputTokens : 20 ,
165+ cacheReadTokens : undefined ,
166+ reasoningTokens : undefined ,
167+ totalCost : expect . closeTo ( 0.00033 , 5 ) ,
168+ } )
161169
162170 // Verify OpenAI client was called with correct parameters.
163171 expect ( mockCreate ) . toHaveBeenCalledWith (
@@ -267,6 +275,57 @@ describe("OpenRouterHandler", () => {
267275 const generator = handler . createMessage ( "test" , [ ] )
268276 await expect ( generator . next ( ) ) . rejects . toThrow ( "OpenRouter API Error 500: API Error" )
269277 } )
278+
279+ it ( "calculates cost locally when OpenRouter API returns incorrect cost (issue #8650)" , async ( ) => {
280+ const handler = new OpenRouterHandler ( {
281+ ...mockOptions ,
282+ openRouterModelId : "anthropic/claude-3.5-sonnet" , // Use Claude 3.5 Sonnet as in the issue
283+ } )
284+
285+ const mockStream = {
286+ async * [ Symbol . asyncIterator ] ( ) {
287+ yield {
288+ id : "test-id" ,
289+ choices : [ { delta : { content : "test" } } ] ,
290+ }
291+ // Simulate the issue: OpenRouter returns incorrect cost ($0.46) for 527k input tokens
292+ // Actual cost should be: (527000 * 3 / 1M) + (7700 * 15 / 1M) = 1.581 + 0.1155 = 1.6965
293+ yield {
294+ id : "test-id" ,
295+ choices : [ { delta : { } } ] ,
296+ usage : {
297+ prompt_tokens : 527000 ,
298+ completion_tokens : 7700 ,
299+ cost : 0.46 , // OpenRouter's incorrect cost value
300+ } ,
301+ }
302+ } ,
303+ }
304+
305+ const mockCreate = vitest . fn ( ) . mockResolvedValue ( mockStream )
306+ ; ( OpenAI as any ) . prototype . chat = {
307+ completions : { create : mockCreate } ,
308+ } as any
309+
310+ const generator = handler . createMessage ( "test" , [ ] )
311+ const chunks = [ ]
312+
313+ for await ( const chunk of generator ) {
314+ chunks . push ( chunk )
315+ }
316+
317+ // Verify that we calculate the correct cost locally
318+ // Model pricing: inputPrice: 3, outputPrice: 15
319+ // Cost = (527000 / 1M * 3) + (7700 / 1M * 15) = 1.581 + 0.1155 = 1.6965
320+ expect ( chunks [ 1 ] ) . toEqual ( {
321+ type : "usage" ,
322+ inputTokens : 527000 ,
323+ outputTokens : 7700 ,
324+ cacheReadTokens : undefined ,
325+ reasoningTokens : undefined ,
326+ totalCost : expect . closeTo ( 1.6965 , 5 ) ,
327+ } )
328+ } )
270329 } )
271330
272331 describe ( "completePrompt" , ( ) => {
0 commit comments