|
@@ -2169,7 +2169,6 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|
|
break
|
|
break
|
|
|
} else {
|
|
} else {
|
|
|
const modelInfo = this.api.getModel().info
|
|
const modelInfo = this.api.getModel().info
|
|
|
- const state = await this.providerRef.deref()?.getState()
|
|
|
|
|
const toolProtocol = resolveToolProtocol(this.apiConfiguration, modelInfo)
|
|
const toolProtocol = resolveToolProtocol(this.apiConfiguration, modelInfo)
|
|
|
nextUserContent = [{ type: "text", text: formatResponse.noToolsUsed(toolProtocol) }]
|
|
nextUserContent = [{ type: "text", text: formatResponse.noToolsUsed(toolProtocol) }]
|
|
|
this.consecutiveMistakeCount++
|
|
this.consecutiveMistakeCount++
|
|
@@ -2960,11 +2959,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|
|
// Apply exponential backoff similar to first-chunk errors when auto-resubmit is enabled
|
|
// Apply exponential backoff similar to first-chunk errors when auto-resubmit is enabled
|
|
|
const stateForBackoff = await this.providerRef.deref()?.getState()
|
|
const stateForBackoff = await this.providerRef.deref()?.getState()
|
|
|
if (stateForBackoff?.autoApprovalEnabled) {
|
|
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
|
|
// Check if task was aborted during the backoff
|
|
|
if (this.abort) {
|
|
if (this.abort) {
|
|
@@ -3066,10 +3061,10 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|
|
// Need to save assistant responses to file before proceeding to
|
|
// Need to save assistant responses to file before proceeding to
|
|
|
// tool use since user can exit at any moment and we wouldn't be
|
|
// tool use since user can exit at any moment and we wouldn't be
|
|
|
// able to save the assistant's response.
|
|
// able to save the assistant's response.
|
|
|
- let didEndLoop = false
|
|
|
|
|
|
|
|
|
|
// Check if we have any content to process (text or tool uses)
|
|
// Check if we have any content to process (text or tool uses)
|
|
|
const hasTextContent = assistantMessage.length > 0
|
|
const hasTextContent = assistantMessage.length > 0
|
|
|
|
|
+
|
|
|
const hasToolUses = this.assistantMessageContent.some(
|
|
const hasToolUses = this.assistantMessageContent.some(
|
|
|
(block) => block.type === "tool_use" || block.type === "mcp_tool_use",
|
|
(block) => block.type === "tool_use" || block.type === "mcp_tool_use",
|
|
|
)
|
|
)
|
|
@@ -3139,10 +3134,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
await this.addToApiConversationHistory(
|
|
await this.addToApiConversationHistory(
|
|
|
- {
|
|
|
|
|
- role: "assistant",
|
|
|
|
|
- content: assistantContent,
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ { role: "assistant", content: assistantContent },
|
|
|
reasoningMessage || undefined,
|
|
reasoningMessage || undefined,
|
|
|
)
|
|
)
|
|
|
|
|
|
|
@@ -3191,7 +3183,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|
|
// Add periodic yielding to prevent blocking
|
|
// Add periodic yielding to prevent blocking
|
|
|
await new Promise((resolve) => setImmediate(resolve))
|
|
await new Promise((resolve) => setImmediate(resolve))
|
|
|
}
|
|
}
|
|
|
- // Continue to next iteration instead of setting didEndLoop from recursive call
|
|
|
|
|
|
|
+
|
|
|
continue
|
|
continue
|
|
|
} else {
|
|
} else {
|
|
|
// If there's no assistant_responses, that means we got no text
|
|
// If there's no assistant_responses, that means we got no text
|
|
@@ -3218,13 +3210,11 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|
|
// Reuse the state variable from above
|
|
// Reuse the state variable from above
|
|
|
if (state?.autoApprovalEnabled) {
|
|
if (state?.autoApprovalEnabled) {
|
|
|
// Auto-retry with backoff - don't persist failure message when retrying
|
|
// 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(
|
|
await this.backoffAndAnnounce(
|
|
|
currentItem.retryAttempt ?? 0,
|
|
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
|
|
// Check if task was aborted during the backoff
|
|
@@ -3811,18 +3801,8 @@ export class Task extends EventEmitter<TaskEvents> 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.
|
|
// 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) {
|
|
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
|
|
// 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
|
|
// CRITICAL: Check if task was aborted during the backoff countdown
|
|
|
// This prevents infinite loops when users cancel during auto-retry
|
|
// This prevents infinite loops when users cancel during auto-retry
|
|
@@ -3870,7 +3850,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Shared exponential backoff for retries (first-chunk and mid-stream)
|
|
// Shared exponential backoff for retries (first-chunk and mid-stream)
|
|
|
- private async backoffAndAnnounce(retryAttempt: number, error: any, header?: string): Promise<void> {
|
|
|
|
|
|
|
+ private async backoffAndAnnounce(retryAttempt: number, error: any): Promise<void> {
|
|
|
try {
|
|
try {
|
|
|
const state = await this.providerRef.deref()?.getState()
|
|
const state = await this.providerRef.deref()?.getState()
|
|
|
const baseDelay = state?.requestDelaySeconds || 5
|
|
const baseDelay = state?.requestDelaySeconds || 5
|
|
@@ -3900,7 +3880,9 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const finalDelay = Math.max(exponentialDelay, rateLimitDelay)
|
|
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
|
|
// Build header text; fall back to error message if none provided
|
|
|
let headerText
|
|
let headerText
|