mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
fix: add timeout mechanism for OpenRouter stream hanging issue
- Add 30-second timeout for first chunk in Task.ts specifically for OpenRouter - Add chunk timeout monitoring in OpenRouterHandler to detect hanging streams - Provide clear error messages when timeouts occur - Fixes #6137 where OpenRouter requests would hang indefinitely
This commit is contained in:
parent
c47de36b05
commit
3312739e9f
2 changed files with 62 additions and 18 deletions
|
|
@ -137,28 +137,48 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
|
|||
const stream = await this.client.chat.completions.create(completionParams)
|
||||
|
||||
let lastUsage: CompletionUsage | undefined = undefined
|
||||
let lastChunkTime = Date.now()
|
||||
const CHUNK_TIMEOUT = 60000 // 60 seconds timeout between chunks
|
||||
|
||||
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}`)
|
||||
throw new Error(`OpenRouter API Error ${error?.code}: ${error?.message}`)
|
||||
// Set up a timeout checker
|
||||
const timeoutChecker = setInterval(() => {
|
||||
const timeSinceLastChunk = Date.now() - lastChunkTime
|
||||
if (timeSinceLastChunk > CHUNK_TIMEOUT) {
|
||||
clearInterval(timeoutChecker)
|
||||
console.error(`OpenRouter stream timeout: No data received for ${CHUNK_TIMEOUT / 1000} seconds`)
|
||||
// The stream will be aborted when the iterator is abandoned
|
||||
}
|
||||
}, 5000) // Check every 5 seconds
|
||||
|
||||
const delta = chunk.choices[0]?.delta
|
||||
try {
|
||||
for await (const chunk of stream) {
|
||||
// Update last chunk time
|
||||
lastChunkTime = Date.now()
|
||||
|
||||
if ("reasoning" in delta && delta.reasoning && typeof delta.reasoning === "string") {
|
||||
yield { type: "reasoning", text: delta.reasoning }
|
||||
}
|
||||
|
||||
if (delta?.content) {
|
||||
yield { type: "text", text: delta.content }
|
||||
}
|
||||
|
||||
if (chunk.usage) {
|
||||
lastUsage = chunk.usage
|
||||
// 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}`)
|
||||
throw new Error(`OpenRouter API Error ${error?.code}: ${error?.message}`)
|
||||
}
|
||||
|
||||
const delta = chunk.choices[0]?.delta
|
||||
|
||||
if ("reasoning" in delta && delta.reasoning && typeof delta.reasoning === "string") {
|
||||
yield { type: "reasoning", text: delta.reasoning }
|
||||
}
|
||||
|
||||
if (delta?.content) {
|
||||
yield { type: "text", text: delta.content }
|
||||
}
|
||||
|
||||
if (chunk.usage) {
|
||||
lastUsage = chunk.usage
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// Clean up the timeout checker
|
||||
clearInterval(timeoutChecker)
|
||||
}
|
||||
|
||||
if (lastUsage) {
|
||||
|
|
|
|||
|
|
@ -1810,7 +1810,31 @@ export class Task extends EventEmitter<ClineEvents> {
|
|||
try {
|
||||
// Awaiting first chunk to see if it will throw an error.
|
||||
this.isWaitingForFirstChunk = true
|
||||
const firstChunk = await iterator.next()
|
||||
|
||||
// Add timeout for OpenRouter to prevent indefinite hanging
|
||||
const OPENROUTER_FIRST_CHUNK_TIMEOUT = 30000 // 30 seconds
|
||||
const isOpenRouter = this.apiConfiguration.apiProvider === "openrouter"
|
||||
|
||||
let firstChunk: Awaited<ReturnType<typeof iterator.next>>
|
||||
if (isOpenRouter) {
|
||||
// Create a timeout promise that rejects after the specified time
|
||||
const timeoutPromise = new Promise<never>((_, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(
|
||||
new Error(
|
||||
`OpenRouter API request timed out after ${OPENROUTER_FIRST_CHUNK_TIMEOUT / 1000} seconds. This may be due to high load on OpenRouter's servers. Please try again later or switch to a different provider.`,
|
||||
),
|
||||
)
|
||||
}, OPENROUTER_FIRST_CHUNK_TIMEOUT)
|
||||
})
|
||||
|
||||
// Race between the actual API call and the timeout
|
||||
firstChunk = await Promise.race([iterator.next(), timeoutPromise])
|
||||
} else {
|
||||
// For non-OpenRouter providers, use the original logic
|
||||
firstChunk = await iterator.next()
|
||||
}
|
||||
|
||||
yield firstChunk.value
|
||||
this.isWaitingForFirstChunk = false
|
||||
} catch (error) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue