diff --git a/src/api/providers/openrouter.ts b/src/api/providers/openrouter.ts index ffd5e946e49..99382ec0ce6 100644 --- a/src/api/providers/openrouter.ts +++ b/src/api/providers/openrouter.ts @@ -42,6 +42,13 @@ type OpenRouterChatCompletionParams = OpenAI.Chat.ChatCompletionCreateParams & { reasoning?: OpenRouterReasoningParams } +// OpenRouter error structure that may include metadata.raw with actual upstream error +interface OpenRouterErrorResponse { + message?: string + code?: number + metadata?: { raw?: string } +} + // See `OpenAI.Chat.Completions.ChatCompletionChunk["usage"]` // `CompletionsAPI.CompletionUsage` // See also: https://openrouter.ai/docs/use-cases/usage-accounting @@ -109,6 +116,29 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH return this.currentReasoningDetails.length > 0 ? this.currentReasoningDetails : undefined } + /** + * Handle OpenRouter streaming error response and report to telemetry. + * OpenRouter may include metadata.raw with the actual upstream provider error. + */ + private handleStreamingError(error: OpenRouterErrorResponse, modelId: string, operation: string): never { + const rawErrorMessage = error?.metadata?.raw || error?.message + + const apiError = Object.assign( + new ApiProviderError( + rawErrorMessage ?? "Unknown error", + this.providerName, + modelId, + operation, + error?.code, + ), + { status: error?.code, error: { message: error?.message, metadata: error?.metadata } }, + ) + + TelemetryService.instance.captureException(apiError) + + throw new Error(`OpenRouter API Error ${error?.code}: ${rawErrorMessage}`) + } + override async *createMessage( systemPrompt: string, messages: Anthropic.Messages.MessageParam[], @@ -226,15 +256,9 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH try { stream = await this.client.chat.completions.create(completionParams, requestOptions) } catch (error) { - TelemetryService.instance.captureException( - new ApiProviderError( - error instanceof Error ? error.message : String(error), - this.providerName, - modelId, - "createMessage", - ), - ) - + const errorMessage = error instanceof Error ? error.message : String(error) + const apiError = new ApiProviderError(errorMessage, this.providerName, modelId, "createMessage") + TelemetryService.instance.captureException(apiError) throw handleOpenAIError(error, this.providerName) } @@ -257,23 +281,7 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH for await (const chunk of stream) { // OpenRouter returns an error object instead of the OpenAI SDK throwing an error. if ("error" in chunk) { - const error = chunk.error as { message?: string; code?: number } - console.error(`OpenRouter API Error: ${error?.code} - ${error?.message}`) - - TelemetryService.instance.captureException( - Object.assign( - new ApiProviderError( - error?.message ?? "Unknown error", - this.providerName, - modelId, - "createMessage", - error?.code, - ), - { status: error?.code }, - ), - ) - - throw new Error(`OpenRouter API Error ${error?.code}: ${error?.message}`) + this.handleStreamingError(chunk.error as OpenRouterErrorResponse, modelId, "createMessage") } const delta = chunk.choices[0]?.delta @@ -468,36 +476,14 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH try { response = await this.client.chat.completions.create(completionParams, requestOptions) } catch (error) { - TelemetryService.instance.captureException( - new ApiProviderError( - error instanceof Error ? error.message : String(error), - this.providerName, - modelId, - "completePrompt", - ), - ) - + const errorMessage = error instanceof Error ? error.message : String(error) + const apiError = new ApiProviderError(errorMessage, this.providerName, modelId, "completePrompt") + TelemetryService.instance.captureException(apiError) throw handleOpenAIError(error, this.providerName) } if ("error" in response) { - const error = response.error as { message?: string; code?: number } - console.error(`OpenRouter API Error: ${error?.code} - ${error?.message}`) - - TelemetryService.instance.captureException( - Object.assign( - new ApiProviderError( - error?.message ?? "Unknown error", - this.providerName, - modelId, - "completePrompt", - error?.code, - ), - { status: error?.code }, - ), - ) - - throw new Error(`OpenRouter API Error ${error?.code}: ${error?.message}`) + this.handleStreamingError(response.error as OpenRouterErrorResponse, modelId, "completePrompt") } const completion = response as OpenAI.Chat.ChatCompletion