Compare commits

...
7 changed files with 104 additions and 2 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Automatically but respectfully retries Gemini Pro 2.5 API calls
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "claude-dev",
"version": "3.8.0",
"version": "3.8.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "claude-dev",
"version": "3.8.0",
"version": "3.8.3",
"license": "Apache-2.0",
"dependencies": {
"@anthropic-ai/bedrock-sdk": "^0.12.4",
+44
View File
@@ -72,6 +72,7 @@ import {
checkIsOpenRouterContextWindowError,
} from "./context-management/context-error-handling"
import { AnthropicHandler } from "../api/providers/anthropic"
// Removed incorrect import for getRandomInt
const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution
@@ -3749,4 +3750,47 @@ export class Cline {
return `<environment_details>\n${details.trim()}\n</environment_details>`
}
async retryFailedRequest(failedMessageTs: number) {
console.log(`Attempting to retry request associated with message ts: ${failedMessageTs}`)
// 1. Find the index of the failed 'api_req_started' or 'api_req_failed' message in clineMessages
const failedClineMessageIndex = this.clineMessages.findIndex((msg) => msg.ts === failedMessageTs)
if (failedClineMessageIndex === -1) {
console.error(`Could not find cline message with ts ${failedMessageTs} to retry.`)
return
}
const failedClineMessage = this.clineMessages[failedClineMessageIndex]
// 2. Find the index of the corresponding 'user' message in apiConversationHistory
// The 'api_req_started' message's conversationHistoryIndex points to the 'user' message that triggered it.
const userMessageApiIndex = failedClineMessage.conversationHistoryIndex
if (
userMessageApiIndex === undefined ||
userMessageApiIndex < 0 ||
userMessageApiIndex >= this.apiConversationHistory.length
) {
console.error(`Invalid conversationHistoryIndex (${userMessageApiIndex}) found for message ts ${failedMessageTs}.`)
return
}
const userMessage = this.apiConversationHistory[userMessageApiIndex]
if (userMessage.role !== "user") {
console.error(`Expected user message at index ${userMessageApiIndex}, but found ${userMessage.role}.`)
return
}
// 3. Clean up clineMessages: Remove the failed message and any subsequent messages
const messagesToKeep = this.clineMessages.slice(0, failedClineMessageIndex)
await this.overwriteClineMessages(messagesToKeep)
// 4. Clean up apiConversationHistory: Remove the user message that caused the failure and any subsequent assistant message
const historyToKeep = this.apiConversationHistory.slice(0, userMessageApiIndex)
await this.overwriteApiConversationHistory(historyToKeep)
// 5. Re-initiate the task loop with the user content that caused the failure
console.log(`Retrying with user content from index ${userMessageApiIndex}`)
await this.say("api_req_retried") // Inform UI about the retry
await this.initiateTaskLoop(userMessage.content as UserContent, false) // isNewTask is false
}
}
+9
View File
@@ -938,6 +938,15 @@ export class ClineProvider implements vscode.WebviewViewProvider {
this.postMessageToWebview({ type: "relinquishControl" })
break
}
case "retryApiRequest": {
if (message.ts && this.cline) {
console.log(`Received retry request for message ts: ${message.ts}`)
// Call a method on the Cline instance to handle the retry
// We'll need to implement this method in Cline.ts
await this.cline.retryFailedRequest(message.ts)
}
break
}
// Add more switch case statements here as more webview message commands
// are created within the webview context (i.e. inside media/main.js)
}
+1
View File
@@ -40,6 +40,7 @@ export interface ExtensionMessage {
| "userCreditsPayments"
| "totalTasksSize"
| "addToInput"
| "retryApiRequest" // Added for automatic retry
text?: string
action?:
| "chatButtonClicked"
+2
View File
@@ -65,8 +65,10 @@ export interface WebviewMessage {
| "fetchUserCreditsData"
| "optionsResponse"
| "requestTotalTasksSize"
| "retryApiRequest" // Added for automatic retry
// | "relaunchChromeDebugMode"
text?: string
ts?: number // Added for retryApiRequest
disabled?: boolean
askResponse?: ClineAskResponse
apiConfiguration?: ApiConfiguration
@@ -3,6 +3,12 @@ import deepEqual from "fast-deep-equal"
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"
import { useEvent, useSize } from "react-use"
import styled from "styled-components"
// Define getRandomInt locally as import path was incorrect
const getRandomInt = (min: number, max: number): number => {
min = Math.ceil(min)
max = Math.floor(max)
return Math.floor(Math.random() * (max - min + 1)) + min
}
import {
ClineApiReqInfo,
ClineAskQuestion,
@@ -136,6 +142,7 @@ export default ChatRow
export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifiedMessage, isLast }: ChatRowContentProps) => {
const { mcpServers, mcpMarketplaceCatalog } = useExtensionState()
const [seeNewChangesDisabled, setSeeNewChangesDisabled] = useState(false)
const [retryAttempted, setRetryAttempted] = useState(false) // State to prevent infinite retries
const [cost, apiReqCancelReason, apiReqStreamingFailedMessage] = useMemo(() => {
if (message.text != null && message.say === "api_req_started") {
@@ -151,6 +158,40 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi
? lastModifiedMessage?.text
: undefined
// Effect for automatic retry on specific errors
useEffect(() => {
const errorText = apiRequestFailedMessage || apiReqStreamingFailedMessage
const shouldRetry =
isLast && // Only retry if it's the last message
!retryAttempted && // Only retry once per message instance
errorText && // Ensure there is an error message
(errorText.includes("503 Service Unavailable") || errorText.includes("model is overloaded"))
let retryTimeoutId;
if (shouldRetry) {
console.log("Detected overload error, scheduling retry for message:", message.ts)
setRetryAttempted(true) // Mark retry as attempted for this instance
const delay = getRandomInt(3000, 7000) // Random delay between 3 and 7 seconds
retryTimeoutId = setTimeout(() => {
console.log(`Retrying API request for message ${message.ts} after ${delay}ms delay.`)
vscode.postMessage({
type: "retryApiRequest",
ts: message.ts,
})
}, delay)
}
// Reset retry flag if the message or error changes (e.g., user manually retries or new message arrives)
// This might need refinement depending on exact interaction flow.
// For now, reset if the message timestamp changes.
return () => {
// Cleanup if needed, potentially reset retryAttempted if component unmounts or message changes significantly
if (retryTimeoutId) {
clearTimeout(retryTimeoutId);
}
}
}, [apiRequestFailedMessage, apiReqStreamingFailedMessage, isLast, message.ts, retryAttempted])
const isCommandExecuting =
isLast &&
(lastModifiedMessage?.ask === "command" || lastModifiedMessage?.say === "command") &&