Skip to content

Commit 098ca59

Browse files
committed
Fixes #5144: Add comprehensive test coverage for OpenRouter upstream_inference_cost
- Added test for BYOK cost calculation using upstream_inference_cost - Added test for graceful handling of missing upstream_inference_cost - Added test for reasoning tokens support - Verified cached token display functionality - All tests pass, confirming existing implementation correctly uses upstream_inference_cost
1 parent 3a8ba27 commit 098ca59

File tree

1 file changed

+143
-0
lines changed

1 file changed

+143
-0
lines changed

src/api/providers/__tests__/openrouter.spec.ts

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)