Skip to content
Merged
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
47 changes: 38 additions & 9 deletions src/core/assistant-message/presentAssistantMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { fetchInstructionsTool } from "../tools/FetchInstructionsTool"
import { listFilesTool } from "../tools/ListFilesTool"
import { readFileTool } from "../tools/ReadFileTool"
import { getSimpleReadFileToolDescription, simpleReadFileTool } from "../tools/simpleReadFileTool"
import { shouldUseSingleFileRead } from "@roo-code/types"
import { shouldUseSingleFileRead, TOOL_PROTOCOL } from "@roo-code/types"
import { writeToFileTool } from "../tools/WriteToFileTool"
import { applyDiffTool } from "../tools/MultiApplyDiffTool"
import { insertContentTool } from "../tools/InsertContentTool"
Expand Down Expand Up @@ -284,10 +284,10 @@ export async function presentAssistantMessage(cline: Task) {
// Native protocol tool calls ALWAYS have an ID (set when parsed from tool_call chunks).
// XML protocol tool calls NEVER have an ID (parsed from XML text).
const toolCallId = (block as any).id
const isNative = !!toolCallId
const toolProtocol = toolCallId ? TOOL_PROTOCOL.NATIVE : TOOL_PROTOCOL.XML

const pushToolResult = (content: ToolResponse) => {
if (isNative && toolCallId) {
if (toolProtocol === TOOL_PROTOCOL.NATIVE) {
// For native protocol, only allow ONE tool_result per tool call
if (hasToolResult) {
console.warn(
Expand Down Expand Up @@ -360,9 +360,14 @@ export async function presentAssistantMessage(cline: Task) {
// Handle both messageResponse and noButtonClicked with text.
if (text) {
await cline.say("user_feedback", text, images)
pushToolResult(formatResponse.toolResult(formatResponse.toolDeniedWithFeedback(text), images))
pushToolResult(
formatResponse.toolResult(
formatResponse.toolDeniedWithFeedback(text, toolProtocol),
images,
),
)
} else {
pushToolResult(formatResponse.toolDenied())
pushToolResult(formatResponse.toolDenied(toolProtocol))
}
cline.didRejectTool = true
return false
Expand All @@ -371,7 +376,9 @@ export async function presentAssistantMessage(cline: Task) {
// Handle yesButtonClicked with text.
if (text) {
await cline.say("user_feedback", text, images)
pushToolResult(formatResponse.toolResult(formatResponse.toolApprovedWithFeedback(text), images))
pushToolResult(
formatResponse.toolResult(formatResponse.toolApprovedWithFeedback(text, toolProtocol), images),
)
}

return true
Expand All @@ -394,7 +401,7 @@ export async function presentAssistantMessage(cline: Task) {
`Error ${action}:\n${error.message ?? JSON.stringify(serializeError(error), null, 2)}`,
)

pushToolResult(formatResponse.toolError(errorString))
pushToolResult(formatResponse.toolError(errorString, toolProtocol))
}

// If block is partial, remove partial closing tag so its not
Expand Down Expand Up @@ -446,7 +453,7 @@ export async function presentAssistantMessage(cline: Task) {
)
} catch (error) {
cline.consecutiveMistakeCount++
pushToolResult(formatResponse.toolError(error.message))
pushToolResult(formatResponse.toolError(error.message, toolProtocol))
break
}

Expand Down Expand Up @@ -485,6 +492,7 @@ export async function presentAssistantMessage(cline: Task) {
pushToolResult(
formatResponse.toolError(
`Tool call repetition limit reached for ${block.name}. Please try a different approach.`,
toolProtocol,
),
)
break
Expand All @@ -499,6 +507,7 @@ export async function presentAssistantMessage(cline: Task) {
handleError,
pushToolResult,
removeClosingTag,
toolProtocol,
})
break
case "update_todo_list":
Expand All @@ -507,19 +516,21 @@ export async function presentAssistantMessage(cline: Task) {
handleError,
pushToolResult,
removeClosingTag,
toolProtocol,
})
break
case "apply_diff": {
await checkpointSaveAndMark(cline)

// Check if this tool call came from native protocol by checking for ID
// Native calls always have IDs, XML calls never do
if (isNative) {
if (toolProtocol === TOOL_PROTOCOL.NATIVE) {
await applyDiffToolClass.handle(cline, block as ToolUse<"apply_diff">, {
askApproval,
handleError,
pushToolResult,
removeClosingTag,
toolProtocol,
})
break
}
Expand All @@ -544,6 +555,7 @@ export async function presentAssistantMessage(cline: Task) {
handleError,
pushToolResult,
removeClosingTag,
toolProtocol,
})
}
break
Expand All @@ -555,6 +567,7 @@ export async function presentAssistantMessage(cline: Task) {
handleError,
pushToolResult,
removeClosingTag,
toolProtocol,
})
break
case "read_file":
Expand All @@ -568,6 +581,7 @@ export async function presentAssistantMessage(cline: Task) {
handleError,
pushToolResult,
removeClosingTag,
toolProtocol,
)
} else {
// Type assertion is safe here because we're in the "read_file" case
Expand All @@ -576,6 +590,7 @@ export async function presentAssistantMessage(cline: Task) {
handleError,
pushToolResult,
removeClosingTag,
toolProtocol,
})
}
break
Expand All @@ -585,6 +600,7 @@ export async function presentAssistantMessage(cline: Task) {
handleError,
pushToolResult,
removeClosingTag,
toolProtocol,
})
break
case "list_files":
Expand All @@ -593,6 +609,7 @@ export async function presentAssistantMessage(cline: Task) {
handleError,
pushToolResult,
removeClosingTag,
toolProtocol,
})
break
case "codebase_search":
Expand All @@ -601,6 +618,7 @@ export async function presentAssistantMessage(cline: Task) {
handleError,
pushToolResult,
removeClosingTag,
toolProtocol,
})
break
case "list_code_definition_names":
Expand All @@ -609,6 +627,7 @@ export async function presentAssistantMessage(cline: Task) {
handleError,
pushToolResult,
removeClosingTag,
toolProtocol,
})
break
case "search_files":
Expand All @@ -617,6 +636,7 @@ export async function presentAssistantMessage(cline: Task) {
handleError,
pushToolResult,
removeClosingTag,
toolProtocol,
})
break
case "browser_action":
Expand All @@ -625,6 +645,7 @@ export async function presentAssistantMessage(cline: Task) {
handleError,
pushToolResult,
removeClosingTag,
toolProtocol,
})
break
case "execute_command":
Expand All @@ -633,6 +654,7 @@ export async function presentAssistantMessage(cline: Task) {
handleError,
pushToolResult,
removeClosingTag,
toolProtocol,
})
break
case "use_mcp_tool":
Expand All @@ -641,6 +663,7 @@ export async function presentAssistantMessage(cline: Task) {
handleError,
pushToolResult,
removeClosingTag,
toolProtocol,
})
break
case "access_mcp_resource":
Expand All @@ -659,6 +682,7 @@ export async function presentAssistantMessage(cline: Task) {
handleError,
pushToolResult,
removeClosingTag,
toolProtocol,
})
break
case "switch_mode":
Expand All @@ -667,6 +691,7 @@ export async function presentAssistantMessage(cline: Task) {
handleError,
pushToolResult,
removeClosingTag,
toolProtocol,
})
break
case "new_task":
Expand All @@ -675,6 +700,7 @@ export async function presentAssistantMessage(cline: Task) {
handleError,
pushToolResult,
removeClosingTag,
toolProtocol,
})
break
case "attempt_completion": {
Expand All @@ -685,6 +711,7 @@ export async function presentAssistantMessage(cline: Task) {
removeClosingTag,
askFinishSubTaskApproval,
toolDescription,
toolProtocol,
}
await attemptCompletionTool.handle(
cline,
Expand All @@ -699,6 +726,7 @@ export async function presentAssistantMessage(cline: Task) {
handleError,
pushToolResult,
removeClosingTag,
toolProtocol,
})
break
case "generate_image":
Expand All @@ -708,6 +736,7 @@ export async function presentAssistantMessage(cline: Task) {
handleError,
pushToolResult,
removeClosingTag,
toolProtocol,
})
break
}
Expand Down
110 changes: 96 additions & 14 deletions src/core/prompts/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,61 @@ import { RooProtectedController } from "../protect/RooProtectedController"
import { ToolProtocol, isNativeProtocol, TOOL_PROTOCOL } from "@roo-code/types"

