Skip to content

Commit 23a214c

Browse files
authored
fix: extract raw error message from OpenRouter metadata (#10039)
OpenRouter wraps upstream provider errors in a generic message but includes the actual error in metadata.raw. This change: - Adds OpenRouterErrorResponse interface for proper typing - Creates handleStreamingError() helper for DRY error handling - Extracts metadata.raw for actionable error messages in PostHog - Includes nested error structure so getErrorMessage() can extract raw message Before: PostHog receives '400 Provider returned error' (generic) After: PostHog receives 'Model xyz not found' (actionable) This enables proper error tracking and debugging via PostHog telemetry.
1 parent d976a9b commit 23a214c

File tree

1 file changed

+38
-52
lines changed

1 file changed

+38
-52
lines changed

src/api/providers/openrouter.ts

Lines changed: 38 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ type OpenRouterChatCompletionParams = OpenAI.Chat.ChatCompletionCreateParams & {
4242
reasoning?: OpenRouterReasoningParams
4343
}
4444

45+
// OpenRouter error structure that may include metadata.raw with actual upstream error
46+
interface OpenRouterErrorResponse {
47+
message?: string
48+
code?: number
49+
metadata?: { raw?: string }
50+
}
51+
4552
// See `OpenAI.Chat.Completions.ChatCompletionChunk["usage"]`
4653
// `CompletionsAPI.CompletionUsage`
4754
// See also: https://openrouter.ai/docs/use-cases/usage-accounting
@@ -109,6 +116,29 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
109116
return this.currentReasoningDetails.length > 0 ? this.currentReasoningDetails : undefined
110117
}
111118

119+
/**
120+
* Handle OpenRouter streaming error response and report to telemetry.
121+
* OpenRouter may include metadata.raw with the actual upstream provider error.
122+
*/
123+
private handleStreamingError(error: OpenRouterErrorResponse, modelId: string, operation: string): never {
124+
const rawErrorMessage = error?.metadata?.raw || error?.message
125+
126+
const apiError = Object.assign(
127+
new ApiProviderError(
128+
rawErrorMessage ?? "Unknown error",
129+
this.providerName,
130+
modelId,
131+
operation,
132+
error?.code,
133+
),
134+
{ status: error?.code, error: { message: error?.message, metadata: error?.metadata } },
135+
)
136+
137+
TelemetryService.instance.captureException(apiError)
138+
139+
throw new Error(`OpenRouter API Error ${error?.code}: ${rawErrorMessage}`)
140+
}
141+
112142
override async *createMessage(
113143
systemPrompt: string,
114144
messages: Anthropic.Messages.MessageParam[],
@@ -226,15 +256,9 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
226256
try {
227257
stream = await this.client.chat.completions.create(completionParams, requestOptions)
228258
} catch (error) {
229-
TelemetryService.instance.captureException(
230-
new ApiProviderError(
231-
error instanceof Error ? error.message : String(error),
232-
this.providerName,
233-
modelId,
234-
"createMessage",
235-
),
236-
)
237-
259+
const errorMessage = error instanceof Error ? error.message : String(error)
260+
const apiError = new ApiProviderError(errorMessage, this.providerName, modelId, "createMessage")
261+
TelemetryService.instance.captureException(apiError)
238262
throw handleOpenAIError(error, this.providerName)
239263
}
240264

@@ -257,23 +281,7 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
257281
for await (const chunk of stream) {
258282
// OpenRouter returns an error object instead of the OpenAI SDK throwing an error.
259283
if ("error" in chunk) {
260-
const error = chunk.error as { message?: string; code?: number }
261-
console.error(`OpenRouter API Error: ${error?.code} - ${error?.message}`)
262-
263-
TelemetryService.instance.captureException(
264-
Object.assign(
265-
new ApiProviderError(
266-
error?.message ?? "Unknown error",
267-
this.providerName,
268-
modelId,
269-
"createMessage",
270-
error?.code,
271-
),
272-
{ status: error?.code },
273-
),
274-
)
275-
276-
throw new Error(`OpenRouter API Error ${error?.code}: ${error?.message}`)
284+
this.handleStreamingError(chunk.error as OpenRouterErrorResponse, modelId, "createMessage")
277285
}
278286

279287
const delta = chunk.choices[0]?.delta
@@ -468,36 +476,14 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
468476
try {
469477
response = await this.client.chat.completions.create(completionParams, requestOptions)
470478
} catch (error) {
471-
TelemetryService.instance.captureException(
472-
new ApiProviderError(
473-
error instanceof Error ? error.message : String(error),
474-
this.providerName,
475-
modelId,
476-
"completePrompt",
477-
),
478-
)
479-
479+
const errorMessage = error instanceof Error ? error.message : String(error)
480+
const apiError = new ApiProviderError(errorMessage, this.providerName, modelId, "completePrompt")
481+
TelemetryService.instance.captureException(apiError)
480482
throw handleOpenAIError(error, this.providerName)
481483
}
482484

483485
if ("error" in response) {
484-
const error = response.error as { message?: string; code?: number }
485-
console.error(`OpenRouter API Error: ${error?.code} - ${error?.message}`)
486-
487-
TelemetryService.instance.captureException(
488-
Object.assign(
489-
new ApiProviderError(
490-
error?.message ?? "Unknown error",
491-
this.providerName,
492-
modelId,
493-
"completePrompt",
494-
error?.code,
495-
),
496-
{ status: error?.code },
497-
),
498-
)
499-
500-
throw new Error(`OpenRouter API Error ${error?.code}: ${error?.message}`)
486+
this.handleStreamingError(response.error as OpenRouterErrorResponse, modelId, "completePrompt")
501487
}
502488

503489
const completion = response as OpenAI.Chat.ChatCompletion

0 commit comments

Comments
 (0)