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
14 changes: 14 additions & 0 deletions packages/types/src/tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,17 @@ export const toolUsageSchema = z.record(
)

export type ToolUsage = z.infer<typeof toolUsageSchema>

/**
* Tool protocol constants
*/
export const TOOL_PROTOCOL = {
XML: "xml",
NATIVE: "native",
} as const

/**
* Tool protocol type for system prompt generation
* Derived from TOOL_PROTOCOL constants to ensure type safety
*/
export type ToolProtocol = (typeof TOOL_PROTOCOL)[keyof typeof TOOL_PROTOCOL]
179 changes: 178 additions & 1 deletion src/core/prompts/__tests__/system-prompt.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,11 @@ __setMockImplementation(
}

const joinedSections = sections.join("\n\n")
const effectiveProtocol = options?.settings?.toolProtocol || "xml"
const skipXmlReferences = effectiveProtocol === "native"
const toolUseRef = skipXmlReferences ? "." : " without interfering with the TOOL USE guidelines."
return joinedSections
? `\n====\n\nUSER'S CUSTOM INSTRUCTIONS\n\nThe following additional instructions are provided by the user, and should be followed to the best of your ability without interfering with the TOOL USE guidelines.\n\n${joinedSections}`
? `\n====\n\nUSER'S CUSTOM INSTRUCTIONS\n\nThe following additional instructions are provided by the user, and should be followed to the best of your ability${toolUseRef}\n\n${joinedSections}`
: ""
},
)
Expand Down Expand Up @@ -581,6 +584,7 @@ describe("SYSTEM_PROMPT", () => {
todoListEnabled: false,
useAgentRules: true,
newTaskRequireTodos: false,
toolProtocol: "xml" as const,
}

const prompt = await SYSTEM_PROMPT(
Expand Down Expand Up @@ -614,6 +618,7 @@ describe("SYSTEM_PROMPT", () => {
todoListEnabled: true,
useAgentRules: true,
newTaskRequireTodos: false,
toolProtocol: "xml" as const,
}

const prompt = await SYSTEM_PROMPT(
Expand Down Expand Up @@ -646,6 +651,7 @@ describe("SYSTEM_PROMPT", () => {
todoListEnabled: true,
useAgentRules: true,
newTaskRequireTodos: false,
toolProtocol: "xml" as const,
}

const prompt = await SYSTEM_PROMPT(
Expand All @@ -672,6 +678,177 @@ describe("SYSTEM_PROMPT", () => {
expect(prompt).toContain("## update_todo_list")
})

it("should include XML tool instructions when disableXmlToolInstructions is false (default)", async () => {
const settings = {
maxConcurrentFileReads: 5,
todoListEnabled: true,
useAgentRules: true,
newTaskRequireTodos: false,
toolProtocol: "xml" as const, // explicitly xml
}

const prompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
false,
undefined, // mcpHub
undefined, // diffStrategy
undefined, // browserViewportSize
defaultModeSlug, // mode
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // diffEnabled
experiments,
true, // enableMcpServerCreation
undefined, // language
undefined, // rooIgnoreInstructions
undefined, // partialReadsEnabled
settings, // settings
)

// Should contain XML guidance sections
expect(prompt).toContain("TOOL USE")
expect(prompt).toContain("XML-style tags")
expect(prompt).toContain("<actual_tool_name>")
expect(prompt).toContain("</actual_tool_name>")
expect(prompt).toContain("Tool Use Guidelines")
expect(prompt).toContain("# Tools")

// Should contain tool descriptions with XML examples
expect(prompt).toContain("## read_file")
expect(prompt).toContain("<read_file>")
expect(prompt).toContain("<path>")

// Should be byte-for-byte compatible with default behavior
const defaultPrompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
false,
undefined,
undefined,
undefined,
defaultModeSlug,
undefined,
undefined,
undefined,
undefined,
experiments,
true,
undefined,
undefined,
undefined,
{
maxConcurrentFileReads: 5,
todoListEnabled: true,
useAgentRules: true,
newTaskRequireTodos: false,
toolProtocol: "xml" as const,
},
)

expect(prompt).toBe(defaultPrompt)
})

it("should include native tool instructions when toolProtocol is native", async () => {
const settings = {
maxConcurrentFileReads: 5,
todoListEnabled: true,
useAgentRules: true,
newTaskRequireTodos: false,
toolProtocol: "native" as const, // native protocol
}

const prompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
false,
undefined, // mcpHub
undefined, // diffStrategy
undefined, // browserViewportSize
defaultModeSlug, // mode
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // diffEnabled
experiments,
true, // enableMcpServerCreation
undefined, // language
undefined, // rooIgnoreInstructions
undefined, // partialReadsEnabled
settings, // settings
)

// Should contain TOOL USE section with native note
expect(prompt).toContain("TOOL USE")
expect(prompt).toContain("provider-native tool-calling mechanism")
expect(prompt).toContain("Do not include XML markup or examples")

// Should NOT contain XML-style tags or examples
expect(prompt).not.toContain("XML-style tags")
expect(prompt).not.toContain("<actual_tool_name>")
expect(prompt).not.toContain("</actual_tool_name>")

// Should contain Tool Use Guidelines section without format-specific guidance
expect(prompt).toContain("Tool Use Guidelines")
// Should NOT contain any protocol-specific formatting instructions
expect(prompt).not.toContain("provider's native tool-calling mechanism")
expect(prompt).not.toContain("XML format specified for each tool")

// Should NOT contain # Tools catalog at all in native mode
expect(prompt).not.toContain("# Tools")
expect(prompt).not.toContain("## read_file")
expect(prompt).not.toContain("## execute_command")
expect(prompt).not.toContain("<read_file>")
expect(prompt).not.toContain("<path>")
expect(prompt).not.toContain("Usage:")
expect(prompt).not.toContain("Examples:")

// Should still contain role definition and other non-XML sections
expect(prompt).toContain(modes[0].roleDefinition)
expect(prompt).toContain("CAPABILITIES")
expect(prompt).toContain("RULES")
expect(prompt).toContain("SYSTEM INFORMATION")
expect(prompt).toContain("OBJECTIVE")
})

it("should default to XML tool instructions when toolProtocol is undefined", async () => {
const settings = {
maxConcurrentFileReads: 5,
todoListEnabled: true,
useAgentRules: true,
newTaskRequireTodos: false,
toolProtocol: "xml" as const,
}

const prompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
false,
undefined, // mcpHub
undefined, // diffStrategy
undefined, // browserViewportSize
defaultModeSlug, // mode
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // diffEnabled
experiments,
true, // enableMcpServerCreation
undefined, // language
undefined, // rooIgnoreInstructions
undefined, // partialReadsEnabled
settings, // settings
)

// Should contain XML guidance (default behavior)
expect(prompt).toContain("TOOL USE")
expect(prompt).toContain("XML-style tags")
expect(prompt).toContain("<actual_tool_name>")
expect(prompt).toContain("Tool Use Guidelines")
expect(prompt).toContain("# Tools")
})

afterAll(() => {
vi.restoreAllMocks()
})
Expand Down
10 changes: 10 additions & 0 deletions src/core/prompts/__tests__/toolProtocolResolver.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// npx vitest core/prompts/__tests__/toolProtocolResolver.spec.ts

import { describe, it, expect } from "vitest"
import { resolveToolProtocol } from "../toolProtocolResolver"

describe("toolProtocolResolver", () => {
it("should default to xml protocol", () => {
expect(resolveToolProtocol()).toBe("xml")
})
})
54 changes: 43 additions & 11 deletions src/core/prompts/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import * as path from "path"
import * as diff from "diff"
import { RooIgnoreController, LOCK_TEXT_SYMBOL } from "../ignore/RooIgnoreController"
import { RooProtectedController } from "../protect/RooProtectedController"
import { resolveToolProtocol, isNativeProtocol } from "./toolProtocolResolver"
import { ToolProtocol } from "@roo-code/types"

export const formatResponse = {
toolDenied: () => `The user denied this operation.`,
Expand All @@ -18,25 +20,36 @@ export const formatResponse = {
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.`,

noToolsUsed: () =>
`[ERROR] You did not use a tool in your previous response! Please retry with a tool use.
noToolsUsed: (protocol?: ToolProtocol) => {
const instructions = getToolInstructionsReminder(protocol)

${toolUseInstructionsReminder}
return `[ERROR] You did not use a tool in your previous response! Please retry with a tool use.

${instructions}

# Next Steps

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.)`,
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.)`
},

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>`,

missingToolParameterError: (paramName: string) =>
`Missing value for required parameter '${paramName}'. Please retry with complete response.\n\n${toolUseInstructionsReminder}`,
missingToolParameterError: (paramName: string, protocol?: ToolProtocol) => {
const instructions = getToolInstructionsReminder(protocol)

lineCountTruncationError: (actualLineCount: number, isNewFile: boolean, diffStrategyEnabled: boolean = false) => {
return `Missing value for required parameter '${paramName}'. Please retry with complete response.\n\n${instructions}`
},

lineCountTruncationError: (
actualLineCount: number,
isNewFile: boolean,
diffStrategyEnabled: boolean = false,
protocol?: ToolProtocol,
) => {
const truncationMessage = `Note: Your response may have been truncated because it exceeded your output limit. You wrote ${actualLineCount} lines of content, but the line_count parameter was either missing or not included in your response.`

const newFileGuidance =
Expand Down Expand Up @@ -65,7 +78,9 @@ Otherwise, if you have not completed the task and do not need additional informa
`RECOMMENDED APPROACH:\n` +
`${existingFileApproaches.join("\n")}\n`

return `${isNewFile ? newFileGuidance : existingFileGuidance}\n${toolUseInstructionsReminder}`
const instructions = getToolInstructionsReminder(protocol)

return `${isNewFile ? newFileGuidance : existingFileGuidance}\n${instructions}`
},

invalidMcpToolArgumentError: (serverName: string, toolName: string) =>
Expand Down Expand Up @@ -220,3 +235,20 @@ I have completed the task...
</attempt_completion>

Always use the actual tool name as the XML tag name for proper parsing and execution.`

const toolUseInstructionsReminderNative = `# Reminder: Instructions for Tool Use

Tools are invoked using the platform's native tool calling mechanism. Each tool requires specific parameters as defined in the tool descriptions. Refer to the tool definitions provided in your system instructions for the correct parameter structure and usage examples.

Always ensure you provide all required parameters for the tool you wish to use.`

/**
* Gets the appropriate tool use instructions reminder based on the protocol.
*
* @param protocol - Optional tool protocol, falls back to default if not provided
* @returns The tool use instructions reminder text
*/
function getToolInstructionsReminder(protocol?: ToolProtocol): string {
const effectiveProtocol = protocol ?? resolveToolProtocol()
return isNativeProtocol(effectiveProtocol) ? toolUseInstructionsReminderNative : toolUseInstructionsReminder
}
10 changes: 8 additions & 2 deletions src/core/prompts/sections/custom-instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Dirent } from "fs"
import { isLanguage } from "@roo-code/types"

import type { SystemPromptSettings } from "../types"
import { getEffectiveProtocol, isNativeProtocol } from "../toolProtocolResolver"

import { LANGUAGES } from "../../../shared/language"
import { getRooDirectoriesForCwd, getGlobalRooDirectory } from "../../../services/roo-config"
Expand Down Expand Up @@ -368,15 +369,20 @@ export async function addCustomInstructions(

const joinedSections = sections.join("\n\n")

const effectiveProtocol = getEffectiveProtocol(options.settings)

return joinedSections
? `
====

USER'S CUSTOM INSTRUCTIONS

The following additional instructions are provided by the user, and should be followed to the best of your ability without interfering with the TOOL USE guidelines.
The following additional instructions are provided by the user, and should be followed to the best of your ability${
isNativeProtocol(effectiveProtocol) ? "." : " without interfering with the TOOL USE guidelines."
}

${joinedSections}`
${joinedSections}
`
: ""
}

Expand Down
8 changes: 7 additions & 1 deletion src/core/prompts/sections/rules.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { DiffStrategy } from "../../../shared/tools"
import { CodeIndexManager } from "../../../services/code-index/manager"
import type { SystemPromptSettings } from "../types"
import { getEffectiveProtocol, isNativeProtocol } from "../toolProtocolResolver"

function getEditingInstructions(diffStrategy?: DiffStrategy): string {
const instructions: string[] = []
Expand Down Expand Up @@ -45,6 +47,7 @@ export function getRulesSection(
supportsComputerUse: boolean,
diffStrategy?: DiffStrategy,
codeIndexManager?: CodeIndexManager,
settings?: SystemPromptSettings,
): string {
const isCodebaseSearchAvailable =
codeIndexManager &&
Expand All @@ -56,12 +59,15 @@ export function getRulesSection(
? "- **CRITICAL: For ANY exploration of code you haven't examined yet in this conversation, you MUST use the `codebase_search` tool FIRST before using search_files or other file exploration tools.** This requirement applies throughout the entire conversation, not just when starting a task. The codebase_search tool uses semantic search to find relevant code based on meaning, not just keywords, making it much more effective for understanding how features are implemented. Even if you've already explored some parts of the codebase, any new area or functionality you need to understand requires using codebase_search first.\n"
: ""

// Determine whether to use XML tool references based on protocol
const effectiveProtocol = getEffectiveProtocol(settings)

return `====

RULES

- The project base directory is: ${cwd.toPosix()}
- All file paths must be relative to this directory. However, commands may change directories in terminals, so respect working directory specified by the response to <execute_command>.
- All file paths must be relative to this directory. However, commands may change directories in terminals, so respect working directory specified by the response to ${isNativeProtocol(effectiveProtocol) ? "execute_command" : "<execute_command>"}.
- You cannot \`cd\` into a different directory to complete a task. You are stuck operating from '${cwd.toPosix()}', so be sure to pass in the correct 'path' parameter when using tools that require a path.
- Do not use the ~ character or $HOME to refer to the home directory.
- Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '${cwd.toPosix()}', and if so prepend with \`cd\`'ing into that directory && then executing the command (as one command since you are stuck operating from '${cwd.toPosix()}'). For example, if you needed to run \`npm install\` in a project outside of '${cwd.toPosix()}', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) && (command, in this case npm install)\`.
Expand Down
Loading