export const formatResponse = {
toolDenied: () => `The user denied this operation.`,
toolDenied: (protocol?: ToolProtocol) => {
if (isNativeProtocol(protocol ?? TOOL_PROTOCOL.XML)) {
return JSON.stringify({
status: "denied",
message: "The user denied this operation.",
})
}
return `The user denied this operation.`
},

toolDeniedWithFeedback: (feedback?: string) =>
`The user denied this operation and provided the following feedback:\n<feedback>\n${feedback}\n</feedback>`,
toolDeniedWithFeedback: (feedback?: string, protocol?: ToolProtocol) => {
if (isNativeProtocol(protocol ?? TOOL_PROTOCOL.XML)) {
return JSON.stringify({
status: "denied",
message: "The user denied this operation and provided the following feedback",
feedback: feedback,
})
}
return `The user denied this operation and provided the following feedback:\n<feedback>\n${feedback}\n</feedback>`
},

toolApprovedWithFeedback: (feedback?: string) =>
`The user approved this operation and provided the following context:\n<feedback>\n${feedback}\n</feedback>`,
toolApprovedWithFeedback: (feedback?: string, protocol?: ToolProtocol) => {
if (isNativeProtocol(protocol ?? TOOL_PROTOCOL.XML)) {
return JSON.stringify({
status: "approved",
message: "The user approved this operation and provided the following context",
feedback: feedback,
})
}
return `The user approved this operation and provided the following context:\n<feedback>\n${feedback}\n</feedback>`
},

toolError: (error?: string) => `The tool execution failed with the following error:\n<error>\n${error}\n</error>`,
toolError: (error?: string, protocol?: ToolProtocol) => {
if (isNativeProtocol(protocol ?? TOOL_PROTOCOL.XML)) {
return JSON.stringify({
status: "error",
message: "The tool execution failed",
error: error,
})
}
return `The tool execution failed with the following error:\n<error>\n${error}\n</error>`
},

rooIgnoreError: (path: string) =>
`Access to ${path} is blocked by the .rooignore file settings. You must try to continue in the task without using this file, or ask the user to update the .rooignore file.`,
rooIgnoreError: (path: string, protocol?: ToolProtocol) => {
if (isNativeProtocol(protocol ?? TOOL_PROTOCOL.XML)) {
return JSON.stringify({
status: "error",
type: "access_denied",
message: "Access blocked by .rooignore",
path: path,
suggestion: "Try to continue without this file, or ask the user to update the .rooignore file",
})
}
return `Access to ${path} is blocked by the .rooignore file settings. You must try to continue in the task without using this file, or ask the user to update the .rooignore file.`
},

noToolsUsed: (protocol?: ToolProtocol) => {
const instructions = getToolInstructionsReminder(protocol)
Expand All @@ -34,8 +77,16 @@ Otherwise, if you have not completed the task and do not need additional informa
(This is an automated message, so do not respond to it conversationally.)`
},

tooManyMistakes: (feedback?: string) =>
`You seem to be having trouble proceeding. The user has provided the following feedback to help guide you:\n<feedback>\n${feedback}\n</feedback>`,
tooManyMistakes: (feedback?: string, protocol?: ToolProtocol) => {
if (isNativeProtocol(protocol ?? TOOL_PROTOCOL.XML)) {
return JSON.stringify({
status: "guidance",
message: "You seem to be having trouble proceeding",
feedback: feedback,
})
}
return `You seem to be having trouble proceeding. The user has provided the following feedback to help guide you:\n<feedback>\n${feedback}\n</feedback>`
},

missingToolParameterError: (paramName: string, protocol?: ToolProtocol) => {
const instructions = getToolInstructionsReminder(protocol)
Expand Down Expand Up @@ -82,15 +133,46 @@ Otherwise, if you have not completed the task and do not need additional informa
return `${isNewFile ? newFileGuidance : existingFileGuidance}\n${instructions}`
},

invalidMcpToolArgumentError: (serverName: string, toolName: string) =>
`Invalid JSON argument used with ${serverName} for ${toolName}. Please retry with a properly formatted JSON argument.`,
invalidMcpToolArgumentError: (serverName: string, toolName: string, protocol?: ToolProtocol) => {
if (isNativeProtocol(protocol ?? TOOL_PROTOCOL.XML)) {
return JSON.stringify({
status: "error",
type: "invalid_argument",
message: "Invalid JSON argument",
server: serverName,
tool: toolName,
suggestion: "Please retry with a properly formatted JSON argument",
})
}
return `Invalid JSON argument used with ${serverName} for ${toolName}. Please retry with a properly formatted JSON argument.`
},

unknownMcpToolError: (serverName: string, toolName: string, availableTools: string[]) => {
unknownMcpToolError: (serverName: string, toolName: string, availableTools: string[], protocol?: ToolProtocol) => {
if (isNativeProtocol(protocol ?? TOOL_PROTOCOL.XML)) {
return JSON.stringify({
status: "error",
type: "unknown_tool",
message: "Tool does not exist on server",
server: serverName,
tool: toolName,
available_tools: availableTools.length > 0 ? availableTools : [],
suggestion: "Please use one of the available tools or check if the server is properly configured",
})
}
const toolsList = availableTools.length > 0 ? availableTools.join(", ") : "No tools available"
return `Tool '${toolName}' does not exist on server '${serverName}'.\n\nAvailable tools on this server: ${toolsList}\n\nPlease use one of the available tools or check if the server is properly configured.`
},

unknownMcpServerError: (serverName: string, availableServers: string[]) => {
unknownMcpServerError: (serverName: string, availableServers: string[], protocol?: ToolProtocol) => {
if (isNativeProtocol(protocol ?? TOOL_PROTOCOL.XML)) {
return JSON.stringify({
status: "error",
type: "unknown_server",
message: "Server is not configured",
server: serverName,
available_servers: availableServers.length > 0 ? availableServers : [],
})
}
const serversList = availableServers.length > 0 ? availableServers.join(", ") : "No servers available"
return `Server '${serverName}' is not configured. Available servers: ${serversList}`
},
Expand Down
2 changes: 1 addition & 1 deletion src/core/prompts/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ async function generatePrompt(
const effectiveProtocol = getEffectiveProtocol(settings?.toolProtocol)

const [modesSection, mcpServersSection] = await Promise.all([
getModesSection(context, isNativeProtocol(effectiveProtocol)),
getModesSection(context),
shouldIncludeMcp
? getMcpServersSection(
mcpHub,
Expand Down
Loading
Loading