Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/core/prompts/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,24 @@ ${instructions}
If you have completed the user's task, use the attempt_completion tool.
If you require additional information from the user, use the ask_followup_question tool.
Otherwise, if you have not completed the task and do not need additional information, then proceed with the next step of the task.
(This is an automated message, so do not respond to it conversationally.)`
},

reasoningOnlyResponse: (protocol?: ToolProtocol) => {
const instructions = getToolInstructionsReminder(protocol)

return `[CONTINUE] Your previous response contained only reasoning/thinking without any text content or tool use. While your reasoning process is valuable, you must now provide actionable output.

${instructions}

# Next Steps

Based on your reasoning, please now:
1. If you have completed the user's task, use the attempt_completion tool.
2. If you need to perform an action, use the appropriate tool.
3. If you require additional information from the user, use the ask_followup_question tool.
4. If you need to communicate something to the user, include text content in your response.

(This is an automated message, so do not respond to it conversationally.)`
},

Expand Down
37 changes: 35 additions & 2 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3209,13 +3209,16 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
// tool use since user can exit at any moment and we wouldn't be
// able to save the assistant's response.

// Check if we have any content to process (text or tool uses)
// Check if we have any content to process (text, tool uses, or reasoning)
const hasTextContent = assistantMessage.length > 0

const hasToolUses = this.assistantMessageContent.some(
(block) => block.type === "tool_use" || block.type === "mcp_tool_use",
)

// Check if we have reasoning content (models like Gemini 3 may return ONLY reasoning)
const hasReasoning = reasoningMessage.length > 0

if (hasTextContent || hasToolUses) {
// Reset counter when we get a successful response with content
this.consecutiveNoAssistantMessagesCount = 0
Expand Down Expand Up @@ -3345,7 +3348,37 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
// Add periodic yielding to prevent blocking
await new Promise((resolve) => setImmediate(resolve))
}


continue
} else if (hasReasoning) {
// Handle reasoning-only response (e.g., Gemini 3 with high reasoning effort)
// The model returned reasoning/thinking content but no text or tool uses.
// This is a valid response that needs prompting to continue with actionable output.
// Reset error counters since we got a valid response (just not actionable yet)
this.consecutiveNoAssistantMessagesCount = 0
this.consecutiveNoToolUseCount = 0

// Save the assistant message with reasoning to history (empty content with reasoning attached)
await this.addToApiConversationHistory(
{ role: "assistant", content: [] },
reasoningMessage || undefined,
)

TelemetryService.instance.captureConversationMessage(this.taskId, "assistant")

// Prompt the model to continue with actionable output
// Use the task's locked protocol for consistent tool instructions
this.userMessageContent.push({
type: "text",
text: formatResponse.reasoningOnlyResponse(this._taskToolProtocol ?? "xml"),
})

// Continue the loop with the prompting message
stack.push({
userContent: [...this.userMessageContent],
includeFileDetails: false,
})

continue
} else {
// If there's no assistant_responses, that means we got no text
Expand Down
Loading