Skip to content

Commit 9f85105

Browse files
Add retry logic to API handlers with @withRetry decorator (RooCodeInc#3596)
1 parent a9e8462 commit 9f85105

File tree

9 files changed

+32
-2
lines changed

9 files changed

+32
-2
lines changed

src/api/providers/asksage.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
askSageDefaultURL,
1010
} from "@shared/api"
1111
import { ApiStream } from "../transform/stream"
12+
import { withRetry } from "../retry"
1213

1314
type AskSageRequest = {
1415
system_prompt: string
@@ -45,6 +46,7 @@ export class AskSageHandler implements ApiHandler {
4546
}
4647
}
4748

49+
@withRetry()
4850
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
4951
try {
5052
const model = this.getModel()

src/api/providers/cline.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { createOpenRouterStream } from "../transform/openrouter-stream"
66
import { ApiStream, ApiStreamUsageChunk } from "../transform/stream"
77
import axios from "axios"
88
import { OpenRouterErrorResponse } from "./types"
9+
import { withRetry } from "../retry"
910

1011
export class ClineHandler implements ApiHandler {
1112
private options: ApiHandlerOptions
@@ -25,6 +26,7 @@ export class ClineHandler implements ApiHandler {
2526
})
2627
}
2728

29+
@withRetry()
2830
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
2931
this.lastGenerationId = undefined
3032

src/api/providers/doubao.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
44
import OpenAI from "openai"
55
import { convertToOpenAiMessages } from "../transform/openai-format"
66
import { ApiStream } from "../transform/stream"
7+
import { withRetry } from "../retry"
78

89
export class DoubaoHandler implements ApiHandler {
910
private options: ApiHandlerOptions
@@ -28,6 +29,7 @@ export class DoubaoHandler implements ApiHandler {
2829
}
2930
}
3031

32+
@withRetry()
3133
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
3234
const model = this.getModel()
3335
let openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [

src/api/providers/gemini.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,22 @@ export class GeminiHandler implements ApiHandler {
171171

172172
// Gemini doesn't include status codes in their errors
173173
// https://github.com/googleapis/js-genai/blob/61f7f27b866c74333ca6331883882489bcb708b9/src/_api_client.ts#L569
174-
if (error.name === "ClientError" && error.message.includes("got status: 429 Too Many Requests.")) {
175-
;(error as any).status = 429
174+
const rateLimitPatterns = [
175+
/got status: 429/i,
176+
/429 Too Many Requests/i,
177+
/rate limit exceeded/i,
178+
/too many requests/i,
179+
]
180+
181+
const isRateLimit =
182+
error.name === "ClientError" && rateLimitPatterns.some((pattern) => pattern.test(error.message))
183+
184+
if (isRateLimit) {
185+
const rateLimitError = Object.assign(new Error(error.message), {
186+
...error,
187+
status: 429,
188+
})
189+
throw rateLimitError
176190
}
177191
} else {
178192
apiError = String(error)

src/api/providers/litellm.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ApiHandlerOptions, liteLlmDefaultModelId, liteLlmModelInfoSaneDefaults
44
import { ApiHandler } from ".."
55
import { ApiStream } from "../transform/stream"
66
import { convertToOpenAiMessages } from "../transform/openai-format"
7+
import { withRetry } from "../retry"
78

89
export class LiteLlmHandler implements ApiHandler {
910
private options: ApiHandlerOptions
@@ -51,6 +52,7 @@ export class LiteLlmHandler implements ApiHandler {
5152
}
5253
}
5354

55+
@withRetry()
5456
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
5557
const formattedMessages = convertToOpenAiMessages(messages)
5658
const systemMessage: OpenAI.Chat.ChatCompletionSystemMessageParam = {

src/api/providers/lmstudio.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ApiHandler } from "../"
44
import { ApiHandlerOptions, ModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
55
import { convertToOpenAiMessages } from "../transform/openai-format"
66
import { ApiStream } from "../transform/stream"
7+
import { withRetry } from "../retry"
78

89
export class LmStudioHandler implements ApiHandler {
910
private options: ApiHandlerOptions
@@ -17,6 +18,7 @@ export class LmStudioHandler implements ApiHandler {
1718
})
1819
}
1920

21+
@withRetry({ retryAllErrors: true })
2022
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
2123
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
2224
{ role: "system", content: systemPrompt },

src/api/providers/qwen.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
import { convertToOpenAiMessages } from "../transform/openai-format"
1515
import { ApiStream } from "../transform/stream"
1616
import { convertToR1Format } from "../transform/r1-format"
17+
import { withRetry } from "../retry"
1718

1819
export class QwenHandler implements ApiHandler {
1920
private options: ApiHandlerOptions
@@ -48,6 +49,7 @@ export class QwenHandler implements ApiHandler {
4849
}
4950
}
5051

52+
@withRetry()
5153
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
5254
const model = this.getModel()
5355
const isDeepseekReasoner = model.id.includes("deepseek-r1")

src/api/providers/vscode-lm.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { convertToVsCodeLmMessages } from "@api/transform/vscode-lm-format"
77
import { SELECTOR_SEPARATOR, stringifyVsCodeLmModelSelector } from "@shared/vsCodeSelectorUtils"
88
import { ApiHandlerOptions, ModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
99
import type { LanguageModelChatSelector as LanguageModelChatSelectorFromTypes } from "./types"
10+
import { withRetry } from "../retry"
1011

1112
// Cline does not update VSCode type definitions or engine requirements to maintain compatibility.
1213
// This declaration (as seen in src/integrations/TerminalManager.ts) provides types for the Language Model API in newer versions of VSCode.
@@ -406,6 +407,7 @@ export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler {
406407
return content
407408
}
408409

410+
@withRetry()
409411
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
410412
// Ensure clean state before starting a new request
411413
this.ensureCleanState()

src/api/providers/xai.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { ApiHandlerOptions, XAIModelId, ModelInfo, xaiDefaultModelId, xaiModels
55
import { convertToOpenAiMessages } from "@api/transform/openai-format"
66
import { ApiStream } from "@api/transform/stream"
77
import { ChatCompletionReasoningEffort } from "openai/resources/chat/completions"
8+
import { withRetry } from "../retry"
89

910
export class XAIHandler implements ApiHandler {
1011
private options: ApiHandlerOptions
@@ -18,6 +19,7 @@ export class XAIHandler implements ApiHandler {
1819
})
1920
}
2021

22+
@withRetry()
2123
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
2224
const modelId = this.getModel().id
2325
// ensure reasoning effort is either "low" or "high" for grok-3-mini

0 commit comments

Comments
 (0)