diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index b515e608554..075caf028cd 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -2169,7 +2169,6 @@ export class Task extends EventEmitter implements TaskLike { break } else { const modelInfo = this.api.getModel().info - const state = await this.providerRef.deref()?.getState() const toolProtocol = resolveToolProtocol(this.apiConfiguration, modelInfo) nextUserContent = [{ type: "text", text: formatResponse.noToolsUsed(toolProtocol) }] this.consecutiveMistakeCount++ @@ -2960,11 +2959,7 @@ export class Task extends EventEmitter implements TaskLike { // Apply exponential backoff similar to first-chunk errors when auto-resubmit is enabled const stateForBackoff = await this.providerRef.deref()?.getState() if (stateForBackoff?.autoApprovalEnabled) { - await this.backoffAndAnnounce( - currentItem.retryAttempt ?? 0, - error, - streamingFailedMessage, - ) + await this.backoffAndAnnounce(currentItem.retryAttempt ?? 0, error) // Check if task was aborted during the backoff if (this.abort) { @@ -3066,10 +3061,10 @@ export class Task extends EventEmitter implements TaskLike { // Need to save assistant responses to file before proceeding to // tool use since user can exit at any moment and we wouldn't be // able to save the assistant's response. - let didEndLoop = false // Check if we have any content to process (text or tool uses) const hasTextContent = assistantMessage.length > 0 + const hasToolUses = this.assistantMessageContent.some( (block) => block.type === "tool_use" || block.type === "mcp_tool_use", ) @@ -3139,10 +3134,7 @@ export class Task extends EventEmitter implements TaskLike { } await this.addToApiConversationHistory( - { - role: "assistant", - content: assistantContent, - }, + { role: "assistant", content: assistantContent }, reasoningMessage || undefined, ) @@ -3191,7 +3183,7 @@ export class Task extends EventEmitter implements TaskLike { // Add periodic yielding to prevent blocking await new Promise((resolve) => setImmediate(resolve)) } - // Continue to next iteration instead of setting didEndLoop from recursive call + continue } else { // If there's no assistant_responses, that means we got no text @@ -3218,13 +3210,11 @@ export class Task extends EventEmitter implements TaskLike { // Reuse the state variable from above if (state?.autoApprovalEnabled) { // Auto-retry with backoff - don't persist failure message when retrying - const errorMsg = - "Unexpected API Response: The language model did not provide any assistant messages. This may indicate an issue with the API or the model's output." - await this.backoffAndAnnounce( currentItem.retryAttempt ?? 0, - new Error("Empty assistant response"), - errorMsg, + new Error( + "Unexpected API Response: The language model did not provide any assistant messages. This may indicate an issue with the API or the model's output.", + ), ) // Check if task was aborted during the backoff @@ -3811,18 +3801,8 @@ export class Task extends EventEmitter implements TaskLike { // note that this api_req_failed ask is unique in that we only present this option if the api hasn't streamed any content yet (ie it fails on the first chunk due), as it would allow them to hit a retry button. However if the api failed mid-stream, it could be in any arbitrary state where some tools may have executed, so that error is handled differently and requires cancelling the task entirely. if (autoApprovalEnabled) { - let errorMsg - - if (error.error?.metadata?.raw) { - errorMsg = JSON.stringify(error.error.metadata.raw, null, 2) - } else if (error.message) { - errorMsg = error.message - } else { - errorMsg = "Unknown error" - } - // Apply shared exponential backoff and countdown UX - await this.backoffAndAnnounce(retryAttempt, error, errorMsg) + await this.backoffAndAnnounce(retryAttempt, error) // CRITICAL: Check if task was aborted during the backoff countdown // This prevents infinite loops when users cancel during auto-retry @@ -3870,7 +3850,7 @@ export class Task extends EventEmitter implements TaskLike { } // Shared exponential backoff for retries (first-chunk and mid-stream) - private async backoffAndAnnounce(retryAttempt: number, error: any, header?: string): Promise { + private async backoffAndAnnounce(retryAttempt: number, error: any): Promise { try { const state = await this.providerRef.deref()?.getState() const baseDelay = state?.requestDelaySeconds || 5 @@ -3900,7 +3880,9 @@ export class Task extends EventEmitter implements TaskLike { } const finalDelay = Math.max(exponentialDelay, rateLimitDelay) - if (finalDelay <= 0) return + if (finalDelay <= 0) { + return + } // Build header text; fall back to error message if none provided let headerText diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index e8eae99855d..22ca6e43576 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -1081,6 +1081,7 @@ export const ChatRowContent = ({ ? "https://github.com/cline/cline/wiki/TroubleShooting-%E2%80%90-%22PowerShell-is-not-recognized-as-an-internal-or-external-command%22" : undefined } + errorDetails={apiReqStreamingFailedMessage} /> )}