@@ -181,6 +181,149 @@ describe("OpenRouterHandler", () => {
181181 )
182182 } )
183183
184+ it ( "calculates cost using upstream_inference_cost for BYOK" , async ( ) => {
185+ const handler = new OpenRouterHandler ( mockOptions )
186+
187+ const mockStream = {
188+ async * [ Symbol . asyncIterator ] ( ) {
189+ yield {
190+ id : "test-id" ,
191+ choices : [ { delta : { content : "test response" } } ] ,
192+ }
193+ yield {
194+ id : "test-id" ,
195+ choices : [ { delta : { } } ] ,
196+ usage : {
197+ prompt_tokens : 100 ,
198+ completion_tokens : 50 ,
199+ cost : 0.002 , // OpenRouter's cost
200+ cost_details : {
201+ upstream_inference_cost : 0.008 , // Upstream provider cost
202+ } ,
203+ prompt_tokens_details : {
204+ cached_tokens : 20 , // Cached tokens
205+ } ,
206+ } ,
207+ }
208+ } ,
209+ }
210+
211+ const mockCreate = vitest . fn ( ) . mockResolvedValue ( mockStream )
212+ ; ( OpenAI as any ) . prototype . chat = {
213+ completions : { create : mockCreate } ,
214+ } as any
215+
216+ const generator = handler . createMessage ( "test" , [ ] )
217+ const chunks = [ ]
218+
219+ for await ( const chunk of generator ) {
220+ chunks . push ( chunk )
221+ }
222+
223+ // Verify that totalCost includes both cost and upstream_inference_cost
224+ expect ( chunks ) . toHaveLength ( 2 )
225+ expect ( chunks [ 1 ] ) . toEqual ( {
226+ type : "usage" ,
227+ inputTokens : 100 ,
228+ outputTokens : 50 ,
229+ cacheReadTokens : 20 ,
230+ totalCost : 0.01 , // 0.002 + 0.008
231+ } )
232+ } )
233+
234+ it ( "handles missing upstream_inference_cost gracefully" , async ( ) => {
235+ const handler = new OpenRouterHandler ( mockOptions )
236+
237+ const mockStream = {
238+ async * [ Symbol . asyncIterator ] ( ) {
239+ yield {
240+ id : "test-id" ,
241+ choices : [ { delta : { content : "test response" } } ] ,
242+ }
243+ yield {
244+ id : "test-id" ,
245+ choices : [ { delta : { } } ] ,
246+ usage : {
247+ prompt_tokens : 100 ,
248+ completion_tokens : 50 ,
249+ cost : 0.005 , // Only OpenRouter cost, no upstream cost
250+ } ,
251+ }
252+ } ,
253+ }
254+
255+ const mockCreate = vitest . fn ( ) . mockResolvedValue ( mockStream )
256+ ; ( OpenAI as any ) . prototype . chat = {
257+ completions : { create : mockCreate } ,
258+ } as any
259+
260+ const generator = handler . createMessage ( "test" , [ ] )
261+ const chunks = [ ]
262+
263+ for await ( const chunk of generator ) {
264+ chunks . push ( chunk )
265+ }
266+
267+ // Verify that totalCost falls back to just the cost field
268+ expect ( chunks ) . toHaveLength ( 2 )
269+ expect ( chunks [ 1 ] ) . toEqual ( {
270+ type : "usage" ,
271+ inputTokens : 100 ,
272+ outputTokens : 50 ,
273+ totalCost : 0.005 , // Just the cost field
274+ } )
275+ } )
276+
277+ it ( "includes reasoning tokens when present" , async ( ) => {
278+ const handler = new OpenRouterHandler ( mockOptions )
279+
280+ const mockStream = {
281+ async * [ Symbol . asyncIterator ] ( ) {
282+ yield {
283+ id : "test-id" ,
284+ choices : [ { delta : { content : "test response" } } ] ,
285+ }
286+ yield {
287+ id : "test-id" ,
288+ choices : [ { delta : { } } ] ,
289+ usage : {
290+ prompt_tokens : 100 ,
291+ completion_tokens : 50 ,
292+ completion_tokens_details : {
293+ reasoning_tokens : 30 ,
294+ } ,
295+ cost : 0.003 ,
296+ cost_details : {
297+ upstream_inference_cost : 0.007 ,
298+ } ,
299+ } ,
300+ }
301+ } ,
302+ }
303+
304+ const mockCreate = vitest . fn ( ) . mockResolvedValue ( mockStream )
305+ ; ( OpenAI as any ) . prototype . chat = {
306+ completions : { create : mockCreate } ,
307+ } as any
308+
309+ const generator = handler . createMessage ( "test" , [ ] )
310+ const chunks = [ ]
311+
312+ for await ( const chunk of generator ) {
313+ chunks . push ( chunk )
314+ }
315+
316+ // Verify reasoning tokens are included
317+ expect ( chunks ) . toHaveLength ( 2 )
318+ expect ( chunks [ 1 ] ) . toEqual ( {
319+ type : "usage" ,
320+ inputTokens : 100 ,
321+ outputTokens : 50 ,
322+ reasoningTokens : 30 ,
323+ totalCost : 0.01 , // 0.003 + 0.007
324+ } )
325+ } )
326+
184327 it ( "supports the middle-out transform" , async ( ) => {
185328 const handler = new OpenRouterHandler ( {
186329 ...mockOptions ,
0 commit comments