@@ -137,39 +137,85 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
137137 const stream = await this . client . chat . completions . create ( completionParams )
138138
139139 let lastUsage : CompletionUsage | undefined = undefined
140-
141- for await ( const chunk of stream ) {
142- // OpenRouter returns an error object instead of the OpenAI SDK throwing an error.
143- if ( "error" in chunk ) {
144- const error = chunk . error as { message ?: string ; code ?: number }
145- console . error ( `OpenRouter API Error: ${ error ?. code } - ${ error ?. message } ` )
146- throw new Error ( `OpenRouter API Error ${ error ?. code } : ${ error ?. message } ` )
147- }
148-
149- const delta = chunk . choices [ 0 ] ?. delta
150-
151- if ( "reasoning" in delta && delta . reasoning && typeof delta . reasoning === "string" ) {
152- yield { type : "reasoning" , text : delta . reasoning }
153- }
154-
155- if ( delta ?. content ) {
156- yield { type : "text" , text : delta . content }
140+ let lastChunkTime = Date . now ( )
141+ const CHUNK_TIMEOUT_MS = 30000 // 30 seconds timeout between chunks
142+
143+ // Set up a timeout check
144+ const timeoutCheck = setInterval ( ( ) => {
145+ const timeSinceLastChunk = Date . now ( ) - lastChunkTime
146+ if ( timeSinceLastChunk > CHUNK_TIMEOUT_MS ) {
147+ clearInterval ( timeoutCheck )
148+ console . error ( `OpenRouter stream timeout: No chunks received for ${ CHUNK_TIMEOUT_MS } ms` , {
149+ modelId,
150+ timeSinceLastChunk,
151+ } )
157152 }
158-
159- if ( chunk . usage ) {
160- lastUsage = chunk . usage
153+ } , 5000 ) // Check every 5 seconds
154+
155+ try {
156+ for await ( const chunk of stream ) {
157+ lastChunkTime = Date . now ( ) // Reset timeout on each chunk
158+ // OpenRouter returns an error object instead of the OpenAI SDK throwing an error.
159+ if ( "error" in chunk ) {
160+ const error = chunk . error as { message ?: string ; code ?: number ; type ?: string }
161+ const errorMessage = error ?. message || "Unknown error"
162+ const errorCode = error ?. code || "unknown"
163+ const errorType = error ?. type || "unknown"
164+
165+ // Log detailed error information
166+ console . error ( `OpenRouter API Error:` , {
167+ code : errorCode ,
168+ type : errorType ,
169+ message : errorMessage ,
170+ modelId,
171+ chunk : JSON . stringify ( chunk ) ,
172+ } )
173+
174+ // Provide more specific error messages for common issues
175+ let userFriendlyMessage = `OpenRouter API Error ${ errorCode } : ${ errorMessage } `
176+
177+ if (
178+ errorMessage . toLowerCase ( ) . includes ( "model not found" ) ||
179+ errorMessage . toLowerCase ( ) . includes ( "invalid model" ) ||
180+ errorCode === 404
181+ ) {
182+ userFriendlyMessage = `Model "${ modelId } " is not available on OpenRouter. Please check if the model ID is correct and if you have access to this model.`
183+ } else if ( errorMessage . toLowerCase ( ) . includes ( "rate limit" ) ) {
184+ userFriendlyMessage = `OpenRouter rate limit exceeded. Please wait a moment and try again.`
185+ } else if ( errorMessage . toLowerCase ( ) . includes ( "unauthorized" ) || errorCode === 401 ) {
186+ userFriendlyMessage = `OpenRouter authentication failed. Please check your API key.`
187+ }
188+
189+ throw new Error ( userFriendlyMessage )
190+ }
191+
192+ const delta = chunk . choices [ 0 ] ?. delta
193+
194+ if ( "reasoning" in delta && delta . reasoning && typeof delta . reasoning === "string" ) {
195+ yield { type : "reasoning" , text : delta . reasoning }
196+ }
197+
198+ if ( delta ?. content ) {
199+ yield { type : "text" , text : delta . content }
200+ }
201+
202+ if ( chunk . usage ) {
203+ lastUsage = chunk . usage
204+ }
161205 }
162- }
163206
164- if ( lastUsage ) {
165- yield {
166- type : "usage" ,
167- inputTokens : lastUsage . prompt_tokens || 0 ,
168- outputTokens : lastUsage . completion_tokens || 0 ,
169- cacheReadTokens : lastUsage . prompt_tokens_details ?. cached_tokens ,
170- reasoningTokens : lastUsage . completion_tokens_details ?. reasoning_tokens ,
171- totalCost : ( lastUsage . cost_details ?. upstream_inference_cost || 0 ) + ( lastUsage . cost || 0 ) ,
207+ if ( lastUsage ) {
208+ yield {
209+ type : "usage" ,
210+ inputTokens : lastUsage . prompt_tokens || 0 ,
211+ outputTokens : lastUsage . completion_tokens || 0 ,
212+ cacheReadTokens : lastUsage . prompt_tokens_details ?. cached_tokens ,
213+ reasoningTokens : lastUsage . completion_tokens_details ?. reasoning_tokens ,
214+ totalCost : ( lastUsage . cost_details ?. upstream_inference_cost || 0 ) + ( lastUsage . cost || 0 ) ,
215+ }
172216 }
217+ } finally {
218+ clearInterval ( timeoutCheck )
173219 }
174220 }
175221
@@ -235,8 +281,36 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
235281 const response = await this . client . chat . completions . create ( completionParams )
236282
237283 if ( "error" in response ) {
238- const error = response . error as { message ?: string ; code ?: number }
239- throw new Error ( `OpenRouter API Error ${ error ?. code } : ${ error ?. message } ` )
284+ const error = response . error as { message ?: string ; code ?: number ; type ?: string }
285+ const errorMessage = error ?. message || "Unknown error"
286+ const errorCode = error ?. code || "unknown"
287+ const errorType = error ?. type || "unknown"
288+
289+ // Log detailed error information
290+ console . error ( `OpenRouter API Error:` , {
291+ code : errorCode ,
292+ type : errorType ,
293+ message : errorMessage ,
294+ modelId,
295+ response : JSON . stringify ( response ) ,
296+ } )
297+
298+ // Provide more specific error messages for common issues
299+ let userFriendlyMessage = `OpenRouter API Error ${ errorCode } : ${ errorMessage } `
300+
301+ if (
302+ errorMessage . toLowerCase ( ) . includes ( "model not found" ) ||
303+ errorMessage . toLowerCase ( ) . includes ( "invalid model" ) ||
304+ errorCode === 404
305+ ) {
306+ userFriendlyMessage = `Model "${ modelId } " is not available on OpenRouter. Please check if the model ID is correct and if you have access to this model.`
307+ } else if ( errorMessage . toLowerCase ( ) . includes ( "rate limit" ) ) {
308+ userFriendlyMessage = `OpenRouter rate limit exceeded. Please wait a moment and try again.`
309+ } else if ( errorMessage . toLowerCase ( ) . includes ( "unauthorized" ) || errorCode === 401 ) {
310+ userFriendlyMessage = `OpenRouter authentication failed. Please check your API key.`
311+ }
312+
313+ throw new Error ( userFriendlyMessage )
240314 }
241315
242316 const completion = response as OpenAI . Chat . ChatCompletion
0 commit comments