Skip to content

Commit a29689d

Browse files
committed
Use OpenRouter's new usage flag for more reliable pricing responses
1 parent 2ca4738 commit a29689d

File tree

3 files changed

+15
-68
lines changed

3 files changed

+15
-68
lines changed

src/api/providers/cline.ts

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export class ClineHandler implements ApiHandler {
2020

2121
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
2222
const model = this.getModel()
23-
const genId = yield* streamOpenRouterFormatRequest(
23+
yield* streamOpenRouterFormatRequest(
2424
this.client,
2525
systemPrompt,
2626
messages,
@@ -29,27 +29,6 @@ export class ClineHandler implements ApiHandler {
2929
this.options.thinkingBudgetTokens,
3030
this.options.openRouterProviderSorting,
3131
)
32-
33-
try {
34-
const response = await axios.get(`https://api.cline.bot/v1/generation?id=${genId}`, {
35-
headers: {
36-
Authorization: `Bearer ${this.options.clineApiKey}`,
37-
},
38-
timeout: 5_000, // this request hangs sometimes
39-
})
40-
41-
const generation = response.data
42-
console.log("cline generation details:", generation)
43-
yield {
44-
type: "usage",
45-
inputTokens: generation?.native_tokens_prompt || 0,
46-
outputTokens: generation?.native_tokens_completion || 0,
47-
totalCost: generation?.total_cost || 0,
48-
}
49-
} catch (error) {
50-
// ignore if fails
51-
console.error("Error fetching cline generation details:", error)
52-
}
5332
}
5433

5534
getModel(): { id: string; info: ModelInfo } {

src/api/providers/openrouter.ts

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export class OpenRouterHandler implements ApiHandler {
2929
@withRetry()
3030
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
3131
const model = this.getModel()
32-
const genId = yield* streamOpenRouterFormatRequest(
32+
yield* streamOpenRouterFormatRequest(
3333
this.client,
3434
systemPrompt,
3535
messages,
@@ -38,45 +38,6 @@ export class OpenRouterHandler implements ApiHandler {
3838
this.options.thinkingBudgetTokens,
3939
this.options.openRouterProviderSorting,
4040
)
41-
42-
if (genId) {
43-
await delay(500) // FIXME: necessary delay to ensure generation endpoint is ready
44-
try {
45-
const generationIterator = this.fetchGenerationDetails(genId)
46-
const generation = (await generationIterator.next()).value
47-
// console.log("OpenRouter generation details:", generation)
48-
yield {
49-
type: "usage",
50-
// cacheWriteTokens: 0,
51-
// cacheReadTokens: 0,
52-
// openrouter generation endpoint fails often
53-
inputTokens: generation?.native_tokens_prompt || 0,
54-
outputTokens: generation?.native_tokens_completion || 0,
55-
totalCost: generation?.total_cost || 0,
56-
}
57-
} catch (error) {
58-
// ignore if fails
59-
console.error("Error fetching OpenRouter generation details:", error)
60-
}
61-
}
62-
}
63-
64-
@withRetry({ maxRetries: 4, baseDelay: 250, maxDelay: 1000, retryAllErrors: true })
65-
async *fetchGenerationDetails(genId: string) {
66-
// console.log("Fetching generation details for:", genId)
67-
try {
68-
const response = await axios.get(`https://openrouter.ai/api/v1/generation?id=${genId}`, {
69-
headers: {
70-
Authorization: `Bearer ${this.options.openRouterApiKey}`,
71-
},
72-
timeout: 5_000, // this request hangs sometimes
73-
})
74-
yield response.data?.data
75-
} catch (error) {
76-
// ignore if fails
77-
console.error("Error fetching OpenRouter generation details:", error)
78-
throw error
79-
}
8041
}
8142

8243
getModel(): { id: string; info: ModelInfo } {

src/api/transform/openrouter-stream.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export async function* streamOpenRouterFormatRequest(
1414
o3MiniReasoningEffort?: string,
1515
thinkingBudgetTokens?: number,
1616
openRouterProviderSorting?: string,
17-
): AsyncGenerator<ApiStreamChunk, string | undefined, unknown> {
17+
): AsyncGenerator<ApiStreamChunk, undefined, unknown> {
1818
// Convert Anthropic messages to OpenAI format
1919
let openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
2020
{ role: "system", content: systemPrompt },
@@ -144,12 +144,14 @@ export async function* streamOpenRouterFormatRequest(
144144
stream: true,
145145
transforms: shouldApplyMiddleOutTransform ? ["middle-out"] : undefined,
146146
include_reasoning: true,
147+
stream_options: { include_usage: true },
147148
...(model.id === "openai/o3-mini" ? { reasoning_effort: o3MiniReasoningEffort || "medium" } : {}),
148149
...(reasoning ? { reasoning } : {}),
149150
...(openRouterProviderSorting ? { provider: { sort: openRouterProviderSorting } } : {}),
150151
})
151152

152-
let genId: string | undefined
153+
// let genId: string | undefined
154+
let didOutputUsage: boolean = false
153155

154156
for await (const chunk of stream) {
155157
// openrouter returns an error object instead of the openai sdk throwing an error
@@ -161,8 +163,15 @@ export async function* streamOpenRouterFormatRequest(
161163
throw new Error(`OpenRouter API Error ${error.code}: ${error.message}${metadataStr}`)
162164
}
163165

164-
if (!genId && chunk.id) {
165-
genId = chunk.id
166+
if (chunk.usage && !didOutputUsage) {
167+
yield {
168+
type: "usage",
169+
inputTokens: chunk.usage.prompt_tokens || 0,
170+
outputTokens: chunk.usage.completion_tokens || 0,
171+
// @ts-ignore-next-line
172+
totalCost: chunk.usage.cost || 0,
173+
}
174+
didOutputUsage = true
166175
}
167176

168177
const delta = chunk.choices[0]?.delta
@@ -182,6 +191,4 @@ export async function* streamOpenRouterFormatRequest(
182191
}
183192
}
184193
}
185-
186-
return genId
187194
}

0 commit comments

Comments
 (0)