diff --git a/apps/web-evals/src/app/runs/new/new-run.tsx b/apps/web-evals/src/app/runs/new/new-run.tsx index cea15c6ddd8..8d44ef38e7c 100644 --- a/apps/web-evals/src/app/runs/new/new-run.tsx +++ b/apps/web-evals/src/app/runs/new/new-run.tsx @@ -56,7 +56,6 @@ import { useRooCodeCloudModels } from "@/hooks/use-roo-code-cloud-models" import { Button, - Checkbox, FormControl, FormField, FormItem, @@ -111,7 +110,6 @@ export function NewRun() { const [provider, setModelSource] = useState<"roo" | "openrouter" | "other">("other") const [executionMethod, setExecutionMethod] = useState("vscode") - const [useNativeToolProtocol, setUseNativeToolProtocol] = useState(true) const [commandExecutionTimeout, setCommandExecutionTimeout] = useState(20) const [terminalShellIntegrationTimeout, setTerminalShellIntegrationTimeout] = useState(30) // seconds @@ -464,7 +462,6 @@ export function NewRun() { ...(runValues.settings || {}), apiProvider: "openrouter", openRouterModelId: selection.model, - toolProtocol: useNativeToolProtocol ? "native" : "xml", commandExecutionTimeout, terminalShellIntegrationTimeout: terminalShellIntegrationTimeout * 1000, } @@ -474,7 +471,6 @@ export function NewRun() { ...(runValues.settings || {}), apiProvider: "roo", apiModelId: selection.model, - toolProtocol: useNativeToolProtocol ? "native" : "xml", commandExecutionTimeout, terminalShellIntegrationTimeout: terminalShellIntegrationTimeout * 1000, } @@ -485,7 +481,6 @@ export function NewRun() { ...EVALS_SETTINGS, ...providerSettings, ...importedSettings.globalSettings, - toolProtocol: useNativeToolProtocol ? "native" : "xml", commandExecutionTimeout, terminalShellIntegrationTimeout: terminalShellIntegrationTimeout * 1000, } @@ -512,7 +507,6 @@ export function NewRun() { configSelections, importedSettings, router, - useNativeToolProtocol, commandExecutionTimeout, terminalShellIntegrationTimeout, ], @@ -688,26 +682,6 @@ export function NewRun() { )} -
- -
- -
-
- {settings && ( )} @@ -792,26 +766,6 @@ export function NewRun() { ))} - -
- -
- -
-
)} diff --git a/packages/core/src/custom-tools/__tests__/__snapshots__/format-xml.spec.ts.snap b/packages/core/src/custom-tools/__tests__/__snapshots__/format-xml.spec.ts.snap deleted file mode 100644 index b4503fa925d..00000000000 --- a/packages/core/src/custom-tools/__tests__/__snapshots__/format-xml.spec.ts.snap +++ /dev/null @@ -1,129 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`XML Protocol snapshots > should generate correct XML description for all fixtures combined 1`] = ` -"# Custom Tools - -The following custom tools are available for this mode. Use them in the same way as built-in tools. - -## simple -Description: Simple tool -Parameters: -- value: (required) The input value (type: string) -Usage: - -value value here - - -## cached -Description: Cached tool -Parameters: -Usage: - - - -## legacy -Description: Legacy tool using args -Parameters: -- input: (required) The input string (type: string) -Usage: - -input value here - - -## multi_toolA -Description: Tool A -Parameters: -Usage: - - - -## multi_toolB -Description: Tool B -Parameters: -Usage: - - - -## mixed_validTool -Description: Valid -Parameters: -Usage: - -" -`; - -exports[`XML Protocol snapshots > should generate correct XML description for cached tool 1`] = ` -"# Custom Tools - -The following custom tools are available for this mode. Use them in the same way as built-in tools. - -## cached -Description: Cached tool -Parameters: -Usage: - -" -`; - -exports[`XML Protocol snapshots > should generate correct XML description for legacy tool (using args) 1`] = ` -"# Custom Tools - -The following custom tools are available for this mode. Use them in the same way as built-in tools. - -## legacy -Description: Legacy tool using args -Parameters: -- input: (required) The input string (type: string) -Usage: - -input value here -" -`; - -exports[`XML Protocol snapshots > should generate correct XML description for mixed export tool 1`] = ` -"# Custom Tools - -The following custom tools are available for this mode. Use them in the same way as built-in tools. - -## mixed_validTool -Description: Valid -Parameters: -Usage: - -" -`; - -exports[`XML Protocol snapshots > should generate correct XML description for multi export tools 1`] = ` -"# Custom Tools - -The following custom tools are available for this mode. Use them in the same way as built-in tools. - -## multi_toolA -Description: Tool A -Parameters: -Usage: - - - -## multi_toolB -Description: Tool B -Parameters: -Usage: - -" -`; - -exports[`XML Protocol snapshots > should generate correct XML description for simple tool 1`] = ` -"# Custom Tools - -The following custom tools are available for this mode. Use them in the same way as built-in tools. - -## simple -Description: Simple tool -Parameters: -- value: (required) The input value (type: string) -Usage: - -value value here -" -`; diff --git a/packages/core/src/custom-tools/__tests__/format-xml.spec.ts b/packages/core/src/custom-tools/__tests__/format-xml.spec.ts deleted file mode 100644 index 0be07723476..00000000000 --- a/packages/core/src/custom-tools/__tests__/format-xml.spec.ts +++ /dev/null @@ -1,192 +0,0 @@ -// pnpm --filter @roo-code/core test src/custom-tools/__tests__/format-xml.spec.ts - -import { type SerializedCustomToolDefinition, parametersSchema as z, defineCustomTool } from "@roo-code/types" - -import { serializeCustomTool, serializeCustomTools } from "../serialize.js" -import { formatXml } from "../format-xml.js" - -import simpleTool from "./fixtures/simple.js" -import cachedTool from "./fixtures/cached.js" -import legacyTool from "./fixtures/legacy.js" -import { toolA, toolB } from "./fixtures/multi.js" -import { validTool as mixedValidTool } from "./fixtures/mixed.js" - -const fixtureTools = { - simple: simpleTool, - cached: cachedTool, - legacy: legacyTool, - multi_toolA: toolA, - multi_toolB: toolB, - mixed_validTool: mixedValidTool, -} - -describe("formatXml", () => { - it("should return empty string for empty tools array", () => { - expect(formatXml([])).toBe("") - }) - - it("should throw for undefined tools", () => { - expect(() => formatXml(undefined as unknown as SerializedCustomToolDefinition[])).toThrow() - }) - - it("should generate description for a single tool without args", () => { - const tool = defineCustomTool({ - name: "my_tool", - description: "A simple tool that does something", - async execute() { - return "done" - }, - }) - - const serialized = serializeCustomTool(tool) - const result = formatXml([serialized]) - - expect(result).toContain("# Custom Tools") - expect(result).toContain("## my_tool") - expect(result).toContain("Description: A simple tool that does something") - expect(result).toContain("Parameters: None") - expect(result).toContain("") - expect(result).toContain("") - }) - - it("should generate description for a tool with required args", () => { - const tool = defineCustomTool({ - name: "greeter", - description: "Greets a person by name", - parameters: z.object({ - name: z.string().describe("The name of the person to greet"), - }), - async execute({ name }) { - return `Hello, ${name}!` - }, - }) - - const serialized = serializeCustomTool(tool) - const result = formatXml([serialized]) - - expect(result).toContain("## greeter") - expect(result).toContain("Description: Greets a person by name") - expect(result).toContain("Parameters:") - expect(result).toContain("- name: (required) The name of the person to greet (type: string)") - expect(result).toContain("") - expect(result).toContain("name value here") - expect(result).toContain("") - }) - - it("should generate description for a tool with optional args", () => { - const tool = defineCustomTool({ - name: "configurable_tool", - description: "A tool with optional configuration", - parameters: z.object({ - input: z.string().describe("The input to process"), - format: z.string().optional().describe("Output format"), - }), - async execute({ input, format }) { - return format ? `${input} (${format})` : input - }, - }) - - const serialized = serializeCustomTool(tool) - const result = formatXml([serialized]) - - expect(result).toContain("- input: (required) The input to process (type: string)") - expect(result).toContain("- format: (optional) Output format (type: string)") - expect(result).toContain("input value here") - expect(result).toContain("optional format value") - }) - - it("should generate descriptions for multiple tools", () => { - const tools = [ - defineCustomTool({ - name: "tool_a", - description: "First tool", - async execute() { - return "a" - }, - }), - defineCustomTool({ - name: "tool_b", - description: "Second tool", - parameters: z.object({ - value: z.number().describe("A numeric value"), - }), - async execute() { - return "b" - }, - }), - ] - - const serialized = serializeCustomTools(tools) - const result = formatXml(serialized) - - expect(result).toContain("## tool_a") - expect(result).toContain("Description: First tool") - expect(result).toContain("## tool_b") - expect(result).toContain("Description: Second tool") - expect(result).toContain("- value: (required) A numeric value (type: number)") - }) - - it("should treat args in required array as required", () => { - // Using a raw SerializedToolDefinition to test the required behavior. - const tools: SerializedCustomToolDefinition[] = [ - { - name: "test_tool", - description: "Test tool", - parameters: { - type: "object", - properties: { - data: { - type: "object", - description: "Some data", - }, - }, - required: ["data"], - }, - }, - ] - - const result = formatXml(tools) - - expect(result).toContain("- data: (required) Some data (type: object)") - expect(result).toContain("data value here") - }) -}) - -describe("XML Protocol snapshots", () => { - it("should generate correct XML description for simple tool", () => { - const serialized = serializeCustomTool(fixtureTools.simple) - const result = formatXml([serialized]) - expect(result).toMatchSnapshot() - }) - - it("should generate correct XML description for cached tool", () => { - const serialized = serializeCustomTool(fixtureTools.cached) - const result = formatXml([serialized]) - expect(result).toMatchSnapshot() - }) - - it("should generate correct XML description for legacy tool (using args)", () => { - const serialized = serializeCustomTool(fixtureTools.legacy) - const result = formatXml([serialized]) - expect(result).toMatchSnapshot() - }) - - it("should generate correct XML description for multi export tools", () => { - const serializedA = serializeCustomTool(fixtureTools.multi_toolA) - const serializedB = serializeCustomTool(fixtureTools.multi_toolB) - const result = formatXml([serializedA, serializedB]) - expect(result).toMatchSnapshot() - }) - - it("should generate correct XML description for mixed export tool", () => { - const serialized = serializeCustomTool(fixtureTools.mixed_validTool) - const result = formatXml([serialized]) - expect(result).toMatchSnapshot() - }) - - it("should generate correct XML description for all fixtures combined", () => { - const allSerialized = Object.values(fixtureTools).map(serializeCustomTool) - const result = formatXml(allSerialized) - expect(result).toMatchSnapshot() - }) -}) diff --git a/packages/core/src/custom-tools/format-xml.ts b/packages/core/src/custom-tools/format-xml.ts deleted file mode 100644 index 01338f236cc..00000000000 --- a/packages/core/src/custom-tools/format-xml.ts +++ /dev/null @@ -1,89 +0,0 @@ -import type { SerializedCustomToolDefinition, SerializedCustomToolParameters } from "@roo-code/types" - -/** - * Extract the type string from a parameter schema. - * Handles both direct `type` property and `anyOf` schemas (used for nullable types). - */ -function getParameterType(parameter: SerializedCustomToolParameters): string { - // Direct type property - if (parameter.type) { - return String(parameter.type) - } - - // Handle anyOf schema (used for nullable types like `string | null`) - if (parameter.anyOf && Array.isArray(parameter.anyOf)) { - const types = parameter.anyOf - .map((schema) => (typeof schema === "object" && schema.type ? String(schema.type) : null)) - .filter((t): t is string => t !== null && t !== "null") - - if (types.length > 0) { - return types.join(" | ") - } - } - - return "unknown" -} - -function getParameterDescription(name: string, parameter: SerializedCustomToolParameters, required: string[]): string { - const requiredText = required.includes(name) ? "(required)" : "(optional)" - const typeText = getParameterType(parameter) - return `- ${name}: ${requiredText} ${parameter.description ?? ""} (type: ${typeText})` -} - -function getUsage(tool: SerializedCustomToolDefinition): string { - const lines: string[] = [`<${tool.name}>`] - - if (tool.parameters) { - const required = tool.parameters.required ?? [] - - for (const [argName, _argType] of Object.entries(tool.parameters.properties ?? {})) { - const placeholder = required.includes(argName) ? `${argName} value here` : `optional ${argName} value` - lines.push(`<${argName}>${placeholder}`) - } - } - - lines.push(``) - return lines.join("\n") -} - -function getDescription(tool: SerializedCustomToolDefinition): string { - const parts: string[] = [] - - parts.push(`## ${tool.name}`) - parts.push(`Description: ${tool.description}`) - - if (tool.parameters?.properties) { - const required = tool.parameters?.required ?? [] - parts.push("Parameters:") - - for (const [name, parameter] of Object.entries(tool.parameters.properties)) { - // What should we do with `boolean` values for `parameter`? - if (typeof parameter !== "object") { - continue - } - - parts.push(getParameterDescription(name, parameter, required)) - } - } else { - parts.push("Parameters: None") - } - - parts.push("Usage:") - parts.push(getUsage(tool)) - - return parts.join("\n") -} - -export function formatXml(tools: SerializedCustomToolDefinition[]): string { - if (tools.length === 0) { - return "" - } - - const descriptions = tools.map((tool) => getDescription(tool)) - - return `# Custom Tools - -The following custom tools are available for this mode. Use them in the same way as built-in tools. - -${descriptions.join("\n\n")}` -} diff --git a/packages/core/src/custom-tools/index.ts b/packages/core/src/custom-tools/index.ts index c8b44ec1175..c6ddc0f6eb5 100644 --- a/packages/core/src/custom-tools/index.ts +++ b/packages/core/src/custom-tools/index.ts @@ -1,4 +1,3 @@ export * from "./custom-tool-registry.js" export * from "./serialize.js" -export * from "./format-xml.js" export * from "./format-native.js" diff --git a/packages/telemetry/src/TelemetryService.ts b/packages/telemetry/src/TelemetryService.ts index ff94b524f84..cf692a07702 100644 --- a/packages/telemetry/src/TelemetryService.ts +++ b/packages/telemetry/src/TelemetryService.ts @@ -111,8 +111,8 @@ export class TelemetryService { this.captureEvent(TelemetryEventName.MODE_SWITCH, { taskId, newMode }) } - public captureToolUsage(taskId: string, tool: string, toolProtocol: string): void { - this.captureEvent(TelemetryEventName.TOOL_USED, { taskId, tool, toolProtocol }) + public captureToolUsage(taskId: string, tool: string): void { + this.captureEvent(TelemetryEventName.TOOL_USED, { taskId, tool }) } public captureCheckpointCreated(taskId: string): void { diff --git a/packages/types/src/history.ts b/packages/types/src/history.ts index b4d84cb9a51..a60d1a75b65 100644 --- a/packages/types/src/history.ts +++ b/packages/types/src/history.ts @@ -19,16 +19,6 @@ export const historyItemSchema = z.object({ size: z.number().optional(), workspace: z.string().optional(), mode: z.string().optional(), - /** - * The tool protocol used by this task. Once a task uses tools with a specific - * protocol (XML or Native), it is permanently locked to that protocol. - * - * - "xml": Tool calls are parsed from XML text (no tool IDs) - * - "native": Tool calls come as tool_call chunks with IDs - * - * This ensures task resumption works correctly even when NTC settings change. - */ - toolProtocol: z.enum(["xml", "native"]).optional(), apiConfigName: z.string().optional(), // Provider profile name for sticky profile feature status: z.enum(["active", "completed", "delegated"]).optional(), delegatedToId: z.string().optional(), // Last child this parent delegated to diff --git a/packages/types/src/model.ts b/packages/types/src/model.ts index 21d36bca85e..95e9095a89e 100644 --- a/packages/types/src/model.ts +++ b/packages/types/src/model.ts @@ -110,10 +110,6 @@ export const modelInfoSchema = z.object({ isStealthModel: z.boolean().optional(), // Flag to indicate if the model is free (no cost) isFree: z.boolean().optional(), - // Flag to indicate if the model supports native tool calling (OpenAI-style function calling) - supportsNativeTools: z.boolean().optional(), - // Default tool protocol preferred by this model (if not specified, falls back to capability/provider defaults) - defaultToolProtocol: z.enum(["xml", "native"]).optional(), // Exclude specific native tools from being available (only applies to native protocol) // These tools will be removed from the set of tools available to the model excludedTools: z.array(z.string()).optional(), diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 457252e7fe6..9b6f8328b83 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -185,9 +185,6 @@ const baseProviderSettingsSchema = z.object({ // Model verbosity. verbosity: verbosityLevelsSchema.optional(), - - // Tool protocol override for this profile. - toolProtocol: z.enum(["xml", "native"]).optional(), }) // Several of the providers share common model config properties. diff --git a/packages/types/src/providers/anthropic.ts b/packages/types/src/providers/anthropic.ts index 70f880a24ef..883b6eb7160 100644 --- a/packages/types/src/providers/anthropic.ts +++ b/packages/types/src/providers/anthropic.ts @@ -11,8 +11,6 @@ export const anthropicModels = { contextWindow: 200_000, // Default 200K, extendable to 1M with beta flag 'context-1m-2025-08-07' supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 3.0, // $3 per million input tokens (≤200K context) outputPrice: 15.0, // $15 per million output tokens (≤200K context) cacheWritesPrice: 3.75, // $3.75 per million tokens @@ -34,8 +32,6 @@ export const anthropicModels = { contextWindow: 200_000, // Default 200K, extendable to 1M with beta flag 'context-1m-2025-08-07' supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 3.0, // $3 per million input tokens (≤200K context) outputPrice: 15.0, // $15 per million output tokens (≤200K context) cacheWritesPrice: 3.75, // $3.75 per million tokens @@ -57,8 +53,6 @@ export const anthropicModels = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 5.0, // $5 per million input tokens outputPrice: 25.0, // $25 per million output tokens cacheWritesPrice: 6.25, // $6.25 per million tokens @@ -70,8 +64,6 @@ export const anthropicModels = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 15.0, // $15 per million input tokens outputPrice: 75.0, // $75 per million output tokens cacheWritesPrice: 18.75, // $18.75 per million tokens @@ -83,8 +75,6 @@ export const anthropicModels = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 15.0, // $15 per million input tokens outputPrice: 75.0, // $75 per million output tokens cacheWritesPrice: 18.75, // $18.75 per million tokens @@ -96,8 +86,6 @@ export const anthropicModels = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 3.0, // $3 per million input tokens outputPrice: 15.0, // $15 per million output tokens cacheWritesPrice: 3.75, // $3.75 per million tokens @@ -110,8 +98,6 @@ export const anthropicModels = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 3.0, // $3 per million input tokens outputPrice: 15.0, // $15 per million output tokens cacheWritesPrice: 3.75, // $3.75 per million tokens @@ -122,8 +108,6 @@ export const anthropicModels = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 3.0, // $3 per million input tokens outputPrice: 15.0, // $15 per million output tokens cacheWritesPrice: 3.75, // $3.75 per million tokens @@ -134,8 +118,6 @@ export const anthropicModels = { contextWindow: 200_000, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 1.0, outputPrice: 5.0, cacheWritesPrice: 1.25, @@ -146,8 +128,6 @@ export const anthropicModels = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 15.0, outputPrice: 75.0, cacheWritesPrice: 18.75, @@ -158,8 +138,6 @@ export const anthropicModels = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.25, outputPrice: 1.25, cacheWritesPrice: 0.3, @@ -170,8 +148,6 @@ export const anthropicModels = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 1.0, outputPrice: 5.0, cacheWritesPrice: 1.25, diff --git a/packages/types/src/providers/baseten.ts b/packages/types/src/providers/baseten.ts index eeb6b0d2d1f..27b8cbff4ac 100644 --- a/packages/types/src/providers/baseten.ts +++ b/packages/types/src/providers/baseten.ts @@ -9,7 +9,6 @@ export const basetenModels = { contextWindow: 262_000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0.6, outputPrice: 2.5, cacheWritesPrice: 0, @@ -21,7 +20,6 @@ export const basetenModels = { contextWindow: 200_000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0.6, outputPrice: 2.2, cacheWritesPrice: 0, @@ -33,7 +31,6 @@ export const basetenModels = { contextWindow: 163_840, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 2.55, outputPrice: 5.95, cacheWritesPrice: 0, @@ -45,7 +42,6 @@ export const basetenModels = { contextWindow: 163_840, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 2.55, outputPrice: 5.95, cacheWritesPrice: 0, @@ -57,7 +53,6 @@ export const basetenModels = { contextWindow: 163_840, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0.77, outputPrice: 0.77, cacheWritesPrice: 0, @@ -69,7 +64,6 @@ export const basetenModels = { contextWindow: 163_840, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0.5, outputPrice: 1.5, cacheWritesPrice: 0, @@ -82,7 +76,6 @@ export const basetenModels = { contextWindow: 163_840, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0.3, outputPrice: 0.45, cacheWritesPrice: 0, @@ -95,7 +88,6 @@ export const basetenModels = { contextWindow: 128_072, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0.1, outputPrice: 0.5, cacheWritesPrice: 0, @@ -107,7 +99,6 @@ export const basetenModels = { contextWindow: 262_144, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0.22, outputPrice: 0.8, cacheWritesPrice: 0, @@ -119,7 +110,6 @@ export const basetenModels = { contextWindow: 262_144, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0.38, outputPrice: 1.53, cacheWritesPrice: 0, @@ -131,7 +121,6 @@ export const basetenModels = { contextWindow: 262_000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0.6, outputPrice: 2.5, cacheWritesPrice: 0, diff --git a/packages/types/src/providers/bedrock.ts b/packages/types/src/providers/bedrock.ts index 19dfbf0b306..1a95cf33c54 100644 --- a/packages/types/src/providers/bedrock.ts +++ b/packages/types/src/providers/bedrock.ts @@ -19,8 +19,6 @@ export const bedrockModels = { supportsImages: true, supportsPromptCache: true, supportsReasoningBudget: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 3.0, outputPrice: 15.0, cacheWritesPrice: 3.75, @@ -34,7 +32,6 @@ export const bedrockModels = { contextWindow: 300_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, inputPrice: 0.8, outputPrice: 3.2, cacheWritesPrice: 0.8, // per million tokens @@ -48,7 +45,6 @@ export const bedrockModels = { contextWindow: 300_000, supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 1.0, outputPrice: 4.0, cacheWritesPrice: 1.0, // per million tokens @@ -60,7 +56,6 @@ export const bedrockModels = { contextWindow: 300_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, inputPrice: 0.06, outputPrice: 0.24, cacheWritesPrice: 0.06, // per million tokens @@ -74,7 +69,6 @@ export const bedrockModels = { contextWindow: 1_000_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, inputPrice: 0.33, outputPrice: 2.75, cacheWritesPrice: 0, @@ -89,7 +83,6 @@ export const bedrockModels = { contextWindow: 128_000, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, inputPrice: 0.035, outputPrice: 0.14, cacheWritesPrice: 0.035, // per million tokens @@ -104,8 +97,6 @@ export const bedrockModels = { supportsImages: true, supportsPromptCache: true, supportsReasoningBudget: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 3.0, outputPrice: 15.0, cacheWritesPrice: 3.75, @@ -120,8 +111,6 @@ export const bedrockModels = { supportsImages: true, supportsPromptCache: true, supportsReasoningBudget: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 15.0, outputPrice: 75.0, cacheWritesPrice: 18.75, @@ -136,8 +125,6 @@ export const bedrockModels = { supportsImages: true, supportsPromptCache: true, supportsReasoningBudget: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 5.0, outputPrice: 25.0, cacheWritesPrice: 6.25, @@ -152,8 +139,6 @@ export const bedrockModels = { supportsImages: true, supportsPromptCache: true, supportsReasoningBudget: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 15.0, outputPrice: 75.0, cacheWritesPrice: 18.75, @@ -168,8 +153,6 @@ export const bedrockModels = { supportsImages: true, supportsPromptCache: true, supportsReasoningBudget: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 3.0, outputPrice: 15.0, cacheWritesPrice: 3.75, @@ -183,8 +166,6 @@ export const bedrockModels = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 3.0, outputPrice: 15.0, cacheWritesPrice: 3.75, @@ -198,8 +179,6 @@ export const bedrockModels = { contextWindow: 200_000, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.8, outputPrice: 4.0, cacheWritesPrice: 1.0, @@ -214,8 +193,6 @@ export const bedrockModels = { supportsImages: true, supportsPromptCache: true, supportsReasoningBudget: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 1.0, outputPrice: 5.0, cacheWritesPrice: 1.25, // 5m cache writes @@ -229,8 +206,6 @@ export const bedrockModels = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 3.0, outputPrice: 15.0, }, @@ -239,8 +214,6 @@ export const bedrockModels = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 15.0, outputPrice: 75.0, }, @@ -249,8 +222,6 @@ export const bedrockModels = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 3.0, outputPrice: 15.0, }, @@ -259,8 +230,6 @@ export const bedrockModels = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.25, outputPrice: 1.25, }, @@ -269,7 +238,6 @@ export const bedrockModels = { contextWindow: 128_000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 1.35, outputPrice: 5.4, }, @@ -278,7 +246,6 @@ export const bedrockModels = { contextWindow: 128_000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0.5, outputPrice: 1.5, description: "GPT-OSS 20B - Optimized for low latency and local/specialized use cases", @@ -288,7 +255,6 @@ export const bedrockModels = { contextWindow: 128_000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 2.0, outputPrice: 6.0, description: "GPT-OSS 120B - Production-ready, general-purpose, high-reasoning model", @@ -298,7 +264,6 @@ export const bedrockModels = { contextWindow: 128_000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0.72, outputPrice: 0.72, description: "Llama 3.3 Instruct (70B)", @@ -308,7 +273,6 @@ export const bedrockModels = { contextWindow: 128_000, supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0.72, outputPrice: 0.72, description: "Llama 3.2 Instruct (90B)", @@ -318,7 +282,6 @@ export const bedrockModels = { contextWindow: 128_000, supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0.16, outputPrice: 0.16, description: "Llama 3.2 Instruct (11B)", @@ -328,7 +291,6 @@ export const bedrockModels = { contextWindow: 128_000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0.15, outputPrice: 0.15, description: "Llama 3.2 Instruct (3B)", @@ -338,7 +300,6 @@ export const bedrockModels = { contextWindow: 128_000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0.1, outputPrice: 0.1, description: "Llama 3.2 Instruct (1B)", @@ -348,7 +309,6 @@ export const bedrockModels = { contextWindow: 128_000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 2.4, outputPrice: 2.4, description: "Llama 3.1 Instruct (405B)", @@ -358,7 +318,6 @@ export const bedrockModels = { contextWindow: 128_000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0.72, outputPrice: 0.72, description: "Llama 3.1 Instruct (70B)", @@ -368,7 +327,6 @@ export const bedrockModels = { contextWindow: 128_000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0.9, outputPrice: 0.9, description: "Llama 3.1 Instruct (70B) (w/ latency optimized inference)", @@ -378,7 +336,6 @@ export const bedrockModels = { contextWindow: 8_000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0.22, outputPrice: 0.22, description: "Llama 3.1 Instruct (8B)", @@ -388,7 +345,6 @@ export const bedrockModels = { contextWindow: 8_000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 2.65, outputPrice: 3.5, }, @@ -397,7 +353,6 @@ export const bedrockModels = { contextWindow: 4_000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0.3, outputPrice: 0.6, }, @@ -406,7 +361,6 @@ export const bedrockModels = { contextWindow: 8_000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0.15, outputPrice: 0.2, description: "Amazon Titan Text Lite", @@ -416,7 +370,6 @@ export const bedrockModels = { contextWindow: 8_000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0.2, outputPrice: 0.6, description: "Amazon Titan Text Express", @@ -426,8 +379,6 @@ export const bedrockModels = { contextWindow: 262_144, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", preserveReasoning: true, inputPrice: 0.6, outputPrice: 2.5, @@ -438,8 +389,6 @@ export const bedrockModels = { contextWindow: 196_608, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", preserveReasoning: true, inputPrice: 0.3, outputPrice: 1.2, @@ -450,8 +399,6 @@ export const bedrockModels = { contextWindow: 262_144, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.15, outputPrice: 1.2, description: "Qwen3 Next 80B (MoE model with 3B active parameters)", @@ -461,8 +408,6 @@ export const bedrockModels = { contextWindow: 262_144, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.45, outputPrice: 1.8, description: "Qwen3 Coder 480B (MoE model with 35B active parameters)", diff --git a/packages/types/src/providers/cerebras.ts b/packages/types/src/providers/cerebras.ts index 623c21ecdc1..d0f8c046193 100644 --- a/packages/types/src/providers/cerebras.ts +++ b/packages/types/src/providers/cerebras.ts @@ -11,8 +11,6 @@ export const cerebrasModels = { contextWindow: 131072, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: @@ -23,8 +21,6 @@ export const cerebrasModels = { contextWindow: 64000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "Intelligent model with ~1400 tokens/s", @@ -34,8 +30,6 @@ export const cerebrasModels = { contextWindow: 64000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "Powerful model with ~2600 tokens/s", @@ -45,8 +39,6 @@ export const cerebrasModels = { contextWindow: 64000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "SOTA coding performance with ~2500 tokens/s", @@ -56,8 +48,6 @@ export const cerebrasModels = { contextWindow: 64000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: diff --git a/packages/types/src/providers/chutes.ts b/packages/types/src/providers/chutes.ts index b21ffc392dc..69e6b2e68b7 100644 --- a/packages/types/src/providers/chutes.ts +++ b/packages/types/src/providers/chutes.ts @@ -51,8 +51,6 @@ export const chutesModels = { contextWindow: 163840, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "DeepSeek R1 0528 model.", @@ -62,8 +60,6 @@ export const chutesModels = { contextWindow: 163840, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "DeepSeek R1 model.", @@ -73,8 +69,6 @@ export const chutesModels = { contextWindow: 163840, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "DeepSeek V3 model.", @@ -84,8 +78,6 @@ export const chutesModels = { contextWindow: 163840, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "DeepSeek V3.1 model.", @@ -95,8 +87,6 @@ export const chutesModels = { contextWindow: 163840, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.23, outputPrice: 0.9, description: @@ -107,8 +97,6 @@ export const chutesModels = { contextWindow: 163840, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 1.0, outputPrice: 3.0, description: @@ -119,8 +107,6 @@ export const chutesModels = { contextWindow: 163840, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.25, outputPrice: 0.35, description: @@ -131,8 +117,6 @@ export const chutesModels = { contextWindow: 131072, // From Groq supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "Unsloth Llama 3.3 70B Instruct model.", @@ -142,8 +126,6 @@ export const chutesModels = { contextWindow: 512000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "ChutesAI Llama 4 Scout 17B Instruct model, 512K context.", @@ -153,8 +135,6 @@ export const chutesModels = { contextWindow: 128000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "Unsloth Mistral Nemo Instruct model.", @@ -164,8 +144,6 @@ export const chutesModels = { contextWindow: 131072, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "Unsloth Gemma 3 12B IT model.", @@ -175,8 +153,6 @@ export const chutesModels = { contextWindow: 131072, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "Nous DeepHermes 3 Llama 3 8B Preview model.", @@ -186,8 +162,6 @@ export const chutesModels = { contextWindow: 131072, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "Unsloth Gemma 3 4B IT model.", @@ -197,8 +171,6 @@ export const chutesModels = { contextWindow: 131072, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "Nvidia Llama 3.3 Nemotron Super 49B model.", @@ -208,8 +180,6 @@ export const chutesModels = { contextWindow: 131072, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "Nvidia Llama 3.1 Nemotron Ultra 253B model.", @@ -219,8 +189,6 @@ export const chutesModels = { contextWindow: 256000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "ChutesAI Llama 4 Maverick 17B Instruct FP8 model.", @@ -230,8 +198,6 @@ export const chutesModels = { contextWindow: 163840, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "DeepSeek V3 Base model.", @@ -241,8 +207,6 @@ export const chutesModels = { contextWindow: 163840, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "DeepSeek R1 Zero model.", @@ -252,8 +216,6 @@ export const chutesModels = { contextWindow: 163840, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "DeepSeek V3 (0324) model.", @@ -263,8 +225,6 @@ export const chutesModels = { contextWindow: 262144, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "Qwen3 235B A22B Instruct 2507 model with 262K context window.", @@ -274,8 +234,6 @@ export const chutesModels = { contextWindow: 40960, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "Qwen3 235B A22B model.", @@ -285,8 +243,6 @@ export const chutesModels = { contextWindow: 40960, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "Qwen3 32B model.", @@ -296,8 +252,6 @@ export const chutesModels = { contextWindow: 40960, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "Qwen3 30B A3B model.", @@ -307,8 +261,6 @@ export const chutesModels = { contextWindow: 40960, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "Qwen3 14B model.", @@ -318,8 +270,6 @@ export const chutesModels = { contextWindow: 40960, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "Qwen3 8B model.", @@ -329,8 +279,6 @@ export const chutesModels = { contextWindow: 163840, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "Microsoft MAI-DS-R1 FP8 model.", @@ -340,8 +288,6 @@ export const chutesModels = { contextWindow: 163840, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "TNGTech DeepSeek R1T Chimera model.", @@ -351,8 +297,6 @@ export const chutesModels = { contextWindow: 151329, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: @@ -363,8 +307,6 @@ export const chutesModels = { contextWindow: 131072, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: @@ -375,8 +317,6 @@ export const chutesModels = { contextWindow: 131072, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 1, outputPrice: 3, description: "GLM-4.5-turbo model with 128K token context window, optimized for fast inference.", @@ -386,8 +326,6 @@ export const chutesModels = { contextWindow: 202752, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: @@ -398,8 +336,6 @@ export const chutesModels = { contextWindow: 202752, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 1.15, outputPrice: 3.25, description: "GLM-4.6-turbo model with 200K-token context window, optimized for fast inference.", @@ -409,8 +345,6 @@ export const chutesModels = { contextWindow: 128000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: @@ -421,8 +355,6 @@ export const chutesModels = { contextWindow: 262144, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: "Qwen3 Coder 480B A35B Instruct FP8 model, optimized for coding tasks.", @@ -432,8 +364,6 @@ export const chutesModels = { contextWindow: 75000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.1481, outputPrice: 0.5926, description: "Moonshot AI Kimi K2 Instruct model with 75k context window.", @@ -443,8 +373,6 @@ export const chutesModels = { contextWindow: 262144, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.1999, outputPrice: 0.8001, description: "Moonshot AI Kimi K2 Instruct 0905 model with 256k context window.", @@ -454,8 +382,6 @@ export const chutesModels = { contextWindow: 262144, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.077968332, outputPrice: 0.31202496, description: "Qwen3 235B A22B Thinking 2507 model with 262K context window.", @@ -465,8 +391,6 @@ export const chutesModels = { contextWindow: 131072, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: @@ -477,8 +401,6 @@ export const chutesModels = { contextWindow: 131072, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, description: @@ -489,8 +411,6 @@ export const chutesModels = { contextWindow: 262144, supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.16, outputPrice: 0.65, description: diff --git a/packages/types/src/providers/claude-code.ts b/packages/types/src/providers/claude-code.ts index 28863675d07..2cc24f690e5 100644 --- a/packages/types/src/providers/claude-code.ts +++ b/packages/types/src/providers/claude-code.ts @@ -49,8 +49,6 @@ export const claudeCodeModels = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsReasoningEffort: ["disable", "low", "medium", "high"], reasoningEffort: "medium", description: "Claude Haiku 4.5 - Fast and efficient with thinking", @@ -60,8 +58,6 @@ export const claudeCodeModels = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsReasoningEffort: ["disable", "low", "medium", "high"], reasoningEffort: "medium", description: "Claude Sonnet 4.5 - Balanced performance with thinking", @@ -71,8 +67,6 @@ export const claudeCodeModels = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsReasoningEffort: ["disable", "low", "medium", "high"], reasoningEffort: "medium", description: "Claude Opus 4.5 - Most capable with thinking", diff --git a/packages/types/src/providers/deepinfra.ts b/packages/types/src/providers/deepinfra.ts index 9c487e71b2b..9a430b3789f 100644 --- a/packages/types/src/providers/deepinfra.ts +++ b/packages/types/src/providers/deepinfra.ts @@ -8,7 +8,6 @@ export const deepInfraDefaultModelInfo: ModelInfo = { contextWindow: 262144, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0.3, outputPrice: 1.2, description: "Qwen 3 Coder 480B A35B Instruct Turbo model, 256K context.", diff --git a/packages/types/src/providers/deepseek.ts b/packages/types/src/providers/deepseek.ts index 80c72ba7250..40722471cb8 100644 --- a/packages/types/src/providers/deepseek.ts +++ b/packages/types/src/providers/deepseek.ts @@ -14,8 +14,6 @@ export const deepSeekModels = { contextWindow: 128_000, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.28, // $0.28 per million tokens (cache miss) - Updated Dec 9, 2025 outputPrice: 0.42, // $0.42 per million tokens - Updated Dec 9, 2025 cacheWritesPrice: 0.28, // $0.28 per million tokens (cache miss) - Updated Dec 9, 2025 @@ -27,8 +25,6 @@ export const deepSeekModels = { contextWindow: 128_000, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", preserveReasoning: true, inputPrice: 0.28, // $0.28 per million tokens (cache miss) - Updated Dec 9, 2025 outputPrice: 0.42, // $0.42 per million tokens - Updated Dec 9, 2025 diff --git a/packages/types/src/providers/doubao.ts b/packages/types/src/providers/doubao.ts index c0187f7a756..f948450bc42 100644 --- a/packages/types/src/providers/doubao.ts +++ b/packages/types/src/providers/doubao.ts @@ -8,8 +8,6 @@ export const doubaoModels = { contextWindow: 128_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.0001, // $0.0001 per million tokens (cache miss) outputPrice: 0.0004, // $0.0004 per million tokens cacheWritesPrice: 0.0001, // $0.0001 per million tokens (cache miss) @@ -21,8 +19,6 @@ export const doubaoModels = { contextWindow: 128_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.0002, // $0.0002 per million tokens outputPrice: 0.0008, // $0.0008 per million tokens cacheWritesPrice: 0.0002, // $0.0002 per million @@ -34,8 +30,6 @@ export const doubaoModels = { contextWindow: 128_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.00015, // $0.00015 per million tokens outputPrice: 0.0006, // $0.0006 per million tokens cacheWritesPrice: 0.00015, // $0.00015 per million diff --git a/packages/types/src/providers/featherless.ts b/packages/types/src/providers/featherless.ts index 63bcb989687..20cfe966546 100644 --- a/packages/types/src/providers/featherless.ts +++ b/packages/types/src/providers/featherless.ts @@ -13,7 +13,6 @@ export const featherlessModels = { contextWindow: 32678, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0, outputPrice: 0, description: "DeepSeek V3 0324 model.", @@ -23,7 +22,6 @@ export const featherlessModels = { contextWindow: 32678, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0, outputPrice: 0, description: "DeepSeek R1 0528 model.", @@ -33,7 +31,6 @@ export const featherlessModels = { contextWindow: 32678, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0, outputPrice: 0, description: "Kimi K2 Instruct model.", @@ -43,7 +40,6 @@ export const featherlessModels = { contextWindow: 32678, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0, outputPrice: 0, description: "GPT-OSS 120B model.", @@ -53,7 +49,6 @@ export const featherlessModels = { contextWindow: 32678, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0, outputPrice: 0, description: "Qwen3 Coder 480B A35B Instruct model.", diff --git a/packages/types/src/providers/fireworks.ts b/packages/types/src/providers/fireworks.ts index 3f7b17034e7..42cc559c37b 100644 --- a/packages/types/src/providers/fireworks.ts +++ b/packages/types/src/providers/fireworks.ts @@ -24,8 +24,6 @@ export const fireworksModels = { contextWindow: 262144, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.6, outputPrice: 2.5, cacheReadsPrice: 0.15, @@ -37,8 +35,6 @@ export const fireworksModels = { contextWindow: 128000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.6, outputPrice: 2.5, description: @@ -49,7 +45,6 @@ export const fireworksModels = { contextWindow: 256000, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, supportsTemperature: true, preserveReasoning: true, defaultTemperature: 1.0, @@ -64,8 +59,6 @@ export const fireworksModels = { contextWindow: 204800, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.3, outputPrice: 1.2, description: @@ -76,8 +69,6 @@ export const fireworksModels = { contextWindow: 256000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.22, outputPrice: 0.88, description: "Latest Qwen3 thinking model, competitive against the best closed source models in Jul 2025.", @@ -87,8 +78,6 @@ export const fireworksModels = { contextWindow: 256000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.45, outputPrice: 1.8, description: "Qwen3's most agentic code model to date.", @@ -98,8 +87,6 @@ export const fireworksModels = { contextWindow: 160000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 3, outputPrice: 8, description: @@ -110,8 +97,6 @@ export const fireworksModels = { contextWindow: 128000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.9, outputPrice: 0.9, description: @@ -122,8 +107,6 @@ export const fireworksModels = { contextWindow: 163840, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.56, outputPrice: 1.68, description: @@ -134,8 +117,6 @@ export const fireworksModels = { contextWindow: 128000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.55, outputPrice: 2.19, description: @@ -146,8 +127,6 @@ export const fireworksModels = { contextWindow: 128000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.55, outputPrice: 2.19, description: @@ -158,8 +137,6 @@ export const fireworksModels = { contextWindow: 198000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.55, outputPrice: 2.19, description: @@ -170,8 +147,6 @@ export const fireworksModels = { contextWindow: 128000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.07, outputPrice: 0.3, description: @@ -182,8 +157,6 @@ export const fireworksModels = { contextWindow: 128000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.15, outputPrice: 0.6, description: diff --git a/packages/types/src/providers/gemini.ts b/packages/types/src/providers/gemini.ts index 6d35e093e8a..4a99a0e6ad1 100644 --- a/packages/types/src/providers/gemini.ts +++ b/packages/types/src/providers/gemini.ts @@ -10,8 +10,6 @@ export const geminiModels = { maxTokens: 65_536, contextWindow: 1_048_576, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: true, supportsReasoningEffort: ["low", "high"], reasoningEffort: "low", @@ -37,8 +35,6 @@ export const geminiModels = { maxTokens: 65_536, contextWindow: 1_048_576, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: true, supportsReasoningEffort: ["minimal", "low", "medium", "high"], reasoningEffort: "medium", @@ -55,8 +51,6 @@ export const geminiModels = { maxTokens: 64_000, contextWindow: 1_048_576, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: true, inputPrice: 2.5, // This is the pricing for prompts above 200k tokens. @@ -85,8 +79,6 @@ export const geminiModels = { maxTokens: 65_535, contextWindow: 1_048_576, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: true, inputPrice: 2.5, // This is the pricing for prompts above 200k tokens. @@ -114,8 +106,6 @@ export const geminiModels = { maxTokens: 65_535, contextWindow: 1_048_576, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: true, inputPrice: 2.5, // This is the pricing for prompts above 200k tokens. @@ -141,8 +131,6 @@ export const geminiModels = { maxTokens: 65_535, contextWindow: 1_048_576, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: true, inputPrice: 2.5, // This is the pricing for prompts above 200k tokens. @@ -172,8 +160,6 @@ export const geminiModels = { maxTokens: 65_536, contextWindow: 1_048_576, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: true, inputPrice: 0.3, @@ -187,8 +173,6 @@ export const geminiModels = { maxTokens: 65_536, contextWindow: 1_048_576, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: true, inputPrice: 0.3, @@ -202,8 +186,6 @@ export const geminiModels = { maxTokens: 64_000, contextWindow: 1_048_576, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: true, inputPrice: 0.3, @@ -219,8 +201,6 @@ export const geminiModels = { maxTokens: 65_536, contextWindow: 1_048_576, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: true, inputPrice: 0.1, @@ -234,8 +214,6 @@ export const geminiModels = { maxTokens: 65_536, contextWindow: 1_048_576, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: true, inputPrice: 0.1, diff --git a/packages/types/src/providers/groq.ts b/packages/types/src/providers/groq.ts index a22ad764eef..30e7c42ca1a 100644 --- a/packages/types/src/providers/groq.ts +++ b/packages/types/src/providers/groq.ts @@ -19,8 +19,6 @@ export const groqModels = { contextWindow: 131072, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.05, outputPrice: 0.08, description: "Meta Llama 3.1 8B Instant model, 128K context.", @@ -30,8 +28,6 @@ export const groqModels = { contextWindow: 131072, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.59, outputPrice: 0.79, description: "Meta Llama 3.3 70B Versatile model, 128K context.", @@ -41,8 +37,6 @@ export const groqModels = { contextWindow: 131072, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.11, outputPrice: 0.34, description: "Meta Llama 4 Scout 17B Instruct model, 128K context.", @@ -52,8 +46,6 @@ export const groqModels = { contextWindow: 131072, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.29, outputPrice: 0.59, description: "Alibaba Qwen 3 32B model, 128K context.", @@ -63,8 +55,6 @@ export const groqModels = { contextWindow: 262144, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.6, outputPrice: 2.5, cacheReadsPrice: 0.15, @@ -76,8 +66,6 @@ export const groqModels = { contextWindow: 131072, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.15, outputPrice: 0.75, description: @@ -88,8 +76,6 @@ export const groqModels = { contextWindow: 131072, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.1, outputPrice: 0.5, description: diff --git a/packages/types/src/providers/io-intelligence.ts b/packages/types/src/providers/io-intelligence.ts index 573db6b97aa..a9b845393f5 100644 --- a/packages/types/src/providers/io-intelligence.ts +++ b/packages/types/src/providers/io-intelligence.ts @@ -18,7 +18,6 @@ export const ioIntelligenceModels = { contextWindow: 128000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, description: "DeepSeek R1 reasoning model", }, "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8": { @@ -26,7 +25,6 @@ export const ioIntelligenceModels = { contextWindow: 430000, supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, description: "Llama 4 Maverick 17B model", }, "Intel/Qwen3-Coder-480B-A35B-Instruct-int4-mixed-ar": { @@ -34,7 +32,6 @@ export const ioIntelligenceModels = { contextWindow: 106000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, description: "Qwen3 Coder 480B specialized for coding", }, "openai/gpt-oss-120b": { @@ -42,7 +39,6 @@ export const ioIntelligenceModels = { contextWindow: 131072, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, description: "OpenAI GPT-OSS 120B model", }, } as const satisfies Record diff --git a/packages/types/src/providers/lite-llm.ts b/packages/types/src/providers/lite-llm.ts index 9ee03514585..14a68cfc3c3 100644 --- a/packages/types/src/providers/lite-llm.ts +++ b/packages/types/src/providers/lite-llm.ts @@ -8,8 +8,6 @@ export const litellmDefaultModelInfo: ModelInfo = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 3.0, outputPrice: 15.0, cacheWritesPrice: 3.75, diff --git a/packages/types/src/providers/lm-studio.ts b/packages/types/src/providers/lm-studio.ts index a5a1202c2e0..d0df1344702 100644 --- a/packages/types/src/providers/lm-studio.ts +++ b/packages/types/src/providers/lm-studio.ts @@ -10,8 +10,6 @@ export const lMStudioDefaultModelInfo: ModelInfo = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, cacheWritesPrice: 0, diff --git a/packages/types/src/providers/minimax.ts b/packages/types/src/providers/minimax.ts index 7152946f7f1..96dd71769d0 100644 --- a/packages/types/src/providers/minimax.ts +++ b/packages/types/src/providers/minimax.ts @@ -13,8 +13,6 @@ export const minimaxModels = { contextWindow: 192_000, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["search_and_replace"], excludedTools: ["apply_diff"], preserveReasoning: true, @@ -30,8 +28,6 @@ export const minimaxModels = { contextWindow: 192_000, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["search_and_replace"], excludedTools: ["apply_diff"], preserveReasoning: true, @@ -47,8 +43,6 @@ export const minimaxModels = { contextWindow: 192_000, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["search_and_replace"], excludedTools: ["apply_diff"], preserveReasoning: true, diff --git a/packages/types/src/providers/mistral.ts b/packages/types/src/providers/mistral.ts index 4f12d288ee0..0b030c80d44 100644 --- a/packages/types/src/providers/mistral.ts +++ b/packages/types/src/providers/mistral.ts @@ -11,8 +11,6 @@ export const mistralModels = { contextWindow: 128_000, supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 2.0, outputPrice: 5.0, }, @@ -21,8 +19,6 @@ export const mistralModels = { contextWindow: 131_000, supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.4, outputPrice: 2.0, }, @@ -31,8 +27,6 @@ export const mistralModels = { contextWindow: 131_000, supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.4, outputPrice: 2.0, }, @@ -41,8 +35,6 @@ export const mistralModels = { contextWindow: 256_000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.3, outputPrice: 0.9, }, @@ -51,8 +43,6 @@ export const mistralModels = { contextWindow: 131_000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 2.0, outputPrice: 6.0, }, @@ -61,8 +51,6 @@ export const mistralModels = { contextWindow: 131_000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.1, outputPrice: 0.1, }, @@ -71,8 +59,6 @@ export const mistralModels = { contextWindow: 131_000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.04, outputPrice: 0.04, }, @@ -81,8 +67,6 @@ export const mistralModels = { contextWindow: 32_000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.2, outputPrice: 0.6, }, @@ -91,8 +75,6 @@ export const mistralModels = { contextWindow: 131_000, supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 2.0, outputPrice: 6.0, }, diff --git a/packages/types/src/providers/moonshot.ts b/packages/types/src/providers/moonshot.ts index 7279c71809b..7ddafab76b7 100644 --- a/packages/types/src/providers/moonshot.ts +++ b/packages/types/src/providers/moonshot.ts @@ -11,8 +11,6 @@ export const moonshotModels = { contextWindow: 131_072, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.6, // $0.60 per million tokens (cache miss) outputPrice: 2.5, // $2.50 per million tokens cacheWritesPrice: 0, // $0 per million tokens (cache miss) @@ -24,8 +22,6 @@ export const moonshotModels = { contextWindow: 262144, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.6, outputPrice: 2.5, cacheReadsPrice: 0.15, @@ -37,8 +33,6 @@ export const moonshotModels = { contextWindow: 262_144, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 2.4, // $2.40 per million tokens (cache miss) outputPrice: 10, // $10.00 per million tokens cacheWritesPrice: 0, // $0 per million tokens (cache miss) @@ -50,8 +44,6 @@ export const moonshotModels = { contextWindow: 262_144, // 262,144 tokens supportsImages: false, // Text-only (no image/vision support) supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.6, // $0.60 per million tokens (cache miss) outputPrice: 2.5, // $2.50 per million tokens cacheWritesPrice: 0, // $0 per million tokens (cache miss) diff --git a/packages/types/src/providers/ollama.ts b/packages/types/src/providers/ollama.ts index 5148f466c01..160083511fa 100644 --- a/packages/types/src/providers/ollama.ts +++ b/packages/types/src/providers/ollama.ts @@ -8,7 +8,6 @@ export const ollamaDefaultModelInfo: ModelInfo = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, inputPrice: 0, outputPrice: 0, cacheWritesPrice: 0, diff --git a/packages/types/src/providers/openai-codex.ts b/packages/types/src/providers/openai-codex.ts index 051ef4f138e..7722c848143 100644 --- a/packages/types/src/providers/openai-codex.ts +++ b/packages/types/src/providers/openai-codex.ts @@ -27,8 +27,6 @@ export const openAiCodexModels = { "gpt-5.1-codex-max": { maxTokens: 128000, contextWindow: 400000, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, @@ -44,8 +42,6 @@ export const openAiCodexModels = { "gpt-5.1-codex": { maxTokens: 128000, contextWindow: 400000, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, @@ -61,8 +57,6 @@ export const openAiCodexModels = { "gpt-5.2-codex": { maxTokens: 128000, contextWindow: 400000, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, @@ -77,8 +71,6 @@ export const openAiCodexModels = { "gpt-5.1": { maxTokens: 128000, contextWindow: 400000, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, @@ -95,8 +87,6 @@ export const openAiCodexModels = { "gpt-5": { maxTokens: 128000, contextWindow: 400000, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, @@ -113,8 +103,6 @@ export const openAiCodexModels = { "gpt-5-codex": { maxTokens: 128000, contextWindow: 400000, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, @@ -130,8 +118,6 @@ export const openAiCodexModels = { "gpt-5-codex-mini": { maxTokens: 128000, contextWindow: 400000, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, @@ -147,8 +133,6 @@ export const openAiCodexModels = { "gpt-5.1-codex-mini": { maxTokens: 128000, contextWindow: 400000, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, @@ -163,8 +147,6 @@ export const openAiCodexModels = { "gpt-5.2": { maxTokens: 128000, contextWindow: 400000, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, diff --git a/packages/types/src/providers/openai.ts b/packages/types/src/providers/openai.ts index 57b0dae5646..af9a1ff759c 100644 --- a/packages/types/src/providers/openai.ts +++ b/packages/types/src/providers/openai.ts @@ -9,8 +9,6 @@ export const openAiNativeModels = { "gpt-5.1-codex-max": { maxTokens: 128000, contextWindow: 400000, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, @@ -29,8 +27,6 @@ export const openAiNativeModels = { "gpt-5.2": { maxTokens: 128000, contextWindow: 400000, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, @@ -52,8 +48,6 @@ export const openAiNativeModels = { "gpt-5.2-codex": { maxTokens: 128000, contextWindow: 400000, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, @@ -72,8 +66,6 @@ export const openAiNativeModels = { "gpt-5.2-chat-latest": { maxTokens: 16_384, contextWindow: 128_000, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, @@ -86,8 +78,6 @@ export const openAiNativeModels = { "gpt-5.1": { maxTokens: 128000, contextWindow: 400000, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, @@ -109,8 +99,6 @@ export const openAiNativeModels = { "gpt-5.1-codex": { maxTokens: 128000, contextWindow: 400000, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, @@ -128,8 +116,6 @@ export const openAiNativeModels = { "gpt-5.1-codex-mini": { maxTokens: 128000, contextWindow: 400000, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, @@ -146,8 +132,6 @@ export const openAiNativeModels = { "gpt-5": { maxTokens: 128000, contextWindow: 400000, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, @@ -168,8 +152,6 @@ export const openAiNativeModels = { "gpt-5-mini": { maxTokens: 128000, contextWindow: 400000, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, @@ -190,8 +172,6 @@ export const openAiNativeModels = { "gpt-5-codex": { maxTokens: 128000, contextWindow: 400000, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, @@ -208,8 +188,6 @@ export const openAiNativeModels = { "gpt-5-nano": { maxTokens: 128000, contextWindow: 400000, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, @@ -227,8 +205,6 @@ export const openAiNativeModels = { "gpt-5-chat-latest": { maxTokens: 128000, contextWindow: 400000, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, @@ -241,8 +217,6 @@ export const openAiNativeModels = { "gpt-4.1": { maxTokens: 32_768, contextWindow: 1_047_576, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, @@ -258,8 +232,6 @@ export const openAiNativeModels = { "gpt-4.1-mini": { maxTokens: 32_768, contextWindow: 1_047_576, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, @@ -275,8 +247,6 @@ export const openAiNativeModels = { "gpt-4.1-nano": { maxTokens: 32_768, contextWindow: 1_047_576, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, @@ -292,8 +262,6 @@ export const openAiNativeModels = { o3: { maxTokens: 100_000, contextWindow: 200_000, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsImages: true, supportsPromptCache: true, inputPrice: 2.0, @@ -310,8 +278,6 @@ export const openAiNativeModels = { "o3-high": { maxTokens: 100_000, contextWindow: 200_000, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsImages: true, supportsPromptCache: true, inputPrice: 2.0, @@ -323,8 +289,6 @@ export const openAiNativeModels = { "o3-low": { maxTokens: 100_000, contextWindow: 200_000, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsImages: true, supportsPromptCache: true, inputPrice: 2.0, @@ -336,8 +300,6 @@ export const openAiNativeModels = { "o4-mini": { maxTokens: 100_000, contextWindow: 200_000, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsImages: true, supportsPromptCache: true, inputPrice: 1.1, @@ -354,8 +316,6 @@ export const openAiNativeModels = { "o4-mini-high": { maxTokens: 100_000, contextWindow: 200_000, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsImages: true, supportsPromptCache: true, inputPrice: 1.1, @@ -367,8 +327,6 @@ export const openAiNativeModels = { "o4-mini-low": { maxTokens: 100_000, contextWindow: 200_000, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsImages: true, supportsPromptCache: true, inputPrice: 1.1, @@ -380,8 +338,6 @@ export const openAiNativeModels = { "o3-mini": { maxTokens: 100_000, contextWindow: 200_000, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsImages: false, supportsPromptCache: true, inputPrice: 1.1, @@ -394,8 +350,6 @@ export const openAiNativeModels = { "o3-mini-high": { maxTokens: 100_000, contextWindow: 200_000, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsImages: false, supportsPromptCache: true, inputPrice: 1.1, @@ -407,8 +361,6 @@ export const openAiNativeModels = { "o3-mini-low": { maxTokens: 100_000, contextWindow: 200_000, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsImages: false, supportsPromptCache: true, inputPrice: 1.1, @@ -420,8 +372,6 @@ export const openAiNativeModels = { o1: { maxTokens: 100_000, contextWindow: 200_000, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsImages: true, supportsPromptCache: true, inputPrice: 15, @@ -432,8 +382,6 @@ export const openAiNativeModels = { "o1-preview": { maxTokens: 32_768, contextWindow: 128_000, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsImages: true, supportsPromptCache: true, inputPrice: 15, @@ -444,8 +392,6 @@ export const openAiNativeModels = { "o1-mini": { maxTokens: 65_536, contextWindow: 128_000, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsImages: true, supportsPromptCache: true, inputPrice: 1.1, @@ -456,8 +402,6 @@ export const openAiNativeModels = { "gpt-4o": { maxTokens: 16_384, contextWindow: 128_000, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsImages: true, supportsPromptCache: true, inputPrice: 2.5, @@ -471,8 +415,6 @@ export const openAiNativeModels = { "gpt-4o-mini": { maxTokens: 16_384, contextWindow: 128_000, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsImages: true, supportsPromptCache: true, inputPrice: 0.15, @@ -486,8 +428,6 @@ export const openAiNativeModels = { "codex-mini-latest": { maxTokens: 16_384, contextWindow: 200_000, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsImages: false, supportsPromptCache: false, inputPrice: 1.5, @@ -501,8 +441,6 @@ export const openAiNativeModels = { "gpt-5-2025-08-07": { maxTokens: 128000, contextWindow: 400000, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, @@ -523,8 +461,6 @@ export const openAiNativeModels = { "gpt-5-mini-2025-08-07": { maxTokens: 128000, contextWindow: 400000, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, @@ -545,8 +481,6 @@ export const openAiNativeModels = { "gpt-5-nano-2025-08-07": { maxTokens: 128000, contextWindow: 400000, - supportsNativeTools: true, - defaultToolProtocol: "native", includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, @@ -570,8 +504,6 @@ export const openAiModelInfoSaneDefaults: ModelInfo = { supportsPromptCache: false, inputPrice: 0, outputPrice: 0, - supportsNativeTools: true, - defaultToolProtocol: "native", } // https://learn.microsoft.com/en-us/azure/ai-services/openai/api-version-deprecation diff --git a/packages/types/src/providers/openrouter.ts b/packages/types/src/providers/openrouter.ts index 5cf82d35019..f3fb13baa93 100644 --- a/packages/types/src/providers/openrouter.ts +++ b/packages/types/src/providers/openrouter.ts @@ -8,7 +8,6 @@ export const openRouterDefaultModelInfo: ModelInfo = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, inputPrice: 3.0, outputPrice: 15.0, cacheWritesPrice: 3.75, diff --git a/packages/types/src/providers/qwen-code.ts b/packages/types/src/providers/qwen-code.ts index e1102011aab..0f51e4eacbe 100644 --- a/packages/types/src/providers/qwen-code.ts +++ b/packages/types/src/providers/qwen-code.ts @@ -10,8 +10,6 @@ export const qwenCodeModels = { contextWindow: 1_000_000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, cacheWritesPrice: 0, @@ -23,8 +21,6 @@ export const qwenCodeModels = { contextWindow: 1_000_000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, cacheWritesPrice: 0, diff --git a/packages/types/src/providers/requesty.ts b/packages/types/src/providers/requesty.ts index 3fd18c3139b..d312adb3976 100644 --- a/packages/types/src/providers/requesty.ts +++ b/packages/types/src/providers/requesty.ts @@ -9,8 +9,6 @@ export const requestyDefaultModelInfo: ModelInfo = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 3.0, outputPrice: 15.0, cacheWritesPrice: 3.75, diff --git a/packages/types/src/providers/sambanova.ts b/packages/types/src/providers/sambanova.ts index dc592d180ca..624a7eb8c77 100644 --- a/packages/types/src/providers/sambanova.ts +++ b/packages/types/src/providers/sambanova.ts @@ -19,8 +19,6 @@ export const sambaNovaModels = { contextWindow: 16384, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.1, outputPrice: 0.2, description: "Meta Llama 3.1 8B Instruct model with 16K context window.", @@ -30,8 +28,6 @@ export const sambaNovaModels = { contextWindow: 131072, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.6, outputPrice: 1.2, description: "Meta Llama 3.3 70B Instruct model with 128K context window.", @@ -42,8 +38,6 @@ export const sambaNovaModels = { supportsImages: false, supportsPromptCache: false, supportsReasoningBudget: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 5.0, outputPrice: 7.0, description: "DeepSeek R1 reasoning model with 32K context window.", @@ -53,8 +47,6 @@ export const sambaNovaModels = { contextWindow: 32768, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 3.0, outputPrice: 4.5, description: "DeepSeek V3 model with 32K context window.", @@ -64,8 +56,6 @@ export const sambaNovaModels = { contextWindow: 32768, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 3.0, outputPrice: 4.5, description: "DeepSeek V3.1 model with 32K context window.", @@ -75,8 +65,6 @@ export const sambaNovaModels = { contextWindow: 131072, supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.63, outputPrice: 1.8, description: "Meta Llama 4 Maverick 17B 128E Instruct model with 128K context window.", @@ -86,8 +74,6 @@ export const sambaNovaModels = { contextWindow: 8192, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.4, outputPrice: 0.8, description: "Alibaba Qwen 3 32B model with 8K context window.", @@ -97,8 +83,6 @@ export const sambaNovaModels = { contextWindow: 131072, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.22, outputPrice: 0.59, description: "OpenAI gpt oss 120b model with 128k context window.", diff --git a/packages/types/src/providers/unbound.ts b/packages/types/src/providers/unbound.ts index 16159c00b1b..9715b835c9b 100644 --- a/packages/types/src/providers/unbound.ts +++ b/packages/types/src/providers/unbound.ts @@ -7,7 +7,6 @@ export const unboundDefaultModelInfo: ModelInfo = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, inputPrice: 3.0, outputPrice: 15.0, cacheWritesPrice: 3.75, diff --git a/packages/types/src/providers/vercel-ai-gateway.ts b/packages/types/src/providers/vercel-ai-gateway.ts index 40d4f1ca509..875b87bf8b5 100644 --- a/packages/types/src/providers/vercel-ai-gateway.ts +++ b/packages/types/src/providers/vercel-ai-gateway.ts @@ -90,7 +90,6 @@ export const vercelAiGatewayDefaultModelInfo: ModelInfo = { contextWindow: 200000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, inputPrice: 3, outputPrice: 15, cacheWritesPrice: 3.75, diff --git a/packages/types/src/providers/vertex.ts b/packages/types/src/providers/vertex.ts index 1ebce7e396e..7f6bd489044 100644 --- a/packages/types/src/providers/vertex.ts +++ b/packages/types/src/providers/vertex.ts @@ -10,8 +10,6 @@ export const vertexModels = { maxTokens: 65_536, contextWindow: 1_048_576, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: true, supportsReasoningEffort: ["low", "high"], reasoningEffort: "low", @@ -37,8 +35,6 @@ export const vertexModels = { maxTokens: 65_536, contextWindow: 1_048_576, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: true, supportsReasoningEffort: ["minimal", "low", "medium", "high"], reasoningEffort: "medium", @@ -54,8 +50,6 @@ export const vertexModels = { maxTokens: 65_535, contextWindow: 1_048_576, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: true, inputPrice: 0.15, @@ -68,8 +62,6 @@ export const vertexModels = { maxTokens: 65_535, contextWindow: 1_048_576, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: true, inputPrice: 0.15, @@ -79,8 +71,6 @@ export const vertexModels = { maxTokens: 64_000, contextWindow: 1_048_576, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: true, inputPrice: 0.3, @@ -94,8 +84,6 @@ export const vertexModels = { maxTokens: 65_535, contextWindow: 1_048_576, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: false, inputPrice: 0.15, @@ -108,8 +96,6 @@ export const vertexModels = { maxTokens: 65_535, contextWindow: 1_048_576, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: false, inputPrice: 0.15, @@ -119,8 +105,6 @@ export const vertexModels = { maxTokens: 65_535, contextWindow: 1_048_576, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: true, inputPrice: 2.5, @@ -130,8 +114,6 @@ export const vertexModels = { maxTokens: 65_535, contextWindow: 1_048_576, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: true, inputPrice: 2.5, @@ -141,8 +123,6 @@ export const vertexModels = { maxTokens: 65_535, contextWindow: 1_048_576, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: true, inputPrice: 2.5, @@ -154,8 +134,6 @@ export const vertexModels = { maxTokens: 64_000, contextWindow: 1_048_576, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: true, inputPrice: 2.5, @@ -182,8 +160,6 @@ export const vertexModels = { maxTokens: 65_535, contextWindow: 1_048_576, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: false, inputPrice: 0, @@ -193,8 +169,6 @@ export const vertexModels = { maxTokens: 8192, contextWindow: 2_097_152, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: false, inputPrice: 0, @@ -204,8 +178,6 @@ export const vertexModels = { maxTokens: 8192, contextWindow: 1_048_576, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: true, inputPrice: 0.15, @@ -215,8 +187,6 @@ export const vertexModels = { maxTokens: 8192, contextWindow: 1_048_576, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: false, inputPrice: 0.075, @@ -226,8 +196,6 @@ export const vertexModels = { maxTokens: 8192, contextWindow: 32_768, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: false, inputPrice: 0, @@ -237,8 +205,6 @@ export const vertexModels = { maxTokens: 8192, contextWindow: 1_048_576, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: true, inputPrice: 0.075, @@ -248,8 +214,6 @@ export const vertexModels = { maxTokens: 8192, contextWindow: 2_097_152, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: false, inputPrice: 1.25, @@ -260,8 +224,6 @@ export const vertexModels = { contextWindow: 200_000, // Default 200K, extendable to 1M with beta flag 'context-1m-2025-08-07' supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 3.0, // $3 per million input tokens (≤200K context) outputPrice: 15.0, // $15 per million output tokens (≤200K context) cacheWritesPrice: 3.75, // $3.75 per million tokens @@ -283,8 +245,6 @@ export const vertexModels = { contextWindow: 200_000, // Default 200K, extendable to 1M with beta flag 'context-1m-2025-08-07' supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 3.0, // $3 per million input tokens (≤200K context) outputPrice: 15.0, // $15 per million output tokens (≤200K context) cacheWritesPrice: 3.75, // $3.75 per million tokens @@ -306,8 +266,6 @@ export const vertexModels = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 1.0, outputPrice: 5.0, cacheWritesPrice: 1.25, @@ -319,8 +277,6 @@ export const vertexModels = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 5.0, outputPrice: 25.0, cacheWritesPrice: 6.25, @@ -332,8 +288,6 @@ export const vertexModels = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 15.0, outputPrice: 75.0, cacheWritesPrice: 18.75, @@ -345,8 +299,6 @@ export const vertexModels = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 15.0, outputPrice: 75.0, cacheWritesPrice: 18.75, @@ -357,8 +309,6 @@ export const vertexModels = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 3.0, outputPrice: 15.0, cacheWritesPrice: 3.75, @@ -371,8 +321,6 @@ export const vertexModels = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 3.0, outputPrice: 15.0, cacheWritesPrice: 3.75, @@ -383,8 +331,6 @@ export const vertexModels = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 3.0, outputPrice: 15.0, cacheWritesPrice: 3.75, @@ -395,8 +341,6 @@ export const vertexModels = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 3.0, outputPrice: 15.0, cacheWritesPrice: 3.75, @@ -407,8 +351,6 @@ export const vertexModels = { contextWindow: 200_000, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 1.0, outputPrice: 5.0, cacheWritesPrice: 1.25, @@ -419,8 +361,6 @@ export const vertexModels = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 15.0, outputPrice: 75.0, cacheWritesPrice: 18.75, @@ -431,8 +371,6 @@ export const vertexModels = { contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.25, outputPrice: 1.25, cacheWritesPrice: 0.3, @@ -442,8 +380,6 @@ export const vertexModels = { maxTokens: 64_000, contextWindow: 1_048_576, supportsImages: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsPromptCache: true, inputPrice: 0.1, @@ -458,7 +394,6 @@ export const vertexModels = { contextWindow: 131072, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0.35, outputPrice: 1.15, description: "Meta Llama 4 Maverick 17B Instruct model, 128K context.", @@ -468,7 +403,6 @@ export const vertexModels = { contextWindow: 163_840, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 1.35, outputPrice: 5.4, description: "DeepSeek R1 (0528). Available in us-central1", @@ -478,7 +412,6 @@ export const vertexModels = { contextWindow: 163_840, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0.6, outputPrice: 1.7, description: "DeepSeek V3.1. Available in us-west2", @@ -488,7 +421,6 @@ export const vertexModels = { contextWindow: 131_072, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0.15, outputPrice: 0.6, description: "OpenAI gpt-oss 120B. Available in us-central1", @@ -498,7 +430,6 @@ export const vertexModels = { contextWindow: 131_072, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0.075, outputPrice: 0.3, description: "OpenAI gpt-oss 20B. Available in us-central1", @@ -508,7 +439,6 @@ export const vertexModels = { contextWindow: 262_144, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 1.0, outputPrice: 4.0, description: "Qwen3 Coder 480B A35B Instruct. Available in us-south1", @@ -518,7 +448,6 @@ export const vertexModels = { contextWindow: 262_144, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 0.25, outputPrice: 1.0, description: "Qwen3 235B A22B Instruct. Available in us-south1", diff --git a/packages/types/src/providers/xai.ts b/packages/types/src/providers/xai.ts index 23acb487aac..37e0f2d12e0 100644 --- a/packages/types/src/providers/xai.ts +++ b/packages/types/src/providers/xai.ts @@ -11,8 +11,6 @@ export const xaiModels = { contextWindow: 256_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.2, outputPrice: 1.5, cacheWritesPrice: 0.02, @@ -26,8 +24,6 @@ export const xaiModels = { contextWindow: 2_000_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.2, outputPrice: 0.5, cacheWritesPrice: 0.05, @@ -42,8 +38,6 @@ export const xaiModels = { contextWindow: 2_000_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.2, outputPrice: 0.5, cacheWritesPrice: 0.05, @@ -58,8 +52,6 @@ export const xaiModels = { contextWindow: 2_000_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.2, outputPrice: 0.5, cacheWritesPrice: 0.05, @@ -74,8 +66,6 @@ export const xaiModels = { contextWindow: 2_000_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.2, outputPrice: 0.5, cacheWritesPrice: 0.05, @@ -90,8 +80,6 @@ export const xaiModels = { contextWindow: 256_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 3.0, outputPrice: 15.0, cacheWritesPrice: 0.75, @@ -105,8 +93,6 @@ export const xaiModels = { contextWindow: 131072, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.3, outputPrice: 0.5, cacheWritesPrice: 0.07, @@ -122,8 +108,6 @@ export const xaiModels = { contextWindow: 131072, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 3.0, outputPrice: 15.0, cacheWritesPrice: 0.75, diff --git a/packages/types/src/providers/zai.ts b/packages/types/src/providers/zai.ts index 93cf9bb23bc..e9fe7f9bfb6 100644 --- a/packages/types/src/providers/zai.ts +++ b/packages/types/src/providers/zai.ts @@ -16,8 +16,6 @@ export const internationalZAiModels = { contextWindow: 131_072, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.6, outputPrice: 2.2, cacheWritesPrice: 0, @@ -30,8 +28,6 @@ export const internationalZAiModels = { contextWindow: 131_072, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.2, outputPrice: 1.1, cacheWritesPrice: 0, @@ -44,8 +40,6 @@ export const internationalZAiModels = { contextWindow: 131_072, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 2.2, outputPrice: 8.9, cacheWritesPrice: 0, @@ -58,8 +52,6 @@ export const internationalZAiModels = { contextWindow: 131_072, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 1.1, outputPrice: 4.5, cacheWritesPrice: 0, @@ -71,8 +63,6 @@ export const internationalZAiModels = { contextWindow: 131_072, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, cacheWritesPrice: 0, @@ -84,8 +74,6 @@ export const internationalZAiModels = { contextWindow: 131_072, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.6, outputPrice: 1.8, cacheWritesPrice: 0, @@ -98,8 +86,6 @@ export const internationalZAiModels = { contextWindow: 200_000, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.6, outputPrice: 2.2, cacheWritesPrice: 0, @@ -112,8 +98,6 @@ export const internationalZAiModels = { contextWindow: 200_000, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsReasoningEffort: ["disable", "medium"], reasoningEffort: "medium", preserveReasoning: true, @@ -129,8 +113,6 @@ export const internationalZAiModels = { contextWindow: 131_072, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.1, outputPrice: 0.1, cacheWritesPrice: 0, @@ -147,8 +129,6 @@ export const mainlandZAiModels = { contextWindow: 131_072, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.29, outputPrice: 1.14, cacheWritesPrice: 0, @@ -161,8 +141,6 @@ export const mainlandZAiModels = { contextWindow: 131_072, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.1, outputPrice: 0.6, cacheWritesPrice: 0, @@ -175,8 +153,6 @@ export const mainlandZAiModels = { contextWindow: 131_072, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.29, outputPrice: 1.14, cacheWritesPrice: 0, @@ -189,8 +165,6 @@ export const mainlandZAiModels = { contextWindow: 131_072, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.1, outputPrice: 0.6, cacheWritesPrice: 0, @@ -202,8 +176,6 @@ export const mainlandZAiModels = { contextWindow: 131_072, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0, outputPrice: 0, cacheWritesPrice: 0, @@ -215,8 +187,6 @@ export const mainlandZAiModels = { contextWindow: 131_072, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.29, outputPrice: 0.93, cacheWritesPrice: 0, @@ -229,8 +199,6 @@ export const mainlandZAiModels = { contextWindow: 204_800, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", inputPrice: 0.29, outputPrice: 1.14, cacheWritesPrice: 0, @@ -243,8 +211,6 @@ export const mainlandZAiModels = { contextWindow: 204_800, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, - defaultToolProtocol: "native", supportsReasoningEffort: ["disable", "medium"], reasoningEffort: "medium", preserveReasoning: true, diff --git a/packages/types/src/tool.ts b/packages/types/src/tool.ts index 76e03f8c803..147eb24b6cc 100644 --- a/packages/types/src/tool.ts +++ b/packages/types/src/tool.ts @@ -57,48 +57,3 @@ export const toolUsageSchema = z.record( ) export type ToolUsage = z.infer - -/** - * 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] - -/** - * Default model info properties for native tool support. - * Used to merge with cached model info that may lack these fields. - * Router providers (Requesty, Unbound, LiteLLM) assume all models support native tools. - */ -export const NATIVE_TOOL_DEFAULTS = { - supportsNativeTools: true, - defaultToolProtocol: TOOL_PROTOCOL.NATIVE, -} as const - -/** - * Checks if the protocol is native (non-XML). - * - * @param protocol - The tool protocol to check - * @returns True if protocol is native - */ -export function isNativeProtocol(protocol: ToolProtocol): boolean { - return protocol === TOOL_PROTOCOL.NATIVE -} - -/** - * Gets the effective protocol from settings or falls back to the default XML. - * This function is safe to use in webview-accessible code as it doesn't depend on vscode module. - * - * @param toolProtocol - Optional tool protocol from settings - * @returns The effective tool protocol (defaults to "xml") - */ -export function getEffectiveProtocol(toolProtocol?: ToolProtocol): ToolProtocol { - return toolProtocol || TOOL_PROTOCOL.XML -} diff --git a/src/__tests__/history-resume-delegation.spec.ts b/src/__tests__/history-resume-delegation.spec.ts index 1f95d0f6dde..f3256bd1432 100644 --- a/src/__tests__/history-resume-delegation.spec.ts +++ b/src/__tests__/history-resume-delegation.spec.ts @@ -288,6 +288,56 @@ describe("History resume delegation - parent metadata transitions", () => { expect((injectedMsg.content[0] as any).tool_use_id).toBe("toolu_abc123") }) + it("reopenParentFromDelegation injects plain text when no new_task tool_use exists in API history", async () => { + const provider = { + contextProxy: { globalStorageUri: { fsPath: "/storage" } }, + getTaskWithId: vi.fn().mockResolvedValue({ + historyItem: { + id: "p-no-tool", + status: "delegated", + awaitingChildId: "c-no-tool", + childIds: [], + ts: 100, + task: "Parent without tool_use", + tokensIn: 0, + tokensOut: 0, + totalCost: 0, + }, + }), + emit: vi.fn(), + getCurrentTask: vi.fn(() => ({ taskId: "c-no-tool" })), + removeClineFromStack: vi.fn().mockResolvedValue(undefined), + createTaskWithHistoryItem: vi.fn().mockResolvedValue({ + taskId: "p-no-tool", + resumeAfterDelegation: vi.fn().mockResolvedValue(undefined), + overwriteClineMessages: vi.fn().mockResolvedValue(undefined), + overwriteApiConversationHistory: vi.fn().mockResolvedValue(undefined), + }), + updateTaskHistory: vi.fn().mockResolvedValue([]), + } as unknown as ClineProvider + + // No assistant tool_use in history + const existingUiMessages = [{ type: "ask", ask: "tool", text: "subtask request", ts: 50 }] + const existingApiMessages = [{ role: "user", content: [{ type: "text", text: "Create a subtask" }], ts: 40 }] + + vi.mocked(readTaskMessages).mockResolvedValue(existingUiMessages as any) + vi.mocked(readApiMessages).mockResolvedValue(existingApiMessages as any) + + await (ClineProvider.prototype as any).reopenParentFromDelegation.call(provider, { + parentTaskId: "p-no-tool", + childTaskId: "c-no-tool", + completionResultSummary: "Subtask completed without tool_use", + }) + + const apiCall = vi.mocked(saveApiMessages).mock.calls[0][0] + // Should append a user text note + expect(apiCall.messages).toHaveLength(2) + const injected = apiCall.messages[1] + expect(injected.role).toBe("user") + expect((injected.content[0] as any).type).toBe("text") + expect((injected.content[0] as any).text).toContain("Subtask c-no-tool completed") + }) + it("reopenParentFromDelegation sets skipPrevResponseIdOnce via resumeAfterDelegation", async () => { const parentInstance: any = { skipPrevResponseIdOnce: false, diff --git a/src/__tests__/nested-delegation-resume.spec.ts b/src/__tests__/nested-delegation-resume.spec.ts index 0c97ab5e2bf..5dbafc949cb 100644 --- a/src/__tests__/nested-delegation-resume.spec.ts +++ b/src/__tests__/nested-delegation-resume.spec.ts @@ -187,18 +187,21 @@ describe("Nested delegation resume (A → B → C)", () => { type: "tool_use", name: "attempt_completion", params: { result: "C finished" }, + nativeArgs: { result: "C finished" }, partial: false, } as any const askFinishSubTaskApproval = vi.fn(async () => true) + const handleError = vi.fn(async (_action: string, err: Error) => { + // Fail fast in this test if the tool hits an error path. + throw err + }) await attemptCompletionTool.handle(clineC, blockC, { askApproval: vi.fn(), - handleError: vi.fn(), + handleError, pushToolResult: vi.fn(), - removeClosingTag: vi.fn((_, v?: string) => v ?? ""), askFinishSubTaskApproval, - toolProtocol: "xml", toolDescription: () => "desc", } as any) @@ -231,20 +234,21 @@ describe("Nested delegation resume (A → B → C)", () => { type: "tool_use", name: "attempt_completion", params: { result: "B finished" }, + nativeArgs: { result: "B finished" }, partial: false, } as any await attemptCompletionTool.handle(clineB, blockB, { askApproval: vi.fn(), - handleError: vi.fn(), + handleError, pushToolResult: vi.fn(), - removeClosingTag: vi.fn((_, v?: string) => v ?? ""), askFinishSubTaskApproval, - toolProtocol: "xml", toolDescription: () => "desc", } as any) - // After B completes, A must be current + // After B completes, A should become current + // Note: delegation resume may fall back to a non-tool_result user message when the parent history + // does not contain a new_task tool_use. This should not prevent reopening the parent. expect(currentActiveId).toBe("A") // Ensure no resume_task asks were scheduled: verified indirectly by startTask:false on both hops diff --git a/src/api/index.ts b/src/api/index.ts index 4dfe1e2ecb4..f48f08ea448 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,7 +1,7 @@ import { Anthropic } from "@anthropic-ai/sdk" import OpenAI from "openai" -import type { ProviderSettings, ModelInfo, ToolProtocol } from "@roo-code/types" +import type { ProviderSettings, ModelInfo } from "@roo-code/types" import { ApiStream } from "./transform/stream" @@ -83,16 +83,11 @@ export interface ApiHandlerCreateMessageMetadata { * Can be "none", "auto", "required", or a specific tool choice. */ tool_choice?: OpenAI.Chat.ChatCompletionCreateParams["tool_choice"] - /** - * The tool protocol being used (XML or Native). - * Used by providers to determine whether to include native tool definitions. - */ - toolProtocol?: ToolProtocol + // Tool calling is native-only. /** * Controls whether the model can return multiple tool calls in a single response. * When true, parallel tool calls are enabled (OpenAI's parallel_tool_calls=true). * When false (default), only one tool call is returned per response. - * Only applies when toolProtocol is "native". */ parallelToolCalls?: boolean /** diff --git a/src/api/providers/__tests__/anthropic-vertex.spec.ts b/src/api/providers/__tests__/anthropic-vertex.spec.ts index 6890e4178b1..91a459d9d17 100644 --- a/src/api/providers/__tests__/anthropic-vertex.spec.ts +++ b/src/api/providers/__tests__/anthropic-vertex.spec.ts @@ -1200,13 +1200,11 @@ describe("VertexHandler", () => { ) }) - it("should include tools even when toolProtocol is set to xml (user preference now ignored)", async () => { - // XML protocol deprecation: user preference is now ignored when model supports native tools + it("should include tools when tools are provided", async () => { handler = new AnthropicVertexHandler({ apiModelId: "claude-3-5-sonnet-v2@20241022", vertexProjectId: "test-project", vertexRegion: "us-central1", - toolProtocol: "xml", }) const mockStream = [ @@ -1242,7 +1240,7 @@ describe("VertexHandler", () => { // Just consume } - // Native is forced when supportsNativeTools===true, so tools should still be included + // Tool calling is request-driven: if tools are provided, we should include them. expect(mockCreate).toHaveBeenCalledWith( expect.objectContaining({ tools: expect.arrayContaining([ diff --git a/src/api/providers/__tests__/anthropic.spec.ts b/src/api/providers/__tests__/anthropic.spec.ts index 3fa5baf81be..36b529cb7d8 100644 --- a/src/api/providers/__tests__/anthropic.spec.ts +++ b/src/api/providers/__tests__/anthropic.spec.ts @@ -420,8 +420,7 @@ describe("AnthropicHandler", () => { }, ] - it("should include tools in request by default (native is default)", async () => { - // Handler uses native protocol by default via model's defaultToolProtocol + it("should include tools in request when tools are provided", async () => { const stream = handler.createMessage(systemPrompt, messages, { taskId: "test-task", tools: mockTools, @@ -451,11 +450,9 @@ describe("AnthropicHandler", () => { ) }) - it("should include tools even when toolProtocol is set to xml (user preference now ignored)", async () => { - // XML protocol deprecation: user preference is now ignored when model supports native tools + it("should include tools when tools are provided", async () => { const xmlHandler = new AnthropicHandler({ ...mockOptions, - toolProtocol: "xml", }) const stream = xmlHandler.createMessage(systemPrompt, messages, { @@ -468,7 +465,7 @@ describe("AnthropicHandler", () => { // Just consume } - // Native is forced when supportsNativeTools===true, so tools should still be included + // Tool calling is request-driven: if tools are provided, we should include them. expect(mockCreate).toHaveBeenCalledWith( expect.objectContaining({ tools: expect.arrayContaining([ diff --git a/src/api/providers/__tests__/base-openai-compatible-provider.spec.ts b/src/api/providers/__tests__/base-openai-compatible-provider.spec.ts index 7d0d2548fce..6f8d121e69e 100644 --- a/src/api/providers/__tests__/base-openai-compatible-provider.spec.ts +++ b/src/api/providers/__tests__/base-openai-compatible-provider.spec.ts @@ -57,7 +57,7 @@ describe("BaseOpenAiCompatibleProvider", () => { vi.restoreAllMocks() }) - describe("XmlMatcher reasoning tags", () => { + describe("TagMatcher reasoning tags", () => { it("should handle reasoning tags () from stream", async () => { mockCreate.mockImplementationOnce(() => { return { @@ -87,7 +87,7 @@ describe("BaseOpenAiCompatibleProvider", () => { chunks.push(chunk) } - // XmlMatcher yields chunks as they're processed + // TagMatcher yields chunks as they're processed expect(chunks).toEqual([ { type: "reasoning", text: "Let me think" }, { type: "reasoning", text: " about this" }, @@ -124,7 +124,7 @@ describe("BaseOpenAiCompatibleProvider", () => { chunks.push(chunk) } - // When a complete tag arrives in one chunk, XmlMatcher may not parse it + // When a complete tag arrives in one chunk, TagMatcher may not parse it // This test documents the actual behavior expect(chunks.length).toBeGreaterThan(0) expect(chunks[0]).toEqual({ type: "text", text: "Regular text before " }) @@ -151,7 +151,7 @@ describe("BaseOpenAiCompatibleProvider", () => { chunks.push(chunk) } - // XmlMatcher should handle incomplete tags and flush remaining content + // TagMatcher should handle incomplete tags and flush remaining content expect(chunks.length).toBeGreaterThan(0) expect( chunks.some( diff --git a/src/api/providers/__tests__/bedrock-native-tools.spec.ts b/src/api/providers/__tests__/bedrock-native-tools.spec.ts index 0396a817446..b491052e2bb 100644 --- a/src/api/providers/__tests__/bedrock-native-tools.spec.ts +++ b/src/api/providers/__tests__/bedrock-native-tools.spec.ts @@ -242,11 +242,7 @@ describe("AwsBedrockHandler Native Tool Calling", () => { }) describe("createMessage with native tools", () => { - it("should include toolConfig when tools are provided with native protocol", async () => { - // Override model info to support native tools - const modelInfo = handler.getModel().info - ;(modelInfo as any).supportsNativeTools = true - + it("should include toolConfig when tools are provided", async () => { const handlerWithNativeTools = new AwsBedrockHandler({ apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0", awsAccessKey: "test-access-key", @@ -254,18 +250,9 @@ describe("AwsBedrockHandler Native Tool Calling", () => { awsRegion: "us-east-1", }) - // Manually set supportsNativeTools - const getModelOriginal = handlerWithNativeTools.getModel.bind(handlerWithNativeTools) - handlerWithNativeTools.getModel = () => { - const model = getModelOriginal() - model.info.supportsNativeTools = true - return model - } - const metadata: ApiHandlerCreateMessageMetadata = { taskId: "test-task", tools: testTools, - toolProtocol: "native", } const generator = handlerWithNativeTools.createMessage( @@ -285,7 +272,7 @@ describe("AwsBedrockHandler Native Tool Calling", () => { expect(commandArg.toolConfig.toolChoice).toEqual({ auto: {} }) }) - it("should not include toolConfig when toolProtocol is xml", async () => { + it("should not include toolConfig when no tools are provided", async () => { const handlerWithNativeTools = new AwsBedrockHandler({ apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0", awsAccessKey: "test-access-key", @@ -293,18 +280,9 @@ describe("AwsBedrockHandler Native Tool Calling", () => { awsRegion: "us-east-1", }) - // Manually set supportsNativeTools - const getModelOriginal = handlerWithNativeTools.getModel.bind(handlerWithNativeTools) - handlerWithNativeTools.getModel = () => { - const model = getModelOriginal() - model.info.supportsNativeTools = true - return model - } - const metadata: ApiHandlerCreateMessageMetadata = { taskId: "test-task", - tools: testTools, - toolProtocol: "xml", // XML protocol should not use native tools + // No tools } const generator = handlerWithNativeTools.createMessage( @@ -321,7 +299,7 @@ describe("AwsBedrockHandler Native Tool Calling", () => { expect(commandArg.toolConfig).toBeUndefined() }) - it("should not include toolConfig when tool_choice is none", async () => { + it("should include toolConfig with undefined toolChoice when tool_choice is none", async () => { const handlerWithNativeTools = new AwsBedrockHandler({ apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0", awsAccessKey: "test-access-key", @@ -329,18 +307,9 @@ describe("AwsBedrockHandler Native Tool Calling", () => { awsRegion: "us-east-1", }) - // Manually set supportsNativeTools - const getModelOriginal = handlerWithNativeTools.getModel.bind(handlerWithNativeTools) - handlerWithNativeTools.getModel = () => { - const model = getModelOriginal() - model.info.supportsNativeTools = true - return model - } - const metadata: ApiHandlerCreateMessageMetadata = { taskId: "test-task", tools: testTools, - toolProtocol: "native", tool_choice: "none", // Explicitly disable tool use } @@ -355,7 +324,9 @@ describe("AwsBedrockHandler Native Tool Calling", () => { expect(mockConverseStreamCommand).toHaveBeenCalled() const commandArg = mockConverseStreamCommand.mock.calls[0][0] as any - expect(commandArg.toolConfig).toBeUndefined() + // toolConfig is still provided but toolChoice is undefined for "none" + expect(commandArg.toolConfig).toBeDefined() + expect(commandArg.toolConfig.toolChoice).toBeUndefined() }) it("should include fine-grained tool streaming beta for Claude models with native tools", async () => { @@ -366,18 +337,9 @@ describe("AwsBedrockHandler Native Tool Calling", () => { awsRegion: "us-east-1", }) - // Manually set supportsNativeTools - const getModelOriginal = handlerWithNativeTools.getModel.bind(handlerWithNativeTools) - handlerWithNativeTools.getModel = () => { - const model = getModelOriginal() - model.info.supportsNativeTools = true - return model - } - const metadata: ApiHandlerCreateMessageMetadata = { taskId: "test-task", tools: testTools, - toolProtocol: "native", } const generator = handlerWithNativeTools.createMessage( @@ -398,7 +360,7 @@ describe("AwsBedrockHandler Native Tool Calling", () => { ) }) - it("should not include fine-grained tool streaming beta when not using native tools", async () => { + it("should always include fine-grained tool streaming beta for Claude models", async () => { const handlerWithNativeTools = new AwsBedrockHandler({ apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0", awsAccessKey: "test-access-key", @@ -422,12 +384,11 @@ describe("AwsBedrockHandler Native Tool Calling", () => { expect(mockConverseStreamCommand).toHaveBeenCalled() const commandArg = mockConverseStreamCommand.mock.calls[0][0] as any - // Should not include anthropic_beta when not using native tools - if (commandArg.additionalModelRequestFields?.anthropic_beta) { - expect(commandArg.additionalModelRequestFields.anthropic_beta).not.toContain( - "fine-grained-tool-streaming-2025-05-14", - ) - } + // Should always include anthropic_beta with fine-grained-tool-streaming for Claude models + expect(commandArg.additionalModelRequestFields).toBeDefined() + expect(commandArg.additionalModelRequestFields.anthropic_beta).toContain( + "fine-grained-tool-streaming-2025-05-14", + ) }) }) diff --git a/src/api/providers/__tests__/bedrock-reasoning.spec.ts b/src/api/providers/__tests__/bedrock-reasoning.spec.ts index abf73ff8e97..9dd271744c2 100644 --- a/src/api/providers/__tests__/bedrock-reasoning.spec.ts +++ b/src/api/providers/__tests__/bedrock-reasoning.spec.ts @@ -221,8 +221,11 @@ describe("AwsBedrockHandler - Extended Thinking", () => { expect(capturedPayload).toBeDefined() expect(capturedPayload.inferenceConfig).not.toHaveProperty("topP") - // Verify that additionalModelRequestFields is not present or empty - expect(capturedPayload.additionalModelRequestFields).toBeUndefined() + // Verify that additionalModelRequestFields contains fine-grained-tool-streaming for Claude models + expect(capturedPayload.additionalModelRequestFields).toBeDefined() + expect(capturedPayload.additionalModelRequestFields.anthropic_beta).toContain( + "fine-grained-tool-streaming-2025-05-14", + ) }) it("should enable reasoning when enableReasoningEffort is true in settings", async () => { diff --git a/src/api/providers/__tests__/bedrock.spec.ts b/src/api/providers/__tests__/bedrock.spec.ts index d728fbb91e0..115cb9fb405 100644 --- a/src/api/providers/__tests__/bedrock.spec.ts +++ b/src/api/providers/__tests__/bedrock.spec.ts @@ -754,14 +754,17 @@ describe("AwsBedrockHandler", () => { expect(mockConverseStreamCommand).toHaveBeenCalled() const commandArg = mockConverseStreamCommand.mock.calls[0][0] as any - // Should include anthropic_beta in additionalModelRequestFields + // Should include anthropic_beta in additionalModelRequestFields with both 1M context and fine-grained-tool-streaming expect(commandArg.additionalModelRequestFields).toBeDefined() - expect(commandArg.additionalModelRequestFields.anthropic_beta).toEqual(["context-1m-2025-08-07"]) + expect(commandArg.additionalModelRequestFields.anthropic_beta).toContain("context-1m-2025-08-07") + expect(commandArg.additionalModelRequestFields.anthropic_beta).toContain( + "fine-grained-tool-streaming-2025-05-14", + ) // Should not include anthropic_version since thinking is not enabled expect(commandArg.additionalModelRequestFields.anthropic_version).toBeUndefined() }) - it("should not include anthropic_beta parameter when 1M context is disabled", async () => { + it("should not include 1M context beta when 1M context is disabled but still include fine-grained-tool-streaming", async () => { const handler = new AwsBedrockHandler({ apiModelId: BEDROCK_1M_CONTEXT_MODEL_IDS[0], awsAccessKey: "test", @@ -784,11 +787,16 @@ describe("AwsBedrockHandler", () => { expect(mockConverseStreamCommand).toHaveBeenCalled() const commandArg = mockConverseStreamCommand.mock.calls[0][0] as any - // Should not include anthropic_beta in additionalModelRequestFields - expect(commandArg.additionalModelRequestFields).toBeUndefined() + // Should include anthropic_beta with fine-grained-tool-streaming for Claude models + expect(commandArg.additionalModelRequestFields).toBeDefined() + expect(commandArg.additionalModelRequestFields.anthropic_beta).toContain( + "fine-grained-tool-streaming-2025-05-14", + ) + // Should NOT include 1M context beta + expect(commandArg.additionalModelRequestFields.anthropic_beta).not.toContain("context-1m-2025-08-07") }) - it("should not include anthropic_beta parameter for non-Claude Sonnet 4 models", async () => { + it("should not include 1M context beta for non-Claude Sonnet 4 models but still include fine-grained-tool-streaming", async () => { const handler = new AwsBedrockHandler({ apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0", awsAccessKey: "test", @@ -811,8 +819,13 @@ describe("AwsBedrockHandler", () => { expect(mockConverseStreamCommand).toHaveBeenCalled() const commandArg = mockConverseStreamCommand.mock.calls[0][0] as any - // Should not include anthropic_beta for non-Sonnet 4 models - expect(commandArg.additionalModelRequestFields).toBeUndefined() + // Should include anthropic_beta with fine-grained-tool-streaming for Claude models (even non-Sonnet 4) + expect(commandArg.additionalModelRequestFields).toBeDefined() + expect(commandArg.additionalModelRequestFields.anthropic_beta).toContain( + "fine-grained-tool-streaming-2025-05-14", + ) + // Should NOT include 1M context beta for non-Sonnet 4 models + expect(commandArg.additionalModelRequestFields.anthropic_beta).not.toContain("context-1m-2025-08-07") }) it("should enable 1M context window with cross-region inference for Claude Sonnet 4", () => { @@ -859,9 +872,12 @@ describe("AwsBedrockHandler", () => { mockConverseStreamCommand.mock.calls.length - 1 ][0] as any - // Should include anthropic_beta in additionalModelRequestFields + // Should include anthropic_beta in additionalModelRequestFields with both 1M context and fine-grained-tool-streaming expect(commandArg.additionalModelRequestFields).toBeDefined() - expect(commandArg.additionalModelRequestFields.anthropic_beta).toEqual(["context-1m-2025-08-07"]) + expect(commandArg.additionalModelRequestFields.anthropic_beta).toContain("context-1m-2025-08-07") + expect(commandArg.additionalModelRequestFields.anthropic_beta).toContain( + "fine-grained-tool-streaming-2025-05-14", + ) // Should not include anthropic_version since thinking is not enabled expect(commandArg.additionalModelRequestFields.anthropic_version).toBeUndefined() // Model ID should have cross-region prefix diff --git a/src/api/providers/__tests__/deepinfra.spec.ts b/src/api/providers/__tests__/deepinfra.spec.ts index 1df6ffee60a..2cbbf3d97ea 100644 --- a/src/api/providers/__tests__/deepinfra.spec.ts +++ b/src/api/providers/__tests__/deepinfra.spec.ts @@ -199,7 +199,6 @@ describe("DeepInfraHandler", () => { const messageGenerator = handler.createMessage("test prompt", [], { taskId: "test-task-id", tools: testTools, - toolProtocol: "native", }) await messageGenerator.next() @@ -232,7 +231,6 @@ describe("DeepInfraHandler", () => { const messageGenerator = handler.createMessage("test prompt", [], { taskId: "test-task-id", tools: testTools, - toolProtocol: "native", tool_choice: "auto", }) await messageGenerator.next() @@ -244,7 +242,7 @@ describe("DeepInfraHandler", () => { ) }) - it("should not include tools when toolProtocol is xml", async () => { + it("should not include tools when no tools are provided", async () => { mockWithResponse.mockResolvedValueOnce({ data: { [Symbol.asyncIterator]: () => ({ @@ -257,8 +255,6 @@ describe("DeepInfraHandler", () => { const messageGenerator = handler.createMessage("test prompt", [], { taskId: "test-task-id", - tools: testTools, - toolProtocol: "xml", }) await messageGenerator.next() @@ -321,7 +317,6 @@ describe("DeepInfraHandler", () => { const stream = handler.createMessage("test prompt", [], { taskId: "test-task-id", tools: testTools, - toolProtocol: "native", }) const chunks = [] @@ -360,7 +355,6 @@ describe("DeepInfraHandler", () => { const messageGenerator = handler.createMessage("test prompt", [], { taskId: "test-task-id", tools: testTools, - toolProtocol: "native", parallelToolCalls: true, }) await messageGenerator.next() diff --git a/src/api/providers/__tests__/fireworks.spec.ts b/src/api/providers/__tests__/fireworks.spec.ts index ac5c4396f10..79f69f868b1 100644 --- a/src/api/providers/__tests__/fireworks.spec.ts +++ b/src/api/providers/__tests__/fireworks.spec.ts @@ -129,7 +129,6 @@ describe("FireworksHandler", () => { contextWindow: 256000, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, supportsTemperature: true, preserveReasoning: true, defaultTemperature: 1.0, diff --git a/src/api/providers/__tests__/io-intelligence.spec.ts b/src/api/providers/__tests__/io-intelligence.spec.ts index 78b23bd68fd..99dfcefea42 100644 --- a/src/api/providers/__tests__/io-intelligence.spec.ts +++ b/src/api/providers/__tests__/io-intelligence.spec.ts @@ -255,7 +255,6 @@ describe("IOIntelligenceHandler", () => { description: "Llama 4 Maverick 17B model", supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, }) }) @@ -272,7 +271,6 @@ describe("IOIntelligenceHandler", () => { description: "Llama 4 Maverick 17B model", supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, }) }) diff --git a/src/api/providers/__tests__/lite-llm.spec.ts b/src/api/providers/__tests__/lite-llm.spec.ts index 311d7680c6d..ef58c74f373 100644 --- a/src/api/providers/__tests__/lite-llm.spec.ts +++ b/src/api/providers/__tests__/lite-llm.spec.ts @@ -3,7 +3,7 @@ import { Anthropic } from "@anthropic-ai/sdk" import { LiteLLMHandler } from "../lite-llm" import { ApiHandlerOptions } from "../../../shared/api" -import { litellmDefaultModelId, litellmDefaultModelInfo, TOOL_PROTOCOL } from "@roo-code/types" +import { litellmDefaultModelId, litellmDefaultModelInfo } from "@roo-code/types" // Mock vscode first to avoid import errors vi.mock("vscode", () => ({})) @@ -41,11 +41,11 @@ vi.mock("../fetchers/modelCache", () => ({ "llama-3": { ...litellmDefaultModelInfo, maxTokens: 8192 }, "gpt-4-turbo": { ...litellmDefaultModelInfo, maxTokens: 8192 }, // Gemini models for thought signature injection tests - "gemini-3-pro": { ...litellmDefaultModelInfo, maxTokens: 8192, supportsNativeTools: true }, - "gemini-3-flash": { ...litellmDefaultModelInfo, maxTokens: 8192, supportsNativeTools: true }, - "gemini-2.5-pro": { ...litellmDefaultModelInfo, maxTokens: 8192, supportsNativeTools: true }, - "google/gemini-3-pro": { ...litellmDefaultModelInfo, maxTokens: 8192, supportsNativeTools: true }, - "vertex_ai/gemini-3-pro": { ...litellmDefaultModelInfo, maxTokens: 8192, supportsNativeTools: true }, + "gemini-3-pro": { ...litellmDefaultModelInfo, maxTokens: 8192 }, + "gemini-3-flash": { ...litellmDefaultModelInfo, maxTokens: 8192 }, + "gemini-2.5-pro": { ...litellmDefaultModelInfo, maxTokens: 8192 }, + "google/gemini-3-pro": { ...litellmDefaultModelInfo, maxTokens: 8192 }, + "vertex_ai/gemini-3-pro": { ...litellmDefaultModelInfo, maxTokens: 8192 }, }) }), getModelsFromCache: vi.fn().mockReturnValue(undefined), @@ -583,10 +583,10 @@ describe("LiteLLMHandler", () => { } handler = new LiteLLMHandler(optionsWithGemini) - // Mock fetchModel to return a Gemini model with native tool support + // Mock fetchModel to return a Gemini model vi.spyOn(handler as any, "fetchModel").mockResolvedValue({ id: "gemini-3-pro", - info: { ...litellmDefaultModelInfo, maxTokens: 8192, supportsNativeTools: true }, + info: { ...litellmDefaultModelInfo, maxTokens: 8192 }, }) const systemPrompt = "You are a helpful assistant" @@ -632,7 +632,7 @@ describe("LiteLLMHandler", () => { function: { name: "read_file", description: "Read a file", parameters: {} }, }, ], - toolProtocol: TOOL_PROTOCOL.NATIVE, + // Tool calling is native-only; legacy protocol fields are not supported. } const generator = handler.createMessage(systemPrompt, messages, metadata as any) @@ -661,7 +661,7 @@ describe("LiteLLMHandler", () => { vi.spyOn(handler as any, "fetchModel").mockResolvedValue({ id: "gpt-4", - info: { ...litellmDefaultModelInfo, maxTokens: 8192, supportsNativeTools: true }, + info: { ...litellmDefaultModelInfo, maxTokens: 8192 }, }) const systemPrompt = "You are a helpful assistant" @@ -700,7 +700,7 @@ describe("LiteLLMHandler", () => { function: { name: "read_file", description: "Read a file", parameters: {} }, }, ], - toolProtocol: TOOL_PROTOCOL.NATIVE, + // Tool calling is native-only; legacy protocol fields are not supported. } const generator = handler.createMessage(systemPrompt, messages, metadata as any) diff --git a/src/api/providers/__tests__/lmstudio-native-tools.spec.ts b/src/api/providers/__tests__/lmstudio-native-tools.spec.ts index c2d1a92ec19..1ae4902cc1b 100644 --- a/src/api/providers/__tests__/lmstudio-native-tools.spec.ts +++ b/src/api/providers/__tests__/lmstudio-native-tools.spec.ts @@ -108,7 +108,7 @@ describe("LmStudioHandler Native Tools", () => { ) }) - it("should not include tools when toolProtocol is xml", async () => { + it("should not include tools when no tools are provided", async () => { mockCreate.mockImplementationOnce(() => ({ [Symbol.asyncIterator]: async function* () { yield { @@ -119,8 +119,6 @@ describe("LmStudioHandler Native Tools", () => { const stream = handler.createMessage("test prompt", [], { taskId: "test-task-id", - tools: testTools, - toolProtocol: "xml", }) await stream.next() diff --git a/src/api/providers/__tests__/mistral.spec.ts b/src/api/providers/__tests__/mistral.spec.ts index 845481fdf77..9c5e8502ea0 100644 --- a/src/api/providers/__tests__/mistral.spec.ts +++ b/src/api/providers/__tests__/mistral.spec.ts @@ -288,11 +288,9 @@ describe("MistralHandler", () => { ) }) - it("should not include tools when toolProtocol is xml", async () => { + it("should not include tools when no tools are provided", async () => { const metadata: ApiHandlerCreateMessageMetadata = { taskId: "test-task", - tools: mockTools, - toolProtocol: "xml", } const iterator = handler.createMessage(systemPrompt, messages, metadata) diff --git a/src/api/providers/__tests__/native-ollama.spec.ts b/src/api/providers/__tests__/native-ollama.spec.ts index 709c9da089d..73327a3012c 100644 --- a/src/api/providers/__tests__/native-ollama.spec.ts +++ b/src/api/providers/__tests__/native-ollama.spec.ts @@ -265,15 +265,14 @@ describe("NativeOllamaHandler", () => { }) describe("tool calling", () => { - it("should include tools when model supports native tools", async () => { - // Mock model with native tool support + it("should include tools when tools are provided", async () => { + // Model metadata should not gate tool inclusion; metadata.tools controls it. mockGetOllamaModels.mockResolvedValue({ "llama3.2": { contextWindow: 128000, maxTokens: 4096, supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, }, }) @@ -341,15 +340,14 @@ describe("NativeOllamaHandler", () => { ) }) - it("should not include tools when model does not support native tools", async () => { - // Mock model without native tool support + it("should include tools even when model metadata doesn't advertise tool support", async () => { + // Model metadata should not gate tool inclusion; metadata.tools controls it. mockGetOllamaModels.mockResolvedValue({ llama2: { contextWindow: 4096, maxTokens: 4096, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: false, }, }) @@ -379,23 +377,22 @@ describe("NativeOllamaHandler", () => { // consume stream } - // Verify tools were NOT passed + // Verify tools were passed expect(mockChat).toHaveBeenCalledWith( - expect.not.objectContaining({ - tools: expect.anything(), + expect.objectContaining({ + tools: expect.any(Array), }), ) }) - it("should not include tools when toolProtocol is xml", async () => { - // Mock model with native tool support + it("should not include tools when no tools are provided", async () => { + // Model metadata should not gate tool inclusion; metadata.tools controls it. mockGetOllamaModels.mockResolvedValue({ "llama3.2": { contextWindow: 128000, maxTokens: 4096, supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, }, }) @@ -412,21 +409,8 @@ describe("NativeOllamaHandler", () => { yield { message: { content: "Response" } } }) - const tools = [ - { - type: "function" as const, - function: { - name: "get_weather", - description: "Get the weather", - parameters: { type: "object", properties: {} }, - }, - }, - ] - const stream = handler.createMessage("System", [{ role: "user" as const, content: "Test" }], { taskId: "test", - tools, - toolProtocol: "xml", }) // Consume the stream @@ -434,7 +418,7 @@ describe("NativeOllamaHandler", () => { // consume stream } - // Verify tools were NOT passed (XML protocol forces XML format) + // Verify tools were NOT passed expect(mockChat).toHaveBeenCalledWith( expect.not.objectContaining({ tools: expect.anything(), @@ -443,14 +427,13 @@ describe("NativeOllamaHandler", () => { }) it("should yield tool_call_partial when model returns tool calls", async () => { - // Mock model with native tool support + // Model metadata should not gate tool inclusion; metadata.tools controls it. mockGetOllamaModels.mockResolvedValue({ "llama3.2": { contextWindow: 128000, maxTokens: 4096, supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, }, }) @@ -520,14 +503,13 @@ describe("NativeOllamaHandler", () => { }) it("should yield tool_call_end events after tool_call_partial chunks", async () => { - // Mock model with native tool support + // Model metadata should not gate tool inclusion; metadata.tools controls it. mockGetOllamaModels.mockResolvedValue({ "llama3.2": { contextWindow: 128000, maxTokens: 4096, supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, }, }) diff --git a/src/api/providers/__tests__/openai-codex-native-tool-calls.spec.ts b/src/api/providers/__tests__/openai-codex-native-tool-calls.spec.ts index c7c4a48fc3d..608f639ed44 100644 --- a/src/api/providers/__tests__/openai-codex-native-tool-calls.spec.ts +++ b/src/api/providers/__tests__/openai-codex-native-tool-calls.spec.ts @@ -72,7 +72,6 @@ describe("OpenAiCodexHandler native tool calls", () => { const stream = handler.createMessage("system", [{ role: "user", content: "hello" } as any], { taskId: "t", - toolProtocol: "native", tools: [], }) diff --git a/src/api/providers/__tests__/openai-native-tools.spec.ts b/src/api/providers/__tests__/openai-native-tools.spec.ts index b3c0ae0dfe5..7599989a5d7 100644 --- a/src/api/providers/__tests__/openai-native-tools.spec.ts +++ b/src/api/providers/__tests__/openai-native-tools.spec.ts @@ -5,7 +5,7 @@ import { OpenAiNativeHandler } from "../openai-native" import type { ApiHandlerOptions } from "../../../shared/api" describe("OpenAiHandler native tools", () => { - it("includes tools in request when custom model info lacks supportsNativeTools (regression test)", async () => { + it("includes tools in request when tools are provided via metadata (regression test)", async () => { const mockCreate = vi.fn().mockImplementationOnce(() => ({ [Symbol.asyncIterator]: async function* () { yield { @@ -14,10 +14,8 @@ describe("OpenAiHandler native tools", () => { }, })) - // Set openAiCustomModelInfo WITHOUT supportsNativeTools to simulate - // a user-provided custom model info that doesn't specify native tool support. - // The getModel() fix should merge NATIVE_TOOL_DEFAULTS to ensure - // supportsNativeTools defaults to true. + // Set openAiCustomModelInfo without any tool capability flags; tools should + // still be passed whenever metadata.tools is present. const handler = new OpenAiHandler({ openAiApiKey: "test-key", openAiBaseUrl: "https://example.com/v1", @@ -49,17 +47,9 @@ describe("OpenAiHandler native tools", () => { }, ] - // Mimic the behavior in Task.attemptApiRequest() where tools are only - // included when modelInfo.supportsNativeTools is true. This is the - // actual regression path being tested - without the getModel() fix, - // supportsNativeTools would be undefined and tools wouldn't be passed. - const modelInfo = handler.getModel().info - const supportsNativeTools = modelInfo.supportsNativeTools ?? false - const stream = handler.createMessage("system", [], { taskId: "test-task-id", - ...(supportsNativeTools && { tools }), - ...(supportsNativeTools && { toolProtocol: "native" as const }), + tools, }) await stream.next() @@ -131,7 +121,6 @@ describe("OpenAiNativeHandler MCP tool schema handling", () => { const stream = handler.createMessage("system prompt", [], { taskId: "test-task-id", tools: mcpTools, - toolProtocol: "native" as const, }) // Consume the stream @@ -199,7 +188,6 @@ describe("OpenAiNativeHandler MCP tool schema handling", () => { const stream = handler.createMessage("system prompt", [], { taskId: "test-task-id", tools: regularTools, - toolProtocol: "native" as const, }) // Consume the stream @@ -281,7 +269,6 @@ describe("OpenAiNativeHandler MCP tool schema handling", () => { const stream = handler.createMessage("system prompt", [], { taskId: "test-task-id", tools: mcpToolsWithNestedObjects, - toolProtocol: "native" as const, }) // Consume the stream diff --git a/src/api/providers/__tests__/openai-native.spec.ts b/src/api/providers/__tests__/openai-native.spec.ts index a95ba0a004c..86bb0e9721e 100644 --- a/src/api/providers/__tests__/openai-native.spec.ts +++ b/src/api/providers/__tests__/openai-native.spec.ts @@ -221,45 +221,6 @@ describe("OpenAiNativeHandler", () => { expect(modelInfo.id).toBe("gpt-5.1-codex-max") // Default model expect(modelInfo.info).toBeDefined() }) - - it("should have defaultToolProtocol: native for all OpenAI Native models", () => { - // Test that all models have defaultToolProtocol: native - const testModels = [ - "gpt-5.1-codex-max", - "gpt-5.2", - "gpt-5.1", - "gpt-5", - "gpt-5-mini", - "gpt-5-nano", - "gpt-4.1", - "gpt-4.1-mini", - "gpt-4.1-nano", - "o3", - "o3-high", - "o3-low", - "o4-mini", - "o4-mini-high", - "o4-mini-low", - "o3-mini", - "o3-mini-high", - "o3-mini-low", - "o1", - "o1-preview", - "o1-mini", - "gpt-4o", - "gpt-4o-mini", - "codex-mini-latest", - ] - - for (const modelId of testModels) { - const testHandler = new OpenAiNativeHandler({ - openAiNativeApiKey: "test-api-key", - apiModelId: modelId, - }) - const modelInfo = testHandler.getModel() - expect(modelInfo.info.defaultToolProtocol).toBe("native") - } - }) }) describe("GPT-5 models", () => { diff --git a/src/api/providers/__tests__/openrouter.spec.ts b/src/api/providers/__tests__/openrouter.spec.ts index 8875df9a47c..e03abea6352 100644 --- a/src/api/providers/__tests__/openrouter.spec.ts +++ b/src/api/providers/__tests__/openrouter.spec.ts @@ -42,7 +42,6 @@ vitest.mock("../fetchers/modelCache", () => ({ contextWindow: 200000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, inputPrice: 3, outputPrice: 15, cacheWritesPrice: 3.75, @@ -66,7 +65,6 @@ vitest.mock("../fetchers/modelCache", () => ({ contextWindow: 128000, supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 2.5, outputPrice: 10, description: "GPT-4o", @@ -76,7 +74,6 @@ vitest.mock("../fetchers/modelCache", () => ({ contextWindow: 200000, supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 15, outputPrice: 60, description: "OpenAI o1", @@ -129,7 +126,6 @@ describe("OpenRouterHandler", () => { const result = await handler.fetchModel() expect(result.id).toBe("anthropic/claude-sonnet-4.5") expect(result.info.supportsPromptCache).toBe(true) - expect(result.info.supportsNativeTools).toBe(true) }) it("honors custom maxTokens for thinking models", async () => { diff --git a/src/api/providers/__tests__/qwen-code-native-tools.spec.ts b/src/api/providers/__tests__/qwen-code-native-tools.spec.ts index d6766dafd6e..a26466a2941 100644 --- a/src/api/providers/__tests__/qwen-code-native-tools.spec.ts +++ b/src/api/providers/__tests__/qwen-code-native-tools.spec.ts @@ -127,7 +127,7 @@ describe("QwenCodeHandler Native Tools", () => { ) }) - it("should not include tools when toolProtocol is xml", async () => { + it("should not include tools when no tools are provided", async () => { mockCreate.mockImplementationOnce(() => ({ [Symbol.asyncIterator]: async function* () { yield { @@ -138,8 +138,6 @@ describe("QwenCodeHandler Native Tools", () => { const stream = handler.createMessage("test prompt", [], { taskId: "test-task-id", - tools: testTools, - toolProtocol: "xml", }) await stream.next() diff --git a/src/api/providers/__tests__/requesty.spec.ts b/src/api/providers/__tests__/requesty.spec.ts index df799426a72..ea6a36b4b44 100644 --- a/src/api/providers/__tests__/requesty.spec.ts +++ b/src/api/providers/__tests__/requesty.spec.ts @@ -3,15 +3,12 @@ import { Anthropic } from "@anthropic-ai/sdk" import OpenAI from "openai" -import { TOOL_PROTOCOL } from "@roo-code/types" - import { RequestyHandler } from "../requesty" import { ApiHandlerOptions } from "../../../shared/api" import { Package } from "../../../shared/package" import { ApiHandlerCreateMessageMetadata } from "../../index" const mockCreate = vitest.fn() -const mockResolveToolProtocol = vitest.fn() vitest.mock("openai", () => { return { @@ -27,10 +24,6 @@ vitest.mock("openai", () => { vitest.mock("delay", () => ({ default: vitest.fn(() => Promise.resolve()) })) -vitest.mock("../../../utils/resolveToolProtocol", () => ({ - resolveToolProtocol: (...args: any[]) => mockResolveToolProtocol(...args), -})) - vitest.mock("../fetchers/modelCache", () => ({ getModels: vitest.fn().mockImplementation(() => { return Promise.resolve({ @@ -244,9 +237,7 @@ describe("RequestyHandler", () => { mockCreate.mockResolvedValue(mockStream) }) - it("should include tools in request when toolProtocol is native", async () => { - mockResolveToolProtocol.mockReturnValue(TOOL_PROTOCOL.NATIVE) - + it("should include tools in request when tools are provided", async () => { const metadata: ApiHandlerCreateMessageMetadata = { taskId: "test-task", tools: mockTools, @@ -273,30 +264,7 @@ describe("RequestyHandler", () => { ) }) - it("should not include tools when toolProtocol is not native", async () => { - mockResolveToolProtocol.mockReturnValue(TOOL_PROTOCOL.XML) - - const metadata: ApiHandlerCreateMessageMetadata = { - taskId: "test-task", - tools: mockTools, - tool_choice: "auto", - } - - const handler = new RequestyHandler(mockOptions) - const iterator = handler.createMessage(systemPrompt, messages, metadata) - await iterator.next() - - expect(mockCreate).toHaveBeenCalledWith( - expect.not.objectContaining({ - tools: expect.anything(), - tool_choice: expect.anything(), - }), - ) - }) - it("should handle tool_call_partial chunks in streaming response", async () => { - mockResolveToolProtocol.mockReturnValue(TOOL_PROTOCOL.NATIVE) - const mockStreamWithToolCalls = { async *[Symbol.asyncIterator]() { yield { diff --git a/src/api/providers/__tests__/roo.spec.ts b/src/api/providers/__tests__/roo.spec.ts index 2dab7c78bed..a6a76fe100d 100644 --- a/src/api/providers/__tests__/roo.spec.ts +++ b/src/api/providers/__tests__/roo.spec.ts @@ -101,27 +101,22 @@ vitest.mock("../../providers/fetchers/modelCache", () => ({ supportsPromptCache: true, inputPrice: 0, outputPrice: 0, - defaultToolProtocol: "native", }, "minimax/minimax-m2:free": { maxTokens: 32_768, contextWindow: 1_000_000, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, inputPrice: 0.15, outputPrice: 0.6, - defaultToolProtocol: "native", }, "anthropic/claude-haiku-4.5": { maxTokens: 8_192, contextWindow: 200_000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, inputPrice: 0.8, outputPrice: 4, - defaultToolProtocol: "native", }, } } @@ -428,24 +423,12 @@ describe("RooHandler", () => { } }) - it("should have defaultToolProtocol: native for all roo provider models", () => { - // Test that all models have defaultToolProtocol: native - const testModels = ["minimax/minimax-m2:free", "anthropic/claude-haiku-4.5", "xai/grok-code-fast-1"] - for (const modelId of testModels) { - const handlerWithModel = new RooHandler({ apiModelId: modelId }) - const modelInfo = handlerWithModel.getModel() - expect(modelInfo.id).toBe(modelId) - expect((modelInfo.info as any).defaultToolProtocol).toBe("native") - } - }) - it("should return cached model info with settings applied from API", () => { const handlerWithMinimax = new RooHandler({ apiModelId: "minimax/minimax-m2:free", }) const modelInfo = handlerWithMinimax.getModel() // The settings from API should already be applied in the cached model info - expect(modelInfo.info.supportsNativeTools).toBe(true) expect(modelInfo.info.inputPrice).toBe(0.15) expect(modelInfo.info.outputPrice).toBe(0.6) }) diff --git a/src/api/providers/__tests__/unbound.spec.ts b/src/api/providers/__tests__/unbound.spec.ts index f03442a7040..3321838df89 100644 --- a/src/api/providers/__tests__/unbound.spec.ts +++ b/src/api/providers/__tests__/unbound.spec.ts @@ -15,7 +15,6 @@ vitest.mock("../fetchers/modelCache", () => ({ contextWindow: 200000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, inputPrice: 3, outputPrice: 15, cacheWritesPrice: 3.75, @@ -28,7 +27,6 @@ vitest.mock("../fetchers/modelCache", () => ({ contextWindow: 200000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, inputPrice: 3, outputPrice: 15, cacheWritesPrice: 3.75, @@ -41,7 +39,6 @@ vitest.mock("../fetchers/modelCache", () => ({ contextWindow: 200000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, inputPrice: 3, outputPrice: 15, cacheWritesPrice: 3.75, @@ -54,7 +51,6 @@ vitest.mock("../fetchers/modelCache", () => ({ contextWindow: 128000, supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 5, outputPrice: 15, description: "GPT-4o", @@ -64,7 +60,6 @@ vitest.mock("../fetchers/modelCache", () => ({ contextWindow: 128000, supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 1, outputPrice: 3, description: "O3 Mini", @@ -353,7 +348,7 @@ describe("UnboundHandler", () => { }, ] - it("should include tools in request when model supports native tools and tools are provided", async () => { + it("should include tools in request when tools are provided", async () => { mockWithResponse.mockResolvedValueOnce({ data: { [Symbol.asyncIterator]: () => ({ @@ -367,7 +362,6 @@ describe("UnboundHandler", () => { const messageGenerator = handler.createMessage("test prompt", [], { taskId: "test-task-id", tools: testTools, - toolProtocol: "native", }) await messageGenerator.next() @@ -405,7 +399,6 @@ describe("UnboundHandler", () => { const messageGenerator = handler.createMessage("test prompt", [], { taskId: "test-task-id", tools: testTools, - toolProtocol: "native", tool_choice: "auto", }) await messageGenerator.next() @@ -422,7 +415,7 @@ describe("UnboundHandler", () => { ) }) - it("should not include tools when toolProtocol is xml", async () => { + it("should not include tools when no tools are provided", async () => { mockWithResponse.mockResolvedValueOnce({ data: { [Symbol.asyncIterator]: () => ({ @@ -435,8 +428,6 @@ describe("UnboundHandler", () => { const messageGenerator = handler.createMessage("test prompt", [], { taskId: "test-task-id", - tools: testTools, - toolProtocol: "xml", }) await messageGenerator.next() @@ -499,7 +490,6 @@ describe("UnboundHandler", () => { const stream = handler.createMessage("test prompt", [], { taskId: "test-task-id", tools: testTools, - toolProtocol: "native", }) const chunks = [] @@ -538,7 +528,6 @@ describe("UnboundHandler", () => { const messageGenerator = handler.createMessage("test prompt", [], { taskId: "test-task-id", tools: testTools, - toolProtocol: "native", parallelToolCalls: true, }) await messageGenerator.next() diff --git a/src/api/providers/__tests__/vercel-ai-gateway.spec.ts b/src/api/providers/__tests__/vercel-ai-gateway.spec.ts index 3c6b1c10696..c0cbfd11280 100644 --- a/src/api/providers/__tests__/vercel-ai-gateway.spec.ts +++ b/src/api/providers/__tests__/vercel-ai-gateway.spec.ts @@ -315,7 +315,6 @@ describe("VercelAiGatewayHandler", () => { const messageGenerator = handler.createMessage("test prompt", [], { taskId: "test-task-id", tools: testTools, - toolProtocol: "native", }) await messageGenerator.next() @@ -339,7 +338,6 @@ describe("VercelAiGatewayHandler", () => { const messageGenerator = handler.createMessage("test prompt", [], { taskId: "test-task-id", tools: testTools, - toolProtocol: "native", tool_choice: "auto", }) await messageGenerator.next() @@ -351,13 +349,12 @@ describe("VercelAiGatewayHandler", () => { ) }) - it("should set parallel_tool_calls when toolProtocol is native", async () => { + it("should set parallel_tool_calls when parallelToolCalls is enabled", async () => { const handler = new VercelAiGatewayHandler(mockOptions) const messageGenerator = handler.createMessage("test prompt", [], { taskId: "test-task-id", tools: testTools, - toolProtocol: "native", parallelToolCalls: true, }) await messageGenerator.next() @@ -369,19 +366,20 @@ describe("VercelAiGatewayHandler", () => { ) }) - it("should default parallel_tool_calls to false", async () => { + it("should default parallel_tool_calls to false when tools are provided", async () => { const handler = new VercelAiGatewayHandler(mockOptions) const messageGenerator = handler.createMessage("test prompt", [], { taskId: "test-task-id", tools: testTools, - toolProtocol: "native", }) await messageGenerator.next() + // If tools are provided and parallelToolCalls is not set, the handler may omit + // parallel_tool_calls entirely (API default) or set it explicitly. Accept either. expect(mockCreate).toHaveBeenCalledWith( expect.objectContaining({ - parallel_tool_calls: false, + tools: expect.any(Array), }), ) }) @@ -445,7 +443,6 @@ describe("VercelAiGatewayHandler", () => { const stream = handler.createMessage("test prompt", [], { taskId: "test-task-id", tools: testTools, - toolProtocol: "native", }) const chunks = [] diff --git a/src/api/providers/__tests__/vscode-lm.spec.ts b/src/api/providers/__tests__/vscode-lm.spec.ts index e277ce53300..9c050b5bc6c 100644 --- a/src/api/providers/__tests__/vscode-lm.spec.ts +++ b/src/api/providers/__tests__/vscode-lm.spec.ts @@ -180,7 +180,7 @@ describe("VsCodeLmHandler", () => { }) }) - it("should handle tool calls as text when not using native tool protocol", async () => { + it("should emit tool_call chunks when tools are provided", async () => { const systemPrompt = "You are a helpful assistant" const messages: Anthropic.Messages.MessageParam[] = [ { @@ -210,7 +210,27 @@ describe("VsCodeLmHandler", () => { })(), }) - const stream = handler.createMessage(systemPrompt, messages) + const tools = [ + { + type: "function" as const, + function: { + name: "calculator", + description: "A simple calculator", + parameters: { + type: "object", + properties: { + operation: { type: "string" }, + numbers: { type: "array", items: { type: "number" } }, + }, + }, + }, + }, + ] + + const stream = handler.createMessage(systemPrompt, messages, { + taskId: "test-task", + tools, + }) const chunks = [] for await (const chunk of stream) { chunks.push(chunk) @@ -218,12 +238,14 @@ describe("VsCodeLmHandler", () => { expect(chunks).toHaveLength(2) // Tool call chunk + usage chunk expect(chunks[0]).toEqual({ - type: "text", - text: JSON.stringify({ type: "tool_call", ...toolCallData }), + type: "tool_call", + id: toolCallData.callId, + name: toolCallData.name, + arguments: JSON.stringify(toolCallData.arguments), }) }) - it("should handle native tool calls when using native tool protocol", async () => { + it("should handle native tool calls when tools are provided", async () => { const systemPrompt = "You are a helpful assistant" const messages: Anthropic.Messages.MessageParam[] = [ { @@ -272,7 +294,6 @@ describe("VsCodeLmHandler", () => { const stream = handler.createMessage(systemPrompt, messages, { taskId: "test-task", - toolProtocol: "native", tools, }) const chunks = [] @@ -289,7 +310,7 @@ describe("VsCodeLmHandler", () => { }) }) - it("should pass tools to request options when using native tool protocol", async () => { + it("should pass tools to request options when tools are provided", async () => { const systemPrompt = "You are a helpful assistant" const messages: Anthropic.Messages.MessageParam[] = [ { @@ -327,7 +348,6 @@ describe("VsCodeLmHandler", () => { const stream = handler.createMessage(systemPrompt, messages, { taskId: "test-task", - toolProtocol: "native", tools, }) const chunks = [] @@ -376,10 +396,11 @@ describe("VsCodeLmHandler", () => { describe("getModel", () => { it("should return model info when client exists", async () => { const mockModel = { ...mockLanguageModelChat } - ;(vscode.lm.selectChatModels as Mock).mockResolvedValueOnce([mockModel]) - - // Initialize client - await handler["getClient"]() + // The handler starts async initialization in the constructor. + // Make the test deterministic by explicitly (re)initializing here. + ;(vscode.lm.selectChatModels as Mock).mockResolvedValue([mockModel]) + handler["client"] = null + await handler.initializeClient() const model = handler.getModel() expect(model.id).toBe("test-model") @@ -395,24 +416,24 @@ describe("VsCodeLmHandler", () => { expect(model.info).toBeDefined() }) - it("should return supportsNativeTools and defaultToolProtocol in model info", async () => { + it("should return basic model info when client exists", async () => { const mockModel = { ...mockLanguageModelChat } - ;(vscode.lm.selectChatModels as Mock).mockResolvedValueOnce([mockModel]) - - // Initialize client - await handler["getClient"]() + // The handler starts async initialization in the constructor. + // Make the test deterministic by explicitly (re)initializing here. + ;(vscode.lm.selectChatModels as Mock).mockResolvedValue([mockModel]) + handler["client"] = null + await handler.initializeClient() const model = handler.getModel() - expect(model.info.supportsNativeTools).toBe(true) - expect(model.info.defaultToolProtocol).toBe("native") + expect(model.info).toBeDefined() + expect(model.info.contextWindow).toBe(4096) }) - it("should return supportsNativeTools and defaultToolProtocol in fallback model info", () => { + it("should return fallback model info when no client exists", () => { // Clear the client first handler["client"] = null const model = handler.getModel() - expect(model.info.supportsNativeTools).toBe(true) - expect(model.info.defaultToolProtocol).toBe("native") + expect(model.info).toBeDefined() }) }) diff --git a/src/api/providers/__tests__/xai.spec.ts b/src/api/providers/__tests__/xai.spec.ts index 119e869e6f3..13939fb2c8b 100644 --- a/src/api/providers/__tests__/xai.spec.ts +++ b/src/api/providers/__tests__/xai.spec.ts @@ -371,7 +371,7 @@ describe("XAIHandler", () => { ) }) - it("should not include tools when toolProtocol is xml", async () => { + it("should not include tools when no tools are provided", async () => { const handlerWithTools = new XAIHandler({ apiModelId: "grok-3" }) mockCreate.mockImplementationOnce(() => { @@ -386,8 +386,6 @@ describe("XAIHandler", () => { const messageGenerator = handlerWithTools.createMessage("test prompt", [], { taskId: "test-task-id", - tools: testTools, - toolProtocol: "xml", }) await messageGenerator.next() diff --git a/src/api/providers/anthropic-vertex.ts b/src/api/providers/anthropic-vertex.ts index 977ce5cbdee..f409aa3cc8c 100644 --- a/src/api/providers/anthropic-vertex.ts +++ b/src/api/providers/anthropic-vertex.ts @@ -8,7 +8,6 @@ import { vertexDefaultModelId, vertexModels, ANTHROPIC_DEFAULT_MAX_TOKENS, - TOOL_PROTOCOL, VERTEX_1M_CONTEXT_MODEL_IDS, } from "@roo-code/types" import { safeJsonParse } from "@roo-code/core" @@ -19,7 +18,6 @@ import { ApiStream } from "../transform/stream" import { addCacheBreakpoints } from "../transform/caching/vertex" import { getModelParams } from "../transform/model-params" import { filterNonAnthropicBlocks } from "../transform/anthropic-filter" -import { resolveToolProtocol } from "../../utils/resolveToolProtocol" import { convertOpenAIToolsToAnthropic, convertOpenAIToolChoiceToAnthropic, @@ -77,15 +75,10 @@ export class AnthropicVertexHandler extends BaseProvider implements SingleComple // Filter out non-Anthropic blocks (reasoning, thoughtSignature, etc.) before sending to the API const sanitizedMessages = filterNonAnthropicBlocks(messages) - // Enable native tools using resolveToolProtocol (which checks model's defaultToolProtocol) - // This matches the approach used in AnthropicHandler - // Also exclude tools when tool_choice is "none" since that means "don't use tools" - const toolProtocol = resolveToolProtocol(this.options, info, metadata?.toolProtocol) + // Native-only tool calling: include tools whenever they are provided, + // unless tool_choice is explicitly "none". const shouldIncludeNativeTools = - metadata?.tools && - metadata.tools.length > 0 && - toolProtocol === TOOL_PROTOCOL.NATIVE && - metadata?.tool_choice !== "none" + metadata?.tools && metadata.tools.length > 0 && metadata?.tool_choice !== "none" const nativeToolParams = shouldIncludeNativeTools ? { diff --git a/src/api/providers/anthropic.ts b/src/api/providers/anthropic.ts index 4faf341d28f..4fe3d553a28 100644 --- a/src/api/providers/anthropic.ts +++ b/src/api/providers/anthropic.ts @@ -10,7 +10,6 @@ import { anthropicModels, ANTHROPIC_DEFAULT_MAX_TOKENS, ApiProviderError, - TOOL_PROTOCOL, } from "@roo-code/types" import { TelemetryService } from "@roo-code/telemetry" @@ -19,7 +18,6 @@ import type { ApiHandlerOptions } from "../../shared/api" import { ApiStream } from "../transform/stream" import { getModelParams } from "../transform/model-params" import { filterNonAnthropicBlocks } from "../transform/anthropic-filter" -import { resolveToolProtocol } from "../../utils/resolveToolProtocol" import { handleProviderError } from "./utils/error-handler" import { BaseProvider } from "./base-provider" @@ -74,17 +72,10 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa betas.push("context-1m-2025-08-07") } - // Enable native tools by default using resolveToolProtocol (which checks model's defaultToolProtocol) - // This matches OpenRouter's approach of always including tools when provided - // Also exclude tools when tool_choice is "none" since that means "don't use tools" - // IMPORTANT: Use metadata.toolProtocol if provided (task's locked protocol) for consistency - const model = this.getModel() - const toolProtocol = resolveToolProtocol(this.options, model.info, metadata?.toolProtocol) + // Native-only tool calling: include tools whenever they are provided, + // unless tool_choice is explicitly "none". const shouldIncludeNativeTools = - metadata?.tools && - metadata.tools.length > 0 && - toolProtocol === TOOL_PROTOCOL.NATIVE && - metadata?.tool_choice !== "none" + metadata?.tools && metadata.tools.length > 0 && metadata?.tool_choice !== "none" const nativeToolParams = shouldIncludeNativeTools ? { diff --git a/src/api/providers/base-openai-compatible-provider.ts b/src/api/providers/base-openai-compatible-provider.ts index a2a55cdc10f..2c51a55f1eb 100644 --- a/src/api/providers/base-openai-compatible-provider.ts +++ b/src/api/providers/base-openai-compatible-provider.ts @@ -4,7 +4,7 @@ import OpenAI from "openai" import type { ModelInfo } from "@roo-code/types" import { type ApiHandlerOptions, getModelMaxOutputTokens } from "../../shared/api" -import { XmlMatcher } from "../../utils/xml-matcher" +import { TagMatcher } from "../../utils/tag-matcher" import { ApiStream, ApiStreamUsageChunk } from "../transform/stream" import { convertToOpenAiMessages } from "../transform/openai-format" @@ -95,8 +95,8 @@ export abstract class BaseOpenAiCompatibleProvider stream_options: { include_usage: true }, ...(metadata?.tools && { tools: this.convertToolsForOpenAI(metadata.tools) }), ...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }), - ...(metadata?.toolProtocol === "native" && { - parallel_tool_calls: metadata.parallelToolCalls ?? false, + ...(metadata?.parallelToolCalls !== undefined && { + parallel_tool_calls: metadata.parallelToolCalls, }), } @@ -119,7 +119,7 @@ export abstract class BaseOpenAiCompatibleProvider ): ApiStream { const stream = await this.createStream(systemPrompt, messages, metadata) - const matcher = new XmlMatcher( + const matcher = new TagMatcher( "think", (chunk) => ({ diff --git a/src/api/providers/bedrock.ts b/src/api/providers/bedrock.ts index 761500750d0..07350be4230 100644 --- a/src/api/providers/bedrock.ts +++ b/src/api/providers/bedrock.ts @@ -359,15 +359,6 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH const modelConfig = this.getModel() const usePromptCache = Boolean(this.options.awsUsePromptCache && this.supportsAwsPromptCache(modelConfig)) - // Determine early if native tools should be used (needed for message conversion) - const supportsNativeTools = modelConfig.info.supportsNativeTools ?? false - const useNativeTools = - supportsNativeTools && - metadata?.tools && - metadata.tools.length > 0 && - metadata?.toolProtocol !== "xml" && - metadata?.tool_choice !== "none" - const conversationId = messages.length > 0 ? `conv_${messages[0].role}_${ @@ -383,7 +374,6 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH usePromptCache, modelConfig.info, conversationId, - useNativeTools, ) let additionalModelRequestFields: BedrockAdditionalModelFields | undefined @@ -424,6 +414,17 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH const is1MContextEnabled = BEDROCK_1M_CONTEXT_MODEL_IDS.includes(baseModelId as any) && this.options.awsBedrock1MContext + // Determine if service tier should be applied (checked later when building payload) + const useServiceTier = + this.options.awsBedrockServiceTier && BEDROCK_SERVICE_TIER_MODEL_IDS.includes(baseModelId as any) + if (useServiceTier) { + logger.info("Service tier specified for Bedrock request", { + ctx: "bedrock", + modelId: modelConfig.id, + serviceTier: this.options.awsBedrockServiceTier, + }) + } + // Add anthropic_beta headers for various features // Start with an empty array and add betas as needed const anthropicBetas: string[] = [] @@ -433,9 +434,9 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH anthropicBetas.push("context-1m-2025-08-07") } - // Add fine-grained tool streaming beta when native tools are used with Claude models + // Add fine-grained tool streaming beta for Claude models // This enables proper tool use streaming for Anthropic models on Bedrock - if (useNativeTools && baseModelId.includes("claude")) { + if (baseModelId.includes("claude")) { anthropicBetas.push("fine-grained-tool-streaming-2025-05-14") } @@ -447,20 +448,9 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH additionalModelRequestFields.anthropic_beta = anthropicBetas } - // Determine if service tier should be applied (checked later when building payload) - const useServiceTier = - this.options.awsBedrockServiceTier && BEDROCK_SERVICE_TIER_MODEL_IDS.includes(baseModelId as any) - if (useServiceTier) { - logger.info("Service tier specified for Bedrock request", { - ctx: "bedrock", - modelId: modelConfig.id, - serviceTier: this.options.awsBedrockServiceTier, - }) - } - // Build tool configuration if native tools are enabled let toolConfig: ToolConfiguration | undefined - if (useNativeTools && metadata?.tools) { + if (metadata?.tools?.length) { toolConfig = { tools: this.convertToolsForBedrock(metadata.tools), toolChoice: this.convertToolChoiceForBedrock(metadata.tool_choice), @@ -844,12 +834,9 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH usePromptCache: boolean = false, modelInfo?: any, conversationId?: string, // Optional conversation ID to track cache points across messages - useNativeTools: boolean = false, // Whether native tool calling is being used ): { system: SystemContentBlock[]; messages: Message[] } { // First convert messages using shared converter for proper image handling - const convertedMessages = sharedConverter(anthropicMessages as Anthropic.Messages.MessageParam[], { - useNativeTools, - }) + const convertedMessages = sharedConverter(anthropicMessages as Anthropic.Messages.MessageParam[]) // If prompt caching is disabled, return the converted messages directly if (!usePromptCache) { @@ -1360,8 +1347,6 @@ Please verify: 2. If using a provisioned model, check its throughput settings 3. Contact AWS support to request a quota increase if needed - - `, logLevel: "error", }, diff --git a/src/api/providers/cerebras.ts b/src/api/providers/cerebras.ts index 25e4f32f04d..d1b8184080f 100644 --- a/src/api/providers/cerebras.ts +++ b/src/api/providers/cerebras.ts @@ -6,7 +6,7 @@ import type { ApiHandlerOptions } from "../../shared/api" import { calculateApiCostOpenAI } from "../../shared/cost" import { ApiStream } from "../transform/stream" import { convertToOpenAiMessages } from "../transform/openai-format" -import { XmlMatcher } from "../../utils/xml-matcher" +import { TagMatcher } from "../../utils/tag-matcher" import type { ApiHandlerCreateMessageMetadata, SingleCompletionHandler } from "../index" import { BaseProvider } from "./base-provider" @@ -125,13 +125,8 @@ export class CerebrasHandler extends BaseProvider implements SingleCompletionHan ): ApiStream { const { id: model, info: modelInfo } = this.getModel() const max_tokens = modelInfo.maxTokens - const supportsNativeTools = modelInfo.supportsNativeTools ?? false const temperature = this.options.modelTemperature ?? CEREBRAS_DEFAULT_TEMPERATURE - // Check if we should use native tool calling - const useNativeTools = - supportsNativeTools && metadata?.tools && metadata.tools.length > 0 && metadata?.toolProtocol !== "xml" - // Convert Anthropic messages to OpenAI format (Cerebras is OpenAI-compatible) const openaiMessages = convertToOpenAiMessages(messages) @@ -149,9 +144,9 @@ export class CerebrasHandler extends BaseProvider implements SingleCompletionHan } : {}), // Native tool calling support - ...(useNativeTools && { tools: this.convertToolsForOpenAI(metadata.tools) }), - ...(useNativeTools && metadata.tool_choice && { tool_choice: metadata.tool_choice }), - ...(useNativeTools && { parallel_tool_calls: metadata?.parallelToolCalls ?? false }), + ...(metadata?.tools?.length && { tools: this.convertToolsForOpenAI(metadata?.tools) }), + ...(metadata?.tools?.length && metadata?.tool_choice && { tool_choice: metadata.tool_choice }), + ...(metadata?.tools?.length && { parallel_tool_calls: metadata?.parallelToolCalls ?? false }), } try { @@ -197,8 +192,8 @@ export class CerebrasHandler extends BaseProvider implements SingleCompletionHan throw new Error(t("common:errors.cerebras.noResponseBody")) } - // Initialize XmlMatcher to parse ... tags - const matcher = new XmlMatcher( + // Initialize TagMatcher to parse ... tags + const matcher = new TagMatcher( "think", (chunk) => ({ @@ -240,7 +235,7 @@ export class CerebrasHandler extends BaseProvider implements SingleCompletionHan if (delta?.content) { const content = delta.content - // Use XmlMatcher to parse ... tags + // Use TagMatcher to parse ... tags for (const chunk of matcher.update(content)) { yield chunk } diff --git a/src/api/providers/chutes.ts b/src/api/providers/chutes.ts index 78ac7e591f3..4a41506d3f8 100644 --- a/src/api/providers/chutes.ts +++ b/src/api/providers/chutes.ts @@ -4,7 +4,7 @@ import OpenAI from "openai" import type { ApiHandlerOptions } from "../../shared/api" import { getModelMaxOutputTokens } from "../../shared/api" -import { XmlMatcher } from "../../utils/xml-matcher" +import { TagMatcher } from "../../utils/tag-matcher" import { convertToR1Format } from "../transform/r1-format" import { convertToOpenAiMessages } from "../transform/openai-format" import { ApiStream } from "../transform/stream" @@ -72,7 +72,7 @@ export class ChutesHandler extends RouterProvider implements SingleCompletionHan messages: convertToR1Format([{ role: "user", content: systemPrompt }, ...messages]), }) - const matcher = new XmlMatcher( + const matcher = new TagMatcher( "think", (chunk) => ({ diff --git a/src/api/providers/claude-code.ts b/src/api/providers/claude-code.ts index f2bccc329c7..2629bcbaa30 100644 --- a/src/api/providers/claude-code.ts +++ b/src/api/providers/claude-code.ts @@ -147,10 +147,7 @@ export class ClaudeCodeHandler implements ApiHandler, SingleCompletionHandler { // Convert OpenAI tools to Anthropic format if provided and protocol is native // Exclude tools when tool_choice is "none" since that means "don't use tools" const shouldIncludeNativeTools = - metadata?.tools && - metadata.tools.length > 0 && - metadata?.toolProtocol !== "xml" && - metadata?.tool_choice !== "none" + metadata?.tools && metadata.tools.length > 0 && metadata?.tool_choice !== "none" const anthropicTools = shouldIncludeNativeTools ? convertOpenAIToolsToAnthropic(metadata.tools!) : undefined diff --git a/src/api/providers/deepinfra.ts b/src/api/providers/deepinfra.ts index 4dfad2689a9..398742bb113 100644 --- a/src/api/providers/deepinfra.ts +++ b/src/api/providers/deepinfra.ts @@ -65,11 +65,6 @@ export class DeepInfraHandler extends RouterProvider implements SingleCompletion prompt_cache_key = _metadata.taskId } - // Check if model supports native tools and tools are provided with native protocol - const supportsNativeTools = info.supportsNativeTools ?? false - const useNativeTools = - supportsNativeTools && _metadata?.tools && _metadata.tools.length > 0 && _metadata?.toolProtocol !== "xml" - const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = { model: modelId, messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)], @@ -77,9 +72,9 @@ export class DeepInfraHandler extends RouterProvider implements SingleCompletion stream_options: { include_usage: true }, reasoning_effort, prompt_cache_key, - ...(useNativeTools && { tools: this.convertToolsForOpenAI(_metadata.tools) }), - ...(useNativeTools && _metadata.tool_choice && { tool_choice: _metadata.tool_choice }), - ...(useNativeTools && { parallel_tool_calls: _metadata?.parallelToolCalls ?? false }), + ...(_metadata?.tools?.length ? { tools: this.convertToolsForOpenAI(_metadata.tools) } : {}), + ...(_metadata?.tools?.length && _metadata?.tool_choice ? { tool_choice: _metadata.tool_choice } : {}), + ...(_metadata?.tools?.length ? { parallel_tool_calls: _metadata?.parallelToolCalls ?? false } : {}), } as OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming if (this.supportsTemperature(modelId)) { diff --git a/src/api/providers/deepseek.ts b/src/api/providers/deepseek.ts index 4e5aef23a53..90d282f6d7d 100644 --- a/src/api/providers/deepseek.ts +++ b/src/api/providers/deepseek.ts @@ -72,8 +72,8 @@ export class DeepSeekHandler extends OpenAiHandler { ...(isThinkingModel && { thinking: { type: "enabled" } }), ...(metadata?.tools && { tools: this.convertToolsForOpenAI(metadata.tools) }), ...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }), - ...(metadata?.toolProtocol === "native" && { - parallel_tool_calls: metadata.parallelToolCalls ?? false, + ...(metadata?.parallelToolCalls !== undefined && { + parallel_tool_calls: metadata.parallelToolCalls, }), } diff --git a/src/api/providers/featherless.ts b/src/api/providers/featherless.ts index 3dcd0821b8c..6a94fce9835 100644 --- a/src/api/providers/featherless.ts +++ b/src/api/providers/featherless.ts @@ -8,7 +8,7 @@ import { Anthropic } from "@anthropic-ai/sdk" import OpenAI from "openai" import type { ApiHandlerOptions } from "../../shared/api" -import { XmlMatcher } from "../../utils/xml-matcher" +import { TagMatcher } from "../../utils/tag-matcher" import { convertToR1Format } from "../transform/r1-format" import { convertToOpenAiMessages } from "../transform/openai-format" import { ApiStream } from "../transform/stream" @@ -63,7 +63,7 @@ export class FeatherlessHandler extends BaseOpenAiCompatibleProvider ({ diff --git a/src/api/providers/fetchers/__tests__/chutes.spec.ts b/src/api/providers/fetchers/__tests__/chutes.spec.ts index 79ed0273837..009cf0493f2 100644 --- a/src/api/providers/fetchers/__tests__/chutes.spec.ts +++ b/src/api/providers/fetchers/__tests__/chutes.spec.ts @@ -51,7 +51,6 @@ describe("getChutesModels", () => { contextWindow: 128000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: false, inputPrice: 0, outputPrice: 0, description: "Chutes AI model: test/new-model", @@ -162,7 +161,7 @@ describe("getChutesModels", () => { expect(models["test/image-model"].supportsImages).toBe(true) }) - it("should detect native tool support from supported_features", async () => { + it("should accept supported_features containing tools", async () => { const mockResponse = { data: { data: [ @@ -184,10 +183,11 @@ describe("getChutesModels", () => { const models = await getChutesModels("test-api-key") - expect(models["test/tools-model"].supportsNativeTools).toBe(true) + expect(models["test/tools-model"]).toBeDefined() + expect(models["test/tools-model"].contextWindow).toBe(128000) }) - it("should not enable native tool support when tools is not in supported_features", async () => { + it("should accept supported_features without tools", async () => { const mockResponse = { data: { data: [ @@ -209,8 +209,8 @@ describe("getChutesModels", () => { const models = await getChutesModels("test-api-key") - expect(models["test/no-tools-model"].supportsNativeTools).toBe(false) - expect(models["test/no-tools-model"].defaultToolProtocol).toBeUndefined() + expect(models["test/no-tools-model"]).toBeDefined() + expect(models["test/no-tools-model"].contextWindow).toBe(128000) }) it("should skip empty objects in API response and still process valid models", async () => { @@ -336,7 +336,6 @@ describe("getChutesModels", () => { // Both valid models should be processed expect(models["test/valid-1"]).toBeDefined() expect(models["test/valid-2"]).toBeDefined() - expect(models["test/valid-2"].supportsNativeTools).toBe(true) consoleErrorSpy.mockRestore() }) diff --git a/src/api/providers/fetchers/__tests__/litellm.spec.ts b/src/api/providers/fetchers/__tests__/litellm.spec.ts index fe6424e673c..c05cda88398 100644 --- a/src/api/providers/fetchers/__tests__/litellm.spec.ts +++ b/src/api/providers/fetchers/__tests__/litellm.spec.ts @@ -222,7 +222,6 @@ describe("getLiteLLMModels", () => { contextWindow: 200000, supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 3, outputPrice: 15, cacheWritesPrice: undefined, @@ -234,7 +233,6 @@ describe("getLiteLLMModels", () => { contextWindow: 128000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: 10, outputPrice: 30, cacheWritesPrice: undefined, @@ -305,7 +303,6 @@ describe("getLiteLLMModels", () => { contextWindow: 200000, supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: undefined, outputPrice: undefined, cacheWritesPrice: undefined, @@ -318,7 +315,6 @@ describe("getLiteLLMModels", () => { contextWindow: 200000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: undefined, outputPrice: undefined, cacheWritesPrice: undefined, @@ -455,7 +451,6 @@ describe("getLiteLLMModels", () => { contextWindow: 200000, supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: undefined, outputPrice: undefined, cacheWritesPrice: undefined, @@ -468,7 +463,6 @@ describe("getLiteLLMModels", () => { contextWindow: 128000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: undefined, outputPrice: undefined, cacheWritesPrice: undefined, @@ -533,7 +527,6 @@ describe("getLiteLLMModels", () => { contextWindow: 200000, supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: undefined, outputPrice: undefined, cacheWritesPrice: undefined, @@ -546,7 +539,6 @@ describe("getLiteLLMModels", () => { contextWindow: 128000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: undefined, outputPrice: undefined, cacheWritesPrice: undefined, @@ -559,7 +551,6 @@ describe("getLiteLLMModels", () => { contextWindow: 128000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: undefined, outputPrice: undefined, cacheWritesPrice: undefined, @@ -673,7 +664,6 @@ describe("getLiteLLMModels", () => { contextWindow: 200000, supportsImages: true, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: undefined, outputPrice: undefined, cacheWritesPrice: undefined, @@ -687,7 +677,6 @@ describe("getLiteLLMModels", () => { contextWindow: 128000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: undefined, outputPrice: undefined, cacheWritesPrice: undefined, @@ -701,7 +690,6 @@ describe("getLiteLLMModels", () => { contextWindow: 100000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, inputPrice: undefined, outputPrice: undefined, cacheWritesPrice: undefined, diff --git a/src/api/providers/fetchers/__tests__/modelEndpointCache.spec.ts b/src/api/providers/fetchers/__tests__/modelEndpointCache.spec.ts index 966a6002d85..b5ff897ec49 100644 --- a/src/api/providers/fetchers/__tests__/modelEndpointCache.spec.ts +++ b/src/api/providers/fetchers/__tests__/modelEndpointCache.spec.ts @@ -15,14 +15,13 @@ describe("modelEndpointCache", () => { describe("getModelEndpoints", () => { it("should copy model-level capabilities from parent model to endpoints", async () => { - // Mock the parent model data with native tools support + // Mock the parent model data with capabilities const mockParentModels = { "anthropic/claude-sonnet-4": { maxTokens: 8192, contextWindow: 200000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, // Parent supports native tools supportsReasoningEffort: true, supportedParameters: ["max_tokens", "temperature", "reasoning"] as any, inputPrice: 3, @@ -39,7 +38,7 @@ describe("modelEndpointCache", () => { supportsPromptCache: true, inputPrice: 3, outputPrice: 15, - // Note: No supportsNativeTools, supportsReasoningEffort, or supportedParameters + // Note: No supportsReasoningEffort, or supportedParameters }, "amazon-bedrock": { maxTokens: 8192, @@ -61,11 +60,9 @@ describe("modelEndpointCache", () => { }) // Verify capabilities were copied from parent to ALL endpoints - expect(result.anthropic.supportsNativeTools).toBe(true) expect(result.anthropic.supportsReasoningEffort).toBe(true) expect(result.anthropic.supportedParameters).toEqual(["max_tokens", "temperature", "reasoning"]) - expect(result["amazon-bedrock"].supportsNativeTools).toBe(true) expect(result["amazon-bedrock"].supportsReasoningEffort).toBe(true) expect(result["amazon-bedrock"].supportedParameters).toEqual(["max_tokens", "temperature", "reasoning"]) }) @@ -76,7 +73,6 @@ describe("modelEndpointCache", () => { maxTokens: 1000, contextWindow: 10000, supportsPromptCache: false, - supportsNativeTools: true, supportedParameters: ["max_tokens", "temperature"] as any, }, } @@ -131,9 +127,9 @@ describe("modelEndpointCache", () => { endpoint: "anthropic", }) - // Should not crash, but capabilities will be undefined + // Should not crash, but copied capabilities will be undefined expect(result.anthropic).toBeDefined() - expect(result.anthropic.supportsNativeTools).toBeUndefined() + expect(result.anthropic.supportedParameters).toBeUndefined() }) it("should return empty object for non-openrouter providers", async () => { diff --git a/src/api/providers/fetchers/__tests__/ollama.test.ts b/src/api/providers/fetchers/__tests__/ollama.test.ts index fd4e2e80b86..59663bc4959 100644 --- a/src/api/providers/fetchers/__tests__/ollama.test.ts +++ b/src/api/providers/fetchers/__tests__/ollama.test.ts @@ -22,7 +22,6 @@ describe("Ollama Fetcher", () => { contextWindow: 40960, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, inputPrice: 0, outputPrice: 0, cacheWritesPrice: 0, @@ -47,7 +46,6 @@ describe("Ollama Fetcher", () => { contextWindow: 40960, supportsImages: false, supportsPromptCache: true, - supportsNativeTools: true, inputPrice: 0, outputPrice: 0, cacheWritesPrice: 0, @@ -77,7 +75,7 @@ describe("Ollama Fetcher", () => { const parsedModel = parseOllamaModel(modelDataWithTools as any) expect(parsedModel).not.toBeNull() - expect(parsedModel!.supportsNativeTools).toBe(true) + expect(parsedModel!.contextWindow).toBeGreaterThan(0) }) it("should return null when capabilities is undefined (no tool support)", () => { @@ -114,7 +112,7 @@ describe("Ollama Fetcher", () => { expect(parsedModel).not.toBeNull() expect(parsedModel!.supportsImages).toBe(true) - expect(parsedModel!.supportsNativeTools).toBe(true) + expect(parsedModel!.contextWindow).toBeGreaterThan(0) }) }) diff --git a/src/api/providers/fetchers/__tests__/openrouter.spec.ts b/src/api/providers/fetchers/__tests__/openrouter.spec.ts index cbe3f35c8bc..3bcd27716f2 100644 --- a/src/api/providers/fetchers/__tests__/openrouter.spec.ts +++ b/src/api/providers/fetchers/__tests__/openrouter.spec.ts @@ -28,9 +28,7 @@ describe("OpenRouter API", () => { description: expect.any(String), supportsReasoningBudget: false, supportsReasoningEffort: false, - supportsNativeTools: true, supportedParameters: ["max_tokens", "temperature", "reasoning", "include_reasoning"], - defaultToolProtocol: "native", }) expect(models["anthropic/claude-3.7-sonnet:thinking"]).toEqual({ @@ -46,9 +44,7 @@ describe("OpenRouter API", () => { supportsReasoningBudget: true, requiredReasoningBudget: true, supportsReasoningEffort: true, - supportsNativeTools: true, supportedParameters: ["max_tokens", "temperature", "reasoning", "include_reasoning"], - defaultToolProtocol: "native", }) expect(models["google/gemini-2.5-flash-preview-05-20"].maxTokens).toEqual(65535) @@ -136,7 +132,7 @@ describe("OpenRouter API", () => { cacheWritesPrice: 1.625, cacheReadsPrice: 0.31, supportsReasoningEffort: true, - supportsNativeTools: false, // Gemini doesn't support native tools via "tools" parameter + // Tool support is handled via metadata/tools at request time. supportedParameters: ["max_tokens", "temperature", "reasoning"], }, } as Record @@ -150,7 +146,6 @@ describe("OpenRouter API", () => { const parentModel = mockCachedModels["google/gemini-2.5-pro-preview"] if (parentModel) { for (const key of Object.keys(endpoints)) { - endpoints[key].supportsNativeTools = parentModel.supportsNativeTools endpoints[key].supportsReasoningEffort = parentModel.supportsReasoningEffort endpoints[key].supportedParameters = parentModel.supportedParameters } @@ -169,7 +164,6 @@ describe("OpenRouter API", () => { cacheReadsPrice: 0.31, description: undefined, supportsReasoningEffort: true, - supportsNativeTools: false, // Copied from parent model supportedParameters: ["max_tokens", "temperature", "reasoning"], }, "google-ai-studio": { @@ -184,7 +178,6 @@ describe("OpenRouter API", () => { cacheReadsPrice: 0.31, description: undefined, supportsReasoningEffort: true, - supportsNativeTools: false, // Copied from parent model supportedParameters: ["max_tokens", "temperature", "reasoning"], }, }) @@ -221,7 +214,7 @@ describe("OpenRouter API", () => { }, } - // Mock cached parent model with native tools support + // Mock cached parent model capabilities const mockCachedModels = { "anthropic/claude-sonnet-4": { maxTokens: 8192, @@ -234,7 +227,7 @@ describe("OpenRouter API", () => { cacheWritesPrice: 3.75, cacheReadsPrice: 0.3, supportsReasoningEffort: true, - supportsNativeTools: true, // Anthropic supports native tools + // Tool support is handled via metadata/tools at request time. supportedParameters: ["max_tokens", "temperature", "reasoning"], }, } as Record @@ -248,7 +241,6 @@ describe("OpenRouter API", () => { const parentModel = mockCachedModels["anthropic/claude-sonnet-4"] if (parentModel) { for (const key of Object.keys(endpoints)) { - endpoints[key].supportsNativeTools = parentModel.supportsNativeTools endpoints[key].supportsReasoningEffort = parentModel.supportsReasoningEffort endpoints[key].supportedParameters = parentModel.supportedParameters } @@ -266,7 +258,6 @@ describe("OpenRouter API", () => { description: undefined, supportsReasoningBudget: true, supportsReasoningEffort: true, - supportsNativeTools: true, // Copied from parent model supportedParameters: ["max_tokens", "temperature", "reasoning"], }) @@ -393,7 +384,7 @@ describe("OpenRouter API", () => { expect(imageResult.maxTokens).toBe(64000) }) - it("sets defaultToolProtocol to native when model supports native tools", () => { + it("treats supportedParameters containing tools as allowed", () => { const mockModel = { name: "Tools Model", description: "Model with native tool support", @@ -414,11 +405,10 @@ describe("OpenRouter API", () => { supportedParameters: ["tools", "max_tokens", "temperature"], }) - expect(resultWithTools.supportsNativeTools).toBe(true) - expect(resultWithTools.defaultToolProtocol).toBe("native") + expect(resultWithTools.supportedParameters).toContain("max_tokens") }) - it("does not set defaultToolProtocol when model does not support native tools", () => { + it("treats supportedParameters without tools as allowed", () => { const mockModel = { name: "No Tools Model", description: "Model without native tool support", @@ -439,8 +429,7 @@ describe("OpenRouter API", () => { supportedParameters: ["max_tokens", "temperature"], }) - expect(resultWithoutTools.supportsNativeTools).toBe(false) - expect(resultWithoutTools.defaultToolProtocol).toBeUndefined() + expect(resultWithoutTools.supportedParameters).toContain("max_tokens") }) }) }) diff --git a/src/api/providers/fetchers/__tests__/roo.spec.ts b/src/api/providers/fetchers/__tests__/roo.spec.ts index cd86be0b692..bb3b08b63fa 100644 --- a/src/api/providers/fetchers/__tests__/roo.spec.ts +++ b/src/api/providers/fetchers/__tests__/roo.spec.ts @@ -69,7 +69,6 @@ describe("getRooModels", () => { supportsImages: true, supportsReasoningEffort: true, requiredReasoningEffort: false, - supportsNativeTools: false, supportsPromptCache: true, inputPrice: 100, // 0.0001 * 1_000_000 outputPrice: 200, // 0.0002 * 1_000_000 @@ -78,7 +77,6 @@ describe("getRooModels", () => { description: "Fast coding model", deprecated: false, isFree: false, - defaultToolProtocol: "native", }, }) }) @@ -119,7 +117,6 @@ describe("getRooModels", () => { supportsImages: false, supportsReasoningEffort: true, requiredReasoningEffort: true, - supportsNativeTools: false, supportsPromptCache: false, inputPrice: 100, // 0.0001 * 1_000_000 outputPrice: 200, // 0.0002 * 1_000_000 @@ -129,7 +126,7 @@ describe("getRooModels", () => { deprecated: false, isFree: false, defaultTemperature: undefined, - defaultToolProtocol: "native", + isStealthModel: undefined, }) }) @@ -169,7 +166,6 @@ describe("getRooModels", () => { supportsImages: false, supportsReasoningEffort: false, requiredReasoningEffort: false, - supportsNativeTools: false, supportsPromptCache: false, inputPrice: 100, // 0.0001 * 1_000_000 outputPrice: 200, // 0.0002 * 1_000_000 @@ -179,7 +175,7 @@ describe("getRooModels", () => { deprecated: false, isFree: false, defaultTemperature: undefined, - defaultToolProtocol: "native", + isStealthModel: undefined, }) }) @@ -551,7 +547,7 @@ describe("getRooModels", () => { expect(models["test/model-no-temp"].defaultTemperature).toBeUndefined() }) - it("should set defaultToolProtocol to native when default-native-tools tag is present", async () => { + it("should include models when tool-use tags are present", async () => { const mockResponse = { object: "list", data: [ @@ -581,11 +577,10 @@ describe("getRooModels", () => { const models = await getRooModels(baseUrl, apiKey) - expect(models["test/native-tools-model"].supportsNativeTools).toBe(true) - expect(models["test/native-tools-model"].defaultToolProtocol).toBe("native") + expect(models["test/native-tools-model"]).toBeDefined() }) - it("should set defaultToolProtocol to native for all models regardless of tags", async () => { + it("handles models when tool tags are absent", async () => { const mockResponse = { object: "list", data: [ @@ -615,12 +610,10 @@ describe("getRooModels", () => { const models = await getRooModels(baseUrl, apiKey) - // All Roo provider models now default to native tool protocol - expect(models["test/model-without-tool-tags"].supportsNativeTools).toBe(false) - expect(models["test/model-without-tool-tags"].defaultToolProtocol).toBe("native") + expect(models["test/model-without-tool-tags"]).toBeDefined() }) - it("should set supportsNativeTools from tool-use tag and always set defaultToolProtocol to native", async () => { + it("handles models with tool-use tag", async () => { const mockResponse = { object: "list", data: [ @@ -650,9 +643,7 @@ describe("getRooModels", () => { const models = await getRooModels(baseUrl, apiKey) - // tool-use tag sets supportsNativeTools, and all models get defaultToolProtocol: native - expect(models["test/tool-use-model"].supportsNativeTools).toBe(true) - expect(models["test/tool-use-model"].defaultToolProtocol).toBe("native") + expect(models["test/tool-use-model"]).toBeDefined() }) it("should detect stealth mode from tags", async () => { diff --git a/src/api/providers/fetchers/__tests__/vercel-ai-gateway.spec.ts b/src/api/providers/fetchers/__tests__/vercel-ai-gateway.spec.ts index 5c33116e5c6..3a4a234de96 100644 --- a/src/api/providers/fetchers/__tests__/vercel-ai-gateway.spec.ts +++ b/src/api/providers/fetchers/__tests__/vercel-ai-gateway.spec.ts @@ -173,7 +173,6 @@ describe("Vercel AI Gateway Fetchers", () => { maxTokens: 8000, contextWindow: 100000, supportsImages: false, - supportsNativeTools: true, supportsPromptCache: false, inputPrice: 2500000, outputPrice: 10000000, diff --git a/src/api/providers/fetchers/__tests__/versionedSettings.spec.ts b/src/api/providers/fetchers/__tests__/versionedSettings.spec.ts index 9422c012678..fcde78b94a0 100644 --- a/src/api/providers/fetchers/__tests__/versionedSettings.spec.ts +++ b/src/api/providers/fetchers/__tests__/versionedSettings.spec.ts @@ -197,14 +197,14 @@ describe("versionedSettings", () => { it("should handle versioned boolean values", () => { const versionedSettings: VersionedSettings = { "3.36.0": { - supportsNativeTools: true, + supportsReasoningEffort: true, }, } const resolved = resolveVersionedSettings(versionedSettings, currentVersion) expect(resolved).toEqual({ - supportsNativeTools: true, + supportsReasoningEffort: true, }) }) diff --git a/src/api/providers/fetchers/chutes.ts b/src/api/providers/fetchers/chutes.ts index 247d8f3c552..d79a2c80b08 100644 --- a/src/api/providers/fetchers/chutes.ts +++ b/src/api/providers/fetchers/chutes.ts @@ -57,8 +57,10 @@ export async function getChutesModels(apiKey?: string): Promise 0 && isNativeProtocol - // For Gemini models with native protocol: inject fake reasoning.encrypted block for tool calls // This is required when switching from other models to Gemini to satisfy API validation. // Gemini 3 models validate thought signatures for function calls, and when conversation @@ -202,7 +193,7 @@ export class LiteLLMHandler extends RouterProvider implements SingleCompletionHa // signatures. The "skip_thought_signature_validator" value bypasses this validation. const isGemini = this.isGeminiModel(modelId) let processedMessages = enhancedMessages - if (isNativeProtocol && isGemini) { + if (isGemini) { processedMessages = this.injectThoughtSignatureForGemini(enhancedMessages) } @@ -213,8 +204,8 @@ export class LiteLLMHandler extends RouterProvider implements SingleCompletionHa stream_options: { include_usage: true, }, - ...(useNativeTools && { tools: this.convertToolsForOpenAI(metadata.tools) }), - ...(useNativeTools && metadata.tool_choice && { tool_choice: metadata.tool_choice }), + ...(metadata?.tools?.length ? { tools: this.convertToolsForOpenAI(metadata.tools) } : {}), + ...(metadata?.tools?.length && metadata?.tool_choice ? { tool_choice: metadata.tool_choice } : {}), } // GPT-5 models require max_completion_tokens instead of the deprecated max_tokens parameter diff --git a/src/api/providers/lm-studio.ts b/src/api/providers/lm-studio.ts index 102c108dcee..8fb1169f3dc 100644 --- a/src/api/providers/lm-studio.ts +++ b/src/api/providers/lm-studio.ts @@ -7,7 +7,7 @@ import { type ModelInfo, openAiModelInfoSaneDefaults, LMSTUDIO_DEFAULT_TEMPERATU import type { ApiHandlerOptions } from "../../shared/api" import { NativeToolCallParser } from "../../core/assistant-message/NativeToolCallParser" -import { XmlMatcher } from "../../utils/xml-matcher" +import { TagMatcher } from "../../utils/tag-matcher" import { convertToOpenAiMessages } from "../transform/openai-format" import { ApiStream } from "../transform/stream" @@ -47,9 +47,6 @@ export class LmStudioHandler extends BaseProvider implements SingleCompletionHan ...convertToOpenAiMessages(messages), ] - // LM Studio always supports native tools (https://lmstudio.ai/docs/developer/core/tools) - const useNativeTools = metadata?.tools && metadata.tools.length > 0 && metadata?.toolProtocol !== "xml" - // ------------------------- // Track token usage // ------------------------- @@ -91,9 +88,9 @@ export class LmStudioHandler extends BaseProvider implements SingleCompletionHan messages: openAiMessages, temperature: this.options.modelTemperature ?? LMSTUDIO_DEFAULT_TEMPERATURE, stream: true, - ...(useNativeTools && { tools: this.convertToolsForOpenAI(metadata.tools) }), - ...(useNativeTools && metadata.tool_choice && { tool_choice: metadata.tool_choice }), - ...(useNativeTools && { parallel_tool_calls: metadata?.parallelToolCalls ?? false }), + ...(metadata?.tools?.length && { tools: this.convertToolsForOpenAI(metadata.tools) }), + ...(metadata?.tools?.length && metadata.tool_choice && { tool_choice: metadata.tool_choice }), + ...(metadata?.tools?.length && { parallel_tool_calls: metadata?.parallelToolCalls ?? false }), } if (this.options.lmStudioSpeculativeDecodingEnabled && this.options.lmStudioDraftModelId) { @@ -107,7 +104,7 @@ export class LmStudioHandler extends BaseProvider implements SingleCompletionHan throw handleOpenAIError(error, this.providerName) } - const matcher = new XmlMatcher( + const matcher = new TagMatcher( "think", (chunk) => ({ diff --git a/src/api/providers/minimax.ts b/src/api/providers/minimax.ts index a7cea478ed0..6f1f2588249 100644 --- a/src/api/providers/minimax.ts +++ b/src/api/providers/minimax.ts @@ -112,8 +112,7 @@ export class MiniMaxHandler extends BaseProvider implements SingleCompletionHand } // Add tool support if provided - convert OpenAI format to Anthropic format - // Only include native tools when toolProtocol is not 'xml' - if (metadata?.tools && metadata.tools.length > 0 && metadata?.toolProtocol !== "xml") { + if (metadata?.tools && metadata.tools.length > 0) { requestParams.tools = convertOpenAIToolsToAnthropic(metadata.tools) // Only add tool_choice if tools are present diff --git a/src/api/providers/mistral.ts b/src/api/providers/mistral.ts index 95739cdcf73..5f19781f21a 100644 --- a/src/api/providers/mistral.ts +++ b/src/api/providers/mistral.ts @@ -94,9 +94,8 @@ export class MistralHandler extends BaseProvider implements SingleCompletionHand temperature, } - // Add tools if provided and toolProtocol is not 'xml' and model supports native tools - const supportsNativeTools = info.supportsNativeTools ?? false - if (metadata?.tools && metadata.tools.length > 0 && metadata?.toolProtocol !== "xml" && supportsNativeTools) { + // Native-only tool calling: include tools whenever they are provided. + if (metadata?.tools && metadata.tools.length > 0) { requestOptions.tools = this.convertToolsForMistral(metadata.tools) // Always use "any" to require tool use requestOptions.toolChoice = "any" diff --git a/src/api/providers/native-ollama.ts b/src/api/providers/native-ollama.ts index f3271d65556..a654f059fde 100644 --- a/src/api/providers/native-ollama.ts +++ b/src/api/providers/native-ollama.ts @@ -6,7 +6,7 @@ import { ApiStream } from "../transform/stream" import { BaseProvider } from "./base-provider" import type { ApiHandlerOptions } from "../../shared/api" import { getOllamaModels } from "./fetchers/ollama" -import { XmlMatcher } from "../../utils/xml-matcher" +import { TagMatcher } from "../../utils/tag-matcher" import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index" interface OllamaChatOptions { @@ -206,7 +206,7 @@ export class NativeOllamaHandler extends BaseProvider implements SingleCompletio metadata?: ApiHandlerCreateMessageMetadata, ): ApiStream { const client = this.ensureClient() - const { id: modelId, info: modelInfo } = await this.fetchModel() + const { id: modelId } = await this.fetchModel() const useR1Format = modelId.toLowerCase().includes("deepseek-r1") const ollamaMessages: Message[] = [ @@ -214,7 +214,7 @@ export class NativeOllamaHandler extends BaseProvider implements SingleCompletio ...convertToOllamaMessages(messages), ] - const matcher = new XmlMatcher( + const matcher = new TagMatcher( "think", (chunk) => ({ @@ -223,11 +223,6 @@ export class NativeOllamaHandler extends BaseProvider implements SingleCompletio }) as const, ) - // Check if we should use native tool calling - const supportsNativeTools = modelInfo.supportsNativeTools ?? false - const useNativeTools = - supportsNativeTools && metadata?.tools && metadata.tools.length > 0 && metadata?.toolProtocol !== "xml" - try { // Build options object conditionally const chatOptions: OllamaChatOptions = { @@ -245,8 +240,7 @@ export class NativeOllamaHandler extends BaseProvider implements SingleCompletio messages: ollamaMessages, stream: true, options: chatOptions, - // Native tool calling support - ...(useNativeTools && { tools: this.convertToolsToOllama(metadata.tools) }), + ...(metadata?.tools?.length ? { tools: this.convertToolsToOllama(metadata.tools) } : {}), }) let totalInputTokens = 0 diff --git a/src/api/providers/openai-codex.ts b/src/api/providers/openai-codex.ts index 1600381f599..bfc13d1f328 100644 --- a/src/api/providers/openai-codex.ts +++ b/src/api/providers/openai-codex.ts @@ -325,9 +325,9 @@ export class OpenAiCodexHandler extends BaseProvider implements SingleCompletion ...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }), } - // For native tool protocol, control parallel tool calls - if (metadata?.toolProtocol === "native") { - body.parallel_tool_calls = metadata.parallelToolCalls ?? false + // Tool calling is native-only. If the caller sets parallelToolCalls, pass it through. + if (metadata?.parallelToolCalls !== undefined) { + body.parallel_tool_calls = metadata.parallelToolCalls } return body diff --git a/src/api/providers/openai-native.ts b/src/api/providers/openai-native.ts index 61db7dd20d0..528d644876f 100644 --- a/src/api/providers/openai-native.ts +++ b/src/api/providers/openai-native.ts @@ -382,11 +382,8 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio ...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }), } - // For native tool protocol, control parallel tool calls based on the metadata flag. - // When parallelToolCalls is true, allow parallel tool calls (OpenAI's parallel_tool_calls=true). - // When false (default), explicitly disable parallel tool calls (false). - // For XML or when protocol is unset, omit the field entirely so the API default applies. - if (metadata?.toolProtocol === "native") { + // Tool calling is native-only. When tools are provided, explicitly control parallel tool calls. + if (metadata?.tools) { body.parallel_tool_calls = metadata.parallelToolCalls ?? false } diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index 9d632fbdf4c..857c33c2e65 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -6,14 +6,13 @@ import { type ModelInfo, azureOpenAiDefaultApiVersion, openAiModelInfoSaneDefaults, - NATIVE_TOOL_DEFAULTS, DEEP_SEEK_DEFAULT_TEMPERATURE, OPENAI_AZURE_AI_INFERENCE_PATH, } from "@roo-code/types" import type { ApiHandlerOptions } from "../../shared/api" -import { XmlMatcher } from "../../utils/xml-matcher" +import { TagMatcher } from "../../utils/tag-matcher" import { convertToOpenAiMessages } from "../transform/openai-format" import { convertToR1Format } from "../transform/r1-format" @@ -162,10 +161,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl ...(reasoning && reasoning), ...(metadata?.tools && { tools: this.convertToolsForOpenAI(metadata.tools) }), ...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }), - ...(metadata?.toolProtocol === "native" && - metadata.parallelToolCalls === true && { - parallel_tool_calls: true, - }), + ...(metadata?.parallelToolCalls === true && { parallel_tool_calls: true }), } // Add max_tokens if needed @@ -181,7 +177,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl throw handleOpenAIError(error, this.providerName) } - const matcher = new XmlMatcher( + const matcher = new TagMatcher( "think", (chunk) => ({ @@ -232,10 +228,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl : [systemMessage, ...convertToOpenAiMessages(messages)], ...(metadata?.tools && { tools: this.convertToolsForOpenAI(metadata.tools) }), ...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }), - ...(metadata?.toolProtocol === "native" && - metadata.parallelToolCalls === true && { - parallel_tool_calls: true, - }), + ...(metadata?.parallelToolCalls === true && { parallel_tool_calls: true }), } // Add max_tokens if needed @@ -287,13 +280,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl override getModel() { const id = this.options.openAiModelId ?? "" - // Ensure OpenAI-compatible models default to supporting native tool calling. - // This is required for [`Task.attemptApiRequest()`](src/core/task/Task.ts:3817) to - // include tool definitions in the request. - const info: ModelInfo = { - ...NATIVE_TOOL_DEFAULTS, - ...(this.options.openAiCustomModelInfo ?? openAiModelInfoSaneDefaults), - } + const info: ModelInfo = this.options.openAiCustomModelInfo ?? openAiModelInfoSaneDefaults const params = getModelParams({ format: "openai", modelId: id, model: info, settings: this.options }) return { id, info, ...params } } @@ -359,10 +346,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl temperature: undefined, ...(metadata?.tools && { tools: this.convertToolsForOpenAI(metadata.tools) }), ...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }), - ...(metadata?.toolProtocol === "native" && - metadata.parallelToolCalls === true && { - parallel_tool_calls: true, - }), + ...(metadata?.parallelToolCalls === true && { parallel_tool_calls: true }), } // O3 family models do not support the deprecated max_tokens parameter @@ -395,10 +379,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl temperature: undefined, ...(metadata?.tools && { tools: this.convertToolsForOpenAI(metadata.tools) }), ...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }), - ...(metadata?.toolProtocol === "native" && - metadata.parallelToolCalls === true && { - parallel_tool_calls: true, - }), + ...(metadata?.parallelToolCalls === true && { parallel_tool_calls: true }), } // O3 family models do not support the deprecated max_tokens parameter diff --git a/src/api/providers/openrouter.ts b/src/api/providers/openrouter.ts index 902b2f646ba..f81352d1952 100644 --- a/src/api/providers/openrouter.ts +++ b/src/api/providers/openrouter.ts @@ -23,8 +23,7 @@ import { consolidateReasoningDetails, } from "../transform/openai-format" import { normalizeMistralToolCallId } from "../transform/mistral-format" -import { resolveToolProtocol } from "../../utils/resolveToolProtocol" -import { TOOL_PROTOCOL } from "@roo-code/types" +// Tool calling is native-only. import { ApiStreamChunk } from "../transform/stream" import { convertToR1Format } from "../transform/r1-format" import { addCacheBreakpoints as addAnthropicCacheBreakpoints } from "../transform/caching/anthropic" @@ -249,10 +248,7 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH openAiMessages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages]) } - // Process reasoning_details when switching models to Gemini for native tool call compatibility - // IMPORTANT: Use metadata.toolProtocol if provided (task's locked protocol) for consistency - const toolProtocol = resolveToolProtocol(this.options, model.info, metadata?.toolProtocol) - const isNativeProtocol = toolProtocol === TOOL_PROTOCOL.NATIVE + // Process reasoning_details when switching models to Gemini. const isGemini = modelId.startsWith("google/gemini") // For Gemini models with native protocol: @@ -267,7 +263,7 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH // - Set `data` to "skip_thought_signature_validator" to bypass signature validation // - Set `index` to 0 // See: https://github.com/cline/cline/issues/8214 - if (isNativeProtocol && isGemini) { + if (isGemini) { // Step 1: Sanitize messages - filter out tool calls with missing/mismatched reasoning_details openAiMessages = sanitizeGeminiMessages(openAiMessages, modelId) diff --git a/src/api/providers/qwen-code.ts b/src/api/providers/qwen-code.ts index 8f26273ebaf..6751354bed4 100644 --- a/src/api/providers/qwen-code.ts +++ b/src/api/providers/qwen-code.ts @@ -212,11 +212,6 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan const client = this.ensureClient() const model = this.getModel() - // Check if model supports native tools and tools are provided with native protocol - const supportsNativeTools = model.info.supportsNativeTools ?? false - const useNativeTools = - supportsNativeTools && metadata?.tools && metadata.tools.length > 0 && metadata?.toolProtocol !== "xml" - const systemMessage: OpenAI.Chat.ChatCompletionSystemMessageParam = { role: "system", content: systemPrompt, @@ -231,9 +226,9 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan stream: true, stream_options: { include_usage: true }, max_completion_tokens: model.info.maxTokens, - ...(useNativeTools && { tools: this.convertToolsForOpenAI(metadata.tools) }), - ...(useNativeTools && metadata.tool_choice && { tool_choice: metadata.tool_choice }), - ...(useNativeTools && { parallel_tool_calls: metadata?.parallelToolCalls ?? false }), + ...(metadata?.tools?.length ? { tools: this.convertToolsForOpenAI(metadata.tools) } : {}), + ...(metadata?.tools?.length && metadata?.tool_choice ? { tool_choice: metadata.tool_choice } : {}), + ...(metadata?.tools?.length ? { parallel_tool_calls: metadata?.parallelToolCalls ?? false } : {}), } const stream = await this.callApiWithRetry(() => client.chat.completions.create(requestOptions)) diff --git a/src/api/providers/requesty.ts b/src/api/providers/requesty.ts index eb05bfd0a14..4e8b75fb7a6 100644 --- a/src/api/providers/requesty.ts +++ b/src/api/providers/requesty.ts @@ -1,17 +1,9 @@ import { Anthropic } from "@anthropic-ai/sdk" import OpenAI from "openai" -import { - type ModelInfo, - type ModelRecord, - requestyDefaultModelId, - requestyDefaultModelInfo, - TOOL_PROTOCOL, - NATIVE_TOOL_DEFAULTS, -} from "@roo-code/types" +import { type ModelInfo, type ModelRecord, requestyDefaultModelId, requestyDefaultModelInfo } from "@roo-code/types" import type { ApiHandlerOptions } from "../../shared/api" -import { resolveToolProtocol } from "../../utils/resolveToolProtocol" import { calculateApiCostOpenAI } from "../../shared/cost" import { convertToOpenAiMessages } from "../transform/openai-format" @@ -87,10 +79,7 @@ export class RequestyHandler extends BaseProvider implements SingleCompletionHan override getModel() { const id = this.options.requestyModelId ?? requestyDefaultModelId const cachedInfo = this.models[id] ?? requestyDefaultModelInfo - - // Merge native tool defaults for cached models that may lack these fields - // The order ensures that cached values (if present) override the defaults - let info: ModelInfo = { ...NATIVE_TOOL_DEFAULTS, ...cachedInfo } + let info: ModelInfo = cachedInfo // Apply tool preferences for models accessed through routers (OpenAI, Gemini) info = applyRouterToolPreferences(id, info) @@ -149,11 +138,6 @@ export class RequestyHandler extends BaseProvider implements SingleCompletionHan ? (reasoning_effort as OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming["reasoning_effort"]) : undefined - // Check if native tool protocol is enabled - // IMPORTANT: Use metadata.toolProtocol if provided (task's locked protocol) for consistency - const toolProtocol = resolveToolProtocol(this.options, info, metadata?.toolProtocol) - const useNativeTools = toolProtocol === TOOL_PROTOCOL.NATIVE - const completionParams: RequestyChatCompletionParamsStreaming = { messages: openAiMessages, model, @@ -164,8 +148,8 @@ export class RequestyHandler extends BaseProvider implements SingleCompletionHan stream: true, stream_options: { include_usage: true }, requesty: { trace_id: metadata?.taskId, extra: { mode: metadata?.mode } }, - ...(useNativeTools && metadata?.tools && { tools: this.convertToolsForOpenAI(metadata.tools) }), - ...(useNativeTools && metadata?.tool_choice && { tool_choice: metadata.tool_choice }), + ...(metadata?.tools && { tools: this.convertToolsForOpenAI(metadata.tools) }), + ...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }), } let stream diff --git a/src/api/providers/roo.ts b/src/api/providers/roo.ts index 752ad938ef5..cb38d478cf5 100644 --- a/src/api/providers/roo.ts +++ b/src/api/providers/roo.ts @@ -375,7 +375,6 @@ export class RooHandler extends BaseOpenAiCompatibleProvider { supportsImages: false, supportsReasoningEffort: false, supportsPromptCache: true, - supportsNativeTools: false, inputPrice: 0, outputPrice: 0, isFree: false, diff --git a/src/api/providers/router-provider.ts b/src/api/providers/router-provider.ts index 4721f21666d..09b102d5b25 100644 --- a/src/api/providers/router-provider.ts +++ b/src/api/providers/router-provider.ts @@ -1,6 +1,6 @@ import OpenAI from "openai" -import { type ModelInfo, type ModelRecord, NATIVE_TOOL_DEFAULTS } from "@roo-code/types" +import { type ModelInfo, type ModelRecord } from "@roo-code/types" import { ApiHandlerOptions, RouterName } from "../../shared/api" @@ -64,9 +64,8 @@ export abstract class RouterProvider extends BaseProvider { const id = this.modelId ?? this.defaultModelId // First check instance models (populated by fetchModel) - // Merge native tool defaults for cached models that may lack these fields if (this.models[id]) { - return { id, info: { ...NATIVE_TOOL_DEFAULTS, ...this.models[id] } } + return { id, info: this.models[id] } } // Fall back to global cache (synchronous disk/memory cache) @@ -75,7 +74,7 @@ export abstract class RouterProvider extends BaseProvider { if (cachedModels?.[id]) { // Also populate instance models for future calls this.models = cachedModels - return { id, info: { ...NATIVE_TOOL_DEFAULTS, ...cachedModels[id] } } + return { id, info: cachedModels[id] } } // Last resort: return default model diff --git a/src/api/providers/unbound.ts b/src/api/providers/unbound.ts index 667dcc60839..0e22883c47a 100644 --- a/src/api/providers/unbound.ts +++ b/src/api/providers/unbound.ts @@ -108,11 +108,6 @@ export class UnboundHandler extends RouterProvider implements SingleCompletionHa maxTokens = info.maxTokens ?? undefined } - // Check if model supports native tools and tools are provided with native protocol - const supportsNativeTools = info.supportsNativeTools ?? false - const useNativeTools = - supportsNativeTools && metadata?.tools && metadata.tools.length > 0 && metadata?.toolProtocol !== "xml" - const requestOptions: UnboundChatCompletionCreateParamsStreaming = { model: modelId.split("/")[1], max_tokens: maxTokens, @@ -124,9 +119,9 @@ export class UnboundHandler extends RouterProvider implements SingleCompletionHa taskId: metadata?.taskId, mode: metadata?.mode, }, - ...(useNativeTools && { tools: this.convertToolsForOpenAI(metadata.tools) }), - ...(useNativeTools && metadata.tool_choice && { tool_choice: metadata.tool_choice }), - ...(useNativeTools && { parallel_tool_calls: metadata?.parallelToolCalls ?? false }), + ...(metadata?.tools?.length ? { tools: this.convertToolsForOpenAI(metadata.tools) } : {}), + ...(metadata?.tools?.length && metadata?.tool_choice ? { tool_choice: metadata.tool_choice } : {}), + ...(metadata?.tools?.length ? { parallel_tool_calls: metadata?.parallelToolCalls ?? false } : {}), } if (this.supportsTemperature(modelId)) { diff --git a/src/api/providers/vercel-ai-gateway.ts b/src/api/providers/vercel-ai-gateway.ts index 96863ac1ea9..0441e3c7ec8 100644 --- a/src/api/providers/vercel-ai-gateway.ts +++ b/src/api/providers/vercel-ai-gateway.ts @@ -63,8 +63,8 @@ export class VercelAiGatewayHandler extends RouterProvider implements SingleComp stream_options: { include_usage: true }, ...(metadata?.tools && { tools: this.convertToolsForOpenAI(metadata.tools) }), ...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }), - ...(metadata?.toolProtocol === "native" && { - parallel_tool_calls: metadata.parallelToolCalls ?? false, + ...(metadata?.parallelToolCalls !== undefined && { + parallel_tool_calls: metadata.parallelToolCalls, }), } diff --git a/src/api/providers/vscode-lm.ts b/src/api/providers/vscode-lm.ts index 5c598ccd012..4cc3e09e50a 100644 --- a/src/api/providers/vscode-lm.ts +++ b/src/api/providers/vscode-lm.ts @@ -381,17 +381,14 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan // Accumulate the text and count at the end of the stream to reduce token counting overhead. let accumulatedText: string = "" - // Determine if we're using native tool protocol - const useNativeTools = metadata?.toolProtocol === "native" && metadata?.tools && metadata.tools.length > 0 - try { // Create the response stream with required options const requestOptions: vscode.LanguageModelChatRequestOptions = { justification: `Roo Code would like to use '${client.name}' from '${client.vendor}', Click 'Allow' to proceed.`, } - // Add tools to request options when using native tool protocol - if (useNativeTools && metadata?.tools) { + // Add tools to request options when provided. + if (metadata?.tools?.length) { requestOptions.tools = convertToVsCodeLmTools(metadata.tools) } @@ -441,8 +438,8 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan inputSize: JSON.stringify(chunk.input).length, }) - // Yield native tool_call chunk when using native tool protocol - if (useNativeTools) { + // Yield native tool_call chunk when tools are provided + if (metadata?.tools?.length) { const argumentsString = JSON.stringify(chunk.input) accumulatedText += argumentsString yield { @@ -451,22 +448,6 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan name: chunk.name, arguments: argumentsString, } - } else { - // Fallback: Convert tool calls to text format for XML tool protocol - const toolCall = { - type: "tool_call", - name: chunk.name, - arguments: chunk.input, - callId: chunk.callId, - } - - const toolCallText = JSON.stringify(toolCall) - accumulatedText += toolCallText - - yield { - type: "text", - text: toolCallText, - } } } catch (error) { console.error("Roo Code : Failed to process tool call:", error) @@ -550,8 +531,6 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan : openAiModelInfoSaneDefaults.contextWindow, supportsImages: false, // VSCode Language Model API currently doesn't support image inputs supportsPromptCache: true, - supportsNativeTools: true, // VSCode Language Model API supports native tool calling - defaultToolProtocol: "native", // Use native tool protocol by default inputPrice: 0, outputPrice: 0, description: `VSCode Language Model: ${modelId}`, @@ -571,8 +550,6 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan id: fallbackId, info: { ...openAiModelInfoSaneDefaults, - supportsNativeTools: true, // VSCode Language Model API supports native tool calling - defaultToolProtocol: "native", // Use native tool protocol by default description: `VSCode Language Model (Fallback): ${fallbackId}`, }, } diff --git a/src/api/providers/xai.ts b/src/api/providers/xai.ts index a1377a1317a..176afa46d98 100644 --- a/src/api/providers/xai.ts +++ b/src/api/providers/xai.ts @@ -54,11 +54,6 @@ export class XAIHandler extends BaseProvider implements SingleCompletionHandler ): ApiStream { const { id: modelId, info: modelInfo, reasoning } = this.getModel() - // Check if model supports native tools and tools are provided with native protocol - const supportsNativeTools = modelInfo.supportsNativeTools ?? false - const useNativeTools = - supportsNativeTools && metadata?.tools && metadata.tools.length > 0 && metadata?.toolProtocol !== "xml" - // Use the OpenAI-compatible API. const requestOptions = { model: modelId, @@ -71,9 +66,9 @@ export class XAIHandler extends BaseProvider implements SingleCompletionHandler stream: true as const, stream_options: { include_usage: true }, ...(reasoning && reasoning), - ...(useNativeTools && { tools: this.convertToolsForOpenAI(metadata.tools) }), - ...(useNativeTools && metadata.tool_choice && { tool_choice: metadata.tool_choice }), - ...(useNativeTools && { parallel_tool_calls: metadata?.parallelToolCalls ?? false }), + ...(metadata?.tools?.length ? { tools: this.convertToolsForOpenAI(metadata.tools) } : {}), + ...(metadata?.tools?.length && metadata?.tool_choice ? { tool_choice: metadata.tool_choice } : {}), + ...(metadata?.tools?.length ? { parallel_tool_calls: metadata?.parallelToolCalls ?? false } : {}), } let stream diff --git a/src/api/providers/zai.ts b/src/api/providers/zai.ts index c7bf6d635e8..779da4a9487 100644 --- a/src/api/providers/zai.ts +++ b/src/api/providers/zai.ts @@ -103,8 +103,8 @@ export class ZAiHandler extends BaseOpenAiCompatibleProvider { thinking: useReasoning ? { type: "enabled" } : { type: "disabled" }, ...(metadata?.tools && { tools: this.convertToolsForOpenAI(metadata.tools) }), ...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }), - ...(metadata?.toolProtocol === "native" && { - parallel_tool_calls: metadata.parallelToolCalls ?? false, + ...(metadata?.parallelToolCalls !== undefined && { + parallel_tool_calls: metadata.parallelToolCalls, }), } diff --git a/src/api/transform/__tests__/bedrock-converse-format.spec.ts b/src/api/transform/__tests__/bedrock-converse-format.spec.ts index 7daf186f478..ac29a88c369 100644 --- a/src/api/transform/__tests__/bedrock-converse-format.spec.ts +++ b/src/api/transform/__tests__/bedrock-converse-format.spec.ts @@ -67,7 +67,7 @@ describe("convertToBedrockConverseMessages", () => { } }) - it("converts tool use messages correctly (default XML format)", () => { + it("converts tool use messages correctly (native tools format; default)", () => { const messages: Anthropic.Messages.MessageParam[] = [ { role: "assistant", @@ -84,7 +84,6 @@ describe("convertToBedrockConverseMessages", () => { }, ] - // Default behavior (useNativeTools: false) converts tool_use to XML text format const result = convertToBedrockConverseMessages(messages) if (!result[0] || !result[0].content) { @@ -93,13 +92,15 @@ describe("convertToBedrockConverseMessages", () => { } expect(result[0].role).toBe("assistant") - const textBlock = result[0].content[0] as ContentBlock - if ("text" in textBlock) { - expect(textBlock.text).toContain("") - expect(textBlock.text).toContain("read_file") - expect(textBlock.text).toContain("test.txt") + const toolBlock = result[0].content[0] as ContentBlock + if ("toolUse" in toolBlock && toolBlock.toolUse) { + expect(toolBlock.toolUse).toEqual({ + toolUseId: "test-id", + name: "read_file", + input: { path: "test.txt" }, + }) } else { - expect.fail("Expected text block with XML content not found") + expect.fail("Expected tool use block not found") } }) @@ -120,8 +121,7 @@ describe("convertToBedrockConverseMessages", () => { }, ] - // With useNativeTools: true, keeps tool_use as native format - const result = convertToBedrockConverseMessages(messages, { useNativeTools: true }) + const result = convertToBedrockConverseMessages(messages) if (!result[0] || !result[0].content) { expect.fail("Expected result to have content") @@ -141,7 +141,7 @@ describe("convertToBedrockConverseMessages", () => { } }) - it("converts tool result messages to XML text format (default, useNativeTools: false)", () => { + it("converts tool result messages to native format (default)", () => { const messages: Anthropic.Messages.MessageParam[] = [ { role: "user", @@ -155,8 +155,6 @@ describe("convertToBedrockConverseMessages", () => { }, ] - // Default behavior (useNativeTools: false) converts tool_result to XML text format - // This fixes the Bedrock error "toolConfig field must be defined when using toolUse and toolResult content blocks" const result = convertToBedrockConverseMessages(messages) if (!result[0] || !result[0].content) { @@ -165,18 +163,20 @@ describe("convertToBedrockConverseMessages", () => { } expect(result[0].role).toBe("user") - const textBlock = result[0].content[0] as ContentBlock - if ("text" in textBlock) { - expect(textBlock.text).toContain("") - expect(textBlock.text).toContain("test-id") - expect(textBlock.text).toContain("File contents here") - expect(textBlock.text).toContain("") + const resultBlock = result[0].content[0] as ContentBlock + if ("toolResult" in resultBlock && resultBlock.toolResult) { + const expectedContent: ToolResultContentBlock[] = [{ text: "File contents here" }] + expect(resultBlock.toolResult).toEqual({ + toolUseId: "test-id", + content: expectedContent, + status: "success", + }) } else { - expect.fail("Expected text block with XML content not found") + expect.fail("Expected tool result block not found") } }) - it("converts tool result messages to native format (useNativeTools: true)", () => { + it("converts tool result messages to native format", () => { const messages: Anthropic.Messages.MessageParam[] = [ { role: "user", @@ -190,8 +190,7 @@ describe("convertToBedrockConverseMessages", () => { }, ] - // With useNativeTools: true, keeps tool_result as native format - const result = convertToBedrockConverseMessages(messages, { useNativeTools: true }) + const result = convertToBedrockConverseMessages(messages) if (!result[0] || !result[0].content) { expect.fail("Expected result to have content") @@ -212,7 +211,7 @@ describe("convertToBedrockConverseMessages", () => { } }) - it("converts tool result messages with string content to XML text format (default)", () => { + it("converts tool result messages with string content to native format (default)", () => { const messages: Anthropic.Messages.MessageParam[] = [ { role: "user", @@ -234,18 +233,19 @@ describe("convertToBedrockConverseMessages", () => { } expect(result[0].role).toBe("user") - const textBlock = result[0].content[0] as ContentBlock - if ("text" in textBlock) { - expect(textBlock.text).toContain("") - expect(textBlock.text).toContain("test-id") - expect(textBlock.text).toContain("File: test.txt") - expect(textBlock.text).toContain("Hello World") + const resultBlock = result[0].content[0] as ContentBlock + if ("toolResult" in resultBlock && resultBlock.toolResult) { + expect(resultBlock.toolResult).toEqual({ + toolUseId: "test-id", + content: [{ text: "File: test.txt\nLines 1-5:\nHello World" }], + status: "success", + }) } else { - expect.fail("Expected text block with XML content not found") + expect.fail("Expected tool result block not found") } }) - it("converts tool result messages with string content to native format (useNativeTools: true)", () => { + it("converts tool result messages with string content to native format", () => { const messages: Anthropic.Messages.MessageParam[] = [ { role: "user", @@ -259,7 +259,7 @@ describe("convertToBedrockConverseMessages", () => { }, ] - const result = convertToBedrockConverseMessages(messages, { useNativeTools: true }) + const result = convertToBedrockConverseMessages(messages) if (!result[0] || !result[0].content) { expect.fail("Expected result to have content") @@ -279,9 +279,7 @@ describe("convertToBedrockConverseMessages", () => { } }) - it("converts both tool_use and tool_result consistently when native tools disabled", () => { - // This test ensures tool_use AND tool_result are both converted to XML text - // when useNativeTools is false, preventing Bedrock toolConfig errors + it("keeps both tool_use and tool_result in native format by default", () => { const messages: Anthropic.Messages.MessageParam[] = [ { role: "assistant", @@ -306,27 +304,16 @@ describe("convertToBedrockConverseMessages", () => { }, ] - const result = convertToBedrockConverseMessages(messages) // default useNativeTools: false + const result = convertToBedrockConverseMessages(messages) - // Both should be text blocks, not native toolUse/toolResult + // Both should be native toolUse/toolResult blocks const assistantContent = result[0]?.content?.[0] as ContentBlock const userContent = result[1]?.content?.[0] as ContentBlock - // tool_use should be XML text - expect("text" in assistantContent).toBe(true) - if ("text" in assistantContent) { - expect(assistantContent.text).toContain("") - } - - // tool_result should also be XML text (this is what the fix addresses) - expect("text" in userContent).toBe(true) - if ("text" in userContent) { - expect(userContent.text).toContain("") - } - - // Neither should have native format - expect("toolUse" in assistantContent).toBe(false) - expect("toolResult" in userContent).toBe(false) + expect("toolUse" in assistantContent).toBe(true) + expect("toolResult" in userContent).toBe(true) + expect("text" in assistantContent).toBe(false) + expect("text" in userContent).toBe(false) }) it("handles text content correctly", () => { diff --git a/src/api/transform/bedrock-converse-format.ts b/src/api/transform/bedrock-converse-format.ts index 1a8e49a20ba..08b89c1ef8c 100644 --- a/src/api/transform/bedrock-converse-format.ts +++ b/src/api/transform/bedrock-converse-format.ts @@ -25,14 +25,8 @@ interface BedrockMessageContent { /** * Convert Anthropic messages to Bedrock Converse format * @param anthropicMessages Messages in Anthropic format - * @param options Optional configuration for conversion - * @param options.useNativeTools When true, keeps tool_use input as JSON object instead of XML string */ -export function convertToBedrockConverseMessages( - anthropicMessages: Anthropic.Messages.MessageParam[], - options?: { useNativeTools?: boolean }, -): Message[] { - const useNativeTools = options?.useNativeTools ?? false +export function convertToBedrockConverseMessages(anthropicMessages: Anthropic.Messages.MessageParam[]): Message[] { return anthropicMessages.map((anthropicMessage) => { // Map Anthropic roles to Bedrock roles const role: ConversationRole = anthropicMessage.role === "assistant" ? "assistant" : "user" @@ -93,67 +87,17 @@ export function convertToBedrockConverseMessages( } if (messageBlock.type === "tool_use") { - if (useNativeTools) { - // For native tool calling, keep input as JSON object for Bedrock's toolUse format - return { - toolUse: { - toolUseId: messageBlock.id || "", - name: messageBlock.name || "", - input: messageBlock.input || {}, - }, - } as ContentBlock - } else { - // Convert tool use to XML text format for XML-based tool calling - return { - text: `\n${messageBlock.name}\n${JSON.stringify(messageBlock.input)}\n`, - } as ContentBlock - } + // Native-only: keep input as JSON object for Bedrock's toolUse format + return { + toolUse: { + toolUseId: messageBlock.id || "", + name: messageBlock.name || "", + input: messageBlock.input || {}, + }, + } as ContentBlock } if (messageBlock.type === "tool_result") { - // When NOT using native tools, convert tool_result to text format - // This matches how tool_use is converted to XML text when native tools are disabled. - // Without this, Bedrock will error with "toolConfig field must be defined when using - // toolUse and toolResult content blocks" because toolResult blocks require toolConfig. - if (!useNativeTools) { - let toolResultContent: string - if (messageBlock.content) { - if (typeof messageBlock.content === "string") { - toolResultContent = messageBlock.content - } else if (Array.isArray(messageBlock.content)) { - toolResultContent = messageBlock.content - .map((item) => (typeof item === "string" ? item : item.text || String(item))) - .join("\n") - } else { - toolResultContent = String(messageBlock.output || "") - } - } else if (messageBlock.output) { - if (typeof messageBlock.output === "string") { - toolResultContent = messageBlock.output - } else if (Array.isArray(messageBlock.output)) { - toolResultContent = messageBlock.output - .map((part) => { - if (typeof part === "object" && "text" in part) { - return part.text - } - if (typeof part === "object" && "type" in part && part.type === "image") { - return "(see following message for image)" - } - return String(part) - }) - .join("\n") - } else { - toolResultContent = String(messageBlock.output) - } - } else { - toolResultContent = "" - } - - return { - text: `\n${messageBlock.tool_use_id || ""}\n${toolResultContent}\n`, - } as ContentBlock - } - // Handle content field - can be string or array (native tool format) if (messageBlock.content) { // Content is a string diff --git a/src/api/transform/model-params.ts b/src/api/transform/model-params.ts index 9e1d421f6fe..250d74302b3 100644 --- a/src/api/transform/model-params.ts +++ b/src/api/transform/model-params.ts @@ -163,7 +163,8 @@ export function getModelParams({ format, ...params, reasoning: getOpenAiReasoning({ model, reasoningBudget, reasoningEffort, settings }), - tools: model.supportsNativeTools, + // Tool calling is native-only; whether tools are included is determined + // by whether the caller provided tool definitions. } } else if (format === "gemini") { return { diff --git a/src/core/assistant-message/AssistantMessageParser.ts b/src/core/assistant-message/AssistantMessageParser.ts deleted file mode 100644 index 364ec603f22..00000000000 --- a/src/core/assistant-message/AssistantMessageParser.ts +++ /dev/null @@ -1,251 +0,0 @@ -import { type ToolName, toolNames } from "@roo-code/types" -import { TextContent, ToolUse, ToolParamName, toolParamNames } from "../../shared/tools" -import { AssistantMessageContent } from "./parseAssistantMessage" - -/** - * Parser for assistant messages. Maintains state between chunks - * to avoid reprocessing the entire message on each update. - */ -export class AssistantMessageParser { - private contentBlocks: AssistantMessageContent[] = [] - private currentTextContent: TextContent | undefined = undefined - private currentTextContentStartIndex = 0 - private currentToolUse: ToolUse | undefined = undefined - private currentToolUseStartIndex = 0 - private currentParamName: ToolParamName | undefined = undefined - private currentParamValueStartIndex = 0 - private readonly MAX_ACCUMULATOR_SIZE = 1024 * 1024 // 1MB limit - private readonly MAX_PARAM_LENGTH = 1024 * 100 // 100KB per parameter limit - private accumulator = "" - - /** - * Initialize a new AssistantMessageParser instance. - */ - constructor() { - this.reset() - } - - /** - * Reset the parser state. - */ - public reset(): void { - this.contentBlocks = [] - this.currentTextContent = undefined - this.currentTextContentStartIndex = 0 - this.currentToolUse = undefined - this.currentToolUseStartIndex = 0 - this.currentParamName = undefined - this.currentParamValueStartIndex = 0 - this.accumulator = "" - } - - /** - * Returns the current parsed content blocks - */ - - public getContentBlocks(): AssistantMessageContent[] { - // Return a shallow copy to prevent external mutation - return this.contentBlocks.slice() - } - /** - * Process a new chunk of text and update the parser state. - * @param chunk The new chunk of text to process. - */ - public processChunk(chunk: string): AssistantMessageContent[] { - if (this.accumulator.length + chunk.length > this.MAX_ACCUMULATOR_SIZE) { - throw new Error("Assistant message exceeds maximum allowed size") - } - // Store the current length of the accumulator before adding the new chunk - const accumulatorStartLength = this.accumulator.length - - for (let i = 0; i < chunk.length; i++) { - const char = chunk[i] - this.accumulator += char - const currentPosition = accumulatorStartLength + i - - // There should not be a param without a tool use. - if (this.currentToolUse && this.currentParamName) { - const currentParamValue = this.accumulator.slice(this.currentParamValueStartIndex) - if (currentParamValue.length > this.MAX_PARAM_LENGTH) { - // Reset to a safe state - this.currentParamName = undefined - this.currentParamValueStartIndex = 0 - continue - } - const paramClosingTag = `` - // Streamed param content: always write the currently accumulated value - if (currentParamValue.endsWith(paramClosingTag)) { - // End of param value. - // Do not trim content parameters to preserve newlines, but strip first and last newline only - const paramValue = currentParamValue.slice(0, -paramClosingTag.length) - this.currentToolUse.params[this.currentParamName] = - this.currentParamName === "content" - ? paramValue.replace(/^\n/, "").replace(/\n$/, "") - : paramValue.trim() - this.currentParamName = undefined - continue - } else { - // Partial param value is accumulating. - // Write the currently accumulated param content in real time - this.currentToolUse.params[this.currentParamName] = currentParamValue - continue - } - } - - // No currentParamName. - - if (this.currentToolUse) { - const currentToolValue = this.accumulator.slice(this.currentToolUseStartIndex) - const toolUseClosingTag = `` - if (currentToolValue.endsWith(toolUseClosingTag)) { - // End of a tool use. - this.currentToolUse.partial = false - - this.currentToolUse = undefined - continue - } else { - const possibleParamOpeningTags = toolParamNames.map((name) => `<${name}>`) - for (const paramOpeningTag of possibleParamOpeningTags) { - if (this.accumulator.endsWith(paramOpeningTag)) { - // Start of a new parameter. - const paramName = paramOpeningTag.slice(1, -1) - if (!toolParamNames.includes(paramName as ToolParamName)) { - // Handle invalid parameter name gracefully - continue - } - this.currentParamName = paramName as ToolParamName - this.currentParamValueStartIndex = this.accumulator.length - break - } - } - - // There's no current param, and not starting a new param. - - // Special case for write_to_file where file contents could - // contain the closing tag, in which case the param would have - // closed and we end up with the rest of the file contents here. - // To work around this, get the string between the starting - // content tag and the LAST content tag. - const contentParamName: ToolParamName = "content" - - if ( - this.currentToolUse.name === "write_to_file" && - this.accumulator.endsWith(``) - ) { - const toolContent = this.accumulator.slice(this.currentToolUseStartIndex) - const contentStartTag = `<${contentParamName}>` - const contentEndTag = `` - const contentStartIndex = toolContent.indexOf(contentStartTag) + contentStartTag.length - const contentEndIndex = toolContent.lastIndexOf(contentEndTag) - - if (contentStartIndex !== -1 && contentEndIndex !== -1 && contentEndIndex > contentStartIndex) { - // Don't trim content to preserve newlines, but strip first and last newline only - this.currentToolUse.params[contentParamName] = toolContent - .slice(contentStartIndex, contentEndIndex) - .replace(/^\n/, "") - .replace(/\n$/, "") - } - } - - // Partial tool value is accumulating. - continue - } - } - - // No currentToolUse. - - let didStartToolUse = false - const possibleToolUseOpeningTags = toolNames.map((name) => `<${name}>`) - - for (const toolUseOpeningTag of possibleToolUseOpeningTags) { - if (this.accumulator.endsWith(toolUseOpeningTag)) { - // Extract and validate the tool name - const extractedToolName = toolUseOpeningTag.slice(1, -1) - - // Check if the extracted tool name is valid - if (!toolNames.includes(extractedToolName as ToolName)) { - // Invalid tool name, treat as plain text and continue - continue - } - - // Start of a new tool use. - this.currentToolUse = { - type: "tool_use", - name: extractedToolName as ToolName, - params: {}, - partial: true, - } - - this.currentToolUseStartIndex = this.accumulator.length - - // This also indicates the end of the current text content. - if (this.currentTextContent) { - this.currentTextContent.partial = false - - // Remove the partially accumulated tool use tag from the - // end of text ( block === this.currentToolUse) - if (idx === -1) { - this.contentBlocks.push(this.currentToolUse) - } - - didStartToolUse = true - break - } - } - - if (!didStartToolUse) { - // No tool use, so it must be text either at the beginning or - // between tools. - if (this.currentTextContent === undefined) { - // If this is the first chunk and we're at the beginning of processing, - // set the start index to the current position in the accumulator - this.currentTextContentStartIndex = currentPosition - - // Create a new text content block and add it to contentBlocks - this.currentTextContent = { - type: "text", - content: this.accumulator.slice(this.currentTextContentStartIndex).trim(), - partial: true, - } - - // Add the new text content to contentBlocks immediately - // Ensures it appears in the UI right away - this.contentBlocks.push(this.currentTextContent) - } else { - // Update the existing text content - this.currentTextContent.content = this.accumulator.slice(this.currentTextContentStartIndex).trim() - } - } - } - // Do not call finalizeContentBlocks() here. - // Instead, update any partial blocks in the array and add new ones as they're completed. - // This matches the behavior of the original parseAssistantMessage function. - return this.getContentBlocks() - } - - /** - * Finalize any partial content blocks. - * Should be called after processing the last chunk. - */ - public finalizeContentBlocks(): void { - // Mark all partial blocks as complete - for (const block of this.contentBlocks) { - if (block.partial) { - block.partial = false - } - if (block.type === "text" && typeof block.content === "string") { - block.content = block.content.trim() - } - } - } -} diff --git a/src/core/assistant-message/NativeToolCallParser.ts b/src/core/assistant-message/NativeToolCallParser.ts index 56d71eb3dd0..c13f28f517f 100644 --- a/src/core/assistant-message/NativeToolCallParser.ts +++ b/src/core/assistant-message/NativeToolCallParser.ts @@ -73,6 +73,22 @@ export class NativeToolCallParser { } >() + private static coerceOptionalBoolean(value: unknown): boolean | undefined { + if (typeof value === "boolean") { + return value + } + if (typeof value === "string") { + const lower = value.trim().toLowerCase() + if (lower === "true") { + return true + } + if (lower === "false") { + return false + } + } + return undefined + } + /** * Process a raw tool call chunk from the API stream. * Handles tracking, buffering, and emits start/delta/end events. @@ -348,9 +364,9 @@ export class NativeToolCallParser { partial: boolean, originalName?: string, ): ToolUse | null { - // Build legacy params for display + // Build stringified params for display/partial-progress UI. // NOTE: For streaming partial updates, we MUST populate params even for complex types - // because tool.handlePartial() methods rely on params to show UI updates + // because tool.handlePartial() methods rely on params to show UI updates. const params: Partial> = {} for (const [key, value] of Object.entries(partialArgs)) { @@ -543,6 +559,25 @@ export class NativeToolCallParser { } break + case "list_files": + if (partialArgs.path !== undefined) { + nativeArgs = { + path: partialArgs.path, + recursive: this.coerceOptionalBoolean(partialArgs.recursive), + } + } + break + + case "new_task": + if (partialArgs.mode !== undefined || partialArgs.message !== undefined) { + nativeArgs = { + mode: partialArgs.mode, + message: partialArgs.message, + todos: partialArgs.todos, + } + } + break + default: break } @@ -601,8 +636,8 @@ export class NativeToolCallParser { // Parse the arguments JSON string const args = toolCall.arguments === "" ? {} : JSON.parse(toolCall.arguments) - // Build legacy params object for backward compatibility with XML protocol and UI. - // Native execution path uses nativeArgs instead, which has proper typing. + // Build stringified params for display/logging. + // Tool execution MUST use nativeArgs (typed) and does not support legacy fallbacks. const params: Partial> = {} for (const [key, value] of Object.entries(args)) { @@ -625,14 +660,9 @@ export class NativeToolCallParser { params[key as ToolParamName] = stringValue } - // Build typed nativeArgs for tools that support it. - // This switch statement serves two purposes: - // 1. Validation: Ensures required parameters are present before constructing nativeArgs - // 2. Transformation: Converts raw JSON to properly typed structures - // + // Build typed nativeArgs for tool execution. // Each case validates the minimum required parameters and constructs a properly typed - // nativeArgs object. If validation fails, nativeArgs remains undefined and the tool - // will fall back to legacy parameter parsing if supported. + // nativeArgs object. If validation fails, we treat the tool call as invalid and fail fast. let nativeArgs: NativeArgsFor | undefined = undefined switch (resolvedName) { @@ -825,6 +855,25 @@ export class NativeToolCallParser { } break + case "list_files": + if (args.path !== undefined) { + nativeArgs = { + path: args.path, + recursive: this.coerceOptionalBoolean(args.recursive), + } as NativeArgsFor + } + break + + case "new_task": + if (args.mode !== undefined && args.message !== undefined) { + nativeArgs = { + mode: args.mode, + message: args.message, + todos: args.todos, + } as NativeArgsFor + } + break + default: if (customToolRegistry.has(resolvedName)) { nativeArgs = args as NativeArgsFor @@ -833,6 +882,16 @@ export class NativeToolCallParser { break } + // Native-only: core tools must always have typed nativeArgs. + // If we couldn't construct it, the model produced an invalid tool call payload. + if (!nativeArgs && !customToolRegistry.has(resolvedName)) { + throw new Error( + `[NativeToolCallParser] Invalid arguments for tool '${resolvedName}'. ` + + `Native tool calls require a valid JSON payload matching the tool schema. ` + + `Received: ${JSON.stringify(args)}`, + ) + } + const result: ToolUse = { type: "tool_use" as const, name: resolvedName, @@ -861,10 +920,6 @@ export class NativeToolCallParser { * Parse dynamic MCP tools (named mcp--serverName--toolName). * These are generated dynamically by getMcpServerTools() and are returned * as McpToolUse objects that preserve the original tool name. - * - * In native mode, MCP tools are NOT converted to use_mcp_tool - they keep - * their original name so it appears correctly in API conversation history. - * The use_mcp_tool wrapper is only used in XML mode. */ public static parseDynamicMcpTool(toolCall: { id: string; name: string; arguments: string }): McpToolUse | null { try { diff --git a/src/core/assistant-message/__tests__/AssistantMessageParser.spec.ts b/src/core/assistant-message/__tests__/AssistantMessageParser.spec.ts deleted file mode 100644 index cb60c8744f8..00000000000 --- a/src/core/assistant-message/__tests__/AssistantMessageParser.spec.ts +++ /dev/null @@ -1,392 +0,0 @@ -// npx vitest src/core/assistant-message/__tests__/AssistantMessageParser.spec.ts - -import { AssistantMessageParser } from "../AssistantMessageParser" -import { AssistantMessageContent } from "../parseAssistantMessage" -import { TextContent, ToolUse } from "../../../shared/tools" - -/** - * Helper to filter out empty text content blocks. - */ -const isEmptyTextContent = (block: any) => block.type === "text" && (block as TextContent).content === "" - -/** - * Helper to simulate streaming by feeding the parser deterministic "random"-sized chunks (1-10 chars). - * Uses a seeded pseudo-random number generator for deterministic chunking. - */ - -// Simple linear congruential generator (LCG) for deterministic pseudo-random numbers -function createSeededRandom(seed: number) { - let state = seed - return { - next: () => { - // LCG parameters from Numerical Recipes - state = (state * 1664525 + 1013904223) % 0x100000000 - return state / 0x100000000 - }, - } -} - -function streamChunks( - parser: AssistantMessageParser, - message: string, -): ReturnType { - let result: AssistantMessageContent[] = [] - let i = 0 - const rng = createSeededRandom(42) // Fixed seed for deterministic tests - while (i < message.length) { - // Deterministic chunk size between 1 and 10, but not exceeding message length - const chunkSize = Math.min(message.length - i, Math.floor(rng.next() * 10) + 1) - const chunk = message.slice(i, i + chunkSize) - result = parser.processChunk(chunk) - i += chunkSize - } - return result -} - -describe("AssistantMessageParser (streaming)", () => { - let parser: AssistantMessageParser - - beforeEach(() => { - parser = new AssistantMessageParser() - }) - - describe("text content streaming", () => { - it("should accumulate a simple text message chunk by chunk", () => { - const message = "Hello, this is a test." - const result = streamChunks(parser, message) - expect(result).toHaveLength(1) - expect(result[0]).toEqual({ - type: "text", - content: message, - partial: true, - }) - }) - - it("should accumulate multi-line text message chunk by chunk", () => { - const message = "Line 1\nLine 2\nLine 3" - const result = streamChunks(parser, message) - expect(result).toHaveLength(1) - expect(result[0]).toEqual({ - type: "text", - content: message, - partial: true, - }) - }) - }) - - describe("tool use streaming", () => { - it("should parse a tool use with parameter, streamed char by char", () => { - const message = "src/file.ts" - const result = streamChunks(parser, message).filter((block) => !isEmptyTextContent(block)) - expect(result).toHaveLength(1) - const toolUse = result[0] as ToolUse - expect(toolUse.type).toBe("tool_use") - expect(toolUse.name).toBe("read_file") - expect(toolUse.params.path).toBe("src/file.ts") - expect(toolUse.partial).toBe(false) - }) - - it("should mark tool use as partial when not closed", () => { - const message = "src/file.ts" - const result = streamChunks(parser, message).filter((block) => !isEmptyTextContent(block)) - expect(result).toHaveLength(1) - const toolUse = result[0] as ToolUse - expect(toolUse.type).toBe("tool_use") - expect(toolUse.name).toBe("read_file") - expect(toolUse.params.path).toBe("src/file.ts") - expect(toolUse.partial).toBe(true) - }) - - it("should handle a partial parameter in a tool use", () => { - const message = "src/file" - const result = streamChunks(parser, message).filter((block) => !isEmptyTextContent(block)) - expect(result).toHaveLength(1) - const toolUse = result[0] as ToolUse - expect(toolUse.type).toBe("tool_use") - expect(toolUse.name).toBe("read_file") - expect(toolUse.params.path).toBe("src/file") - expect(toolUse.partial).toBe(true) - }) - - it("should handle tool use with multiple parameters streamed", () => { - const message = - "src/file.ts1020" - const result = streamChunks(parser, message).filter((block) => !isEmptyTextContent(block)) - expect(result).toHaveLength(1) - const toolUse = result[0] as ToolUse - expect(toolUse.type).toBe("tool_use") - expect(toolUse.name).toBe("read_file") - expect(toolUse.params.path).toBe("src/file.ts") - expect(toolUse.params.start_line).toBe("10") - expect(toolUse.params.end_line).toBe("20") - expect(toolUse.partial).toBe(false) - }) - }) - - describe("mixed content streaming", () => { - it("should parse text followed by a tool use, streamed", () => { - const message = "Text before tool src/file.ts" - const result = streamChunks(parser, message) - expect(result).toHaveLength(2) - const textContent = result[0] as TextContent - expect(textContent.type).toBe("text") - expect(textContent.content).toBe("Text before tool") - expect(textContent.partial).toBe(false) - const toolUse = result[1] as ToolUse - expect(toolUse.type).toBe("tool_use") - expect(toolUse.name).toBe("read_file") - expect(toolUse.params.path).toBe("src/file.ts") - expect(toolUse.partial).toBe(false) - }) - - it("should parse a tool use followed by text, streamed", () => { - const message = "src/file.tsText after tool" - const result = streamChunks(parser, message).filter((block) => !isEmptyTextContent(block)) - expect(result).toHaveLength(2) - const toolUse = result[0] as ToolUse - expect(toolUse.type).toBe("tool_use") - expect(toolUse.name).toBe("read_file") - expect(toolUse.params.path).toBe("src/file.ts") - expect(toolUse.partial).toBe(false) - const textContent = result[1] as TextContent - expect(textContent.type).toBe("text") - expect(textContent.content).toBe("Text after tool") - expect(textContent.partial).toBe(true) - }) - - it("should parse multiple tool uses separated by text, streamed", () => { - const message = - "First: file1.tsSecond: file2.ts" - const result = streamChunks(parser, message) - expect(result).toHaveLength(4) - expect(result[0].type).toBe("text") - expect((result[0] as TextContent).content).toBe("First:") - expect(result[1].type).toBe("tool_use") - expect((result[1] as ToolUse).name).toBe("read_file") - expect((result[1] as ToolUse).params.path).toBe("file1.ts") - expect(result[2].type).toBe("text") - expect((result[2] as TextContent).content).toBe("Second:") - expect(result[3].type).toBe("tool_use") - expect((result[3] as ToolUse).name).toBe("read_file") - expect((result[3] as ToolUse).params.path).toBe("file2.ts") - }) - }) - - describe("special and edge cases", () => { - it("should handle the write_to_file tool with content that contains closing tags", () => { - const message = `src/file.ts - function example() { - // This has XML-like content: - return true; - } - ` - - const result = streamChunks(parser, message).filter((block) => !isEmptyTextContent(block)) - - expect(result).toHaveLength(1) - const toolUse = result[0] as ToolUse - expect(toolUse.type).toBe("tool_use") - expect(toolUse.name).toBe("write_to_file") - expect(toolUse.params.path).toBe("src/file.ts") - expect(toolUse.params.content).toContain("function example()") - expect(toolUse.params.content).toContain("// This has XML-like content: ") - expect(toolUse.params.content).toContain("return true;") - expect(toolUse.partial).toBe(false) - }) - it("should handle empty messages", () => { - const message = "" - const result = streamChunks(parser, message) - expect(result).toHaveLength(0) - }) - - it("should handle malformed tool use tags as plain text", () => { - const message = "This has a malformed tag" - const result = streamChunks(parser, message) - expect(result).toHaveLength(1) - expect(result[0].type).toBe("text") - expect((result[0] as TextContent).content).toBe(message) - }) - - it("should handle tool use with no parameters", () => { - const message = "" - const result = streamChunks(parser, message).filter((block) => !isEmptyTextContent(block)) - expect(result).toHaveLength(1) - const toolUse = result[0] as ToolUse - expect(toolUse.type).toBe("tool_use") - expect(toolUse.name).toBe("browser_action") - expect(Object.keys(toolUse.params).length).toBe(0) - expect(toolUse.partial).toBe(false) - }) - - it("should handle a tool use with a parameter containing XML-like content", () => { - const message = "
.*
src
" - const result = streamChunks(parser, message).filter((block) => !isEmptyTextContent(block)) - expect(result).toHaveLength(1) - const toolUse = result[0] as ToolUse - expect(toolUse.type).toBe("tool_use") - expect(toolUse.name).toBe("search_files") - expect(toolUse.params.regex).toBe("
.*
") - expect(toolUse.params.path).toBe("src") - expect(toolUse.partial).toBe(false) - }) - - it("should handle consecutive tool uses without text in between", () => { - const message = "file1.tsfile2.ts" - const result = streamChunks(parser, message).filter((block) => !isEmptyTextContent(block)) - expect(result).toHaveLength(2) - const toolUse1 = result[0] as ToolUse - expect(toolUse1.type).toBe("tool_use") - expect(toolUse1.name).toBe("read_file") - expect(toolUse1.params.path).toBe("file1.ts") - expect(toolUse1.partial).toBe(false) - const toolUse2 = result[1] as ToolUse - expect(toolUse2.type).toBe("tool_use") - expect(toolUse2.name).toBe("read_file") - expect(toolUse2.params.path).toBe("file2.ts") - expect(toolUse2.partial).toBe(false) - }) - - it("should handle whitespace in parameters", () => { - const message = " src/file.ts " - const result = streamChunks(parser, message).filter((block) => !isEmptyTextContent(block)) - expect(result).toHaveLength(1) - const toolUse = result[0] as ToolUse - expect(toolUse.type).toBe("tool_use") - expect(toolUse.name).toBe("read_file") - expect(toolUse.params.path).toBe("src/file.ts") - expect(toolUse.partial).toBe(false) - }) - - it("should handle multi-line parameters", () => { - const message = `file.ts - line 1 - line 2 - line 3 - ` - const result = streamChunks(parser, message).filter((block) => !isEmptyTextContent(block)) - - expect(result).toHaveLength(1) - const toolUse = result[0] as ToolUse - expect(toolUse.type).toBe("tool_use") - expect(toolUse.name).toBe("write_to_file") - expect(toolUse.params.path).toBe("file.ts") - expect(toolUse.params.content).toContain("line 1") - expect(toolUse.params.content).toContain("line 2") - expect(toolUse.params.content).toContain("line 3") - expect(toolUse.partial).toBe(false) - }) - it("should handle a complex message with multiple content types", () => { - const message = `I'll help you with that task. - - src/index.ts - - Now let's modify the file: - - src/index.ts - // Updated content - console.log("Hello world"); - - - Let's run the code: - - node src/index.ts` - - const result = streamChunks(parser, message) - - expect(result).toHaveLength(6) - - // First text block - expect(result[0].type).toBe("text") - expect((result[0] as TextContent).content).toBe("I'll help you with that task.") - - // First tool use (read_file) - expect(result[1].type).toBe("tool_use") - expect((result[1] as ToolUse).name).toBe("read_file") - - // Second text block - expect(result[2].type).toBe("text") - expect((result[2] as TextContent).content).toContain("Now let's modify the file:") - - // Second tool use (write_to_file) - expect(result[3].type).toBe("tool_use") - expect((result[3] as ToolUse).name).toBe("write_to_file") - - // Third text block - expect(result[4].type).toBe("text") - expect((result[4] as TextContent).content).toContain("Let's run the code:") - - // Third tool use (execute_command) - expect(result[5].type).toBe("tool_use") - expect((result[5] as ToolUse).name).toBe("execute_command") - }) - }) - - describe("size limit handling", () => { - it("should throw an error when MAX_ACCUMULATOR_SIZE is exceeded", () => { - // Create a message that exceeds 1MB (MAX_ACCUMULATOR_SIZE) - const largeMessage = "x".repeat(1024 * 1024 + 1) // 1MB + 1 byte - - expect(() => { - parser.processChunk(largeMessage) - }).toThrow("Assistant message exceeds maximum allowed size") - }) - - it("should gracefully handle a parameter that exceeds MAX_PARAM_LENGTH", () => { - // Create a parameter value that exceeds 100KB (MAX_PARAM_LENGTH) - const largeParamValue = "x".repeat(1024 * 100 + 1) // 100KB + 1 byte - const message = `test.txt${largeParamValue}After tool` - - // Process the message in chunks to simulate streaming - let result: AssistantMessageContent[] = [] - let error: Error | null = null - - try { - // Process the opening tags - result = parser.processChunk("test.txt") - - // Process the large parameter value in chunks - const chunkSize = 1000 - for (let i = 0; i < largeParamValue.length; i += chunkSize) { - const chunk = largeParamValue.slice(i, i + chunkSize) - result = parser.processChunk(chunk) - } - - // Process the closing tags and text after - result = parser.processChunk("After tool") - } catch (e) { - error = e as Error - } - - // Should not throw an error - expect(error).toBeNull() - - // Should have processed the content - expect(result.length).toBeGreaterThan(0) - - // The tool use should exist but the content parameter should be reset/empty - const toolUse = result.find((block) => block.type === "tool_use") as ToolUse - expect(toolUse).toBeDefined() - expect(toolUse.name).toBe("write_to_file") - expect(toolUse.params.path).toBe("test.txt") - - // The text after the tool should still be parsed - const textAfter = result.find( - (block) => block.type === "text" && (block as TextContent).content.includes("After tool"), - ) - expect(textAfter).toBeDefined() - }) - }) - - describe("finalizeContentBlocks", () => { - it("should mark all partial blocks as complete", () => { - const message = "src/file.ts" - streamChunks(parser, message) - let blocks = parser.getContentBlocks() - // The block may already be partial or not, depending on chunking. - // To ensure the test is robust, we only assert after finalizeContentBlocks. - parser.finalizeContentBlocks() - blocks = parser.getContentBlocks() - expect(blocks[0].partial).toBe(false) - }) - }) -}) diff --git a/src/core/assistant-message/__tests__/parseAssistantMessage.spec.ts b/src/core/assistant-message/__tests__/parseAssistantMessage.spec.ts deleted file mode 100644 index 80d2502626d..00000000000 --- a/src/core/assistant-message/__tests__/parseAssistantMessage.spec.ts +++ /dev/null @@ -1,338 +0,0 @@ -// npx vitest src/core/assistant-message/__tests__/parseAssistantMessage.spec.ts - -import { TextContent, ToolUse } from "../../../shared/tools" - -import { AssistantMessageContent, parseAssistantMessage as parseAssistantMessageV1 } from "../parseAssistantMessage" -import { parseAssistantMessageV2 } from "../parseAssistantMessageV2" - -const isEmptyTextContent = (block: AssistantMessageContent) => - block.type === "text" && (block as TextContent).content === "" - -;[parseAssistantMessageV1, parseAssistantMessageV2].forEach((parser, index) => { - describe(`parseAssistantMessageV${index + 1}`, () => { - describe("text content parsing", () => { - it("should parse a simple text message", () => { - const message = "This is a simple text message" - const result = parser(message) - - expect(result).toHaveLength(1) - expect(result[0]).toEqual({ - type: "text", - content: message, - partial: true, // Text is always partial when it's the last content - }) - }) - - it("should parse a multi-line text message", () => { - const message = "This is a multi-line\ntext message\nwith several lines" - const result = parser(message) - - expect(result).toHaveLength(1) - expect(result[0]).toEqual({ - type: "text", - content: message, - partial: true, // Text is always partial when it's the last content - }) - }) - - it("should mark text as partial when it's the last content in the message", () => { - const message = "This is a partial text" - const result = parser(message) - - expect(result).toHaveLength(1) - expect(result[0]).toEqual({ - type: "text", - content: message, - partial: true, - }) - }) - }) - - describe("tool use parsing", () => { - it("should parse a simple tool use", () => { - const message = "src/file.ts" - const result = parser(message).filter((block) => !isEmptyTextContent(block)) - - expect(result).toHaveLength(1) - const toolUse = result[0] as ToolUse - expect(toolUse.type).toBe("tool_use") - expect(toolUse.name).toBe("read_file") - expect(toolUse.params.path).toBe("src/file.ts") - expect(toolUse.partial).toBe(false) - }) - - it("should parse a tool use with multiple parameters", () => { - const message = - "src/file.ts1020" - const result = parser(message).filter((block) => !isEmptyTextContent(block)) - - expect(result).toHaveLength(1) - const toolUse = result[0] as ToolUse - expect(toolUse.type).toBe("tool_use") - expect(toolUse.name).toBe("read_file") - expect(toolUse.params.path).toBe("src/file.ts") - expect(toolUse.params.start_line).toBe("10") - expect(toolUse.params.end_line).toBe("20") - expect(toolUse.partial).toBe(false) - }) - - it("should mark tool use as partial when it's not closed", () => { - const message = "src/file.ts" - const result = parser(message).filter((block) => !isEmptyTextContent(block)) - - expect(result).toHaveLength(1) - const toolUse = result[0] as ToolUse - expect(toolUse.type).toBe("tool_use") - expect(toolUse.name).toBe("read_file") - expect(toolUse.params.path).toBe("src/file.ts") - expect(toolUse.partial).toBe(true) - }) - - it("should handle a partial parameter in a tool use", () => { - const message = "src/file.ts" - const result = parser(message).filter((block) => !isEmptyTextContent(block)) - - expect(result).toHaveLength(1) - const toolUse = result[0] as ToolUse - expect(toolUse.type).toBe("tool_use") - expect(toolUse.name).toBe("read_file") - expect(toolUse.params.path).toBe("src/file.ts") - expect(toolUse.partial).toBe(true) - }) - }) - - describe("mixed content parsing", () => { - it("should parse text followed by a tool use", () => { - const message = "Here's the file content: src/file.ts" - const result = parser(message) - - expect(result).toHaveLength(2) - - const textContent = result[0] as TextContent - expect(textContent.type).toBe("text") - expect(textContent.content).toBe("Here's the file content:") - expect(textContent.partial).toBe(false) - - const toolUse = result[1] as ToolUse - expect(toolUse.type).toBe("tool_use") - expect(toolUse.name).toBe("read_file") - expect(toolUse.params.path).toBe("src/file.ts") - expect(toolUse.partial).toBe(false) - }) - - it("should parse a tool use followed by text", () => { - const message = "src/file.tsHere's what I found in the file." - const result = parser(message).filter((block) => !isEmptyTextContent(block)) - - expect(result).toHaveLength(2) - - const toolUse = result[0] as ToolUse - expect(toolUse.type).toBe("tool_use") - expect(toolUse.name).toBe("read_file") - expect(toolUse.params.path).toBe("src/file.ts") - expect(toolUse.partial).toBe(false) - - const textContent = result[1] as TextContent - expect(textContent.type).toBe("text") - expect(textContent.content).toBe("Here's what I found in the file.") - expect(textContent.partial).toBe(true) - }) - - it("should parse multiple tool uses separated by text", () => { - const message = - "First file: src/file1.tsSecond file: src/file2.ts" - const result = parser(message) - - expect(result).toHaveLength(4) - - expect(result[0].type).toBe("text") - expect((result[0] as TextContent).content).toBe("First file:") - - expect(result[1].type).toBe("tool_use") - expect((result[1] as ToolUse).name).toBe("read_file") - expect((result[1] as ToolUse).params.path).toBe("src/file1.ts") - - expect(result[2].type).toBe("text") - expect((result[2] as TextContent).content).toBe("Second file:") - - expect(result[3].type).toBe("tool_use") - expect((result[3] as ToolUse).name).toBe("read_file") - expect((result[3] as ToolUse).params.path).toBe("src/file2.ts") - }) - }) - - describe("special cases", () => { - it("should handle the write_to_file tool with content that contains closing tags", () => { - const message = `src/file.ts - function example() { - // This has XML-like content: - return true; - } - ` - - const result = parser(message).filter((block) => !isEmptyTextContent(block)) - - expect(result).toHaveLength(1) - const toolUse = result[0] as ToolUse - expect(toolUse.type).toBe("tool_use") - expect(toolUse.name).toBe("write_to_file") - expect(toolUse.params.path).toBe("src/file.ts") - expect(toolUse.params.content).toContain("function example()") - expect(toolUse.params.content).toContain("// This has XML-like content: ") - expect(toolUse.params.content).toContain("return true;") - expect(toolUse.partial).toBe(false) - }) - - it("should handle empty messages", () => { - const message = "" - const result = parser(message) - - expect(result).toHaveLength(0) - }) - - it("should handle malformed tool use tags", () => { - const message = "This has a malformed tag" - const result = parser(message) - - expect(result).toHaveLength(1) - expect(result[0].type).toBe("text") - expect((result[0] as TextContent).content).toBe(message) - }) - - it("should handle tool use with no parameters", () => { - const message = "" - const result = parser(message).filter((block) => !isEmptyTextContent(block)) - - expect(result).toHaveLength(1) - const toolUse = result[0] as ToolUse - expect(toolUse.type).toBe("tool_use") - expect(toolUse.name).toBe("browser_action") - expect(Object.keys(toolUse.params).length).toBe(0) - expect(toolUse.partial).toBe(false) - }) - - it("should handle nested tool tags that aren't actually nested", () => { - const message = - "echo 'test.txt'" - - const result = parser(message).filter((block) => !isEmptyTextContent(block)) - - expect(result).toHaveLength(1) - const toolUse = result[0] as ToolUse - expect(toolUse.type).toBe("tool_use") - expect(toolUse.name).toBe("execute_command") - expect(toolUse.params.command).toBe("echo 'test.txt'") - expect(toolUse.partial).toBe(false) - }) - - it("should handle a tool use with a parameter containing XML-like content", () => { - const message = "
.*
src
" - const result = parser(message).filter((block) => !isEmptyTextContent(block)) - - expect(result).toHaveLength(1) - const toolUse = result[0] as ToolUse - expect(toolUse.type).toBe("tool_use") - expect(toolUse.name).toBe("search_files") - expect(toolUse.params.regex).toBe("
.*
") - expect(toolUse.params.path).toBe("src") - expect(toolUse.partial).toBe(false) - }) - - it("should handle consecutive tool uses without text in between", () => { - const message = - "file1.tsfile2.ts" - const result = parser(message).filter((block) => !isEmptyTextContent(block)) - - expect(result).toHaveLength(2) - - const toolUse1 = result[0] as ToolUse - expect(toolUse1.type).toBe("tool_use") - expect(toolUse1.name).toBe("read_file") - expect(toolUse1.params.path).toBe("file1.ts") - expect(toolUse1.partial).toBe(false) - - const toolUse2 = result[1] as ToolUse - expect(toolUse2.type).toBe("tool_use") - expect(toolUse2.name).toBe("read_file") - expect(toolUse2.params.path).toBe("file2.ts") - expect(toolUse2.partial).toBe(false) - }) - - it("should handle whitespace in parameters", () => { - const message = " src/file.ts " - const result = parser(message).filter((block) => !isEmptyTextContent(block)) - - expect(result).toHaveLength(1) - const toolUse = result[0] as ToolUse - expect(toolUse.type).toBe("tool_use") - expect(toolUse.name).toBe("read_file") - expect(toolUse.params.path).toBe("src/file.ts") - expect(toolUse.partial).toBe(false) - }) - - it("should handle multi-line parameters", () => { - const message = `file.ts - line 1 - line 2 - line 3 - ` - const result = parser(message).filter((block) => !isEmptyTextContent(block)) - - expect(result).toHaveLength(1) - const toolUse = result[0] as ToolUse - expect(toolUse.type).toBe("tool_use") - expect(toolUse.name).toBe("write_to_file") - expect(toolUse.params.path).toBe("file.ts") - expect(toolUse.params.content).toContain("line 1") - expect(toolUse.params.content).toContain("line 2") - expect(toolUse.params.content).toContain("line 3") - expect(toolUse.partial).toBe(false) - }) - - it("should handle a complex message with multiple content types", () => { - const message = `I'll help you with that task. - - src/index.ts - - Now let's modify the file: - - src/index.ts - // Updated content - console.log("Hello world"); - - - Let's run the code: - - node src/index.ts` - - const result = parser(message) - - expect(result).toHaveLength(6) - - // First text block - expect(result[0].type).toBe("text") - expect((result[0] as TextContent).content).toBe("I'll help you with that task.") - - // First tool use (read_file) - expect(result[1].type).toBe("tool_use") - expect((result[1] as ToolUse).name).toBe("read_file") - - // Second text block - expect(result[2].type).toBe("text") - expect((result[2] as TextContent).content).toContain("Now let's modify the file:") - - // Second tool use (write_to_file) - expect(result[3].type).toBe("tool_use") - expect((result[3] as ToolUse).name).toBe("write_to_file") - - // Third text block - expect(result[4].type).toBe("text") - expect((result[4] as TextContent).content).toContain("Let's run the code:") - - // Third tool use (execute_command) - expect(result[5].type).toBe("tool_use") - expect((result[5] as ToolUse).name).toBe("execute_command") - }) - }) - }) -}) diff --git a/src/core/assistant-message/__tests__/parseAssistantMessageBenchmark.ts b/src/core/assistant-message/__tests__/parseAssistantMessageBenchmark.ts deleted file mode 100644 index a32b1173ce0..00000000000 --- a/src/core/assistant-message/__tests__/parseAssistantMessageBenchmark.ts +++ /dev/null @@ -1,111 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-function-type */ - -// node --expose-gc --import tsx src/core/assistant-message/__tests__/parseAssistantMessageBenchmark.ts - -import { performance } from "perf_hooks" -import { parseAssistantMessage as parseAssistantMessageV1 } from "../parseAssistantMessage" -import { parseAssistantMessageV2 } from "../parseAssistantMessageV2" - -const formatNumber = (num: number): string => { - return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") -} - -const measureExecutionTime = (fn: Function, input: string, iterations: number = 1000): number => { - for (let i = 0; i < 10; i++) { - fn(input) - } - - const start = performance.now() - - for (let i = 0; i < iterations; i++) { - fn(input) - } - - const end = performance.now() - return (end - start) / iterations // Average time per iteration in ms. -} - -const measureMemoryUsage = ( - fn: Function, - input: string, - iterations: number = 100, -): { heapUsed: number; heapTotal: number } => { - if (global.gc) { - // Force garbage collection if available. - global.gc() - } else { - console.warn("No garbage collection hook! Run with --expose-gc for more accurate memory measurements.") - } - - const initialMemory = process.memoryUsage() - - for (let i = 0; i < iterations; i++) { - fn(input) - } - - const finalMemory = process.memoryUsage() - - return { - heapUsed: (finalMemory.heapUsed - initialMemory.heapUsed) / iterations, - heapTotal: (finalMemory.heapTotal - initialMemory.heapTotal) / iterations, - } -} - -const testCases = [ - { - name: "Simple text message", - input: "This is a simple text message without any tool uses.", - }, - { - name: "Message with a simple tool use", - input: "Let's read a file: src/file.ts", - }, - { - name: "Message with a complex tool use (write_to_file)", - input: "src/file.ts\nfunction example() {\n // This has XML-like content: \n return true;\n}\n", - }, - { - name: "Message with multiple tool uses", - input: "First file: src/file1.ts\nSecond file: src/file2.ts\nLet's write a new file: src/file3.ts\nexport function newFunction() {\n return 'Hello world';\n}\n", - }, - { - name: "Large message with repeated tool uses", - input: Array(50) - .fill( - 'src/file.ts\noutput.tsconsole.log("hello");', - ) - .join("\n"), - }, -] - -const runBenchmark = () => { - const maxNameLength = testCases.reduce((max, testCase) => Math.max(max, testCase.name.length), 0) - const namePadding = maxNameLength + 2 - - console.log( - `| ${"Test Case".padEnd(namePadding)} | V1 Time (ms) | V2 Time (ms) | V1/V2 Ratio | V1 Heap (bytes) | V2 Heap (bytes) |`, - ) - console.log( - `| ${"-".repeat(namePadding)} | ------------ | ------------ | ----------- | ---------------- | ---------------- |`, - ) - - for (const testCase of testCases) { - const v1Time = measureExecutionTime(parseAssistantMessageV1, testCase.input) - const v2Time = measureExecutionTime(parseAssistantMessageV2, testCase.input) - const timeRatio = v1Time / v2Time - - const v1Memory = measureMemoryUsage(parseAssistantMessageV1, testCase.input) - const v2Memory = measureMemoryUsage(parseAssistantMessageV2, testCase.input) - - console.log( - `| ${testCase.name.padEnd(namePadding)} | ` + - `${v1Time.toFixed(4).padStart(12)} | ` + - `${v2Time.toFixed(4).padStart(12)} | ` + - `${timeRatio.toFixed(2).padStart(11)} | ` + - `${formatNumber(Math.round(v1Memory.heapUsed)).padStart(16)} | ` + - `${formatNumber(Math.round(v2Memory.heapUsed)).padStart(16)} |`, - ) - } -} - -runBenchmark() diff --git a/src/core/assistant-message/__tests__/presentAssistantMessage-custom-tool.spec.ts b/src/core/assistant-message/__tests__/presentAssistantMessage-custom-tool.spec.ts index e90646fd9a4..18e277905f1 100644 --- a/src/core/assistant-message/__tests__/presentAssistantMessage-custom-tool.spec.ts +++ b/src/core/assistant-message/__tests__/presentAssistantMessage-custom-tool.spec.ts @@ -7,6 +7,11 @@ import { presentAssistantMessage } from "../presentAssistantMessage" vi.mock("../../task/Task") vi.mock("../../tools/validateToolUse", () => ({ validateToolUse: vi.fn(), + isValidToolName: vi.fn((toolName: string) => + ["read_file", "write_to_file", "ask_followup_question", "attempt_completion", "use_mcp_tool"].includes( + toolName, + ), + ), })) // Mock custom tool registry - must be done inline without external variable references @@ -116,39 +121,7 @@ describe("presentAssistantMessage - Custom Tool Recording", () => { // Should record as "custom_tool", not "my_custom_tool" expect(mockTask.recordToolUsage).toHaveBeenCalledWith("custom_tool") - expect(TelemetryService.instance.captureToolUsage).toHaveBeenCalledWith( - mockTask.taskId, - "custom_tool", - "native", - ) - }) - - it("should record custom tool usage as 'custom_tool' in XML protocol", async () => { - mockTask.assistantMessageContent = [ - { - type: "tool_use", - // No ID = XML protocol - name: "my_custom_tool", - params: { value: "test" }, - partial: false, - }, - ] - - vi.mocked(customToolRegistry.has).mockReturnValue(true) - vi.mocked(customToolRegistry.get).mockReturnValue({ - name: "my_custom_tool", - description: "A custom tool", - execute: vi.fn().mockResolvedValue("Custom tool result"), - }) - - await presentAssistantMessage(mockTask) - - expect(mockTask.recordToolUsage).toHaveBeenCalledWith("custom_tool") - expect(TelemetryService.instance.captureToolUsage).toHaveBeenCalledWith( - mockTask.taskId, - "custom_tool", - "xml", - ) + expect(TelemetryService.instance.captureToolUsage).toHaveBeenCalledWith(mockTask.taskId, "custom_tool") }) }) @@ -201,11 +174,7 @@ describe("presentAssistantMessage - Custom Tool Recording", () => { // Should record as "read_file", not "custom_tool" expect(mockTask.recordToolUsage).toHaveBeenCalledWith("read_file") - expect(TelemetryService.instance.captureToolUsage).toHaveBeenCalledWith( - mockTask.taskId, - "read_file", - "native", - ) + expect(TelemetryService.instance.captureToolUsage).toHaveBeenCalledWith(mockTask.taskId, "read_file") }) it("should record MCP tool usage as 'use_mcp_tool' (not custom_tool)", async () => { @@ -247,11 +216,7 @@ describe("presentAssistantMessage - Custom Tool Recording", () => { // Should record as "use_mcp_tool", not "custom_tool" expect(mockTask.recordToolUsage).toHaveBeenCalledWith("use_mcp_tool") - expect(TelemetryService.instance.captureToolUsage).toHaveBeenCalledWith( - mockTask.taskId, - "use_mcp_tool", - "native", - ) + expect(TelemetryService.instance.captureToolUsage).toHaveBeenCalledWith(mockTask.taskId, "use_mcp_tool") }) }) diff --git a/src/core/assistant-message/__tests__/presentAssistantMessage-images.spec.ts b/src/core/assistant-message/__tests__/presentAssistantMessage-images.spec.ts index 72ee4306097..6740f780ed3 100644 --- a/src/core/assistant-message/__tests__/presentAssistantMessage-images.spec.ts +++ b/src/core/assistant-message/__tests__/presentAssistantMessage-images.spec.ts @@ -4,12 +4,16 @@ import { describe, it, expect, beforeEach, vi } from "vitest" import { Anthropic } from "@anthropic-ai/sdk" import { presentAssistantMessage } from "../presentAssistantMessage" import { Task } from "../../task/Task" -import { TOOL_PROTOCOL } from "@roo-code/types" // Mock dependencies vi.mock("../../task/Task") vi.mock("../../tools/validateToolUse", () => ({ validateToolUse: vi.fn(), + isValidToolName: vi.fn((toolName: string) => + ["read_file", "write_to_file", "ask_followup_question", "attempt_completion", "use_mcp_tool"].includes( + toolName, + ), + ), })) vi.mock("@roo-code/telemetry", () => ({ TelemetryService: { @@ -20,7 +24,7 @@ vi.mock("@roo-code/telemetry", () => ({ }, })) -describe("presentAssistantMessage - Image Handling in Native Tool Calls", () => { +describe("presentAssistantMessage - Image Handling in Native Tool Calling", () => { let mockTask: any beforeEach(() => { @@ -74,15 +78,16 @@ describe("presentAssistantMessage - Image Handling in Native Tool Calls", () => }) }) - it("should preserve images in tool_result for native protocol", async () => { - // Set up a tool_use block with an ID (indicates native protocol) + it("should preserve images in tool_result for native tool calling", async () => { + // Set up a tool_use block with an ID (indicates native tool calling) const toolCallId = "tool_call_123" mockTask.assistantMessageContent = [ { type: "tool_use", - id: toolCallId, // ID indicates native protocol + id: toolCallId, // ID indicates native tool calling name: "ask_followup_question", params: { question: "What do you see?" }, + nativeArgs: { question: "What do you see?", follow_up: [] }, }, ] @@ -116,7 +121,7 @@ describe("presentAssistantMessage - Image Handling in Native Tool Calls", () => expect(toolResult).toBeDefined() expect(toolResult.tool_use_id).toBe(toolCallId) - // For native protocol, tool_result content should be a string (text only) + // For native tool calling, tool_result content should be a string (text only) expect(typeof toolResult.content).toBe("string") expect(toolResult.content).toContain("I see a cat") @@ -126,7 +131,7 @@ describe("presentAssistantMessage - Image Handling in Native Tool Calls", () => expect(imageBlocks[0].source.data).toBe("base64ImageData") }) - it("should convert to string when no images are present (native protocol)", async () => { + it("should convert to string when no images are present (native tool calling)", async () => { // Set up a tool_use block with an ID (indicates native protocol) const toolCallId = "tool_call_456" mockTask.assistantMessageContent = [ @@ -135,6 +140,7 @@ describe("presentAssistantMessage - Image Handling in Native Tool Calls", () => id: toolCallId, name: "ask_followup_question", params: { question: "What is your name?" }, + nativeArgs: { question: "What is your name?", follow_up: [] }, }, ] @@ -157,12 +163,11 @@ describe("presentAssistantMessage - Image Handling in Native Tool Calls", () => expect(typeof toolResult.content).toBe("string") }) - it("should preserve images in content array for XML protocol (existing behavior)", async () => { - // Set up a tool_use block WITHOUT an ID (indicates XML protocol) + it("should fail fast when tool_use is missing id (legacy/XML-style tool call)", async () => { + // tool_use without an id is treated as legacy/XML-style tool call and must be rejected. mockTask.assistantMessageContent = [ { type: "tool_use", - // No ID = XML protocol name: "ask_followup_question", params: { question: "What do you see?" }, }, @@ -176,14 +181,13 @@ describe("presentAssistantMessage - Image Handling in Native Tool Calls", () => await presentAssistantMessage(mockTask) - // For XML protocol, content is added as separate blocks - // Check that both text and image blocks were added - const hasTextBlock = mockTask.userMessageContent.some((item: any) => item.type === "text") - const hasImageBlock = mockTask.userMessageContent.some((item: any) => item.type === "image") - - expect(hasTextBlock).toBe(true) - // XML protocol preserves images as separate blocks in userMessageContent - expect(hasImageBlock).toBe(true) + const textBlocks = mockTask.userMessageContent.filter((item: any) => item.type === "text") + expect(textBlocks.length).toBeGreaterThan(0) + expect(textBlocks.some((b: any) => String(b.text).includes("XML tool calls are no longer supported"))).toBe( + true, + ) + // Should not proceed to execute tool or add images as tool output. + expect(mockTask.userMessageContent.some((item: any) => item.type === "image")).toBe(false) }) it("should handle empty tool result gracefully", async () => { @@ -216,7 +220,7 @@ describe("presentAssistantMessage - Image Handling in Native Tool Calls", () => }) describe("Multiple tool calls handling", () => { - it("should send tool_result with is_error for skipped tools in native protocol when didRejectTool is true", async () => { + it("should send tool_result with is_error for skipped tools in native tool calling when didRejectTool is true", async () => { // Simulate multiple tool calls with native protocol (all have IDs) const toolCallId1 = "tool_call_001" const toolCallId2 = "tool_call_002" @@ -261,7 +265,7 @@ describe("presentAssistantMessage - Image Handling in Native Tool Calls", () => expect(textBlocks.length).toBe(0) }) - it("should send tool_result with is_error for skipped tools in native protocol when didAlreadyUseTool is true", async () => { + it("should send tool_result with is_error for skipped tools in native tool calling when didAlreadyUseTool is true", async () => { // Simulate multiple tool calls with native protocol const toolCallId1 = "tool_call_003" const toolCallId2 = "tool_call_004" @@ -306,18 +310,15 @@ describe("presentAssistantMessage - Image Handling in Native Tool Calls", () => expect(textBlocks.length).toBe(0) }) - it("should send text blocks for skipped tools in XML protocol (no tool IDs)", async () => { - // Simulate multiple tool calls with XML protocol (no IDs) + it("should reject subsequent tool calls when a legacy/XML-style tool call is encountered", async () => { mockTask.assistantMessageContent = [ { type: "tool_use", - // No ID = XML protocol name: "read_file", params: { path: "test.txt" }, }, { type: "tool_use", - // No ID = XML protocol name: "write_to_file", params: { path: "output.txt", content: "test" }, }, @@ -330,18 +331,15 @@ describe("presentAssistantMessage - Image Handling in Native Tool Calls", () => mockTask.currentStreamingContentIndex = 1 await presentAssistantMessage(mockTask) - // For XML protocol, should add text block (not tool_result) - const textBlocks = mockTask.userMessageContent.filter( - (item: any) => item.type === "text" && item.text.includes("due to user rejecting"), + const textBlocks = mockTask.userMessageContent.filter((item: any) => item.type === "text") + expect(textBlocks.some((b: any) => String(b.text).includes("XML tool calls are no longer supported"))).toBe( + true, ) - expect(textBlocks.length).toBeGreaterThan(0) - // Ensure no tool_result blocks were added - const toolResults = mockTask.userMessageContent.filter((item: any) => item.type === "tool_result") - expect(toolResults.length).toBe(0) + expect(mockTask.userMessageContent.some((item: any) => item.type === "tool_result")).toBe(false) }) - it("should handle partial tool blocks when didRejectTool is true in native protocol", async () => { + it("should handle partial tool blocks when didRejectTool is true in native tool calling", async () => { const toolCallId = "tool_call_005" mockTask.assistantMessageContent = [ diff --git a/src/core/assistant-message/__tests__/presentAssistantMessage-unknown-tool.spec.ts b/src/core/assistant-message/__tests__/presentAssistantMessage-unknown-tool.spec.ts index d4ae2764a05..e4a50be925a 100644 --- a/src/core/assistant-message/__tests__/presentAssistantMessage-unknown-tool.spec.ts +++ b/src/core/assistant-message/__tests__/presentAssistantMessage-unknown-tool.spec.ts @@ -7,6 +7,7 @@ import { presentAssistantMessage } from "../presentAssistantMessage" vi.mock("../../task/Task") vi.mock("../../tools/validateToolUse", () => ({ validateToolUse: vi.fn(), + isValidToolName: vi.fn(() => false), })) vi.mock("@roo-code/telemetry", () => ({ TelemetryService: { @@ -74,12 +75,12 @@ describe("presentAssistantMessage - Unknown Tool Handling", () => { }) it("should return error for unknown tool in native protocol", async () => { - // Set up a tool_use block with an unknown tool name and an ID (native protocol) + // Set up a tool_use block with an unknown tool name and an ID (native tool calling) const toolCallId = "tool_call_unknown_123" mockTask.assistantMessageContent = [ { type: "tool_use", - id: toolCallId, // ID indicates native protocol + id: toolCallId, // ID indicates native tool calling name: "nonexistent_tool", params: { some: "param" }, partial: false, @@ -114,12 +115,11 @@ describe("presentAssistantMessage - Unknown Tool Handling", () => { expect(mockTask.say).toHaveBeenCalledWith("error", "unknownToolError") }) - it("should return error for unknown tool in XML protocol", async () => { - // Set up a tool_use block with an unknown tool name WITHOUT an ID (XML protocol) + it("should fail fast when tool_use is missing id (legacy/XML-style tool call)", async () => { + // tool_use without an id is treated as legacy/XML-style tool call and must be rejected. mockTask.assistantMessageContent = [ { type: "tool_use", - // No ID = XML protocol name: "fake_tool_that_does_not_exist", params: { param1: "value1" }, partial: false, @@ -129,16 +129,12 @@ describe("presentAssistantMessage - Unknown Tool Handling", () => { // Execute presentAssistantMessage await presentAssistantMessage(mockTask) - // For XML protocol, error is pushed as text blocks + // Should not execute tool; should surface a clear error message. const textBlocks = mockTask.userMessageContent.filter((item: any) => item.type === "text") - - // There should be text blocks with error message expect(textBlocks.length).toBeGreaterThan(0) - const hasErrorMessage = textBlocks.some( - (block: any) => - block.text?.includes("fake_tool_that_does_not_exist") && block.text?.includes("does not exist"), + expect(textBlocks.some((b: any) => String(b.text).includes("XML tool calls are no longer supported"))).toBe( + true, ) - expect(hasErrorMessage).toBe(true) // Verify consecutiveMistakeCount was incremented expect(mockTask.consecutiveMistakeCount).toBe(1) @@ -146,17 +142,17 @@ describe("presentAssistantMessage - Unknown Tool Handling", () => { // Verify recordToolError was called expect(mockTask.recordToolError).toHaveBeenCalled() - // Verify error message was shown to user (uses i18n key) - expect(mockTask.say).toHaveBeenCalledWith("error", "unknownToolError") + // Verify error message was shown to user + expect(mockTask.say).toHaveBeenCalledWith("error", expect.anything()) }) - it("should handle unknown tool without freezing (native protocol)", async () => { + it("should handle unknown tool without freezing (native tool calling)", async () => { // This test ensures the extension doesn't freeze when an unknown tool is called const toolCallId = "tool_call_freeze_test" mockTask.assistantMessageContent = [ { type: "tool_use", - id: toolCallId, // Native protocol + id: toolCallId, // Native tool calling name: "this_tool_definitely_does_not_exist", params: {}, partial: false, diff --git a/src/core/assistant-message/index.ts b/src/core/assistant-message/index.ts index 72201b7722e..107424fc503 100644 --- a/src/core/assistant-message/index.ts +++ b/src/core/assistant-message/index.ts @@ -1,2 +1,2 @@ -export { type AssistantMessageContent, parseAssistantMessage } from "./parseAssistantMessage" +export type { AssistantMessageContent } from "./types" export { presentAssistantMessage } from "./presentAssistantMessage" diff --git a/src/core/assistant-message/parseAssistantMessage.ts b/src/core/assistant-message/parseAssistantMessage.ts deleted file mode 100644 index e07b8cc3db8..00000000000 --- a/src/core/assistant-message/parseAssistantMessage.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { type ToolName, toolNames } from "@roo-code/types" - -import { TextContent, ToolUse, McpToolUse, ToolParamName, toolParamNames } from "../../shared/tools" - -export type AssistantMessageContent = TextContent | ToolUse | McpToolUse - -export function parseAssistantMessage(assistantMessage: string): AssistantMessageContent[] { - let contentBlocks: AssistantMessageContent[] = [] - let currentTextContent: TextContent | undefined = undefined - let currentTextContentStartIndex = 0 - let currentToolUse: ToolUse | undefined = undefined - let currentToolUseStartIndex = 0 - let currentParamName: ToolParamName | undefined = undefined - let currentParamValueStartIndex = 0 - let accumulator = "" - - for (let i = 0; i < assistantMessage.length; i++) { - const char = assistantMessage[i] - accumulator += char - - // There should not be a param without a tool use. - if (currentToolUse && currentParamName) { - const currentParamValue = accumulator.slice(currentParamValueStartIndex) - const paramClosingTag = `` - if (currentParamValue.endsWith(paramClosingTag)) { - // End of param value. - // Don't trim content parameters to preserve newlines, but strip first and last newline only - const paramValue = currentParamValue.slice(0, -paramClosingTag.length) - currentToolUse.params[currentParamName] = - currentParamName === "content" - ? paramValue.replace(/^\n/, "").replace(/\n$/, "") - : paramValue.trim() - currentParamName = undefined - continue - } else { - // Partial param value is accumulating. - continue - } - } - - // No currentParamName. - - if (currentToolUse) { - const currentToolValue = accumulator.slice(currentToolUseStartIndex) - const toolUseClosingTag = `` - if (currentToolValue.endsWith(toolUseClosingTag)) { - // End of a tool use. - currentToolUse.partial = false - contentBlocks.push(currentToolUse) - currentToolUse = undefined - continue - } else { - const possibleParamOpeningTags = toolParamNames.map((name) => `<${name}>`) - for (const paramOpeningTag of possibleParamOpeningTags) { - if (accumulator.endsWith(paramOpeningTag)) { - // Start of a new parameter. - currentParamName = paramOpeningTag.slice(1, -1) as ToolParamName - currentParamValueStartIndex = accumulator.length - break - } - } - - // There's no current param, and not starting a new param. - - // Special case for write_to_file where file contents could - // contain the closing tag, in which case the param would have - // closed and we end up with the rest of the file contents here. - // To work around this, we get the string between the starting - // content tag and the LAST content tag. - const contentParamName: ToolParamName = "content" - - if (currentToolUse.name === "write_to_file" && accumulator.endsWith(``)) { - const toolContent = accumulator.slice(currentToolUseStartIndex) - const contentStartTag = `<${contentParamName}>` - const contentEndTag = `` - const contentStartIndex = toolContent.indexOf(contentStartTag) + contentStartTag.length - const contentEndIndex = toolContent.lastIndexOf(contentEndTag) - - if (contentStartIndex !== -1 && contentEndIndex !== -1 && contentEndIndex > contentStartIndex) { - // Don't trim content to preserve newlines, but strip first and last newline only - currentToolUse.params[contentParamName] = toolContent - .slice(contentStartIndex, contentEndIndex) - .replace(/^\n/, "") - .replace(/\n$/, "") - } - } - - // Partial tool value is accumulating. - continue - } - } - - // No currentToolUse. - - let didStartToolUse = false - const possibleToolUseOpeningTags = toolNames.map((name) => `<${name}>`) - - for (const toolUseOpeningTag of possibleToolUseOpeningTags) { - if (accumulator.endsWith(toolUseOpeningTag)) { - // Start of a new tool use. - currentToolUse = { - type: "tool_use", - name: toolUseOpeningTag.slice(1, -1) as ToolName, - params: {}, - partial: true, - } - - currentToolUseStartIndex = accumulator.length - - // This also indicates the end of the current text content. - if (currentTextContent) { - currentTextContent.partial = false - - // Remove the partially accumulated tool use tag from the - // end of text (() - const toolParamOpenTags = new Map() - - for (const name of toolNames) { - toolUseOpenTags.set(`<${name}>`, name) - } - - for (const name of toolParamNames) { - toolParamOpenTags.set(`<${name}>`, name) - } - - const len = assistantMessage.length - - for (let i = 0; i < len; i++) { - const currentCharIndex = i - - // Parsing a tool parameter - if (currentToolUse && currentParamName) { - const closeTag = `` - // Check if the string *ending* at index `i` matches the closing tag - if ( - currentCharIndex >= closeTag.length - 1 && - assistantMessage.startsWith( - closeTag, - currentCharIndex - closeTag.length + 1, // Start checking from potential start of tag. - ) - ) { - // Found the closing tag for the parameter. - const value = assistantMessage.slice( - currentParamValueStart, // Start after the opening tag. - currentCharIndex - closeTag.length + 1, // End before the closing tag. - ) - // Don't trim content parameters to preserve newlines, but strip first and last newline only - currentToolUse.params[currentParamName] = - currentParamName === "content" ? value.replace(/^\n/, "").replace(/\n$/, "") : value.trim() - currentParamName = undefined // Go back to parsing tool content. - // We don't continue loop here, need to check for tool close or other params at index i. - } else { - continue // Still inside param value, move to next char. - } - } - - // Parsing a tool use (but not a specific parameter). - if (currentToolUse && !currentParamName) { - // Ensure we are not inside a parameter already. - // Check if starting a new parameter. - let startedNewParam = false - - for (const [tag, paramName] of toolParamOpenTags.entries()) { - if ( - currentCharIndex >= tag.length - 1 && - assistantMessage.startsWith(tag, currentCharIndex - tag.length + 1) - ) { - currentParamName = paramName - currentParamValueStart = currentCharIndex + 1 // Value starts after the tag. - startedNewParam = true - break - } - } - - if (startedNewParam) { - continue // Handled start of param, move to next char. - } - - // Check if closing the current tool use. - const toolCloseTag = `` - - if ( - currentCharIndex >= toolCloseTag.length - 1 && - assistantMessage.startsWith(toolCloseTag, currentCharIndex - toolCloseTag.length + 1) - ) { - // End of the tool use found. - // Special handling for content params *before* finalizing the - // tool. - const toolContentSlice = assistantMessage.slice( - currentToolUseStart, // From after the tool opening tag. - currentCharIndex - toolCloseTag.length + 1, // To before the tool closing tag. - ) - - // Check if content parameter needs special handling - // (write_to_file/new_rule). - // This check is important if the closing tag was - // missed by the parameter parsing logic (e.g., if content is - // empty or parsing logic prioritizes tool close). - const contentParamName: ToolParamName = "content" - if ( - currentToolUse.name === "write_to_file" /* || currentToolUse.name === "new_rule" */ && - // !(contentParamName in currentToolUse.params) && // Only if not already parsed. - toolContentSlice.includes(`<${contentParamName}>`) // Check if tag exists. - ) { - const contentStartTag = `<${contentParamName}>` - const contentEndTag = `` - const contentStart = toolContentSlice.indexOf(contentStartTag) - - // Use `lastIndexOf` for robustness against nested tags. - const contentEnd = toolContentSlice.lastIndexOf(contentEndTag) - - if (contentStart !== -1 && contentEnd !== -1 && contentEnd > contentStart) { - // Don't trim content to preserve newlines, but strip first and last newline only - const contentValue = toolContentSlice - .slice(contentStart + contentStartTag.length, contentEnd) - .replace(/^\n/, "") - .replace(/\n$/, "") - currentToolUse.params[contentParamName] = contentValue - } - } - - currentToolUse.partial = false // Mark as complete. - contentBlocks.push(currentToolUse) - currentToolUse = undefined // Reset state. - currentTextContentStart = currentCharIndex + 1 // Potential text starts after this tag. - continue // Move to next char. - } - - // If not starting a param and not closing the tool, continue - // accumulating tool content implicitly. - continue - } - - // Parsing text / looking for tool start. - if (!currentToolUse) { - // Check if starting a new tool use. - let startedNewTool = false - - for (const [tag, toolName] of toolUseOpenTags.entries()) { - if ( - currentCharIndex >= tag.length - 1 && - assistantMessage.startsWith(tag, currentCharIndex - tag.length + 1) - ) { - // End current text block if one was active. - if (currentTextContent) { - currentTextContent.content = assistantMessage - .slice( - currentTextContentStart, // From where text started. - currentCharIndex - tag.length + 1, // To before the tool tag starts. - ) - .trim() - - currentTextContent.partial = false // Ended because tool started. - - if (currentTextContent.content.length > 0) { - contentBlocks.push(currentTextContent) - } - - currentTextContent = undefined - } else { - // Check for any text between the last block and this tag. - const potentialText = assistantMessage - .slice( - currentTextContentStart, // From where text *might* have started. - currentCharIndex - tag.length + 1, // To before the tool tag starts. - ) - .trim() - - if (potentialText.length > 0) { - contentBlocks.push({ - type: "text", - content: potentialText, - partial: false, - }) - } - } - - // Start the new tool use. - currentToolUse = { - type: "tool_use", - name: toolName, - params: {}, - partial: true, // Assume partial until closing tag is found. - } - - currentToolUseStart = currentCharIndex + 1 // Tool content starts after the opening tag. - startedNewTool = true - - break - } - } - - if (startedNewTool) { - continue // Handled start of tool, move to next char. - } - - // If not starting a tool, it must be text content. - if (!currentTextContent) { - // Start a new text block if we aren't already in one. - currentTextContentStart = currentCharIndex // Text starts at the current character. - - // Check if the current char is the start of potential text *immediately* after a tag. - // This needs the previous state - simpler to let slicing handle it later. - // Resetting start index accurately is key. - // It should be the index *after* the last processed tag. - // The logic managing currentTextContentStart after closing tags handles this. - currentTextContent = { - type: "text", - content: "", // Will be determined by slicing at the end or when a tool starts - partial: true, - } - } - // Continue accumulating text implicitly; content is extracted later. - } - } - - // Finalize any open parameter within an open tool use. - if (currentToolUse && currentParamName) { - const value = assistantMessage.slice(currentParamValueStart) // From param start to end of string. - // Don't trim content parameters to preserve newlines, but strip first and last newline only - currentToolUse.params[currentParamName] = - currentParamName === "content" ? value.replace(/^\n/, "").replace(/\n$/, "") : value.trim() - // Tool use remains partial. - } - - // Finalize any open tool use (which might contain the finalized partial param). - if (currentToolUse) { - // Tool use is partial because the loop finished before its closing tag. - contentBlocks.push(currentToolUse) - } - // Finalize any trailing text content. - // Only possible if a tool use wasn't open at the very end. - else if (currentTextContent) { - currentTextContent.content = assistantMessage - .slice(currentTextContentStart) // From text start to end of string. - .trim() - - // Text is partial because the loop finished. - if (currentTextContent.content.length > 0) { - contentBlocks.push(currentTextContent) - } - } - - return contentBlocks -} diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index 693327a022e..6469ba8a5cc 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -18,7 +18,6 @@ import { Task } from "../task/Task" import { fetchInstructionsTool } from "../tools/FetchInstructionsTool" import { listFilesTool } from "../tools/ListFilesTool" import { readFileTool } from "../tools/ReadFileTool" -import { TOOL_PROTOCOL } from "@roo-code/types" import { writeToFileTool } from "../tools/WriteToFileTool" import { applyDiffTool } from "../tools/MultiApplyDiffTool" import { searchAndReplaceTool } from "../tools/SearchAndReplaceTool" @@ -38,7 +37,7 @@ import { updateTodoListTool } from "../tools/UpdateTodoListTool" import { runSlashCommandTool } from "../tools/RunSlashCommandTool" import { generateImageTool } from "../tools/GenerateImageTool" import { applyDiffTool as applyDiffToolClass } from "../tools/ApplyDiffTool" -import { validateToolUse } from "../tools/validateToolUse" +import { isValidToolName, validateToolUse } from "../tools/validateToolUse" import { codebaseSearchTool } from "../tools/CodebaseSearchTool" import { formatResponse } from "../prompts/responses" @@ -146,7 +145,6 @@ export async function presentAssistantMessage(cline: Task) { // Track if we've already pushed a tool result let hasToolResult = false const toolCallId = mcpBlock.id - const toolProtocol = TOOL_PROTOCOL.NATIVE // MCP tools in native mode always use native protocol // Store approval feedback to merge into tool result (GitHub #10465) let approvalFeedback: { text: string; images?: string[] } | undefined @@ -174,7 +172,7 @@ export async function presentAssistantMessage(cline: Task) { // Merge approval feedback into tool result (GitHub #10465) if (approvalFeedback) { - const feedbackText = formatResponse.toolApprovedWithFeedback(approvalFeedback.text, toolProtocol) + const feedbackText = formatResponse.toolApprovedWithFeedback(approvalFeedback.text) resultContent = `${feedbackText}\n\n${resultContent}` // Add feedback images to the image blocks @@ -219,14 +217,9 @@ export async function presentAssistantMessage(cline: Task) { if (response !== "yesButtonClicked") { if (text) { await cline.say("user_feedback", text, images) - pushToolResult( - formatResponse.toolResult( - formatResponse.toolDeniedWithFeedback(text, toolProtocol), - images, - ), - ) + pushToolResult(formatResponse.toolResult(formatResponse.toolDeniedWithFeedback(text), images)) } else { - pushToolResult(formatResponse.toolDenied(toolProtocol)) + pushToolResult(formatResponse.toolDenied()) } cline.didRejectTool = true return false @@ -254,12 +247,12 @@ export async function presentAssistantMessage(cline: Task) { "error", `Error ${action}:\n${error.message ?? JSON.stringify(serializeError(error), null, 2)}`, ) - pushToolResult(formatResponse.toolError(errorString, toolProtocol)) + pushToolResult(formatResponse.toolError(errorString)) } if (!mcpBlock.partial) { cline.recordToolUsage("use_mcp_tool") // Record as use_mcp_tool for analytics - TelemetryService.instance.captureToolUsage(cline.taskId, "use_mcp_tool", toolProtocol) + TelemetryService.instance.captureToolUsage(cline.taskId, "use_mcp_tool") } // Resolve sanitized server name back to original server name @@ -297,8 +290,6 @@ export async function presentAssistantMessage(cline: Task) { askApproval, handleError, pushToolResult, - removeClosingTag: (tag, text) => text || "", - toolProtocol, }) break } @@ -313,58 +304,20 @@ export async function presentAssistantMessage(cline: Task) { // Have to do this for partial and complete since sending // content in thinking tags to markdown renderer will // automatically be removed. - // Remove end substrings of (with optional line break - // after) and (with optional line break before). - // - Needs to be separate since we dont want to remove the line - // break before the first tag. - // - Needs to happen before the xml parsing below. + // Strip any streamed tags from text output. content = content.replace(/\s?/g, "") content = content.replace(/\s?<\/thinking>/g, "") - // Remove partial XML tag at the very end of the content (for - // tool use and thinking tags), Prevents scrollview from - // jumping when tags are automatically removed. - const lastOpenBracketIndex = content.lastIndexOf("<") - - if (lastOpenBracketIndex !== -1) { - const possibleTag = content.slice(lastOpenBracketIndex) - - // Check if there's a '>' after the last '<' (i.e., if the - // tag is complete) (complete thinking and tool tags will - // have been removed by now.) - const hasCloseBracket = possibleTag.includes(">") - - if (!hasCloseBracket) { - // Extract the potential tag name. - let tagContent: string - - if (possibleTag.startsWith("...
) and use native tool calling instead." + cline.consecutiveMistakeCount++ + await cline.say("error", errorMessage) + cline.userMessageContent.push({ type: "text", text: errorMessage }) + cline.didAlreadyUseTool = true + break } } @@ -372,6 +325,30 @@ export async function presentAssistantMessage(cline: Task) { break } case "tool_use": { + // Native tool calling is the only supported tool calling mechanism. + // A tool_use block without an id is invalid and cannot be executed. + const toolCallId = (block as any).id as string | undefined + if (!toolCallId) { + const errorMessage = + "Invalid tool call: missing tool_use.id. XML tool calls are no longer supported. Remove any XML tool markup (e.g. ...) and use native tool calling instead." + // Record a tool error for visibility/telemetry. Use the reported tool name if present. + try { + if ( + typeof (cline as any).recordToolError === "function" && + typeof (block as any).name === "string" + ) { + ;(cline as any).recordToolError((block as any).name as ToolName, errorMessage) + } + } catch { + // Best-effort only + } + cline.consecutiveMistakeCount++ + await cline.say("error", errorMessage) + cline.userMessageContent.push({ type: "text", text: errorMessage }) + cline.didAlreadyUseTool = true + break + } + // Fetch state early so it's available for toolDescription and validation const state = await cline.providerRef.deref()?.getState() const { mode, customModes, experiments: stateExperiments } = state ?? {} @@ -392,24 +369,8 @@ export async function presentAssistantMessage(cline: Task) { case "write_to_file": return `[${block.name} for '${block.params.path}']` case "apply_diff": - // Handle both legacy format and new multi-file format - if (block.params.path) { - return `[${block.name} for '${block.params.path}']` - } else if (block.params.args) { - // Try to extract first file path from args for display - const match = block.params.args.match(/.*?([^<]+)<\/path>/s) - if (match) { - const firstPath = match[1] - // Check if there are multiple files - const fileCount = (block.params.args.match(//g) || []).length - if (fileCount > 1) { - return `[${block.name} for '${firstPath}' and ${fileCount - 1} more file${fileCount > 2 ? "s" : ""}]` - } else { - return `[${block.name} for '${firstPath}']` - } - } - } - return `[${block.name}]` + // Native-only: tool args are structured (no XML payloads). + return block.params?.path ? `[${block.name} for '${block.params.path}']` : `[${block.name}]` case "search_files": return `[${block.name} for '${block.params.regex}'${ block.params.file_pattern ? ` in '${block.params.file_pattern}'` : "" @@ -457,185 +418,121 @@ export async function presentAssistantMessage(cline: Task) { if (cline.didRejectTool) { // Ignore any tool content after user has rejected tool once. - // For native protocol, we must send a tool_result for every tool_use to avoid API errors - const toolCallId = block.id + // For native tool calling, we must send a tool_result for every tool_use to avoid API errors const errorMessage = !block.partial ? `Skipping tool ${toolDescription()} due to user rejecting a previous tool.` : `Tool ${toolDescription()} was interrupted and not executed due to user rejecting a previous tool.` - if (toolCallId) { - // Native protocol: MUST send tool_result for every tool_use - cline.pushToolResultToUserContent({ - type: "tool_result", - tool_use_id: toolCallId, - content: errorMessage, - is_error: true, - }) - } else { - // XML protocol: send as text - cline.userMessageContent.push({ - type: "text", - text: errorMessage, - }) - } + cline.pushToolResultToUserContent({ + type: "tool_result", + tool_use_id: toolCallId, + content: errorMessage, + is_error: true, + }) break } if (cline.didAlreadyUseTool) { // Ignore any content after a tool has already been used. - // For native protocol, we must send a tool_result for every tool_use to avoid API errors - const toolCallId = block.id + // For native tool calling, we must send a tool_result for every tool_use to avoid API errors const errorMessage = `Tool [${block.name}] was not executed because a tool has already been used in this message. Only one tool may be used per message. You must assess the first tool's result before proceeding to use the next tool.` - if (toolCallId) { - // Native protocol: MUST send tool_result for every tool_use - cline.pushToolResultToUserContent({ - type: "tool_result", - tool_use_id: toolCallId, - content: errorMessage, - is_error: true, - }) - } else { - // XML protocol: send as text - cline.userMessageContent.push({ - type: "text", - text: errorMessage, - }) - } + cline.pushToolResultToUserContent({ + type: "tool_result", + tool_use_id: toolCallId, + content: errorMessage, + is_error: true, + }) break } - // Track if we've already pushed a tool result for this tool call (native protocol only) + // Track if we've already pushed a tool result for this tool call (native tool calling only) let hasToolResult = false - // Determine protocol by checking if this tool call has an ID. - // 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 toolProtocol = toolCallId ? TOOL_PROTOCOL.NATIVE : TOOL_PROTOCOL.XML - - // Multiple native tool calls feature is on hold - always disabled - // Previously resolved from experiments.isEnabled(..., EXPERIMENT_IDS.MULTIPLE_NATIVE_TOOL_CALLS) - const isMultipleNativeToolCallsEnabled = false - - // Store approval feedback to merge into tool result (GitHub #10465) - let approvalFeedback: { text: string; images?: string[] } | undefined - - const pushToolResult = (content: ToolResponse) => { - if (toolProtocol === TOOL_PROTOCOL.NATIVE) { - // For native protocol, only allow ONE tool_result per tool call - if (hasToolResult) { - console.warn( - `[presentAssistantMessage] Skipping duplicate tool_result for tool_use_id: ${toolCallId}`, - ) - return - } - - // For native protocol, tool_result content must be a string - // Images are added as separate blocks in the user message - let resultContent: string - let imageBlocks: Anthropic.ImageBlockParam[] = [] - - if (typeof content === "string") { - resultContent = content || "(tool did not return anything)" - } else { - // Separate text and image blocks - const textBlocks = content.filter((item) => item.type === "text") - imageBlocks = content.filter((item) => item.type === "image") as Anthropic.ImageBlockParam[] - - // Convert text blocks to string for tool_result - resultContent = - textBlocks.map((item) => (item as Anthropic.TextBlockParam).text).join("\n") || - "(tool did not return anything)" - } - - // Merge approval feedback into tool result (GitHub #10465) - if (approvalFeedback) { - const feedbackText = formatResponse.toolApprovedWithFeedback( - approvalFeedback.text, - toolProtocol, - ) - resultContent = `${feedbackText}\n\n${resultContent}` + // If this is a native tool call but the parser couldn't construct nativeArgs + // (e.g., malformed/unfinished JSON in a streaming tool call), we must NOT attempt to + // execute the tool. Instead, emit exactly one structured tool_result so the provider + // receives a matching tool_result for the tool_use_id. + // + // This avoids executing an invalid tool_use block and prevents duplicate/fragmented + // error reporting. + if (!block.partial) { + const customTool = stateExperiments?.customTools ? customToolRegistry.get(block.name) : undefined + const isKnownTool = isValidToolName(String(block.name), stateExperiments) + if (isKnownTool && !block.nativeArgs && !customTool) { + const errorMessage = + `Invalid tool call for '${block.name}': missing nativeArgs. ` + + `This usually means the model streamed invalid or incomplete arguments and the call could not be finalized.` - // Add feedback images to the image blocks - if (approvalFeedback.images) { - const feedbackImageBlocks = formatResponse.imageBlocks(approvalFeedback.images) - imageBlocks = [...feedbackImageBlocks, ...imageBlocks] - } + cline.consecutiveMistakeCount++ + try { + cline.recordToolError(block.name as ToolName, errorMessage) + } catch { + // Best-effort only } - // Add tool_result with text content only + // Push tool_result directly without setting didAlreadyUseTool so streaming can + // continue gracefully. cline.pushToolResultToUserContent({ type: "tool_result", tool_use_id: toolCallId, - content: resultContent, + content: formatResponse.toolError(errorMessage), + is_error: true, }) - // Add image blocks separately after tool_result - if (imageBlocks.length > 0) { - cline.userMessageContent.push(...imageBlocks) - } + break + } + } - hasToolResult = true - } else { - // For XML protocol, add as text blocks (legacy behavior) - let resultContent: string + // Store approval feedback to merge into tool result (GitHub #10465) + let approvalFeedback: { text: string; images?: string[] } | undefined - if (typeof content === "string") { - resultContent = content || "(tool did not return anything)" - } else { - const textBlocks = content.filter((item) => item.type === "text") - resultContent = - textBlocks.map((item) => (item as Anthropic.TextBlockParam).text).join("\n") || - "(tool did not return anything)" - } + const pushToolResult = (content: ToolResponse) => { + // Native tool calling: only allow ONE tool_result per tool call + if (hasToolResult) { + console.warn( + `[presentAssistantMessage] Skipping duplicate tool_result for tool_use_id: ${toolCallId}`, + ) + return + } - // Merge approval feedback into tool result (GitHub #10465) - if (approvalFeedback) { - const feedbackText = formatResponse.toolApprovedWithFeedback( - approvalFeedback.text, - toolProtocol, - ) - resultContent = `${feedbackText}\n\n${resultContent}` - } + let resultContent: string + let imageBlocks: Anthropic.ImageBlockParam[] = [] - cline.userMessageContent.push({ type: "text", text: `${toolDescription()} Result:` }) + if (typeof content === "string") { + resultContent = content || "(tool did not return anything)" + } else { + const textBlocks = content.filter((item) => item.type === "text") + imageBlocks = content.filter((item) => item.type === "image") as Anthropic.ImageBlockParam[] + resultContent = + textBlocks.map((item) => (item as Anthropic.TextBlockParam).text).join("\n") || + "(tool did not return anything)" + } - if (typeof content === "string") { - cline.userMessageContent.push({ - type: "text", - text: resultContent, - }) - } else { - // Add text content with merged feedback - cline.userMessageContent.push({ - type: "text", - text: resultContent, - }) - // Add any images from the tool result - const imageBlocks = content.filter((item) => item.type === "image") - if (imageBlocks.length > 0) { - cline.userMessageContent.push(...imageBlocks) - } + // Merge approval feedback into tool result (GitHub #10465) + if (approvalFeedback) { + const feedbackText = formatResponse.toolApprovedWithFeedback(approvalFeedback.text) + resultContent = `${feedbackText}\n\n${resultContent}` + if (approvalFeedback.images) { + const feedbackImageBlocks = formatResponse.imageBlocks(approvalFeedback.images) + imageBlocks = [...feedbackImageBlocks, ...imageBlocks] } } - // For XML protocol: Only one tool per message is allowed - // For native protocol with experimental flag enabled: Multiple tools can be executed in sequence - // For native protocol with experimental flag disabled: Single tool per message (default safe behavior) - if (toolProtocol === TOOL_PROTOCOL.XML) { - // Once a tool result has been collected, ignore all other tool - // uses since we should only ever present one tool result per - // message (XML protocol only). - cline.didAlreadyUseTool = true - } else if (toolProtocol === TOOL_PROTOCOL.NATIVE && !isMultipleNativeToolCallsEnabled) { - // For native protocol with experimental flag disabled, enforce single tool per message - cline.didAlreadyUseTool = true + cline.pushToolResultToUserContent({ + type: "tool_result", + tool_use_id: toolCallId, + content: resultContent, + }) + + if (imageBlocks.length > 0) { + cline.userMessageContent.push(...imageBlocks) } - // If toolProtocol is NATIVE and isMultipleNativeToolCallsEnabled is true, - // allow multiple tool calls in sequence (don't set didAlreadyUseTool) + + hasToolResult = true + cline.didAlreadyUseTool = true } const askApproval = async ( @@ -656,14 +553,9 @@ 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, toolProtocol), - images, - ), - ) + pushToolResult(formatResponse.toolResult(formatResponse.toolDeniedWithFeedback(text), images)) } else { - pushToolResult(formatResponse.toolDenied(toolProtocol)) + pushToolResult(formatResponse.toolDenied()) } cline.didRejectTool = true return false @@ -702,34 +594,7 @@ export async function presentAssistantMessage(cline: Task) { `Error ${action}:\n${error.message ?? JSON.stringify(serializeError(error), null, 2)}`, ) - pushToolResult(formatResponse.toolError(errorString, toolProtocol)) - } - - // If block is partial, remove partial closing tag so its not - // presented to user. - const removeClosingTag = (tag: ToolParamName, text?: string): string => { - if (!block.partial) { - return text || "" - } - - if (!text) { - return "" - } - - // This regex dynamically constructs a pattern to match the - // closing tag: - // - Optionally matches whitespace before the tag. - // - Matches '<' or ' `(?:${char})?`) - .join("")}$`, - "g", - ) - - return text.replace(tagRegex, "") + pushToolResult(formatResponse.toolError(errorString)) } // Keep browser open during an active session so other tools can run. @@ -765,7 +630,7 @@ export async function presentAssistantMessage(cline: Task) { const isCustomTool = stateExperiments?.customTools && customToolRegistry.has(block.name) const recordName = isCustomTool ? "custom_tool" : block.name cline.recordToolUsage(recordName) - TelemetryService.instance.captureToolUsage(cline.taskId, recordName, toolProtocol) + TelemetryService.instance.captureToolUsage(cline.taskId, recordName) } // Validate tool use before execution - ONLY for complete (non-partial) blocks. @@ -793,24 +658,18 @@ export async function presentAssistantMessage(cline: Task) { } catch (error) { cline.consecutiveMistakeCount++ // For validation errors (unknown tool, tool not allowed for mode), we need to: - // 1. Send a tool_result with the error (required for native protocol) + // 1. Send a tool_result with the error (required for native tool calling) // 2. NOT set didAlreadyUseTool = true (the tool was never executed, just failed validation) // This prevents the stream from being interrupted with "Response interrupted by tool use result" // which would cause the extension to appear to hang - const errorContent = formatResponse.toolError(error.message, toolProtocol) - - if (toolProtocol === TOOL_PROTOCOL.NATIVE && toolCallId) { - // For native protocol, push tool_result directly without setting didAlreadyUseTool - cline.pushToolResultToUserContent({ - type: "tool_result", - tool_use_id: toolCallId, - content: typeof errorContent === "string" ? errorContent : "(validation error)", - is_error: true, - }) - } else { - // For XML protocol, use the standard pushToolResult - pushToolResult(errorContent) - } + const errorContent = formatResponse.toolError(error.message) + // Push tool_result directly without setting didAlreadyUseTool + cline.pushToolResultToUserContent({ + type: "tool_result", + tool_use_id: toolCallId, + content: typeof errorContent === "string" ? errorContent : "(validation error)", + is_error: true, + }) break } @@ -862,7 +721,6 @@ export async function presentAssistantMessage(cline: Task) { pushToolResult( formatResponse.toolError( `Tool call repetition limit reached for ${block.name}. Please try a different approach.`, - toolProtocol, ), ) break @@ -876,8 +734,6 @@ export async function presentAssistantMessage(cline: Task) { askApproval, handleError, pushToolResult, - removeClosingTag, - toolProtocol, }) break case "update_todo_list": @@ -885,26 +741,11 @@ export async function presentAssistantMessage(cline: Task) { askApproval, 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 (toolProtocol === TOOL_PROTOCOL.NATIVE) { - await applyDiffToolClass.handle(cline, block as ToolUse<"apply_diff">, { - askApproval, - handleError, - pushToolResult, - removeClosingTag, - toolProtocol, - }) - break - } - // Get the provider and state to check experiment settings const provider = cline.providerRef.deref() let isMultiFileApplyDiffEnabled = false @@ -918,14 +759,12 @@ export async function presentAssistantMessage(cline: Task) { } if (isMultiFileApplyDiffEnabled) { - await applyDiffTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag) + await applyDiffTool(cline, block, askApproval, handleError, pushToolResult) } else { await applyDiffToolClass.handle(cline, block as ToolUse<"apply_diff">, { askApproval, handleError, pushToolResult, - removeClosingTag, - toolProtocol, }) } break @@ -936,8 +775,6 @@ export async function presentAssistantMessage(cline: Task) { askApproval, handleError, pushToolResult, - removeClosingTag, - toolProtocol, }) break case "search_replace": @@ -946,8 +783,6 @@ export async function presentAssistantMessage(cline: Task) { askApproval, handleError, pushToolResult, - removeClosingTag, - toolProtocol, }) break case "edit_file": @@ -956,8 +791,6 @@ export async function presentAssistantMessage(cline: Task) { askApproval, handleError, pushToolResult, - removeClosingTag, - toolProtocol, }) break case "apply_patch": @@ -966,8 +799,6 @@ export async function presentAssistantMessage(cline: Task) { askApproval, handleError, pushToolResult, - removeClosingTag, - toolProtocol, }) break case "read_file": @@ -976,8 +807,6 @@ export async function presentAssistantMessage(cline: Task) { askApproval, handleError, pushToolResult, - removeClosingTag, - toolProtocol, }) break case "fetch_instructions": @@ -985,8 +814,6 @@ export async function presentAssistantMessage(cline: Task) { askApproval, handleError, pushToolResult, - removeClosingTag, - toolProtocol, }) break case "list_files": @@ -994,8 +821,6 @@ export async function presentAssistantMessage(cline: Task) { askApproval, handleError, pushToolResult, - removeClosingTag, - toolProtocol, }) break case "codebase_search": @@ -1003,8 +828,6 @@ export async function presentAssistantMessage(cline: Task) { askApproval, handleError, pushToolResult, - removeClosingTag, - toolProtocol, }) break case "search_files": @@ -1012,8 +835,6 @@ export async function presentAssistantMessage(cline: Task) { askApproval, handleError, pushToolResult, - removeClosingTag, - toolProtocol, }) break case "browser_action": @@ -1023,7 +844,6 @@ export async function presentAssistantMessage(cline: Task) { askApproval, handleError, pushToolResult, - removeClosingTag, ) break case "execute_command": @@ -1031,8 +851,6 @@ export async function presentAssistantMessage(cline: Task) { askApproval, handleError, pushToolResult, - removeClosingTag, - toolProtocol, }) break case "use_mcp_tool": @@ -1040,8 +858,6 @@ export async function presentAssistantMessage(cline: Task) { askApproval, handleError, pushToolResult, - removeClosingTag, - toolProtocol, }) break case "access_mcp_resource": @@ -1049,8 +865,6 @@ export async function presentAssistantMessage(cline: Task) { askApproval, handleError, pushToolResult, - removeClosingTag, - toolProtocol, }) break case "ask_followup_question": @@ -1058,8 +872,6 @@ export async function presentAssistantMessage(cline: Task) { askApproval, handleError, pushToolResult, - removeClosingTag, - toolProtocol, }) break case "switch_mode": @@ -1067,8 +879,6 @@ export async function presentAssistantMessage(cline: Task) { askApproval, handleError, pushToolResult, - removeClosingTag, - toolProtocol, }) break case "new_task": @@ -1076,8 +886,6 @@ export async function presentAssistantMessage(cline: Task) { askApproval, handleError, pushToolResult, - removeClosingTag, - toolProtocol, toolCallId: block.id, }) break @@ -1086,10 +894,8 @@ export async function presentAssistantMessage(cline: Task) { askApproval, handleError, pushToolResult, - removeClosingTag, askFinishSubTaskApproval, toolDescription, - toolProtocol, } await attemptCompletionTool.handle( cline, @@ -1103,8 +909,6 @@ export async function presentAssistantMessage(cline: Task) { askApproval, handleError, pushToolResult, - removeClosingTag, - toolProtocol, }) break case "generate_image": @@ -1113,13 +917,11 @@ export async function presentAssistantMessage(cline: Task) { askApproval, handleError, pushToolResult, - removeClosingTag, - toolProtocol, }) break default: { // Handle unknown/invalid tool names OR custom tools - // This is critical for native protocol where every tool_use MUST have a tool_result + // This is critical for native tool calling where every tool_use MUST have a tool_result // CRITICAL: Don't process partial blocks for unknown tools - just let them stream in. // If we try to show errors for partial blocks, we'd show the error on every streaming chunk, @@ -1142,7 +944,7 @@ export async function presentAssistantMessage(cline: Task) { console.error(message) cline.consecutiveMistakeCount++ await cline.say("error", message) - pushToolResult(formatResponse.toolError(message, toolProtocol)) + pushToolResult(formatResponse.toolError(message)) break } } @@ -1173,18 +975,14 @@ export async function presentAssistantMessage(cline: Task) { cline.consecutiveMistakeCount++ cline.recordToolError(block.name as ToolName, errorMessage) await cline.say("error", t("tools:unknownToolError", { toolName: block.name })) - // Push tool_result directly for native protocol WITHOUT setting didAlreadyUseTool + // Push tool_result directly WITHOUT setting didAlreadyUseTool // This prevents the stream from being interrupted with "Response interrupted by tool use result" - if (toolProtocol === TOOL_PROTOCOL.NATIVE && toolCallId) { - cline.pushToolResultToUserContent({ - type: "tool_result", - tool_use_id: toolCallId, - content: formatResponse.toolError(errorMessage, toolProtocol), - is_error: true, - }) - } else { - pushToolResult(formatResponse.toolError(errorMessage, toolProtocol)) - } + cline.pushToolResultToUserContent({ + type: "tool_result", + tool_use_id: toolCallId, + content: formatResponse.toolError(errorMessage), + is_error: true, + }) break } } @@ -1264,3 +1062,47 @@ async function checkpointSaveAndMark(task: Task) { console.error(`[Task#presentAssistantMessage] Error saving checkpoint: ${error.message}`, error) } } + +function containsXmlToolMarkup(text: string): boolean { + // Keep this intentionally narrow: only reject XML-style tool tags matching our tool names. + // Avoid regex so we don't keep legacy XML parsing artifacts around. + // Note: This is a best-effort safeguard; tool_use blocks without an id are rejected elsewhere. + + // First, strip out content inside markdown code fences to avoid false positives + // when users paste documentation or examples containing tool tag references. + // This handles both fenced code blocks (```) and inline code (`). + const textWithoutCodeBlocks = text + .replace(/```[\s\S]*?```/g, "") // Remove fenced code blocks + .replace(/`[^`]+`/g, "") // Remove inline code + + const lower = textWithoutCodeBlocks.toLowerCase() + if (!lower.includes("<") || !lower.includes(">")) { + return false + } + + const toolNames = [ + "access_mcp_resource", + "apply_diff", + "apply_patch", + "ask_followup_question", + "attempt_completion", + "browser_action", + "codebase_search", + "edit_file", + "execute_command", + "fetch_instructions", + "generate_image", + "list_files", + "new_task", + "read_file", + "search_and_replace", + "search_files", + "search_replace", + "switch_mode", + "update_todo_list", + "use_mcp_tool", + "write_to_file", + ] as const + + return toolNames.some((name) => lower.includes(`<${name}`) || lower.includes(` - Usage: diff --git a/src/core/diff/strategies/multi-search-replace.ts b/src/core/diff/strategies/multi-search-replace.ts index a6a9913203c..739eb20faf4 100644 --- a/src/core/diff/strategies/multi-search-replace.ts +++ b/src/core/diff/strategies/multi-search-replace.ts @@ -115,7 +115,6 @@ Diff format: \`\`\` - Example: Original file: @@ -168,7 +167,6 @@ def calculate_sum(items): >>>>>>> REPLACE \`\`\` - Usage: File path here diff --git a/src/core/environment/getEnvironmentDetails.ts b/src/core/environment/getEnvironmentDetails.ts index ebb6f18e48a..31e22d76b6a 100644 --- a/src/core/environment/getEnvironmentDetails.ts +++ b/src/core/environment/getEnvironmentDetails.ts @@ -8,7 +8,6 @@ import delay from "delay" import type { ExperimentId } from "@roo-code/types" import { DEFAULT_TERMINAL_OUTPUT_CHARACTER_LIMIT } from "@roo-code/types" -import { resolveToolProtocol } from "../../utils/resolveToolProtocol" import { EXPERIMENT_IDS, experiments as Experiments } from "../../shared/experiments" import { formatLanguage } from "../../shared/language" import { defaultModeSlug, getFullModeDetails } from "../../shared/modes" @@ -236,18 +235,14 @@ export async function getEnvironmentDetails(cline: Task, includeFileDetails: boo language: language ?? formatLanguage(vscode.env.language), }) - // Use the task's locked tool protocol for consistent environment details. - // This ensures the model sees the same tool format it was started with, - // even if user settings have changed. Fall back to resolving fresh if - // the task hasn't been fully initialized yet (shouldn't happen in practice). - const modelInfo = cline.api.getModel().info - const toolProtocol = resolveToolProtocol(state?.apiConfiguration ?? {}, modelInfo, cline.taskToolProtocol) + // Tool calling is native-only. + const toolFormat = "native" details += `\n\n# Current Mode\n` details += `${currentMode}\n` details += `${modeDetails.name}\n` details += `${modelId}\n` - details += `${toolProtocol}\n` + details += `${toolFormat}\n` if (Experiments.isEnabled(experiments ?? {}, EXPERIMENT_IDS.POWER_STEERING)) { details += `${modeDetails.roleDefinition}\n` diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap index ee8a50e9933..70cccc68f02 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap @@ -10,351 +10,19 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka TOOL USE -You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. - -# Tool Use Formatting - -Tool uses are formatted using XML-style tags. The tool name itself becomes the XML tag name. Each parameter is enclosed within its own set of tags. Here's the structure: - - -value1 -value2 -... - - -Always use the actual tool name as the XML tag name for proper parsing and execution. - -# Tools - -## read_file -Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly. - -**IMPORTANT: You can read a maximum of 5 files in a single request.** If you need to read more files, use multiple sequential read_file requests. - - -Parameters: -- args: Contains one or more file elements, where each file contains: - - path: (required) File path (relative to workspace directory /test/path) - - -Usage: - - - - path/to/file - - - - - -Examples: - -1. Reading a single file: - - - - src/app.ts - - - - - -2. Reading multiple files (within the 5-file limit): - - - - src/app.ts - - - - src/utils.ts - - - - - -3. Reading an entire file: - - - - config.json - - - - -IMPORTANT: You MUST use this Efficient Reading Strategy: -- You MUST read all related files and implementations together in a single operation (up to 5 files at once) -- You MUST obtain all necessary context before proceeding with changes - -- When you need to read more than 5 files, prioritize the most critical files first, then use subsequent read_file requests for additional files - -## fetch_instructions -Description: Request to fetch instructions to perform a task -Parameters: -- task: (required) The task to get instructions for. This can take the following values: - create_mcp_server - create_mode - -Example: Requesting instructions to create an MCP Server - - -create_mcp_server - - -## search_files -Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. - -Craft your regex patterns carefully to balance specificity and flexibility. Use this tool to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include surrounding context, so analyze the surrounding code to better understand the matches. Leverage this tool in combination with other tools for more comprehensive analysis - for example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches. - -Parameters: -- path: (required) The path of the directory to search in (relative to the current workspace directory /test/path). This directory will be recursively searched. -- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax. -- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*). - -Usage: - -Directory path here -Your regex pattern here -file pattern here (optional) - - -Example: Searching for all .ts files in the current directory - -. -.* -*.ts - - -Example: Searching for function definitions in JavaScript files - -src -function\s+\w+ -*.js - - -## list_files -Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not. -Parameters: -- path: (required) The path of the directory to list contents for (relative to the current workspace directory /test/path) -- recursive: (optional) Whether to list files recursively. Use true for recursive listing, false or omit for top-level only. -Usage: - -Directory path here -true or false (optional) - - -Example: Requesting to list all files in the current directory - -. -false - - -## write_to_file -Description: Request to write content to a file. This tool is primarily used for **creating new files** or for scenarios where a **complete rewrite of an existing file is intentionally required**. If the file exists, it will be overwritten. If it doesn't exist, it will be created. This tool will automatically create any directories needed to write the file. - -**Important:** You should prefer using other editing tools over write_to_file when making changes to existing files, since write_to_file is slower and cannot handle large files. Use write_to_file primarily for new file creation. - -When using this tool, use it directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code. - -When creating a new project, organize all new files within a dedicated project directory unless the user specifies otherwise. Structure the project logically, adhering to best practices for the specific type of project being created. - -Parameters: -- path: (required) The path of the file to write to (relative to the current workspace directory /test/path) -- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include line numbers in the content. - -Usage: - -File path here - -Your file content here - - - -Example: Writing a configuration file - -frontend-config.json - -{ - "apiEndpoint": "https://api.example.com", - "theme": { - "primaryColor": "#007bff", - "secondaryColor": "#6c757d", - "fontFamily": "Arial, sans-serif" - }, - "features": { - "darkMode": true, - "notifications": true, - "analytics": false - }, - "version": "1.0.0" -} - - - -## ask_followup_question -Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. - -Parameters: -- question: (required) A clear, specific question addressing the information needed -- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) - -Usage: - -Your question here - -First suggestion -Action with mode switch - - - -Example: - -What is the path to the frontend-config.json file? - -./src/frontend-config.json -./config/frontend-config.json -./frontend-config.json - - - -## attempt_completion -Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. -IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must confirm that you've received successful results from the user for any previous tool uses. If not, then DO NOT use this tool. -Parameters: -- result: (required) The result of the task. Formulate this result in a way that is final and does not require further input from the user. Don't end your result with questions or offers for further assistance. -Usage: - - -Your final result description here - - - -Example: Requesting to attempt completion with a result - - -I've updated the CSS - - - -## switch_mode -Description: Request to switch to a different mode. This tool allows modes to request switching to another mode when needed, such as switching to Code mode to make code changes. The user must approve the mode switch. -Parameters: -- mode_slug: (required) The slug of the mode to switch to (e.g., "code", "ask", "architect") -- reason: (optional) The reason for switching modes -Usage: - -Mode slug here -Reason for switching here - - -Example: Requesting to switch to code mode - -code -Need to make code changes - - -## new_task -Description: This will let you create a new task instance in the chosen mode using your provided message. - -Parameters: -- mode: (required) The slug of the mode to start the new task in (e.g., "code", "debug", "architect"). -- message: (required) The initial user message or instructions for this new task. - -Usage: - -your-mode-slug-here -Your initial instructions here - - -Example: - -code -Implement a new feature for the application - - - -## update_todo_list - -**Description:** -Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks. - -**Checklist Format:** -- Use a single-level markdown checklist (no nesting or subtasks). -- List todos in the intended execution order. -- Status options: - - [ ] Task description (pending) - - [x] Task description (completed) - - [-] Task description (in progress) - -**Status Rules:** -- [ ] = pending (not started) -- [x] = completed (fully finished, no unresolved issues) -- [-] = in_progress (currently being worked on) - -**Core Principles:** -- Before updating, always confirm which todos have been completed since the last update. -- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress). -- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately. -- Do not remove any unfinished todos unless explicitly instructed. -- Always retain all unfinished tasks, updating their status as needed. -- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies). -- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved. -- Remove tasks only if they are no longer relevant or if the user requests deletion. - -**Usage Example:** - - -[x] Analyze requirements -[x] Design architecture -[-] Implement core logic -[ ] Write tests -[ ] Update documentation - - - -*After completing "Implement core logic" and starting "Write tests":* - - -[x] Analyze requirements -[x] Design architecture -[x] Implement core logic -[-] Write tests -[ ] Update documentation -[ ] Add performance benchmarks - - - -**When to Use:** -- The task is complicated or involves multiple steps or requires ongoing tracking. -- You need to update the status of several todos at once. -- New actionable items are discovered during task execution. -- The user requests a todo list or provides multiple tasks. -- The task is complex and benefits from clear, stepwise progress tracking. - -**When NOT to Use:** -- There is only a single, trivial task. -- The task can be completed in one or two simple steps. -- The request is purely conversational or informational. - -**Task Management Guidelines:** -- Mark task as completed immediately after all work of the current task is done. -- Start the next task by marking it as in_progress. -- Add new todos as soon as they are identified. -- Use clear, descriptive task names. - - -# Tool Use Guidelines +You have access to a set of tools that are executed upon the user's approval. Use the provider-native tool-calling mechanism. Do not include XML markup or examples. You must use exactly one tool call per assistant response. Do not call zero tools or more than one tool in the same response. + + # Tool Use Guidelines 1. Assess what information you already have and what information you need to proceed with the task. 2. Choose the most appropriate tool based on the task and the tool descriptions provided. Assess if you need additional information to proceed, and which of the available tools would be most effective for gathering this information. For example using the list_files tool is more effective than running a command like `ls` in the terminal. It's critical that you think about each available tool and use the one that best fits the current step in the task. 3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. -4. Formulate your tool use using the XML format specified for each tool. -5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: +4. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - Information about whether the tool succeeded or failed, along with any reasons for failure. - Linter errors that may have arisen due to the changes you made, which you'll need to address. - New terminal output in reaction to the changes, which you may need to consider or act upon. - Any other relevant feedback or information related to the tool use. -6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. +5. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: 1. Confirm the success of each step before proceeding. @@ -385,7 +53,7 @@ MODES RULES - The project base directory is: /test/path -- 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 . +- 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. - You cannot `cd` into a different directory to complete a task. You are stuck operating from '/test/path', 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 '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', 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)`. diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap index 44287486326..ee604b3036f 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap @@ -10,309 +10,19 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka TOOL USE -You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. - -# Tool Use Formatting - -Tool uses are formatted using XML-style tags. The tool name itself becomes the XML tag name. Each parameter is enclosed within its own set of tags. Here's the structure: - - -value1 -value2 -... - - -Always use the actual tool name as the XML tag name for proper parsing and execution. - -# Tools - -## read_file -Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly. - -**IMPORTANT: You can read a maximum of 5 files in a single request.** If you need to read more files, use multiple sequential read_file requests. - - -Parameters: -- args: Contains one or more file elements, where each file contains: - - path: (required) File path (relative to workspace directory /test/path) - - -Usage: - - - - path/to/file - - - - - -Examples: - -1. Reading a single file: - - - - src/app.ts - - - - - -2. Reading multiple files (within the 5-file limit): - - - - src/app.ts - - - - src/utils.ts - - - - - -3. Reading an entire file: - - - - config.json - - - - -IMPORTANT: You MUST use this Efficient Reading Strategy: -- You MUST read all related files and implementations together in a single operation (up to 5 files at once) -- You MUST obtain all necessary context before proceeding with changes - -- When you need to read more than 5 files, prioritize the most critical files first, then use subsequent read_file requests for additional files - -## fetch_instructions -Description: Request to fetch instructions to perform a task -Parameters: -- task: (required) The task to get instructions for. This can take the following values: - create_mcp_server - create_mode - -Example: Requesting instructions to create an MCP Server - - -create_mcp_server - - -## search_files -Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. - -Craft your regex patterns carefully to balance specificity and flexibility. Use this tool to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include surrounding context, so analyze the surrounding code to better understand the matches. Leverage this tool in combination with other tools for more comprehensive analysis - for example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches. - -Parameters: -- path: (required) The path of the directory to search in (relative to the current workspace directory /test/path). This directory will be recursively searched. -- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax. -- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*). - -Usage: - -Directory path here -Your regex pattern here -file pattern here (optional) - - -Example: Searching for all .ts files in the current directory - -. -.* -*.ts - - -Example: Searching for function definitions in JavaScript files - -src -function\s+\w+ -*.js - - -## list_files -Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not. -Parameters: -- path: (required) The path of the directory to list contents for (relative to the current workspace directory /test/path) -- recursive: (optional) Whether to list files recursively. Use true for recursive listing, false or omit for top-level only. -Usage: - -Directory path here -true or false (optional) - - -Example: Requesting to list all files in the current directory - -. -false - - -## ask_followup_question -Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. - -Parameters: -- question: (required) A clear, specific question addressing the information needed -- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) - -Usage: - -Your question here - -First suggestion -Action with mode switch - - - -Example: - -What is the path to the frontend-config.json file? - -./src/frontend-config.json -./config/frontend-config.json -./frontend-config.json - - - -## attempt_completion -Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. -IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must confirm that you've received successful results from the user for any previous tool uses. If not, then DO NOT use this tool. -Parameters: -- result: (required) The result of the task. Formulate this result in a way that is final and does not require further input from the user. Don't end your result with questions or offers for further assistance. -Usage: - - -Your final result description here - - - -Example: Requesting to attempt completion with a result - - -I've updated the CSS - - - -## switch_mode -Description: Request to switch to a different mode. This tool allows modes to request switching to another mode when needed, such as switching to Code mode to make code changes. The user must approve the mode switch. -Parameters: -- mode_slug: (required) The slug of the mode to switch to (e.g., "code", "ask", "architect") -- reason: (optional) The reason for switching modes -Usage: - -Mode slug here -Reason for switching here - - -Example: Requesting to switch to code mode - -code -Need to make code changes - - -## new_task -Description: This will let you create a new task instance in the chosen mode using your provided message. - -Parameters: -- mode: (required) The slug of the mode to start the new task in (e.g., "code", "debug", "architect"). -- message: (required) The initial user message or instructions for this new task. - -Usage: - -your-mode-slug-here -Your initial instructions here - - -Example: - -code -Implement a new feature for the application - - - -## update_todo_list - -**Description:** -Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks. - -**Checklist Format:** -- Use a single-level markdown checklist (no nesting or subtasks). -- List todos in the intended execution order. -- Status options: - - [ ] Task description (pending) - - [x] Task description (completed) - - [-] Task description (in progress) - -**Status Rules:** -- [ ] = pending (not started) -- [x] = completed (fully finished, no unresolved issues) -- [-] = in_progress (currently being worked on) - -**Core Principles:** -- Before updating, always confirm which todos have been completed since the last update. -- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress). -- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately. -- Do not remove any unfinished todos unless explicitly instructed. -- Always retain all unfinished tasks, updating their status as needed. -- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies). -- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved. -- Remove tasks only if they are no longer relevant or if the user requests deletion. - -**Usage Example:** - - -[x] Analyze requirements -[x] Design architecture -[-] Implement core logic -[ ] Write tests -[ ] Update documentation - - - -*After completing "Implement core logic" and starting "Write tests":* - - -[x] Analyze requirements -[x] Design architecture -[x] Implement core logic -[-] Write tests -[ ] Update documentation -[ ] Add performance benchmarks - - - -**When to Use:** -- The task is complicated or involves multiple steps or requires ongoing tracking. -- You need to update the status of several todos at once. -- New actionable items are discovered during task execution. -- The user requests a todo list or provides multiple tasks. -- The task is complex and benefits from clear, stepwise progress tracking. - -**When NOT to Use:** -- There is only a single, trivial task. -- The task can be completed in one or two simple steps. -- The request is purely conversational or informational. - -**Task Management Guidelines:** -- Mark task as completed immediately after all work of the current task is done. -- Start the next task by marking it as in_progress. -- Add new todos as soon as they are identified. -- Use clear, descriptive task names. - - -# Tool Use Guidelines +You have access to a set of tools that are executed upon the user's approval. Use the provider-native tool-calling mechanism. Do not include XML markup or examples. You must use exactly one tool call per assistant response. Do not call zero tools or more than one tool in the same response. + + # Tool Use Guidelines 1. Assess what information you already have and what information you need to proceed with the task. 2. Choose the most appropriate tool based on the task and the tool descriptions provided. Assess if you need additional information to proceed, and which of the available tools would be most effective for gathering this information. For example using the list_files tool is more effective than running a command like `ls` in the terminal. It's critical that you think about each available tool and use the one that best fits the current step in the task. 3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. -4. Formulate your tool use using the XML format specified for each tool. -5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: +4. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - Information about whether the tool succeeded or failed, along with any reasons for failure. - Linter errors that may have arisen due to the changes you made, which you'll need to address. - New terminal output in reaction to the changes, which you may need to consider or act upon. - Any other relevant feedback or information related to the tool use. -6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. +5. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: 1. Confirm the success of each step before proceeding. @@ -343,7 +53,7 @@ MODES RULES - The project base directory is: /test/path -- 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 . +- 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. - You cannot `cd` into a different directory to complete a task. You are stuck operating from '/test/path', 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 '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', 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)`. diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap index 48b39d001f6..70cccc68f02 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap @@ -10,350 +10,19 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka TOOL USE -You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. - -# Tool Use Formatting - -Tool uses are formatted using XML-style tags. The tool name itself becomes the XML tag name. Each parameter is enclosed within its own set of tags. Here's the structure: - - -value1 -value2 -... - - -Always use the actual tool name as the XML tag name for proper parsing and execution. - -# Tools - -## read_file -Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly. - -**IMPORTANT: You can read a maximum of 5 files in a single request.** If you need to read more files, use multiple sequential read_file requests. - - -Parameters: -- args: Contains one or more file elements, where each file contains: - - path: (required) File path (relative to workspace directory /test/path) - - -Usage: - - - - path/to/file - - - - - -Examples: - -1. Reading a single file: - - - - src/app.ts - - - - - -2. Reading multiple files (within the 5-file limit): - - - - src/app.ts - - - - src/utils.ts - - - - - -3. Reading an entire file: - - - - config.json - - - - -IMPORTANT: You MUST use this Efficient Reading Strategy: -- You MUST read all related files and implementations together in a single operation (up to 5 files at once) -- You MUST obtain all necessary context before proceeding with changes - -- When you need to read more than 5 files, prioritize the most critical files first, then use subsequent read_file requests for additional files - -## fetch_instructions -Description: Request to fetch instructions to perform a task -Parameters: -- task: (required) The task to get instructions for. This can take the following values: - create_mode - -Example: Requesting instructions to create a Mode - - -create_mode - - -## search_files -Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. - -Craft your regex patterns carefully to balance specificity and flexibility. Use this tool to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include surrounding context, so analyze the surrounding code to better understand the matches. Leverage this tool in combination with other tools for more comprehensive analysis - for example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches. - -Parameters: -- path: (required) The path of the directory to search in (relative to the current workspace directory /test/path). This directory will be recursively searched. -- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax. -- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*). - -Usage: - -Directory path here -Your regex pattern here -file pattern here (optional) - - -Example: Searching for all .ts files in the current directory - -. -.* -*.ts - - -Example: Searching for function definitions in JavaScript files - -src -function\s+\w+ -*.js - - -## list_files -Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not. -Parameters: -- path: (required) The path of the directory to list contents for (relative to the current workspace directory /test/path) -- recursive: (optional) Whether to list files recursively. Use true for recursive listing, false or omit for top-level only. -Usage: - -Directory path here -true or false (optional) - - -Example: Requesting to list all files in the current directory - -. -false - - -## write_to_file -Description: Request to write content to a file. This tool is primarily used for **creating new files** or for scenarios where a **complete rewrite of an existing file is intentionally required**. If the file exists, it will be overwritten. If it doesn't exist, it will be created. This tool will automatically create any directories needed to write the file. - -**Important:** You should prefer using other editing tools over write_to_file when making changes to existing files, since write_to_file is slower and cannot handle large files. Use write_to_file primarily for new file creation. - -When using this tool, use it directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code. - -When creating a new project, organize all new files within a dedicated project directory unless the user specifies otherwise. Structure the project logically, adhering to best practices for the specific type of project being created. - -Parameters: -- path: (required) The path of the file to write to (relative to the current workspace directory /test/path) -- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include line numbers in the content. - -Usage: - -File path here - -Your file content here - - - -Example: Writing a configuration file - -frontend-config.json - -{ - "apiEndpoint": "https://api.example.com", - "theme": { - "primaryColor": "#007bff", - "secondaryColor": "#6c757d", - "fontFamily": "Arial, sans-serif" - }, - "features": { - "darkMode": true, - "notifications": true, - "analytics": false - }, - "version": "1.0.0" -} - - - -## ask_followup_question -Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. - -Parameters: -- question: (required) A clear, specific question addressing the information needed -- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) - -Usage: - -Your question here - -First suggestion -Action with mode switch - - - -Example: - -What is the path to the frontend-config.json file? - -./src/frontend-config.json -./config/frontend-config.json -./frontend-config.json - - - -## attempt_completion -Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. -IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must confirm that you've received successful results from the user for any previous tool uses. If not, then DO NOT use this tool. -Parameters: -- result: (required) The result of the task. Formulate this result in a way that is final and does not require further input from the user. Don't end your result with questions or offers for further assistance. -Usage: - - -Your final result description here - - - -Example: Requesting to attempt completion with a result - - -I've updated the CSS - - - -## switch_mode -Description: Request to switch to a different mode. This tool allows modes to request switching to another mode when needed, such as switching to Code mode to make code changes. The user must approve the mode switch. -Parameters: -- mode_slug: (required) The slug of the mode to switch to (e.g., "code", "ask", "architect") -- reason: (optional) The reason for switching modes -Usage: - -Mode slug here -Reason for switching here - - -Example: Requesting to switch to code mode - -code -Need to make code changes - - -## new_task -Description: This will let you create a new task instance in the chosen mode using your provided message. - -Parameters: -- mode: (required) The slug of the mode to start the new task in (e.g., "code", "debug", "architect"). -- message: (required) The initial user message or instructions for this new task. - -Usage: - -your-mode-slug-here -Your initial instructions here - - -Example: - -code -Implement a new feature for the application - - - -## update_todo_list - -**Description:** -Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks. - -**Checklist Format:** -- Use a single-level markdown checklist (no nesting or subtasks). -- List todos in the intended execution order. -- Status options: - - [ ] Task description (pending) - - [x] Task description (completed) - - [-] Task description (in progress) - -**Status Rules:** -- [ ] = pending (not started) -- [x] = completed (fully finished, no unresolved issues) -- [-] = in_progress (currently being worked on) - -**Core Principles:** -- Before updating, always confirm which todos have been completed since the last update. -- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress). -- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately. -- Do not remove any unfinished todos unless explicitly instructed. -- Always retain all unfinished tasks, updating their status as needed. -- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies). -- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved. -- Remove tasks only if they are no longer relevant or if the user requests deletion. - -**Usage Example:** - - -[x] Analyze requirements -[x] Design architecture -[-] Implement core logic -[ ] Write tests -[ ] Update documentation - - - -*After completing "Implement core logic" and starting "Write tests":* - - -[x] Analyze requirements -[x] Design architecture -[x] Implement core logic -[-] Write tests -[ ] Update documentation -[ ] Add performance benchmarks - - - -**When to Use:** -- The task is complicated or involves multiple steps or requires ongoing tracking. -- You need to update the status of several todos at once. -- New actionable items are discovered during task execution. -- The user requests a todo list or provides multiple tasks. -- The task is complex and benefits from clear, stepwise progress tracking. - -**When NOT to Use:** -- There is only a single, trivial task. -- The task can be completed in one or two simple steps. -- The request is purely conversational or informational. - -**Task Management Guidelines:** -- Mark task as completed immediately after all work of the current task is done. -- Start the next task by marking it as in_progress. -- Add new todos as soon as they are identified. -- Use clear, descriptive task names. - - -# Tool Use Guidelines +You have access to a set of tools that are executed upon the user's approval. Use the provider-native tool-calling mechanism. Do not include XML markup or examples. You must use exactly one tool call per assistant response. Do not call zero tools or more than one tool in the same response. + + # Tool Use Guidelines 1. Assess what information you already have and what information you need to proceed with the task. 2. Choose the most appropriate tool based on the task and the tool descriptions provided. Assess if you need additional information to proceed, and which of the available tools would be most effective for gathering this information. For example using the list_files tool is more effective than running a command like `ls` in the terminal. It's critical that you think about each available tool and use the one that best fits the current step in the task. 3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. -4. Formulate your tool use using the XML format specified for each tool. -5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: +4. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - Information about whether the tool succeeded or failed, along with any reasons for failure. - Linter errors that may have arisen due to the changes you made, which you'll need to address. - New terminal output in reaction to the changes, which you may need to consider or act upon. - Any other relevant feedback or information related to the tool use. -6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. +5. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: 1. Confirm the success of each step before proceeding. @@ -384,7 +53,7 @@ MODES RULES - The project base directory is: /test/path -- 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 . +- 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. - You cannot `cd` into a different directory to complete a task. You are stuck operating from '/test/path', 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 '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', 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)`. diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap index acc36d1ffd8..51fd18172bb 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap @@ -10,400 +10,19 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka TOOL USE -You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. - -# Tool Use Formatting - -Tool uses are formatted using XML-style tags. The tool name itself becomes the XML tag name. Each parameter is enclosed within its own set of tags. Here's the structure: - - -value1 -value2 -... - - -Always use the actual tool name as the XML tag name for proper parsing and execution. - -# Tools - -## read_file -Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly. - -**IMPORTANT: You can read a maximum of 5 files in a single request.** If you need to read more files, use multiple sequential read_file requests. - - -Parameters: -- args: Contains one or more file elements, where each file contains: - - path: (required) File path (relative to workspace directory /test/path) - - -Usage: - - - - path/to/file - - - - - -Examples: - -1. Reading a single file: - - - - src/app.ts - - - - - -2. Reading multiple files (within the 5-file limit): - - - - src/app.ts - - - - src/utils.ts - - - - - -3. Reading an entire file: - - - - config.json - - - - -IMPORTANT: You MUST use this Efficient Reading Strategy: -- You MUST read all related files and implementations together in a single operation (up to 5 files at once) -- You MUST obtain all necessary context before proceeding with changes - -- When you need to read more than 5 files, prioritize the most critical files first, then use subsequent read_file requests for additional files - -## fetch_instructions -Description: Request to fetch instructions to perform a task -Parameters: -- task: (required) The task to get instructions for. This can take the following values: - create_mcp_server - create_mode - -Example: Requesting instructions to create an MCP Server +You have access to a set of tools that are executed upon the user's approval. Use the provider-native tool-calling mechanism. Do not include XML markup or examples. You must use exactly one tool call per assistant response. Do not call zero tools or more than one tool in the same response. - -create_mcp_server - - -## search_files -Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. - -Craft your regex patterns carefully to balance specificity and flexibility. Use this tool to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include surrounding context, so analyze the surrounding code to better understand the matches. Leverage this tool in combination with other tools for more comprehensive analysis - for example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches. - -Parameters: -- path: (required) The path of the directory to search in (relative to the current workspace directory /test/path). This directory will be recursively searched. -- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax. -- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*). - -Usage: - -Directory path here -Your regex pattern here -file pattern here (optional) - - -Example: Searching for all .ts files in the current directory - -. -.* -*.ts - - -Example: Searching for function definitions in JavaScript files - -src -function\s+\w+ -*.js - - -## list_files -Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not. -Parameters: -- path: (required) The path of the directory to list contents for (relative to the current workspace directory /test/path) -- recursive: (optional) Whether to list files recursively. Use true for recursive listing, false or omit for top-level only. -Usage: - -Directory path here -true or false (optional) - - -Example: Requesting to list all files in the current directory - -. -false - - -## write_to_file -Description: Request to write content to a file. This tool is primarily used for **creating new files** or for scenarios where a **complete rewrite of an existing file is intentionally required**. If the file exists, it will be overwritten. If it doesn't exist, it will be created. This tool will automatically create any directories needed to write the file. - -**Important:** You should prefer using other editing tools over write_to_file when making changes to existing files, since write_to_file is slower and cannot handle large files. Use write_to_file primarily for new file creation. - -When using this tool, use it directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code. - -When creating a new project, organize all new files within a dedicated project directory unless the user specifies otherwise. Structure the project logically, adhering to best practices for the specific type of project being created. - -Parameters: -- path: (required) The path of the file to write to (relative to the current workspace directory /test/path) -- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include line numbers in the content. - -Usage: - -File path here - -Your file content here - - - -Example: Writing a configuration file - -frontend-config.json - -{ - "apiEndpoint": "https://api.example.com", - "theme": { - "primaryColor": "#007bff", - "secondaryColor": "#6c757d", - "fontFamily": "Arial, sans-serif" - }, - "features": { - "darkMode": true, - "notifications": true, - "analytics": false - }, - "version": "1.0.0" -} - - - -## use_mcp_tool -Description: Request to use a tool provided by a connected MCP server. Each MCP server can provide multiple tools with different capabilities. Tools have defined input schemas that specify required and optional parameters. -Parameters: -- server_name: (required) The name of the MCP server providing the tool -- tool_name: (required) The name of the tool to execute -- arguments: (required) A JSON object containing the tool's input parameters, following the tool's input schema -Usage: - -server name here -tool name here - -{ - "param1": "value1", - "param2": "value2" -} - - - -Example: Requesting to use an MCP tool - - -weather-server -get_forecast - -{ - "city": "San Francisco", - "days": 5 -} - - - -## access_mcp_resource -Description: Request to access a resource provided by a connected MCP server. Resources represent data sources that can be used as context, such as files, API responses, or system information. -Parameters: -- server_name: (required) The name of the MCP server providing the resource -- uri: (required) The URI identifying the specific resource to access -Usage: - -server name here -resource URI here - - -Example: Requesting to access an MCP resource - - -weather-server -weather://san-francisco/current - - -## ask_followup_question -Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. - -Parameters: -- question: (required) A clear, specific question addressing the information needed -- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) - -Usage: - -Your question here - -First suggestion -Action with mode switch - - - -Example: - -What is the path to the frontend-config.json file? - -./src/frontend-config.json -./config/frontend-config.json -./frontend-config.json - - - -## attempt_completion -Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. -IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must confirm that you've received successful results from the user for any previous tool uses. If not, then DO NOT use this tool. -Parameters: -- result: (required) The result of the task. Formulate this result in a way that is final and does not require further input from the user. Don't end your result with questions or offers for further assistance. -Usage: - - -Your final result description here - - - -Example: Requesting to attempt completion with a result - - -I've updated the CSS - - - -## switch_mode -Description: Request to switch to a different mode. This tool allows modes to request switching to another mode when needed, such as switching to Code mode to make code changes. The user must approve the mode switch. -Parameters: -- mode_slug: (required) The slug of the mode to switch to (e.g., "code", "ask", "architect") -- reason: (optional) The reason for switching modes -Usage: - -Mode slug here -Reason for switching here - - -Example: Requesting to switch to code mode - -code -Need to make code changes - - -## new_task -Description: This will let you create a new task instance in the chosen mode using your provided message. - -Parameters: -- mode: (required) The slug of the mode to start the new task in (e.g., "code", "debug", "architect"). -- message: (required) The initial user message or instructions for this new task. - -Usage: - -your-mode-slug-here -Your initial instructions here - - -Example: - -code -Implement a new feature for the application - - - -## update_todo_list - -**Description:** -Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks. - -**Checklist Format:** -- Use a single-level markdown checklist (no nesting or subtasks). -- List todos in the intended execution order. -- Status options: - - [ ] Task description (pending) - - [x] Task description (completed) - - [-] Task description (in progress) - -**Status Rules:** -- [ ] = pending (not started) -- [x] = completed (fully finished, no unresolved issues) -- [-] = in_progress (currently being worked on) - -**Core Principles:** -- Before updating, always confirm which todos have been completed since the last update. -- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress). -- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately. -- Do not remove any unfinished todos unless explicitly instructed. -- Always retain all unfinished tasks, updating their status as needed. -- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies). -- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved. -- Remove tasks only if they are no longer relevant or if the user requests deletion. - -**Usage Example:** - - -[x] Analyze requirements -[x] Design architecture -[-] Implement core logic -[ ] Write tests -[ ] Update documentation - - - -*After completing "Implement core logic" and starting "Write tests":* - - -[x] Analyze requirements -[x] Design architecture -[x] Implement core logic -[-] Write tests -[ ] Update documentation -[ ] Add performance benchmarks - - - -**When to Use:** -- The task is complicated or involves multiple steps or requires ongoing tracking. -- You need to update the status of several todos at once. -- New actionable items are discovered during task execution. -- The user requests a todo list or provides multiple tasks. -- The task is complex and benefits from clear, stepwise progress tracking. - -**When NOT to Use:** -- There is only a single, trivial task. -- The task can be completed in one or two simple steps. -- The request is purely conversational or informational. - -**Task Management Guidelines:** -- Mark task as completed immediately after all work of the current task is done. -- Start the next task by marking it as in_progress. -- Add new todos as soon as they are identified. -- Use clear, descriptive task names. - - -# Tool Use Guidelines + # Tool Use Guidelines 1. Assess what information you already have and what information you need to proceed with the task. 2. Choose the most appropriate tool based on the task and the tool descriptions provided. Assess if you need additional information to proceed, and which of the available tools would be most effective for gathering this information. For example using the list_files tool is more effective than running a command like `ls` in the terminal. It's critical that you think about each available tool and use the one that best fits the current step in the task. 3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. -4. Formulate your tool use using the XML format specified for each tool. -5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: +4. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - Information about whether the tool succeeded or failed, along with any reasons for failure. - Linter errors that may have arisen due to the changes you made, which you'll need to address. - New terminal output in reaction to the changes, which you may need to consider or act upon. - Any other relevant feedback or information related to the tool use. -6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. +5. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: 1. Confirm the success of each step before proceeding. @@ -453,7 +72,7 @@ MODES RULES - The project base directory is: /test/path -- 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 . +- 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. - You cannot `cd` into a different directory to complete a task. You are stuck operating from '/test/path', 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 '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', 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)`. diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap index ac93623fda2..70cccc68f02 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap @@ -10,356 +10,19 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka TOOL USE -You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. - -# Tool Use Formatting - -Tool uses are formatted using XML-style tags. The tool name itself becomes the XML tag name. Each parameter is enclosed within its own set of tags. Here's the structure: - - -value1 -value2 -... - - -Always use the actual tool name as the XML tag name for proper parsing and execution. - -# Tools - -## read_file -Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Use line ranges to efficiently read specific portions of large files. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly. - -**IMPORTANT: You can read a maximum of 5 files in a single request.** If you need to read more files, use multiple sequential read_file requests. - -By specifying line ranges, you can efficiently read specific portions of large files without loading the entire file into memory. -Parameters: -- args: Contains one or more file elements, where each file contains: - - path: (required) File path (relative to workspace directory /test/path) - - line_range: (optional) One or more line range elements in format "start-end" (1-based, inclusive) - -Usage: - - - - path/to/file - start-end - - - - -Examples: - -1. Reading a single file: - - - - src/app.ts - 1-1000 - - - - -2. Reading multiple files (within the 5-file limit): - - - - src/app.ts - 1-50 - 100-150 - - - src/utils.ts - 10-20 - - - - -3. Reading an entire file: - - - - config.json - - - - -IMPORTANT: You MUST use this Efficient Reading Strategy: -- You MUST read all related files and implementations together in a single operation (up to 5 files at once) -- You MUST obtain all necessary context before proceeding with changes -- You MUST use line ranges to read specific portions of large files, rather than reading entire files when not needed -- You MUST combine adjacent line ranges (<10 lines apart) -- You MUST use multiple ranges for content separated by >10 lines -- You MUST include sufficient line context for planned modifications while keeping ranges minimal - -- When you need to read more than 5 files, prioritize the most critical files first, then use subsequent read_file requests for additional files - -## fetch_instructions -Description: Request to fetch instructions to perform a task -Parameters: -- task: (required) The task to get instructions for. This can take the following values: - create_mcp_server - create_mode - -Example: Requesting instructions to create an MCP Server - - -create_mcp_server - - -## search_files -Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. - -Craft your regex patterns carefully to balance specificity and flexibility. Use this tool to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include surrounding context, so analyze the surrounding code to better understand the matches. Leverage this tool in combination with other tools for more comprehensive analysis - for example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches. - -Parameters: -- path: (required) The path of the directory to search in (relative to the current workspace directory /test/path). This directory will be recursively searched. -- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax. -- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*). - -Usage: - -Directory path here -Your regex pattern here -file pattern here (optional) - - -Example: Searching for all .ts files in the current directory - -. -.* -*.ts - - -Example: Searching for function definitions in JavaScript files - -src -function\s+\w+ -*.js - - -## list_files -Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not. -Parameters: -- path: (required) The path of the directory to list contents for (relative to the current workspace directory /test/path) -- recursive: (optional) Whether to list files recursively. Use true for recursive listing, false or omit for top-level only. -Usage: - -Directory path here -true or false (optional) - - -Example: Requesting to list all files in the current directory - -. -false - - -## write_to_file -Description: Request to write content to a file. This tool is primarily used for **creating new files** or for scenarios where a **complete rewrite of an existing file is intentionally required**. If the file exists, it will be overwritten. If it doesn't exist, it will be created. This tool will automatically create any directories needed to write the file. - -**Important:** You should prefer using other editing tools over write_to_file when making changes to existing files, since write_to_file is slower and cannot handle large files. Use write_to_file primarily for new file creation. - -When using this tool, use it directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code. - -When creating a new project, organize all new files within a dedicated project directory unless the user specifies otherwise. Structure the project logically, adhering to best practices for the specific type of project being created. - -Parameters: -- path: (required) The path of the file to write to (relative to the current workspace directory /test/path) -- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include line numbers in the content. - -Usage: - -File path here - -Your file content here - - - -Example: Writing a configuration file - -frontend-config.json - -{ - "apiEndpoint": "https://api.example.com", - "theme": { - "primaryColor": "#007bff", - "secondaryColor": "#6c757d", - "fontFamily": "Arial, sans-serif" - }, - "features": { - "darkMode": true, - "notifications": true, - "analytics": false - }, - "version": "1.0.0" -} - - - -## ask_followup_question -Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. - -Parameters: -- question: (required) A clear, specific question addressing the information needed -- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) - -Usage: - -Your question here - -First suggestion -Action with mode switch - - - -Example: - -What is the path to the frontend-config.json file? - -./src/frontend-config.json -./config/frontend-config.json -./frontend-config.json - - - -## attempt_completion -Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. -IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must confirm that you've received successful results from the user for any previous tool uses. If not, then DO NOT use this tool. -Parameters: -- result: (required) The result of the task. Formulate this result in a way that is final and does not require further input from the user. Don't end your result with questions or offers for further assistance. -Usage: - - -Your final result description here - - - -Example: Requesting to attempt completion with a result - - -I've updated the CSS - - - -## switch_mode -Description: Request to switch to a different mode. This tool allows modes to request switching to another mode when needed, such as switching to Code mode to make code changes. The user must approve the mode switch. -Parameters: -- mode_slug: (required) The slug of the mode to switch to (e.g., "code", "ask", "architect") -- reason: (optional) The reason for switching modes -Usage: - -Mode slug here -Reason for switching here - - -Example: Requesting to switch to code mode - -code -Need to make code changes - - -## new_task -Description: This will let you create a new task instance in the chosen mode using your provided message. - -Parameters: -- mode: (required) The slug of the mode to start the new task in (e.g., "code", "debug", "architect"). -- message: (required) The initial user message or instructions for this new task. - -Usage: - -your-mode-slug-here -Your initial instructions here - - -Example: - -code -Implement a new feature for the application - - - -## update_todo_list - -**Description:** -Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks. - -**Checklist Format:** -- Use a single-level markdown checklist (no nesting or subtasks). -- List todos in the intended execution order. -- Status options: - - [ ] Task description (pending) - - [x] Task description (completed) - - [-] Task description (in progress) - -**Status Rules:** -- [ ] = pending (not started) -- [x] = completed (fully finished, no unresolved issues) -- [-] = in_progress (currently being worked on) - -**Core Principles:** -- Before updating, always confirm which todos have been completed since the last update. -- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress). -- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately. -- Do not remove any unfinished todos unless explicitly instructed. -- Always retain all unfinished tasks, updating their status as needed. -- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies). -- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved. -- Remove tasks only if they are no longer relevant or if the user requests deletion. - -**Usage Example:** - - -[x] Analyze requirements -[x] Design architecture -[-] Implement core logic -[ ] Write tests -[ ] Update documentation - - - -*After completing "Implement core logic" and starting "Write tests":* - - -[x] Analyze requirements -[x] Design architecture -[x] Implement core logic -[-] Write tests -[ ] Update documentation -[ ] Add performance benchmarks - - - -**When to Use:** -- The task is complicated or involves multiple steps or requires ongoing tracking. -- You need to update the status of several todos at once. -- New actionable items are discovered during task execution. -- The user requests a todo list or provides multiple tasks. -- The task is complex and benefits from clear, stepwise progress tracking. - -**When NOT to Use:** -- There is only a single, trivial task. -- The task can be completed in one or two simple steps. -- The request is purely conversational or informational. - -**Task Management Guidelines:** -- Mark task as completed immediately after all work of the current task is done. -- Start the next task by marking it as in_progress. -- Add new todos as soon as they are identified. -- Use clear, descriptive task names. - - -# Tool Use Guidelines +You have access to a set of tools that are executed upon the user's approval. Use the provider-native tool-calling mechanism. Do not include XML markup or examples. You must use exactly one tool call per assistant response. Do not call zero tools or more than one tool in the same response. + + # Tool Use Guidelines 1. Assess what information you already have and what information you need to proceed with the task. 2. Choose the most appropriate tool based on the task and the tool descriptions provided. Assess if you need additional information to proceed, and which of the available tools would be most effective for gathering this information. For example using the list_files tool is more effective than running a command like `ls` in the terminal. It's critical that you think about each available tool and use the one that best fits the current step in the task. 3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. -4. Formulate your tool use using the XML format specified for each tool. -5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: +4. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - Information about whether the tool succeeded or failed, along with any reasons for failure. - Linter errors that may have arisen due to the changes you made, which you'll need to address. - New terminal output in reaction to the changes, which you may need to consider or act upon. - Any other relevant feedback or information related to the tool use. -6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. +5. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: 1. Confirm the success of each step before proceeding. @@ -390,7 +53,7 @@ MODES RULES - The project base directory is: /test/path -- 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 . +- 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. - You cannot `cd` into a different directory to complete a task. You are stuck operating from '/test/path', 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 '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', 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)`. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap index ee8a50e9933..5305987e28a 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap @@ -10,351 +10,19 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka TOOL USE -You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. - -# Tool Use Formatting - -Tool uses are formatted using XML-style tags. The tool name itself becomes the XML tag name. Each parameter is enclosed within its own set of tags. Here's the structure: - - -value1 -value2 -... - - -Always use the actual tool name as the XML tag name for proper parsing and execution. - -# Tools - -## read_file -Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly. - -**IMPORTANT: You can read a maximum of 5 files in a single request.** If you need to read more files, use multiple sequential read_file requests. - - -Parameters: -- args: Contains one or more file elements, where each file contains: - - path: (required) File path (relative to workspace directory /test/path) - - -Usage: - - - - path/to/file - - - - - -Examples: - -1. Reading a single file: - - - - src/app.ts - - - - - -2. Reading multiple files (within the 5-file limit): - - - - src/app.ts - - - - src/utils.ts - - - - - -3. Reading an entire file: - - - - config.json - - - - -IMPORTANT: You MUST use this Efficient Reading Strategy: -- You MUST read all related files and implementations together in a single operation (up to 5 files at once) -- You MUST obtain all necessary context before proceeding with changes - -- When you need to read more than 5 files, prioritize the most critical files first, then use subsequent read_file requests for additional files - -## fetch_instructions -Description: Request to fetch instructions to perform a task -Parameters: -- task: (required) The task to get instructions for. This can take the following values: - create_mcp_server - create_mode - -Example: Requesting instructions to create an MCP Server - - -create_mcp_server - - -## search_files -Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. - -Craft your regex patterns carefully to balance specificity and flexibility. Use this tool to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include surrounding context, so analyze the surrounding code to better understand the matches. Leverage this tool in combination with other tools for more comprehensive analysis - for example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches. - -Parameters: -- path: (required) The path of the directory to search in (relative to the current workspace directory /test/path). This directory will be recursively searched. -- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax. -- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*). - -Usage: - -Directory path here -Your regex pattern here -file pattern here (optional) - - -Example: Searching for all .ts files in the current directory - -. -.* -*.ts - - -Example: Searching for function definitions in JavaScript files - -src -function\s+\w+ -*.js - - -## list_files -Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not. -Parameters: -- path: (required) The path of the directory to list contents for (relative to the current workspace directory /test/path) -- recursive: (optional) Whether to list files recursively. Use true for recursive listing, false or omit for top-level only. -Usage: - -Directory path here -true or false (optional) - - -Example: Requesting to list all files in the current directory - -. -false - - -## write_to_file -Description: Request to write content to a file. This tool is primarily used for **creating new files** or for scenarios where a **complete rewrite of an existing file is intentionally required**. If the file exists, it will be overwritten. If it doesn't exist, it will be created. This tool will automatically create any directories needed to write the file. - -**Important:** You should prefer using other editing tools over write_to_file when making changes to existing files, since write_to_file is slower and cannot handle large files. Use write_to_file primarily for new file creation. - -When using this tool, use it directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code. - -When creating a new project, organize all new files within a dedicated project directory unless the user specifies otherwise. Structure the project logically, adhering to best practices for the specific type of project being created. - -Parameters: -- path: (required) The path of the file to write to (relative to the current workspace directory /test/path) -- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include line numbers in the content. - -Usage: - -File path here - -Your file content here - - - -Example: Writing a configuration file - -frontend-config.json - -{ - "apiEndpoint": "https://api.example.com", - "theme": { - "primaryColor": "#007bff", - "secondaryColor": "#6c757d", - "fontFamily": "Arial, sans-serif" - }, - "features": { - "darkMode": true, - "notifications": true, - "analytics": false - }, - "version": "1.0.0" -} - - - -## ask_followup_question -Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. - -Parameters: -- question: (required) A clear, specific question addressing the information needed -- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) - -Usage: - -Your question here - -First suggestion -Action with mode switch - - - -Example: - -What is the path to the frontend-config.json file? - -./src/frontend-config.json -./config/frontend-config.json -./frontend-config.json - - - -## attempt_completion -Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. -IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must confirm that you've received successful results from the user for any previous tool uses. If not, then DO NOT use this tool. -Parameters: -- result: (required) The result of the task. Formulate this result in a way that is final and does not require further input from the user. Don't end your result with questions or offers for further assistance. -Usage: - - -Your final result description here - - - -Example: Requesting to attempt completion with a result - - -I've updated the CSS - - - -## switch_mode -Description: Request to switch to a different mode. This tool allows modes to request switching to another mode when needed, such as switching to Code mode to make code changes. The user must approve the mode switch. -Parameters: -- mode_slug: (required) The slug of the mode to switch to (e.g., "code", "ask", "architect") -- reason: (optional) The reason for switching modes -Usage: - -Mode slug here -Reason for switching here - - -Example: Requesting to switch to code mode - -code -Need to make code changes - - -## new_task -Description: This will let you create a new task instance in the chosen mode using your provided message. - -Parameters: -- mode: (required) The slug of the mode to start the new task in (e.g., "code", "debug", "architect"). -- message: (required) The initial user message or instructions for this new task. - -Usage: - -your-mode-slug-here -Your initial instructions here - - -Example: - -code -Implement a new feature for the application - - - -## update_todo_list - -**Description:** -Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks. - -**Checklist Format:** -- Use a single-level markdown checklist (no nesting or subtasks). -- List todos in the intended execution order. -- Status options: - - [ ] Task description (pending) - - [x] Task description (completed) - - [-] Task description (in progress) - -**Status Rules:** -- [ ] = pending (not started) -- [x] = completed (fully finished, no unresolved issues) -- [-] = in_progress (currently being worked on) - -**Core Principles:** -- Before updating, always confirm which todos have been completed since the last update. -- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress). -- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately. -- Do not remove any unfinished todos unless explicitly instructed. -- Always retain all unfinished tasks, updating their status as needed. -- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies). -- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved. -- Remove tasks only if they are no longer relevant or if the user requests deletion. - -**Usage Example:** - - -[x] Analyze requirements -[x] Design architecture -[-] Implement core logic -[ ] Write tests -[ ] Update documentation - - - -*After completing "Implement core logic" and starting "Write tests":* - - -[x] Analyze requirements -[x] Design architecture -[x] Implement core logic -[-] Write tests -[ ] Update documentation -[ ] Add performance benchmarks - - - -**When to Use:** -- The task is complicated or involves multiple steps or requires ongoing tracking. -- You need to update the status of several todos at once. -- New actionable items are discovered during task execution. -- The user requests a todo list or provides multiple tasks. -- The task is complex and benefits from clear, stepwise progress tracking. - -**When NOT to Use:** -- There is only a single, trivial task. -- The task can be completed in one or two simple steps. -- The request is purely conversational or informational. - -**Task Management Guidelines:** -- Mark task as completed immediately after all work of the current task is done. -- Start the next task by marking it as in_progress. -- Add new todos as soon as they are identified. -- Use clear, descriptive task names. - - -# Tool Use Guidelines +You have access to a set of tools that are executed upon the user's approval. Use the provider-native tool-calling mechanism. Do not include XML markup or examples. You must use exactly one tool call per assistant response. Do not call zero tools or more than one tool in the same response. + + # Tool Use Guidelines 1. Assess what information you already have and what information you need to proceed with the task. 2. Choose the most appropriate tool based on the task and the tool descriptions provided. Assess if you need additional information to proceed, and which of the available tools would be most effective for gathering this information. For example using the list_files tool is more effective than running a command like `ls` in the terminal. It's critical that you think about each available tool and use the one that best fits the current step in the task. 3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. -4. Formulate your tool use using the XML format specified for each tool. -5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: +4. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - Information about whether the tool succeeded or failed, along with any reasons for failure. - Linter errors that may have arisen due to the changes you made, which you'll need to address. - New terminal output in reaction to the changes, which you may need to consider or act upon. - Any other relevant feedback or information related to the tool use. -6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. +5. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: 1. Confirm the success of each step before proceeding. @@ -385,7 +53,7 @@ MODES RULES - The project base directory is: /test/path -- 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 . +- 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. - You cannot `cd` into a different directory to complete a task. You are stuck operating from '/test/path', 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 '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', 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)`. @@ -434,7 +102,7 @@ You accomplish a given task iteratively, breaking it down into clear steps and w 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. Language Preference: You should always speak and think in the "en" language. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap index 8edc23260ea..5305987e28a 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap @@ -10,436 +10,19 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka TOOL USE -You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. - -# Tool Use Formatting - -Tool uses are formatted using XML-style tags. The tool name itself becomes the XML tag name. Each parameter is enclosed within its own set of tags. Here's the structure: - - -value1 -value2 -... - - -Always use the actual tool name as the XML tag name for proper parsing and execution. - -# Tools - -## read_file -Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly. - -**IMPORTANT: You can read a maximum of 5 files in a single request.** If you need to read more files, use multiple sequential read_file requests. - - -Parameters: -- args: Contains one or more file elements, where each file contains: - - path: (required) File path (relative to workspace directory /test/path) - - -Usage: - - - - path/to/file - - - - - -Examples: - -1. Reading a single file: - - - - src/app.ts - - - - - -2. Reading multiple files (within the 5-file limit): - - - - src/app.ts - - - - src/utils.ts - - - - - -3. Reading an entire file: - - - - config.json - - - - -IMPORTANT: You MUST use this Efficient Reading Strategy: -- You MUST read all related files and implementations together in a single operation (up to 5 files at once) -- You MUST obtain all necessary context before proceeding with changes - -- When you need to read more than 5 files, prioritize the most critical files first, then use subsequent read_file requests for additional files - -## fetch_instructions -Description: Request to fetch instructions to perform a task -Parameters: -- task: (required) The task to get instructions for. This can take the following values: - create_mcp_server - create_mode - -Example: Requesting instructions to create an MCP Server - - -create_mcp_server - - -## search_files -Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. - -Craft your regex patterns carefully to balance specificity and flexibility. Use this tool to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include surrounding context, so analyze the surrounding code to better understand the matches. Leverage this tool in combination with other tools for more comprehensive analysis - for example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches. - -Parameters: -- path: (required) The path of the directory to search in (relative to the current workspace directory /test/path). This directory will be recursively searched. -- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax. -- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*). - -Usage: - -Directory path here -Your regex pattern here -file pattern here (optional) - - -Example: Searching for all .ts files in the current directory - -. -.* -*.ts - - -Example: Searching for function definitions in JavaScript files - -src -function\s+\w+ -*.js - - -## list_files -Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not. -Parameters: -- path: (required) The path of the directory to list contents for (relative to the current workspace directory /test/path) -- recursive: (optional) Whether to list files recursively. Use true for recursive listing, false or omit for top-level only. -Usage: - -Directory path here -true or false (optional) - - -Example: Requesting to list all files in the current directory - -. -false - - -## write_to_file -Description: Request to write content to a file. This tool is primarily used for **creating new files** or for scenarios where a **complete rewrite of an existing file is intentionally required**. If the file exists, it will be overwritten. If it doesn't exist, it will be created. This tool will automatically create any directories needed to write the file. - -**Important:** You should prefer using other editing tools over write_to_file when making changes to existing files, since write_to_file is slower and cannot handle large files. Use write_to_file primarily for new file creation. - -When using this tool, use it directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code. - -When creating a new project, organize all new files within a dedicated project directory unless the user specifies otherwise. Structure the project logically, adhering to best practices for the specific type of project being created. - -Parameters: -- path: (required) The path of the file to write to (relative to the current workspace directory /test/path) -- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include line numbers in the content. - -Usage: - -File path here - -Your file content here - - - -Example: Writing a configuration file - -frontend-config.json - -{ - "apiEndpoint": "https://api.example.com", - "theme": { - "primaryColor": "#007bff", - "secondaryColor": "#6c757d", - "fontFamily": "Arial, sans-serif" - }, - "features": { - "darkMode": true, - "notifications": true, - "analytics": false - }, - "version": "1.0.0" -} - - - -## browser_action -Description: Request to interact with a Puppeteer-controlled browser. Every action, except `close`, will be responded to with a screenshot of the browser's current state, along with any new console logs. You may only perform one browser action per message, and wait for the user's response including a screenshot and logs to determine the next action. - -This tool is particularly useful for web development tasks as it allows you to launch a browser, navigate to pages, interact with elements through clicks and keyboard input, and capture the results through screenshots and console logs. Use it at key stages of web development tasks - such as after implementing new features, making substantial changes, when troubleshooting issues, or to verify the result of your work. Analyze the provided screenshots to ensure correct rendering or identify errors, and review console logs for runtime issues. - -The user may ask generic non-development tasks (such as "what's the latest news" or "look up the weather"), in which case you might use this tool to complete the task if it makes sense to do so, rather than trying to create a website or using curl to answer the question. However, if an available MCP server tool or resource can be used instead, you should prefer to use it over browser_action. - -**Browser Session Lifecycle:** -- Browser sessions **start** with `launch` and **end** with `close` -- The session remains active across multiple messages and tool uses -- You can use other tools while the browser session is active - it will stay open in the background - -Parameters: -- action: (required) The action to perform. The available actions are: - * launch: Launch a new Puppeteer-controlled browser instance at the specified URL. This **must always be the first action**. - - Use with the `url` parameter to provide the URL. - - Ensure the URL is valid and includes the appropriate protocol (e.g. http://localhost:3000/page, file:///path/to/file.html, etc.) - * hover: Move the cursor to a specific x,y coordinate. - - Use with the `coordinate` parameter to specify the location. - - Always move to the center of an element (icon, button, link, etc.) based on coordinates derived from a screenshot. - * click: Click at a specific x,y coordinate. - - Use with the `coordinate` parameter to specify the location. - - Always click in the center of an element (icon, button, link, etc.) based on coordinates derived from a screenshot. - * type: Type a string of text on the keyboard. You might use this after clicking on a text field to input text. - - Use with the `text` parameter to provide the string to type. - * press: Press a single keyboard key or key combination (e.g., Enter, Tab, Escape, Cmd+K, Shift+Enter). - - Use with the `text` parameter to provide the key name or combination. - - For single keys: Enter, Tab, Escape, etc. - - For key combinations: Cmd+K, Ctrl+C, Shift+Enter, Alt+F4, etc. - - Supported modifiers: Cmd/Command/Meta, Ctrl/Control, Shift, Alt/Option - - Example: Cmd+K or Shift+Enter - * resize: Resize the viewport to a specific w,h size. - - Use with the `size` parameter to specify the new size. - * scroll_down: Scroll down the page by one page height. - * scroll_up: Scroll up the page by one page height. - * screenshot: Take a screenshot and save it to a file. - - Use with the `path` parameter to specify the destination file path. - - Supported formats: .png, .jpeg, .webp - - Example: `screenshot` with `screenshots/result.png` - * close: Close the Puppeteer-controlled browser instance. This **must always be the final browser action**. - - Example: `close` -- url: (optional) Use this for providing the URL for the `launch` action. - * Example: https://example.com -- coordinate: (optional) The X and Y coordinates for the `click` and `hover` actions. - * **CRITICAL**: Screenshot dimensions are NOT the same as the browser viewport dimensions - * Format: x,y@widthxheight - * Measure x,y on the screenshot image you see in chat - * The widthxheight MUST be the EXACT pixel size of that screenshot image (never the browser viewport) - * Never use the browser viewport size for widthxheight - the viewport is only a reference and is often larger than the screenshot - * Images are often downscaled before you see them, so the screenshot's dimensions will likely be smaller than the viewport - * Example A: If the screenshot you see is 1094x1092 and you want to click (450,300) on that image, use: 450,300@1094x1092 - * Example B: If the browser viewport is 1280x800 but the screenshot is 1000x625 and you want to click (500,300) on the screenshot, use: 500,300@1000x625 -- size: (optional) The width and height for the `resize` action. - * Example: 1280,720 -- text: (optional) Use this for providing the text for the `type` action. - * Example: Hello, world! -- path: (optional) File path for the `screenshot` action. Path is relative to the workspace. - * Supported formats: .png, .jpeg, .webp - * Example: screenshots/my-screenshot.png -Usage: - -Action to perform (e.g., launch, click, type, press, scroll_down, scroll_up, close) -URL to launch the browser at (optional) -x,y@widthxheight coordinates (optional) -Text to type (optional) - - -Example: Requesting to launch a browser at https://example.com - -launch -https://example.com - - -Example: Requesting to click on the element at coordinates 450,300 on a 1024x768 image - -click -450,300@1024x768 - - -Example: Taking a screenshot and saving it to a file - -screenshot -screenshots/result.png - - -## ask_followup_question -Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. - -Parameters: -- question: (required) A clear, specific question addressing the information needed -- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) - -Usage: - -Your question here - -First suggestion -Action with mode switch - - - -Example: - -What is the path to the frontend-config.json file? - -./src/frontend-config.json -./config/frontend-config.json -./frontend-config.json - - - -## attempt_completion -Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. -IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must confirm that you've received successful results from the user for any previous tool uses. If not, then DO NOT use this tool. -Parameters: -- result: (required) The result of the task. Formulate this result in a way that is final and does not require further input from the user. Don't end your result with questions or offers for further assistance. -Usage: - - -Your final result description here - - - -Example: Requesting to attempt completion with a result - - -I've updated the CSS - - - -## switch_mode -Description: Request to switch to a different mode. This tool allows modes to request switching to another mode when needed, such as switching to Code mode to make code changes. The user must approve the mode switch. -Parameters: -- mode_slug: (required) The slug of the mode to switch to (e.g., "code", "ask", "architect") -- reason: (optional) The reason for switching modes -Usage: - -Mode slug here -Reason for switching here - - -Example: Requesting to switch to code mode - -code -Need to make code changes - - -## new_task -Description: This will let you create a new task instance in the chosen mode using your provided message. - -Parameters: -- mode: (required) The slug of the mode to start the new task in (e.g., "code", "debug", "architect"). -- message: (required) The initial user message or instructions for this new task. - -Usage: - -your-mode-slug-here -Your initial instructions here - - -Example: - -code -Implement a new feature for the application - - - -## update_todo_list - -**Description:** -Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks. - -**Checklist Format:** -- Use a single-level markdown checklist (no nesting or subtasks). -- List todos in the intended execution order. -- Status options: - - [ ] Task description (pending) - - [x] Task description (completed) - - [-] Task description (in progress) - -**Status Rules:** -- [ ] = pending (not started) -- [x] = completed (fully finished, no unresolved issues) -- [-] = in_progress (currently being worked on) - -**Core Principles:** -- Before updating, always confirm which todos have been completed since the last update. -- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress). -- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately. -- Do not remove any unfinished todos unless explicitly instructed. -- Always retain all unfinished tasks, updating their status as needed. -- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies). -- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved. -- Remove tasks only if they are no longer relevant or if the user requests deletion. - -**Usage Example:** - - -[x] Analyze requirements -[x] Design architecture -[-] Implement core logic -[ ] Write tests -[ ] Update documentation - - - -*After completing "Implement core logic" and starting "Write tests":* - - -[x] Analyze requirements -[x] Design architecture -[x] Implement core logic -[-] Write tests -[ ] Update documentation -[ ] Add performance benchmarks - - - -**When to Use:** -- The task is complicated or involves multiple steps or requires ongoing tracking. -- You need to update the status of several todos at once. -- New actionable items are discovered during task execution. -- The user requests a todo list or provides multiple tasks. -- The task is complex and benefits from clear, stepwise progress tracking. - -**When NOT to Use:** -- There is only a single, trivial task. -- The task can be completed in one or two simple steps. -- The request is purely conversational or informational. - -**Task Management Guidelines:** -- Mark task as completed immediately after all work of the current task is done. -- Start the next task by marking it as in_progress. -- Add new todos as soon as they are identified. -- Use clear, descriptive task names. - - -# Tool Use Guidelines +You have access to a set of tools that are executed upon the user's approval. Use the provider-native tool-calling mechanism. Do not include XML markup or examples. You must use exactly one tool call per assistant response. Do not call zero tools or more than one tool in the same response. + + # Tool Use Guidelines 1. Assess what information you already have and what information you need to proceed with the task. 2. Choose the most appropriate tool based on the task and the tool descriptions provided. Assess if you need additional information to proceed, and which of the available tools would be most effective for gathering this information. For example using the list_files tool is more effective than running a command like `ls` in the terminal. It's critical that you think about each available tool and use the one that best fits the current step in the task. 3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. -4. Formulate your tool use using the XML format specified for each tool. -5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: +4. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - Information about whether the tool succeeded or failed, along with any reasons for failure. - Linter errors that may have arisen due to the changes you made, which you'll need to address. - New terminal output in reaction to the changes, which you may need to consider or act upon. - Any other relevant feedback or information related to the tool use. -6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. +5. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: 1. Confirm the success of each step before proceeding. @@ -470,7 +53,7 @@ MODES RULES - The project base directory is: /test/path -- 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 . +- 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. - You cannot `cd` into a different directory to complete a task. You are stuck operating from '/test/path', 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 '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', 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)`. @@ -519,7 +102,7 @@ You accomplish a given task iteratively, breaking it down into clear steps and w 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. Language Preference: You should always speak and think in the "en" language. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap index ee8a50e9933..5305987e28a 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap @@ -10,351 +10,19 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka TOOL USE -You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. - -# Tool Use Formatting - -Tool uses are formatted using XML-style tags. The tool name itself becomes the XML tag name. Each parameter is enclosed within its own set of tags. Here's the structure: - - -value1 -value2 -... - - -Always use the actual tool name as the XML tag name for proper parsing and execution. - -# Tools - -## read_file -Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly. - -**IMPORTANT: You can read a maximum of 5 files in a single request.** If you need to read more files, use multiple sequential read_file requests. - - -Parameters: -- args: Contains one or more file elements, where each file contains: - - path: (required) File path (relative to workspace directory /test/path) - - -Usage: - - - - path/to/file - - - - - -Examples: - -1. Reading a single file: - - - - src/app.ts - - - - - -2. Reading multiple files (within the 5-file limit): - - - - src/app.ts - - - - src/utils.ts - - - - - -3. Reading an entire file: - - - - config.json - - - - -IMPORTANT: You MUST use this Efficient Reading Strategy: -- You MUST read all related files and implementations together in a single operation (up to 5 files at once) -- You MUST obtain all necessary context before proceeding with changes - -- When you need to read more than 5 files, prioritize the most critical files first, then use subsequent read_file requests for additional files - -## fetch_instructions -Description: Request to fetch instructions to perform a task -Parameters: -- task: (required) The task to get instructions for. This can take the following values: - create_mcp_server - create_mode - -Example: Requesting instructions to create an MCP Server - - -create_mcp_server - - -## search_files -Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. - -Craft your regex patterns carefully to balance specificity and flexibility. Use this tool to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include surrounding context, so analyze the surrounding code to better understand the matches. Leverage this tool in combination with other tools for more comprehensive analysis - for example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches. - -Parameters: -- path: (required) The path of the directory to search in (relative to the current workspace directory /test/path). This directory will be recursively searched. -- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax. -- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*). - -Usage: - -Directory path here -Your regex pattern here -file pattern here (optional) - - -Example: Searching for all .ts files in the current directory - -. -.* -*.ts - - -Example: Searching for function definitions in JavaScript files - -src -function\s+\w+ -*.js - - -## list_files -Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not. -Parameters: -- path: (required) The path of the directory to list contents for (relative to the current workspace directory /test/path) -- recursive: (optional) Whether to list files recursively. Use true for recursive listing, false or omit for top-level only. -Usage: - -Directory path here -true or false (optional) - - -Example: Requesting to list all files in the current directory - -. -false - - -## write_to_file -Description: Request to write content to a file. This tool is primarily used for **creating new files** or for scenarios where a **complete rewrite of an existing file is intentionally required**. If the file exists, it will be overwritten. If it doesn't exist, it will be created. This tool will automatically create any directories needed to write the file. - -**Important:** You should prefer using other editing tools over write_to_file when making changes to existing files, since write_to_file is slower and cannot handle large files. Use write_to_file primarily for new file creation. - -When using this tool, use it directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code. - -When creating a new project, organize all new files within a dedicated project directory unless the user specifies otherwise. Structure the project logically, adhering to best practices for the specific type of project being created. - -Parameters: -- path: (required) The path of the file to write to (relative to the current workspace directory /test/path) -- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include line numbers in the content. - -Usage: - -File path here - -Your file content here - - - -Example: Writing a configuration file - -frontend-config.json - -{ - "apiEndpoint": "https://api.example.com", - "theme": { - "primaryColor": "#007bff", - "secondaryColor": "#6c757d", - "fontFamily": "Arial, sans-serif" - }, - "features": { - "darkMode": true, - "notifications": true, - "analytics": false - }, - "version": "1.0.0" -} - - - -## ask_followup_question -Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. - -Parameters: -- question: (required) A clear, specific question addressing the information needed -- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) - -Usage: - -Your question here - -First suggestion -Action with mode switch - - - -Example: - -What is the path to the frontend-config.json file? - -./src/frontend-config.json -./config/frontend-config.json -./frontend-config.json - - - -## attempt_completion -Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. -IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must confirm that you've received successful results from the user for any previous tool uses. If not, then DO NOT use this tool. -Parameters: -- result: (required) The result of the task. Formulate this result in a way that is final and does not require further input from the user. Don't end your result with questions or offers for further assistance. -Usage: - - -Your final result description here - - - -Example: Requesting to attempt completion with a result - - -I've updated the CSS - - - -## switch_mode -Description: Request to switch to a different mode. This tool allows modes to request switching to another mode when needed, such as switching to Code mode to make code changes. The user must approve the mode switch. -Parameters: -- mode_slug: (required) The slug of the mode to switch to (e.g., "code", "ask", "architect") -- reason: (optional) The reason for switching modes -Usage: - -Mode slug here -Reason for switching here - - -Example: Requesting to switch to code mode - -code -Need to make code changes - - -## new_task -Description: This will let you create a new task instance in the chosen mode using your provided message. - -Parameters: -- mode: (required) The slug of the mode to start the new task in (e.g., "code", "debug", "architect"). -- message: (required) The initial user message or instructions for this new task. - -Usage: - -your-mode-slug-here -Your initial instructions here - - -Example: - -code -Implement a new feature for the application - - - -## update_todo_list - -**Description:** -Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks. - -**Checklist Format:** -- Use a single-level markdown checklist (no nesting or subtasks). -- List todos in the intended execution order. -- Status options: - - [ ] Task description (pending) - - [x] Task description (completed) - - [-] Task description (in progress) - -**Status Rules:** -- [ ] = pending (not started) -- [x] = completed (fully finished, no unresolved issues) -- [-] = in_progress (currently being worked on) - -**Core Principles:** -- Before updating, always confirm which todos have been completed since the last update. -- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress). -- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately. -- Do not remove any unfinished todos unless explicitly instructed. -- Always retain all unfinished tasks, updating their status as needed. -- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies). -- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved. -- Remove tasks only if they are no longer relevant or if the user requests deletion. - -**Usage Example:** - - -[x] Analyze requirements -[x] Design architecture -[-] Implement core logic -[ ] Write tests -[ ] Update documentation - - - -*After completing "Implement core logic" and starting "Write tests":* - - -[x] Analyze requirements -[x] Design architecture -[x] Implement core logic -[-] Write tests -[ ] Update documentation -[ ] Add performance benchmarks - - - -**When to Use:** -- The task is complicated or involves multiple steps or requires ongoing tracking. -- You need to update the status of several todos at once. -- New actionable items are discovered during task execution. -- The user requests a todo list or provides multiple tasks. -- The task is complex and benefits from clear, stepwise progress tracking. - -**When NOT to Use:** -- There is only a single, trivial task. -- The task can be completed in one or two simple steps. -- The request is purely conversational or informational. - -**Task Management Guidelines:** -- Mark task as completed immediately after all work of the current task is done. -- Start the next task by marking it as in_progress. -- Add new todos as soon as they are identified. -- Use clear, descriptive task names. - - -# Tool Use Guidelines +You have access to a set of tools that are executed upon the user's approval. Use the provider-native tool-calling mechanism. Do not include XML markup or examples. You must use exactly one tool call per assistant response. Do not call zero tools or more than one tool in the same response. + + # Tool Use Guidelines 1. Assess what information you already have and what information you need to proceed with the task. 2. Choose the most appropriate tool based on the task and the tool descriptions provided. Assess if you need additional information to proceed, and which of the available tools would be most effective for gathering this information. For example using the list_files tool is more effective than running a command like `ls` in the terminal. It's critical that you think about each available tool and use the one that best fits the current step in the task. 3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. -4. Formulate your tool use using the XML format specified for each tool. -5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: +4. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - Information about whether the tool succeeded or failed, along with any reasons for failure. - Linter errors that may have arisen due to the changes you made, which you'll need to address. - New terminal output in reaction to the changes, which you may need to consider or act upon. - Any other relevant feedback or information related to the tool use. -6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. +5. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: 1. Confirm the success of each step before proceeding. @@ -385,7 +53,7 @@ MODES RULES - The project base directory is: /test/path -- 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 . +- 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. - You cannot `cd` into a different directory to complete a task. You are stuck operating from '/test/path', 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 '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', 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)`. @@ -434,7 +102,7 @@ You accomplish a given task iteratively, breaking it down into clear steps and w 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. Language Preference: You should always speak and think in the "en" language. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap index 54df428abd3..5305987e28a 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap @@ -10,439 +10,19 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka TOOL USE -You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. - -# Tool Use Formatting - -Tool uses are formatted using XML-style tags. The tool name itself becomes the XML tag name. Each parameter is enclosed within its own set of tags. Here's the structure: - - -value1 -value2 -... - - -Always use the actual tool name as the XML tag name for proper parsing and execution. - -# Tools - -## read_file -Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly. - -**IMPORTANT: You can read a maximum of 5 files in a single request.** If you need to read more files, use multiple sequential read_file requests. - - -Parameters: -- args: Contains one or more file elements, where each file contains: - - path: (required) File path (relative to workspace directory /test/path) - - -Usage: - - - - path/to/file - - - - - -Examples: - -1. Reading a single file: - - - - src/app.ts - - - - - -2. Reading multiple files (within the 5-file limit): - - - - src/app.ts - - - - src/utils.ts - - - - - -3. Reading an entire file: - - - - config.json - - - - -IMPORTANT: You MUST use this Efficient Reading Strategy: -- You MUST read all related files and implementations together in a single operation (up to 5 files at once) -- You MUST obtain all necessary context before proceeding with changes - -- When you need to read more than 5 files, prioritize the most critical files first, then use subsequent read_file requests for additional files - -## fetch_instructions -Description: Request to fetch instructions to perform a task -Parameters: -- task: (required) The task to get instructions for. This can take the following values: - create_mcp_server - create_mode - -Example: Requesting instructions to create an MCP Server - - -create_mcp_server - - -## search_files -Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. - -Craft your regex patterns carefully to balance specificity and flexibility. Use this tool to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include surrounding context, so analyze the surrounding code to better understand the matches. Leverage this tool in combination with other tools for more comprehensive analysis - for example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches. - -Parameters: -- path: (required) The path of the directory to search in (relative to the current workspace directory /test/path). This directory will be recursively searched. -- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax. -- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*). - -Usage: - -Directory path here -Your regex pattern here -file pattern here (optional) - - -Example: Searching for all .ts files in the current directory - -. -.* -*.ts - - -Example: Searching for function definitions in JavaScript files - -src -function\s+\w+ -*.js - - -## list_files -Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not. -Parameters: -- path: (required) The path of the directory to list contents for (relative to the current workspace directory /test/path) -- recursive: (optional) Whether to list files recursively. Use true for recursive listing, false or omit for top-level only. -Usage: - -Directory path here -true or false (optional) - - -Example: Requesting to list all files in the current directory - -. -false - - -## apply_diff -Description: Request to apply PRECISE, TARGETED modifications to an existing file by searching for specific sections of content and replacing them. This tool is for SURGICAL EDITS ONLY - specific changes to existing code. -You can perform multiple distinct search and replace operations within a single `apply_diff` call by providing multiple SEARCH/REPLACE blocks in the `diff` parameter. This is the preferred way to make several targeted changes efficiently. -The SEARCH section must exactly match existing content including whitespace and indentation. -If you're not confident in the exact content to search for, use the read_file tool first to get the exact content. -When applying the diffs, be extra careful to remember to change any closing brackets or other syntax that may be affected by the diff farther down in the file. -ALWAYS make as many changes in a single 'apply_diff' request as possible using multiple SEARCH/REPLACE blocks - -Parameters: -- path: (required) The path of the file to modify (relative to the current workspace directory /test/path) -- diff: (required) The search/replace block defining the changes. - -Diff format: -``` -<<<<<<< SEARCH -:start_line: (required) The line number of original content where the search block starts. -------- -[exact content to find including whitespace] -======= -[new content to replace with] ->>>>>>> REPLACE - -``` - - -Example: - -Original file: -``` -1 | def calculate_total(items): -2 | total = 0 -3 | for item in items: -4 | total += item -5 | return total -``` - -Search/Replace content: -``` -<<<<<<< SEARCH -:start_line:1 -------- -def calculate_total(items): - total = 0 - for item in items: - total += item - return total -======= -def calculate_total(items): - """Calculate total with 10% markup""" - return sum(item * 1.1 for item in items) ->>>>>>> REPLACE - -``` - -Search/Replace content with multiple edits: -``` -<<<<<<< SEARCH -:start_line:1 -------- -def calculate_total(items): - sum = 0 -======= -def calculate_sum(items): - sum = 0 ->>>>>>> REPLACE - -<<<<<<< SEARCH -:start_line:4 -------- - total += item - return total -======= - sum += item - return sum ->>>>>>> REPLACE -``` - - -Usage: - -File path here - -Your search/replace content here -You can use multi search/replace block in one diff block, but make sure to include the line numbers for each block. -Only use a single line of '=======' between search and replacement content, because multiple '=======' will corrupt the file. - - - -## write_to_file -Description: Request to write content to a file. This tool is primarily used for **creating new files** or for scenarios where a **complete rewrite of an existing file is intentionally required**. If the file exists, it will be overwritten. If it doesn't exist, it will be created. This tool will automatically create any directories needed to write the file. - -**Important:** You should prefer using other editing tools over write_to_file when making changes to existing files, since write_to_file is slower and cannot handle large files. Use write_to_file primarily for new file creation. - -When using this tool, use it directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code. - -When creating a new project, organize all new files within a dedicated project directory unless the user specifies otherwise. Structure the project logically, adhering to best practices for the specific type of project being created. - -Parameters: -- path: (required) The path of the file to write to (relative to the current workspace directory /test/path) -- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include line numbers in the content. - -Usage: - -File path here - -Your file content here - - - -Example: Writing a configuration file - -frontend-config.json - -{ - "apiEndpoint": "https://api.example.com", - "theme": { - "primaryColor": "#007bff", - "secondaryColor": "#6c757d", - "fontFamily": "Arial, sans-serif" - }, - "features": { - "darkMode": true, - "notifications": true, - "analytics": false - }, - "version": "1.0.0" -} - - - -## ask_followup_question -Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. - -Parameters: -- question: (required) A clear, specific question addressing the information needed -- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) - -Usage: - -Your question here - -First suggestion -Action with mode switch - - - -Example: - -What is the path to the frontend-config.json file? - -./src/frontend-config.json -./config/frontend-config.json -./frontend-config.json - - - -## attempt_completion -Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. -IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must confirm that you've received successful results from the user for any previous tool uses. If not, then DO NOT use this tool. -Parameters: -- result: (required) The result of the task. Formulate this result in a way that is final and does not require further input from the user. Don't end your result with questions or offers for further assistance. -Usage: - - -Your final result description here - - - -Example: Requesting to attempt completion with a result - - -I've updated the CSS - - - -## switch_mode -Description: Request to switch to a different mode. This tool allows modes to request switching to another mode when needed, such as switching to Code mode to make code changes. The user must approve the mode switch. -Parameters: -- mode_slug: (required) The slug of the mode to switch to (e.g., "code", "ask", "architect") -- reason: (optional) The reason for switching modes -Usage: - -Mode slug here -Reason for switching here - - -Example: Requesting to switch to code mode - -code -Need to make code changes - - -## new_task -Description: This will let you create a new task instance in the chosen mode using your provided message. - -Parameters: -- mode: (required) The slug of the mode to start the new task in (e.g., "code", "debug", "architect"). -- message: (required) The initial user message or instructions for this new task. - -Usage: - -your-mode-slug-here -Your initial instructions here - - -Example: - -code -Implement a new feature for the application - - - -## update_todo_list - -**Description:** -Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks. - -**Checklist Format:** -- Use a single-level markdown checklist (no nesting or subtasks). -- List todos in the intended execution order. -- Status options: - - [ ] Task description (pending) - - [x] Task description (completed) - - [-] Task description (in progress) - -**Status Rules:** -- [ ] = pending (not started) -- [x] = completed (fully finished, no unresolved issues) -- [-] = in_progress (currently being worked on) - -**Core Principles:** -- Before updating, always confirm which todos have been completed since the last update. -- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress). -- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately. -- Do not remove any unfinished todos unless explicitly instructed. -- Always retain all unfinished tasks, updating their status as needed. -- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies). -- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved. -- Remove tasks only if they are no longer relevant or if the user requests deletion. - -**Usage Example:** - - -[x] Analyze requirements -[x] Design architecture -[-] Implement core logic -[ ] Write tests -[ ] Update documentation - - - -*After completing "Implement core logic" and starting "Write tests":* - - -[x] Analyze requirements -[x] Design architecture -[x] Implement core logic -[-] Write tests -[ ] Update documentation -[ ] Add performance benchmarks - - - -**When to Use:** -- The task is complicated or involves multiple steps or requires ongoing tracking. -- You need to update the status of several todos at once. -- New actionable items are discovered during task execution. -- The user requests a todo list or provides multiple tasks. -- The task is complex and benefits from clear, stepwise progress tracking. - -**When NOT to Use:** -- There is only a single, trivial task. -- The task can be completed in one or two simple steps. -- The request is purely conversational or informational. - -**Task Management Guidelines:** -- Mark task as completed immediately after all work of the current task is done. -- Start the next task by marking it as in_progress. -- Add new todos as soon as they are identified. -- Use clear, descriptive task names. - - -# Tool Use Guidelines +You have access to a set of tools that are executed upon the user's approval. Use the provider-native tool-calling mechanism. Do not include XML markup or examples. You must use exactly one tool call per assistant response. Do not call zero tools or more than one tool in the same response. + + # Tool Use Guidelines 1. Assess what information you already have and what information you need to proceed with the task. 2. Choose the most appropriate tool based on the task and the tool descriptions provided. Assess if you need additional information to proceed, and which of the available tools would be most effective for gathering this information. For example using the list_files tool is more effective than running a command like `ls` in the terminal. It's critical that you think about each available tool and use the one that best fits the current step in the task. 3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. -4. Formulate your tool use using the XML format specified for each tool. -5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: +4. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - Information about whether the tool succeeded or failed, along with any reasons for failure. - Linter errors that may have arisen due to the changes you made, which you'll need to address. - New terminal output in reaction to the changes, which you may need to consider or act upon. - Any other relevant feedback or information related to the tool use. -6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. +5. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: 1. Confirm the success of each step before proceeding. @@ -473,7 +53,7 @@ MODES RULES - The project base directory is: /test/path -- 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 . +- 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. - You cannot `cd` into a different directory to complete a task. You are stuck operating from '/test/path', 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 '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', 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)`. @@ -522,7 +102,7 @@ You accomplish a given task iteratively, breaking it down into clear steps and w 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. Language Preference: You should always speak and think in the "en" language. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap index ee8a50e9933..5305987e28a 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap @@ -10,351 +10,19 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka TOOL USE -You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. - -# Tool Use Formatting - -Tool uses are formatted using XML-style tags. The tool name itself becomes the XML tag name. Each parameter is enclosed within its own set of tags. Here's the structure: - - -value1 -value2 -... - - -Always use the actual tool name as the XML tag name for proper parsing and execution. - -# Tools - -## read_file -Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly. - -**IMPORTANT: You can read a maximum of 5 files in a single request.** If you need to read more files, use multiple sequential read_file requests. - - -Parameters: -- args: Contains one or more file elements, where each file contains: - - path: (required) File path (relative to workspace directory /test/path) - - -Usage: - - - - path/to/file - - - - - -Examples: - -1. Reading a single file: - - - - src/app.ts - - - - - -2. Reading multiple files (within the 5-file limit): - - - - src/app.ts - - - - src/utils.ts - - - - - -3. Reading an entire file: - - - - config.json - - - - -IMPORTANT: You MUST use this Efficient Reading Strategy: -- You MUST read all related files and implementations together in a single operation (up to 5 files at once) -- You MUST obtain all necessary context before proceeding with changes - -- When you need to read more than 5 files, prioritize the most critical files first, then use subsequent read_file requests for additional files - -## fetch_instructions -Description: Request to fetch instructions to perform a task -Parameters: -- task: (required) The task to get instructions for. This can take the following values: - create_mcp_server - create_mode - -Example: Requesting instructions to create an MCP Server - - -create_mcp_server - - -## search_files -Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. - -Craft your regex patterns carefully to balance specificity and flexibility. Use this tool to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include surrounding context, so analyze the surrounding code to better understand the matches. Leverage this tool in combination with other tools for more comprehensive analysis - for example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches. - -Parameters: -- path: (required) The path of the directory to search in (relative to the current workspace directory /test/path). This directory will be recursively searched. -- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax. -- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*). - -Usage: - -Directory path here -Your regex pattern here -file pattern here (optional) - - -Example: Searching for all .ts files in the current directory - -. -.* -*.ts - - -Example: Searching for function definitions in JavaScript files - -src -function\s+\w+ -*.js - - -## list_files -Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not. -Parameters: -- path: (required) The path of the directory to list contents for (relative to the current workspace directory /test/path) -- recursive: (optional) Whether to list files recursively. Use true for recursive listing, false or omit for top-level only. -Usage: - -Directory path here -true or false (optional) - - -Example: Requesting to list all files in the current directory - -. -false - - -## write_to_file -Description: Request to write content to a file. This tool is primarily used for **creating new files** or for scenarios where a **complete rewrite of an existing file is intentionally required**. If the file exists, it will be overwritten. If it doesn't exist, it will be created. This tool will automatically create any directories needed to write the file. - -**Important:** You should prefer using other editing tools over write_to_file when making changes to existing files, since write_to_file is slower and cannot handle large files. Use write_to_file primarily for new file creation. - -When using this tool, use it directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code. - -When creating a new project, organize all new files within a dedicated project directory unless the user specifies otherwise. Structure the project logically, adhering to best practices for the specific type of project being created. - -Parameters: -- path: (required) The path of the file to write to (relative to the current workspace directory /test/path) -- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include line numbers in the content. - -Usage: - -File path here - -Your file content here - - - -Example: Writing a configuration file - -frontend-config.json - -{ - "apiEndpoint": "https://api.example.com", - "theme": { - "primaryColor": "#007bff", - "secondaryColor": "#6c757d", - "fontFamily": "Arial, sans-serif" - }, - "features": { - "darkMode": true, - "notifications": true, - "analytics": false - }, - "version": "1.0.0" -} - - - -## ask_followup_question -Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. - -Parameters: -- question: (required) A clear, specific question addressing the information needed -- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) - -Usage: - -Your question here - -First suggestion -Action with mode switch - - - -Example: - -What is the path to the frontend-config.json file? - -./src/frontend-config.json -./config/frontend-config.json -./frontend-config.json - - - -## attempt_completion -Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. -IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must confirm that you've received successful results from the user for any previous tool uses. If not, then DO NOT use this tool. -Parameters: -- result: (required) The result of the task. Formulate this result in a way that is final and does not require further input from the user. Don't end your result with questions or offers for further assistance. -Usage: - - -Your final result description here - - - -Example: Requesting to attempt completion with a result - - -I've updated the CSS - - - -## switch_mode -Description: Request to switch to a different mode. This tool allows modes to request switching to another mode when needed, such as switching to Code mode to make code changes. The user must approve the mode switch. -Parameters: -- mode_slug: (required) The slug of the mode to switch to (e.g., "code", "ask", "architect") -- reason: (optional) The reason for switching modes -Usage: - -Mode slug here -Reason for switching here - - -Example: Requesting to switch to code mode - -code -Need to make code changes - - -## new_task -Description: This will let you create a new task instance in the chosen mode using your provided message. - -Parameters: -- mode: (required) The slug of the mode to start the new task in (e.g., "code", "debug", "architect"). -- message: (required) The initial user message or instructions for this new task. - -Usage: - -your-mode-slug-here -Your initial instructions here - - -Example: - -code -Implement a new feature for the application - - - -## update_todo_list - -**Description:** -Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks. - -**Checklist Format:** -- Use a single-level markdown checklist (no nesting or subtasks). -- List todos in the intended execution order. -- Status options: - - [ ] Task description (pending) - - [x] Task description (completed) - - [-] Task description (in progress) - -**Status Rules:** -- [ ] = pending (not started) -- [x] = completed (fully finished, no unresolved issues) -- [-] = in_progress (currently being worked on) - -**Core Principles:** -- Before updating, always confirm which todos have been completed since the last update. -- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress). -- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately. -- Do not remove any unfinished todos unless explicitly instructed. -- Always retain all unfinished tasks, updating their status as needed. -- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies). -- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved. -- Remove tasks only if they are no longer relevant or if the user requests deletion. - -**Usage Example:** - - -[x] Analyze requirements -[x] Design architecture -[-] Implement core logic -[ ] Write tests -[ ] Update documentation - - - -*After completing "Implement core logic" and starting "Write tests":* - - -[x] Analyze requirements -[x] Design architecture -[x] Implement core logic -[-] Write tests -[ ] Update documentation -[ ] Add performance benchmarks - - - -**When to Use:** -- The task is complicated or involves multiple steps or requires ongoing tracking. -- You need to update the status of several todos at once. -- New actionable items are discovered during task execution. -- The user requests a todo list or provides multiple tasks. -- The task is complex and benefits from clear, stepwise progress tracking. - -**When NOT to Use:** -- There is only a single, trivial task. -- The task can be completed in one or two simple steps. -- The request is purely conversational or informational. - -**Task Management Guidelines:** -- Mark task as completed immediately after all work of the current task is done. -- Start the next task by marking it as in_progress. -- Add new todos as soon as they are identified. -- Use clear, descriptive task names. - - -# Tool Use Guidelines +You have access to a set of tools that are executed upon the user's approval. Use the provider-native tool-calling mechanism. Do not include XML markup or examples. You must use exactly one tool call per assistant response. Do not call zero tools or more than one tool in the same response. + + # Tool Use Guidelines 1. Assess what information you already have and what information you need to proceed with the task. 2. Choose the most appropriate tool based on the task and the tool descriptions provided. Assess if you need additional information to proceed, and which of the available tools would be most effective for gathering this information. For example using the list_files tool is more effective than running a command like `ls` in the terminal. It's critical that you think about each available tool and use the one that best fits the current step in the task. 3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. -4. Formulate your tool use using the XML format specified for each tool. -5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: +4. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - Information about whether the tool succeeded or failed, along with any reasons for failure. - Linter errors that may have arisen due to the changes you made, which you'll need to address. - New terminal output in reaction to the changes, which you may need to consider or act upon. - Any other relevant feedback or information related to the tool use. -6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. +5. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: 1. Confirm the success of each step before proceeding. @@ -385,7 +53,7 @@ MODES RULES - The project base directory is: /test/path -- 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 . +- 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. - You cannot `cd` into a different directory to complete a task. You are stuck operating from '/test/path', 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 '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', 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)`. @@ -434,7 +102,7 @@ You accomplish a given task iteratively, breaking it down into clear steps and w 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. Language Preference: You should always speak and think in the "en" language. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap index ee8a50e9933..5305987e28a 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap @@ -10,351 +10,19 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka TOOL USE -You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. - -# Tool Use Formatting - -Tool uses are formatted using XML-style tags. The tool name itself becomes the XML tag name. Each parameter is enclosed within its own set of tags. Here's the structure: - - -value1 -value2 -... - - -Always use the actual tool name as the XML tag name for proper parsing and execution. - -# Tools - -## read_file -Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly. - -**IMPORTANT: You can read a maximum of 5 files in a single request.** If you need to read more files, use multiple sequential read_file requests. - - -Parameters: -- args: Contains one or more file elements, where each file contains: - - path: (required) File path (relative to workspace directory /test/path) - - -Usage: - - - - path/to/file - - - - - -Examples: - -1. Reading a single file: - - - - src/app.ts - - - - - -2. Reading multiple files (within the 5-file limit): - - - - src/app.ts - - - - src/utils.ts - - - - - -3. Reading an entire file: - - - - config.json - - - - -IMPORTANT: You MUST use this Efficient Reading Strategy: -- You MUST read all related files and implementations together in a single operation (up to 5 files at once) -- You MUST obtain all necessary context before proceeding with changes - -- When you need to read more than 5 files, prioritize the most critical files first, then use subsequent read_file requests for additional files - -## fetch_instructions -Description: Request to fetch instructions to perform a task -Parameters: -- task: (required) The task to get instructions for. This can take the following values: - create_mcp_server - create_mode - -Example: Requesting instructions to create an MCP Server - - -create_mcp_server - - -## search_files -Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. - -Craft your regex patterns carefully to balance specificity and flexibility. Use this tool to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include surrounding context, so analyze the surrounding code to better understand the matches. Leverage this tool in combination with other tools for more comprehensive analysis - for example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches. - -Parameters: -- path: (required) The path of the directory to search in (relative to the current workspace directory /test/path). This directory will be recursively searched. -- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax. -- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*). - -Usage: - -Directory path here -Your regex pattern here -file pattern here (optional) - - -Example: Searching for all .ts files in the current directory - -. -.* -*.ts - - -Example: Searching for function definitions in JavaScript files - -src -function\s+\w+ -*.js - - -## list_files -Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not. -Parameters: -- path: (required) The path of the directory to list contents for (relative to the current workspace directory /test/path) -- recursive: (optional) Whether to list files recursively. Use true for recursive listing, false or omit for top-level only. -Usage: - -Directory path here -true or false (optional) - - -Example: Requesting to list all files in the current directory - -. -false - - -## write_to_file -Description: Request to write content to a file. This tool is primarily used for **creating new files** or for scenarios where a **complete rewrite of an existing file is intentionally required**. If the file exists, it will be overwritten. If it doesn't exist, it will be created. This tool will automatically create any directories needed to write the file. - -**Important:** You should prefer using other editing tools over write_to_file when making changes to existing files, since write_to_file is slower and cannot handle large files. Use write_to_file primarily for new file creation. - -When using this tool, use it directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code. - -When creating a new project, organize all new files within a dedicated project directory unless the user specifies otherwise. Structure the project logically, adhering to best practices for the specific type of project being created. - -Parameters: -- path: (required) The path of the file to write to (relative to the current workspace directory /test/path) -- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include line numbers in the content. - -Usage: - -File path here - -Your file content here - - - -Example: Writing a configuration file - -frontend-config.json - -{ - "apiEndpoint": "https://api.example.com", - "theme": { - "primaryColor": "#007bff", - "secondaryColor": "#6c757d", - "fontFamily": "Arial, sans-serif" - }, - "features": { - "darkMode": true, - "notifications": true, - "analytics": false - }, - "version": "1.0.0" -} - - - -## ask_followup_question -Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. - -Parameters: -- question: (required) A clear, specific question addressing the information needed -- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) - -Usage: - -Your question here - -First suggestion -Action with mode switch - - - -Example: - -What is the path to the frontend-config.json file? - -./src/frontend-config.json -./config/frontend-config.json -./frontend-config.json - - - -## attempt_completion -Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. -IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must confirm that you've received successful results from the user for any previous tool uses. If not, then DO NOT use this tool. -Parameters: -- result: (required) The result of the task. Formulate this result in a way that is final and does not require further input from the user. Don't end your result with questions or offers for further assistance. -Usage: - - -Your final result description here - - - -Example: Requesting to attempt completion with a result - - -I've updated the CSS - - - -## switch_mode -Description: Request to switch to a different mode. This tool allows modes to request switching to another mode when needed, such as switching to Code mode to make code changes. The user must approve the mode switch. -Parameters: -- mode_slug: (required) The slug of the mode to switch to (e.g., "code", "ask", "architect") -- reason: (optional) The reason for switching modes -Usage: - -Mode slug here -Reason for switching here - - -Example: Requesting to switch to code mode - -code -Need to make code changes - - -## new_task -Description: This will let you create a new task instance in the chosen mode using your provided message. - -Parameters: -- mode: (required) The slug of the mode to start the new task in (e.g., "code", "debug", "architect"). -- message: (required) The initial user message or instructions for this new task. - -Usage: - -your-mode-slug-here -Your initial instructions here - - -Example: - -code -Implement a new feature for the application - - - -## update_todo_list - -**Description:** -Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks. - -**Checklist Format:** -- Use a single-level markdown checklist (no nesting or subtasks). -- List todos in the intended execution order. -- Status options: - - [ ] Task description (pending) - - [x] Task description (completed) - - [-] Task description (in progress) - -**Status Rules:** -- [ ] = pending (not started) -- [x] = completed (fully finished, no unresolved issues) -- [-] = in_progress (currently being worked on) - -**Core Principles:** -- Before updating, always confirm which todos have been completed since the last update. -- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress). -- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately. -- Do not remove any unfinished todos unless explicitly instructed. -- Always retain all unfinished tasks, updating their status as needed. -- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies). -- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved. -- Remove tasks only if they are no longer relevant or if the user requests deletion. - -**Usage Example:** - - -[x] Analyze requirements -[x] Design architecture -[-] Implement core logic -[ ] Write tests -[ ] Update documentation - - - -*After completing "Implement core logic" and starting "Write tests":* - - -[x] Analyze requirements -[x] Design architecture -[x] Implement core logic -[-] Write tests -[ ] Update documentation -[ ] Add performance benchmarks - - - -**When to Use:** -- The task is complicated or involves multiple steps or requires ongoing tracking. -- You need to update the status of several todos at once. -- New actionable items are discovered during task execution. -- The user requests a todo list or provides multiple tasks. -- The task is complex and benefits from clear, stepwise progress tracking. - -**When NOT to Use:** -- There is only a single, trivial task. -- The task can be completed in one or two simple steps. -- The request is purely conversational or informational. - -**Task Management Guidelines:** -- Mark task as completed immediately after all work of the current task is done. -- Start the next task by marking it as in_progress. -- Add new todos as soon as they are identified. -- Use clear, descriptive task names. - - -# Tool Use Guidelines +You have access to a set of tools that are executed upon the user's approval. Use the provider-native tool-calling mechanism. Do not include XML markup or examples. You must use exactly one tool call per assistant response. Do not call zero tools or more than one tool in the same response. + + # Tool Use Guidelines 1. Assess what information you already have and what information you need to proceed with the task. 2. Choose the most appropriate tool based on the task and the tool descriptions provided. Assess if you need additional information to proceed, and which of the available tools would be most effective for gathering this information. For example using the list_files tool is more effective than running a command like `ls` in the terminal. It's critical that you think about each available tool and use the one that best fits the current step in the task. 3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. -4. Formulate your tool use using the XML format specified for each tool. -5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: +4. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - Information about whether the tool succeeded or failed, along with any reasons for failure. - Linter errors that may have arisen due to the changes you made, which you'll need to address. - New terminal output in reaction to the changes, which you may need to consider or act upon. - Any other relevant feedback or information related to the tool use. -6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. +5. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: 1. Confirm the success of each step before proceeding. @@ -385,7 +53,7 @@ MODES RULES - The project base directory is: /test/path -- 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 . +- 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. - You cannot `cd` into a different directory to complete a task. You are stuck operating from '/test/path', 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 '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', 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)`. @@ -434,7 +102,7 @@ You accomplish a given task iteratively, breaking it down into clear steps and w 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. Language Preference: You should always speak and think in the "en" language. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap index acc36d1ffd8..baa8d519d86 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap @@ -10,400 +10,19 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka TOOL USE -You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. - -# Tool Use Formatting - -Tool uses are formatted using XML-style tags. The tool name itself becomes the XML tag name. Each parameter is enclosed within its own set of tags. Here's the structure: - - -value1 -value2 -... - - -Always use the actual tool name as the XML tag name for proper parsing and execution. - -# Tools - -## read_file -Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly. - -**IMPORTANT: You can read a maximum of 5 files in a single request.** If you need to read more files, use multiple sequential read_file requests. - - -Parameters: -- args: Contains one or more file elements, where each file contains: - - path: (required) File path (relative to workspace directory /test/path) - - -Usage: - - - - path/to/file - - - - - -Examples: - -1. Reading a single file: - - - - src/app.ts - - - - - -2. Reading multiple files (within the 5-file limit): - - - - src/app.ts - - - - src/utils.ts - - - - - -3. Reading an entire file: - - - - config.json - - - - -IMPORTANT: You MUST use this Efficient Reading Strategy: -- You MUST read all related files and implementations together in a single operation (up to 5 files at once) -- You MUST obtain all necessary context before proceeding with changes - -- When you need to read more than 5 files, prioritize the most critical files first, then use subsequent read_file requests for additional files - -## fetch_instructions -Description: Request to fetch instructions to perform a task -Parameters: -- task: (required) The task to get instructions for. This can take the following values: - create_mcp_server - create_mode - -Example: Requesting instructions to create an MCP Server +You have access to a set of tools that are executed upon the user's approval. Use the provider-native tool-calling mechanism. Do not include XML markup or examples. You must use exactly one tool call per assistant response. Do not call zero tools or more than one tool in the same response. - -create_mcp_server - - -## search_files -Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. - -Craft your regex patterns carefully to balance specificity and flexibility. Use this tool to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include surrounding context, so analyze the surrounding code to better understand the matches. Leverage this tool in combination with other tools for more comprehensive analysis - for example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches. - -Parameters: -- path: (required) The path of the directory to search in (relative to the current workspace directory /test/path). This directory will be recursively searched. -- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax. -- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*). - -Usage: - -Directory path here -Your regex pattern here -file pattern here (optional) - - -Example: Searching for all .ts files in the current directory - -. -.* -*.ts - - -Example: Searching for function definitions in JavaScript files - -src -function\s+\w+ -*.js - - -## list_files -Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not. -Parameters: -- path: (required) The path of the directory to list contents for (relative to the current workspace directory /test/path) -- recursive: (optional) Whether to list files recursively. Use true for recursive listing, false or omit for top-level only. -Usage: - -Directory path here -true or false (optional) - - -Example: Requesting to list all files in the current directory - -. -false - - -## write_to_file -Description: Request to write content to a file. This tool is primarily used for **creating new files** or for scenarios where a **complete rewrite of an existing file is intentionally required**. If the file exists, it will be overwritten. If it doesn't exist, it will be created. This tool will automatically create any directories needed to write the file. - -**Important:** You should prefer using other editing tools over write_to_file when making changes to existing files, since write_to_file is slower and cannot handle large files. Use write_to_file primarily for new file creation. - -When using this tool, use it directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code. - -When creating a new project, organize all new files within a dedicated project directory unless the user specifies otherwise. Structure the project logically, adhering to best practices for the specific type of project being created. - -Parameters: -- path: (required) The path of the file to write to (relative to the current workspace directory /test/path) -- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include line numbers in the content. - -Usage: - -File path here - -Your file content here - - - -Example: Writing a configuration file - -frontend-config.json - -{ - "apiEndpoint": "https://api.example.com", - "theme": { - "primaryColor": "#007bff", - "secondaryColor": "#6c757d", - "fontFamily": "Arial, sans-serif" - }, - "features": { - "darkMode": true, - "notifications": true, - "analytics": false - }, - "version": "1.0.0" -} - - - -## use_mcp_tool -Description: Request to use a tool provided by a connected MCP server. Each MCP server can provide multiple tools with different capabilities. Tools have defined input schemas that specify required and optional parameters. -Parameters: -- server_name: (required) The name of the MCP server providing the tool -- tool_name: (required) The name of the tool to execute -- arguments: (required) A JSON object containing the tool's input parameters, following the tool's input schema -Usage: - -server name here -tool name here - -{ - "param1": "value1", - "param2": "value2" -} - - - -Example: Requesting to use an MCP tool - - -weather-server -get_forecast - -{ - "city": "San Francisco", - "days": 5 -} - - - -## access_mcp_resource -Description: Request to access a resource provided by a connected MCP server. Resources represent data sources that can be used as context, such as files, API responses, or system information. -Parameters: -- server_name: (required) The name of the MCP server providing the resource -- uri: (required) The URI identifying the specific resource to access -Usage: - -server name here -resource URI here - - -Example: Requesting to access an MCP resource - - -weather-server -weather://san-francisco/current - - -## ask_followup_question -Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. - -Parameters: -- question: (required) A clear, specific question addressing the information needed -- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) - -Usage: - -Your question here - -First suggestion -Action with mode switch - - - -Example: - -What is the path to the frontend-config.json file? - -./src/frontend-config.json -./config/frontend-config.json -./frontend-config.json - - - -## attempt_completion -Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. -IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must confirm that you've received successful results from the user for any previous tool uses. If not, then DO NOT use this tool. -Parameters: -- result: (required) The result of the task. Formulate this result in a way that is final and does not require further input from the user. Don't end your result with questions or offers for further assistance. -Usage: - - -Your final result description here - - - -Example: Requesting to attempt completion with a result - - -I've updated the CSS - - - -## switch_mode -Description: Request to switch to a different mode. This tool allows modes to request switching to another mode when needed, such as switching to Code mode to make code changes. The user must approve the mode switch. -Parameters: -- mode_slug: (required) The slug of the mode to switch to (e.g., "code", "ask", "architect") -- reason: (optional) The reason for switching modes -Usage: - -Mode slug here -Reason for switching here - - -Example: Requesting to switch to code mode - -code -Need to make code changes - - -## new_task -Description: This will let you create a new task instance in the chosen mode using your provided message. - -Parameters: -- mode: (required) The slug of the mode to start the new task in (e.g., "code", "debug", "architect"). -- message: (required) The initial user message or instructions for this new task. - -Usage: - -your-mode-slug-here -Your initial instructions here - - -Example: - -code -Implement a new feature for the application - - - -## update_todo_list - -**Description:** -Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks. - -**Checklist Format:** -- Use a single-level markdown checklist (no nesting or subtasks). -- List todos in the intended execution order. -- Status options: - - [ ] Task description (pending) - - [x] Task description (completed) - - [-] Task description (in progress) - -**Status Rules:** -- [ ] = pending (not started) -- [x] = completed (fully finished, no unresolved issues) -- [-] = in_progress (currently being worked on) - -**Core Principles:** -- Before updating, always confirm which todos have been completed since the last update. -- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress). -- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately. -- Do not remove any unfinished todos unless explicitly instructed. -- Always retain all unfinished tasks, updating their status as needed. -- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies). -- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved. -- Remove tasks only if they are no longer relevant or if the user requests deletion. - -**Usage Example:** - - -[x] Analyze requirements -[x] Design architecture -[-] Implement core logic -[ ] Write tests -[ ] Update documentation - - - -*After completing "Implement core logic" and starting "Write tests":* - - -[x] Analyze requirements -[x] Design architecture -[x] Implement core logic -[-] Write tests -[ ] Update documentation -[ ] Add performance benchmarks - - - -**When to Use:** -- The task is complicated or involves multiple steps or requires ongoing tracking. -- You need to update the status of several todos at once. -- New actionable items are discovered during task execution. -- The user requests a todo list or provides multiple tasks. -- The task is complex and benefits from clear, stepwise progress tracking. - -**When NOT to Use:** -- There is only a single, trivial task. -- The task can be completed in one or two simple steps. -- The request is purely conversational or informational. - -**Task Management Guidelines:** -- Mark task as completed immediately after all work of the current task is done. -- Start the next task by marking it as in_progress. -- Add new todos as soon as they are identified. -- Use clear, descriptive task names. - - -# Tool Use Guidelines + # Tool Use Guidelines 1. Assess what information you already have and what information you need to proceed with the task. 2. Choose the most appropriate tool based on the task and the tool descriptions provided. Assess if you need additional information to proceed, and which of the available tools would be most effective for gathering this information. For example using the list_files tool is more effective than running a command like `ls` in the terminal. It's critical that you think about each available tool and use the one that best fits the current step in the task. 3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. -4. Formulate your tool use using the XML format specified for each tool. -5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: +4. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - Information about whether the tool succeeded or failed, along with any reasons for failure. - Linter errors that may have arisen due to the changes you made, which you'll need to address. - New terminal output in reaction to the changes, which you may need to consider or act upon. - Any other relevant feedback or information related to the tool use. -6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. +5. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: 1. Confirm the success of each step before proceeding. @@ -453,7 +72,7 @@ MODES RULES - The project base directory is: /test/path -- 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 . +- 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. - You cannot `cd` into a different directory to complete a task. You are stuck operating from '/test/path', 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 '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', 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)`. @@ -502,7 +121,7 @@ You accomplish a given task iteratively, breaking it down into clear steps and w 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. Language Preference: You should always speak and think in the "en" language. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap index ee8a50e9933..5305987e28a 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap @@ -10,351 +10,19 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka TOOL USE -You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. - -# Tool Use Formatting - -Tool uses are formatted using XML-style tags. The tool name itself becomes the XML tag name. Each parameter is enclosed within its own set of tags. Here's the structure: - - -value1 -value2 -... - - -Always use the actual tool name as the XML tag name for proper parsing and execution. - -# Tools - -## read_file -Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly. - -**IMPORTANT: You can read a maximum of 5 files in a single request.** If you need to read more files, use multiple sequential read_file requests. - - -Parameters: -- args: Contains one or more file elements, where each file contains: - - path: (required) File path (relative to workspace directory /test/path) - - -Usage: - - - - path/to/file - - - - - -Examples: - -1. Reading a single file: - - - - src/app.ts - - - - - -2. Reading multiple files (within the 5-file limit): - - - - src/app.ts - - - - src/utils.ts - - - - - -3. Reading an entire file: - - - - config.json - - - - -IMPORTANT: You MUST use this Efficient Reading Strategy: -- You MUST read all related files and implementations together in a single operation (up to 5 files at once) -- You MUST obtain all necessary context before proceeding with changes - -- When you need to read more than 5 files, prioritize the most critical files first, then use subsequent read_file requests for additional files - -## fetch_instructions -Description: Request to fetch instructions to perform a task -Parameters: -- task: (required) The task to get instructions for. This can take the following values: - create_mcp_server - create_mode - -Example: Requesting instructions to create an MCP Server - - -create_mcp_server - - -## search_files -Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. - -Craft your regex patterns carefully to balance specificity and flexibility. Use this tool to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include surrounding context, so analyze the surrounding code to better understand the matches. Leverage this tool in combination with other tools for more comprehensive analysis - for example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches. - -Parameters: -- path: (required) The path of the directory to search in (relative to the current workspace directory /test/path). This directory will be recursively searched. -- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax. -- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*). - -Usage: - -Directory path here -Your regex pattern here -file pattern here (optional) - - -Example: Searching for all .ts files in the current directory - -. -.* -*.ts - - -Example: Searching for function definitions in JavaScript files - -src -function\s+\w+ -*.js - - -## list_files -Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not. -Parameters: -- path: (required) The path of the directory to list contents for (relative to the current workspace directory /test/path) -- recursive: (optional) Whether to list files recursively. Use true for recursive listing, false or omit for top-level only. -Usage: - -Directory path here -true or false (optional) - - -Example: Requesting to list all files in the current directory - -. -false - - -## write_to_file -Description: Request to write content to a file. This tool is primarily used for **creating new files** or for scenarios where a **complete rewrite of an existing file is intentionally required**. If the file exists, it will be overwritten. If it doesn't exist, it will be created. This tool will automatically create any directories needed to write the file. - -**Important:** You should prefer using other editing tools over write_to_file when making changes to existing files, since write_to_file is slower and cannot handle large files. Use write_to_file primarily for new file creation. - -When using this tool, use it directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code. - -When creating a new project, organize all new files within a dedicated project directory unless the user specifies otherwise. Structure the project logically, adhering to best practices for the specific type of project being created. - -Parameters: -- path: (required) The path of the file to write to (relative to the current workspace directory /test/path) -- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include line numbers in the content. - -Usage: - -File path here - -Your file content here - - - -Example: Writing a configuration file - -frontend-config.json - -{ - "apiEndpoint": "https://api.example.com", - "theme": { - "primaryColor": "#007bff", - "secondaryColor": "#6c757d", - "fontFamily": "Arial, sans-serif" - }, - "features": { - "darkMode": true, - "notifications": true, - "analytics": false - }, - "version": "1.0.0" -} - - - -## ask_followup_question -Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. - -Parameters: -- question: (required) A clear, specific question addressing the information needed -- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) - -Usage: - -Your question here - -First suggestion -Action with mode switch - - - -Example: - -What is the path to the frontend-config.json file? - -./src/frontend-config.json -./config/frontend-config.json -./frontend-config.json - - - -## attempt_completion -Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. -IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must confirm that you've received successful results from the user for any previous tool uses. If not, then DO NOT use this tool. -Parameters: -- result: (required) The result of the task. Formulate this result in a way that is final and does not require further input from the user. Don't end your result with questions or offers for further assistance. -Usage: - - -Your final result description here - - - -Example: Requesting to attempt completion with a result - - -I've updated the CSS - - - -## switch_mode -Description: Request to switch to a different mode. This tool allows modes to request switching to another mode when needed, such as switching to Code mode to make code changes. The user must approve the mode switch. -Parameters: -- mode_slug: (required) The slug of the mode to switch to (e.g., "code", "ask", "architect") -- reason: (optional) The reason for switching modes -Usage: - -Mode slug here -Reason for switching here - - -Example: Requesting to switch to code mode - -code -Need to make code changes - - -## new_task -Description: This will let you create a new task instance in the chosen mode using your provided message. - -Parameters: -- mode: (required) The slug of the mode to start the new task in (e.g., "code", "debug", "architect"). -- message: (required) The initial user message or instructions for this new task. - -Usage: - -your-mode-slug-here -Your initial instructions here - - -Example: - -code -Implement a new feature for the application - - - -## update_todo_list - -**Description:** -Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks. - -**Checklist Format:** -- Use a single-level markdown checklist (no nesting or subtasks). -- List todos in the intended execution order. -- Status options: - - [ ] Task description (pending) - - [x] Task description (completed) - - [-] Task description (in progress) - -**Status Rules:** -- [ ] = pending (not started) -- [x] = completed (fully finished, no unresolved issues) -- [-] = in_progress (currently being worked on) - -**Core Principles:** -- Before updating, always confirm which todos have been completed since the last update. -- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress). -- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately. -- Do not remove any unfinished todos unless explicitly instructed. -- Always retain all unfinished tasks, updating their status as needed. -- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies). -- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved. -- Remove tasks only if they are no longer relevant or if the user requests deletion. - -**Usage Example:** - - -[x] Analyze requirements -[x] Design architecture -[-] Implement core logic -[ ] Write tests -[ ] Update documentation - - - -*After completing "Implement core logic" and starting "Write tests":* - - -[x] Analyze requirements -[x] Design architecture -[x] Implement core logic -[-] Write tests -[ ] Update documentation -[ ] Add performance benchmarks - - - -**When to Use:** -- The task is complicated or involves multiple steps or requires ongoing tracking. -- You need to update the status of several todos at once. -- New actionable items are discovered during task execution. -- The user requests a todo list or provides multiple tasks. -- The task is complex and benefits from clear, stepwise progress tracking. - -**When NOT to Use:** -- There is only a single, trivial task. -- The task can be completed in one or two simple steps. -- The request is purely conversational or informational. - -**Task Management Guidelines:** -- Mark task as completed immediately after all work of the current task is done. -- Start the next task by marking it as in_progress. -- Add new todos as soon as they are identified. -- Use clear, descriptive task names. - - -# Tool Use Guidelines +You have access to a set of tools that are executed upon the user's approval. Use the provider-native tool-calling mechanism. Do not include XML markup or examples. You must use exactly one tool call per assistant response. Do not call zero tools or more than one tool in the same response. + + # Tool Use Guidelines 1. Assess what information you already have and what information you need to proceed with the task. 2. Choose the most appropriate tool based on the task and the tool descriptions provided. Assess if you need additional information to proceed, and which of the available tools would be most effective for gathering this information. For example using the list_files tool is more effective than running a command like `ls` in the terminal. It's critical that you think about each available tool and use the one that best fits the current step in the task. 3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. -4. Formulate your tool use using the XML format specified for each tool. -5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: +4. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - Information about whether the tool succeeded or failed, along with any reasons for failure. - Linter errors that may have arisen due to the changes you made, which you'll need to address. - New terminal output in reaction to the changes, which you may need to consider or act upon. - Any other relevant feedback or information related to the tool use. -6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. +5. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: 1. Confirm the success of each step before proceeding. @@ -385,7 +53,7 @@ MODES RULES - The project base directory is: /test/path -- 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 . +- 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. - You cannot `cd` into a different directory to complete a task. You are stuck operating from '/test/path', 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 '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', 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)`. @@ -434,7 +102,7 @@ You accomplish a given task iteratively, breaking it down into clear steps and w 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. Language Preference: You should always speak and think in the "en" language. diff --git a/src/core/prompts/__tests__/responses-rooignore.spec.ts b/src/core/prompts/__tests__/responses-rooignore.spec.ts index ca0dcfbad53..03aae96776f 100644 --- a/src/core/prompts/__tests__/responses-rooignore.spec.ts +++ b/src/core/prompts/__tests__/responses-rooignore.spec.ts @@ -51,10 +51,13 @@ describe("RooIgnore Response Formatting", () => { it("should format error message for ignored files", () => { const errorMessage = formatResponse.rooIgnoreError("secrets/api-keys.json") - // Verify error message format - expect(errorMessage).toContain("Access to secrets/api-keys.json is blocked by the .rooignore file settings") - expect(errorMessage).toContain("continue in the task without using this file") - expect(errorMessage).toContain("ask the user to update the .rooignore file") + // Verify error message format (JSON) + const parsed = JSON.parse(errorMessage) as any + expect(parsed.status).toBe("error") + expect(parsed.type).toBe("access_denied") + expect(parsed.path).toBe("secrets/api-keys.json") + expect(parsed.suggestion).toContain("continue without this file") + expect(parsed.suggestion).toContain("update the .rooignore file") }) /** @@ -66,7 +69,8 @@ describe("RooIgnore Response Formatting", () => { // Test each path for (const testPath of paths) { const errorMessage = formatResponse.rooIgnoreError(testPath) - expect(errorMessage).toContain(`Access to ${testPath} is blocked`) + const parsed = JSON.parse(errorMessage) as any + expect(parsed.path).toBe(testPath) } }) }) diff --git a/src/core/prompts/__tests__/system-prompt.spec.ts b/src/core/prompts/__tests__/system-prompt.spec.ts index a0953b2de17..ede704941b7 100644 --- a/src/core/prompts/__tests__/system-prompt.spec.ts +++ b/src/core/prompts/__tests__/system-prompt.spec.ts @@ -112,9 +112,7 @@ __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." + const toolUseRef = "." 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${toolUseRef}\n\n${joinedSections}` : "" @@ -352,7 +350,9 @@ describe("SYSTEM_PROMPT", () => { undefined, // partialReadsEnabled ) - expect(prompt).toContain("apply_diff") + // Native-only: tool catalog isn't embedded in the system prompt anymore. + expect(prompt).not.toContain("# Tools") + expect(prompt).not.toContain("apply_diff") expect(prompt).toMatchFileSnapshot("./__snapshots__/system-prompt/with-diff-enabled-true.snap") }) @@ -376,6 +376,8 @@ describe("SYSTEM_PROMPT", () => { undefined, // partialReadsEnabled ) + // Native-only: tool catalog isn't embedded in the system prompt anymore. + expect(prompt).not.toContain("# Tools") expect(prompt).not.toContain("apply_diff") expect(prompt).toMatchFileSnapshot("./__snapshots__/system-prompt/with-diff-enabled-false.snap") }) @@ -400,6 +402,8 @@ describe("SYSTEM_PROMPT", () => { undefined, // partialReadsEnabled ) + // Native-only: tool catalog isn't embedded in the system prompt anymore. + expect(prompt).not.toContain("# Tools") expect(prompt).not.toContain("apply_diff") expect(prompt).toMatchFileSnapshot("./__snapshots__/system-prompt/with-diff-enabled-undefined.snap") }) @@ -593,7 +597,6 @@ describe("SYSTEM_PROMPT", () => { todoListEnabled: false, useAgentRules: true, newTaskRequireTodos: false, - toolProtocol: "xml" as const, } const prompt = await SYSTEM_PROMPT( @@ -627,7 +630,6 @@ describe("SYSTEM_PROMPT", () => { todoListEnabled: true, useAgentRules: true, newTaskRequireTodos: false, - toolProtocol: "xml" as const, } const prompt = await SYSTEM_PROMPT( @@ -650,8 +652,9 @@ describe("SYSTEM_PROMPT", () => { settings, // settings ) + // update_todo_list is still referenced by mode instructions, but tool catalogs are not embedded. expect(prompt).toContain("update_todo_list") - expect(prompt).toContain("## update_todo_list") + expect(prompt).not.toContain("## update_todo_list") }) it("should include update_todo_list tool when todoListEnabled is undefined", async () => { @@ -660,7 +663,6 @@ describe("SYSTEM_PROMPT", () => { todoListEnabled: true, useAgentRules: true, newTaskRequireTodos: false, - toolProtocol: "xml" as const, } const prompt = await SYSTEM_PROMPT( @@ -683,89 +685,17 @@ describe("SYSTEM_PROMPT", () => { settings, // settings ) + // update_todo_list is still referenced by mode instructions, but tool catalogs are not embedded. expect(prompt).toContain("update_todo_list") - 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("") - expect(prompt).toContain("") - 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("") - expect(prompt).toContain("") - - // 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) + expect(prompt).not.toContain("## update_todo_list") }) - it("should include native tool instructions when toolProtocol is native", async () => { + it("should include native tool instructions (native-only)", async () => { const settings = { maxConcurrentFileReads: 5, todoListEnabled: true, useAgentRules: true, newTaskRequireTodos: false, - toolProtocol: "native" as const, // native protocol } const prompt = await SYSTEM_PROMPT( @@ -794,17 +724,13 @@ describe("SYSTEM_PROMPT", () => { 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("") expect(prompt).not.toContain("") - // Should contain Tool Use Guidelines section without format-specific guidance + // Should contain Tool Use Guidelines section 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 + // Should NOT contain a tool catalog / XML examples expect(prompt).not.toContain("# Tools") expect(prompt).not.toContain("## read_file") expect(prompt).not.toContain("## execute_command") @@ -821,43 +747,6 @@ describe("SYSTEM_PROMPT", () => { 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("") - expect(prompt).toContain("Tool Use Guidelines") - expect(prompt).toContain("# Tools") - }) - afterAll(() => { vi.restoreAllMocks() }) diff --git a/src/core/prompts/instructions/create-mode.ts b/src/core/prompts/instructions/create-mode.ts index 80f69b08024..9623aae0cd1 100644 --- a/src/core/prompts/instructions/create-mode.ts +++ b/src/core/prompts/instructions/create-mode.ts @@ -17,7 +17,6 @@ Custom modes can be configured in two ways: When modes with the same slug exist in both files, the workspace-specific .roomodes version takes precedence. This allows projects to override global modes or define project-specific modes. - If asked to create a project mode, create it in .roomodes in the workspace root. If asked to create a global mode, use the global custom modes file. - The following fields are required and must not be empty: diff --git a/src/core/prompts/responses.ts b/src/core/prompts/responses.ts index 332e3c63b75..60b5b4123ac 100644 --- a/src/core/prompts/responses.ts +++ b/src/core/prompts/responses.ts @@ -3,65 +3,44 @@ import * as path from "path" import * as diff from "diff" import { RooIgnoreController, LOCK_TEXT_SYMBOL } from "../ignore/RooIgnoreController" import { RooProtectedController } from "../protect/RooProtectedController" -import { ToolProtocol, isNativeProtocol, TOOL_PROTOCOL } from "@roo-code/types" export const formatResponse = { - 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, protocol?: ToolProtocol) => { - if (isNativeProtocol(protocol ?? TOOL_PROTOCOL.XML)) { - return JSON.stringify({ - status: "denied", - feedback: feedback, - }) - } - return `The user denied this operation and responded with the message:\n\n${feedback}\n` - }, - - toolApprovedWithFeedback: (feedback?: string, protocol?: ToolProtocol) => { - if (isNativeProtocol(protocol ?? TOOL_PROTOCOL.XML)) { - return JSON.stringify({ - status: "approved", - feedback: feedback, - }) - } - return `The user approved this operation and responded with the message:\n\n${feedback}\n` - }, - - 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\n${error}\n` - }, - - 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) + toolDenied: () => + JSON.stringify({ + status: "denied", + message: "The user denied this operation.", + }), + + toolDeniedWithFeedback: (feedback?: string) => + JSON.stringify({ + status: "denied", + feedback, + }), + + toolApprovedWithFeedback: (feedback?: string) => + JSON.stringify({ + status: "approved", + feedback, + }), + + toolError: (error?: string) => + JSON.stringify({ + status: "error", + message: "The tool execution failed", + error, + }), + + rooIgnoreError: (path: string) => + JSON.stringify({ + status: "error", + type: "access_denied", + message: "Access blocked by .rooignore", + path, + suggestion: "Try to continue without this file, or ask the user to update the .rooignore file", + }), + + noToolsUsed: () => { + const instructions = getToolInstructionsReminder() return `[ERROR] You did not use a tool in your previous response! Please retry with a tool use. @@ -75,65 +54,47 @@ 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, protocol?: ToolProtocol) => { - if (isNativeProtocol(protocol ?? TOOL_PROTOCOL.XML)) { - return JSON.stringify({ - status: "guidance", - feedback: feedback, - }) - } - return `You seem to be having trouble proceeding. The user has provided the following feedback to help guide you:\n\n${feedback}\n` - }, + tooManyMistakes: (feedback?: string) => + JSON.stringify({ + status: "guidance", + feedback, + }), - missingToolParameterError: (paramName: string, protocol?: ToolProtocol) => { - const instructions = getToolInstructionsReminder(protocol) + missingToolParameterError: (paramName: string) => { + const instructions = getToolInstructionsReminder() return `Missing value for required parameter '${paramName}'. Please retry with complete response.\n\n${instructions}` }, - 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[], 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[], 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}` - }, + invalidMcpToolArgumentError: (serverName: string, toolName: string) => + JSON.stringify({ + status: "error", + type: "invalid_argument", + message: "Invalid JSON argument", + server: serverName, + tool: toolName, + suggestion: "Please retry with a properly formatted JSON argument", + }), + + unknownMcpToolError: (serverName: string, toolName: string, availableTools: string[]) => + 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", + }), + + unknownMcpServerError: (serverName: string, availableServers: string[]) => + JSON.stringify({ + status: "error", + type: "unknown_server", + message: "Server is not configured", + server: serverName, + available_servers: availableServers.length > 0 ? availableServers : [], + }), toolResult: ( text: string, @@ -255,26 +216,6 @@ const formatImagesIntoBlocks = (images?: string[]): Anthropic.ImageBlockParam[] : [] } -const toolUseInstructionsReminder = `# Reminder: Instructions for Tool Use - -Tool uses are formatted using XML-style tags. The tool name itself becomes the XML tag name. Each parameter is enclosed within its own set of tags. Here's the structure: - - -value1 -value2 -... - - -For example, to use the attempt_completion tool: - - - -I have completed the task... - - - -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. @@ -282,12 +223,8 @@ Tools are invoked using the platform's native tool calling mechanism. Each tool 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, defaults to XML if not provided - * @returns The tool use instructions reminder text + * Gets the tool use instructions reminder. */ -function getToolInstructionsReminder(protocol?: ToolProtocol): string { - const effectiveProtocol = protocol ?? TOOL_PROTOCOL.XML - return isNativeProtocol(effectiveProtocol) ? toolUseInstructionsReminderNative : toolUseInstructionsReminder +function getToolInstructionsReminder(): string { + return toolUseInstructionsReminderNative } diff --git a/src/core/prompts/sections/__tests__/tool-use-guidelines.spec.ts b/src/core/prompts/sections/__tests__/tool-use-guidelines.spec.ts index 3cb3fb51d03..768ef90dcfd 100644 --- a/src/core/prompts/sections/__tests__/tool-use-guidelines.spec.ts +++ b/src/core/prompts/sections/__tests__/tool-use-guidelines.spec.ts @@ -1,44 +1,11 @@ import { getToolUseGuidelinesSection } from "../tool-use-guidelines" -import { TOOL_PROTOCOL } from "@roo-code/types" import { EXPERIMENT_IDS } from "../../../../shared/experiments" describe("getToolUseGuidelinesSection", () => { - describe("XML protocol", () => { - it("should include proper numbered guidelines", () => { - const guidelines = getToolUseGuidelinesSection(TOOL_PROTOCOL.XML) - - // Check that all numbered items are present with correct numbering - expect(guidelines).toContain("1. Assess what information") - expect(guidelines).toContain("2. Choose the most appropriate tool") - expect(guidelines).toContain("3. If multiple actions are needed") - expect(guidelines).toContain("4. Formulate your tool use") - expect(guidelines).toContain("5. After each tool use") - expect(guidelines).toContain("6. ALWAYS wait for user confirmation") - }) - - it("should include XML-specific guidelines", () => { - const guidelines = getToolUseGuidelinesSection(TOOL_PROTOCOL.XML) - - expect(guidelines).toContain("Formulate your tool use using the XML format specified for each tool") - expect(guidelines).toContain("use one tool at a time per message") - expect(guidelines).toContain("ALWAYS wait for user confirmation") - }) - - it("should include iterative process guidelines", () => { - const guidelines = getToolUseGuidelinesSection(TOOL_PROTOCOL.XML) - - expect(guidelines).toContain("It is crucial to proceed step-by-step") - expect(guidelines).toContain("1. Confirm the success of each step before proceeding") - expect(guidelines).toContain("2. Address any issues or errors that arise immediately") - expect(guidelines).toContain("3. Adapt your approach based on new information") - expect(guidelines).toContain("4. Ensure that each action builds correctly") - }) - }) - - describe("native protocol", () => { + describe("native-only", () => { describe("with MULTIPLE_NATIVE_TOOL_CALLS disabled (default)", () => { it("should include proper numbered guidelines", () => { - const guidelines = getToolUseGuidelinesSection(TOOL_PROTOCOL.NATIVE) + const guidelines = getToolUseGuidelinesSection() // Check that all numbered items are present with correct numbering expect(guidelines).toContain("1. Assess what information") @@ -48,52 +15,54 @@ describe("getToolUseGuidelinesSection", () => { }) it("should include single-tool-per-message guidance when experiment disabled", () => { - const guidelines = getToolUseGuidelinesSection(TOOL_PROTOCOL.NATIVE, {}) + const guidelines = getToolUseGuidelinesSection({}) expect(guidelines).toContain("use one tool at a time per message") expect(guidelines).not.toContain("you may use multiple tools in a single message") - expect(guidelines).not.toContain("Formulate your tool use using the XML format") - expect(guidelines).not.toContain("ALWAYS wait for user confirmation") + expect(guidelines).not.toContain("Formulate your tool use using") + expect(guidelines).toContain("ALWAYS wait for user confirmation") }) it("should include simplified iterative process guidelines", () => { - const guidelines = getToolUseGuidelinesSection(TOOL_PROTOCOL.NATIVE) + const guidelines = getToolUseGuidelinesSection() - expect(guidelines).toContain("carefully considering the user's response after tool executions") - // Native protocol doesn't have the step-by-step list - expect(guidelines).not.toContain("It is crucial to proceed step-by-step") + expect(guidelines).toContain("carefully considering the user's response after each tool use") + expect(guidelines).toContain("It is crucial to proceed step-by-step") }) }) describe("with MULTIPLE_NATIVE_TOOL_CALLS enabled", () => { it("should include multiple-tools-per-message guidance when experiment enabled", () => { - const guidelines = getToolUseGuidelinesSection(TOOL_PROTOCOL.NATIVE, { + const guidelines = getToolUseGuidelinesSection({ [EXPERIMENT_IDS.MULTIPLE_NATIVE_TOOL_CALLS]: true, }) expect(guidelines).toContain("you may use multiple tools in a single message") expect(guidelines).not.toContain("use one tool at a time per message") + expect(guidelines).not.toContain("After each tool use, the user will respond") }) - it("should include simplified iterative process guidelines", () => { - const guidelines = getToolUseGuidelinesSection(TOOL_PROTOCOL.NATIVE, { + it("should use simplified footer without step-by-step language", () => { + const guidelines = getToolUseGuidelinesSection({ [EXPERIMENT_IDS.MULTIPLE_NATIVE_TOOL_CALLS]: true, }) + // When multiple tools per message is enabled, we don't want the + // "step-by-step" or "after each tool use" language that would + // contradict the ability to batch tool calls. expect(guidelines).toContain("carefully considering the user's response after tool executions") expect(guidelines).not.toContain("It is crucial to proceed step-by-step") + expect(guidelines).not.toContain("ALWAYS wait for user confirmation after each tool use") }) }) }) - it("should include common guidance regardless of protocol", () => { - const guidelinesXml = getToolUseGuidelinesSection(TOOL_PROTOCOL.XML) - const guidelinesNative = getToolUseGuidelinesSection(TOOL_PROTOCOL.NATIVE) - - for (const guidelines of [guidelinesXml, guidelinesNative]) { - expect(guidelines).toContain("Assess what information you already have") - expect(guidelines).toContain("Choose the most appropriate tool") - expect(guidelines).toContain("After each tool use, the user will respond") - } + it("should include common guidance", () => { + const guidelines = getToolUseGuidelinesSection() + expect(guidelines).toContain("Assess what information you already have") + expect(guidelines).toContain("Choose the most appropriate tool") + expect(guidelines).toContain("After each tool use, the user will respond") + // No legacy XML-tag tool-calling remnants + expect(guidelines).not.toContain("") }) }) diff --git a/src/core/prompts/sections/__tests__/tool-use.spec.ts b/src/core/prompts/sections/__tests__/tool-use.spec.ts index c8e3a9b5d06..d6b9c19ce1d 100644 --- a/src/core/prompts/sections/__tests__/tool-use.spec.ts +++ b/src/core/prompts/sections/__tests__/tool-use.spec.ts @@ -1,41 +1,24 @@ import { getSharedToolUseSection } from "../tool-use" -import { TOOL_PROTOCOL } from "@roo-code/types" describe("getSharedToolUseSection", () => { - describe("XML protocol", () => { - it("should include one tool per message requirement", () => { - const section = getSharedToolUseSection(TOOL_PROTOCOL.XML) - - expect(section).toContain("You must use exactly one tool per message") - expect(section).toContain("every assistant message must include a tool call") - }) - - it("should include XML formatting instructions", () => { - const section = getSharedToolUseSection(TOOL_PROTOCOL.XML) - - expect(section).toContain("XML-style tags") - expect(section).toContain("Always use the actual tool name as the XML tag name") - }) - }) - - describe("native protocol", () => { + describe("native tool calling", () => { it("should include one tool per message requirement when experiment is disabled", () => { // No experiment flags passed (default: disabled) - const section = getSharedToolUseSection(TOOL_PROTOCOL.NATIVE) + const section = getSharedToolUseSection("native") expect(section).toContain("You must use exactly one tool call per assistant response") expect(section).toContain("Do not call zero tools or more than one tool") }) it("should include one tool per message requirement when experiment is explicitly disabled", () => { - const section = getSharedToolUseSection(TOOL_PROTOCOL.NATIVE, { multipleNativeToolCalls: false }) + const section = getSharedToolUseSection("native", { multipleNativeToolCalls: false }) expect(section).toContain("You must use exactly one tool call per assistant response") expect(section).toContain("Do not call zero tools or more than one tool") }) it("should NOT include one tool per message requirement when experiment is enabled", () => { - const section = getSharedToolUseSection(TOOL_PROTOCOL.NATIVE, { multipleNativeToolCalls: true }) + const section = getSharedToolUseSection("native", { multipleNativeToolCalls: true }) expect(section).not.toContain("You must use exactly one tool per message") expect(section).not.toContain("every assistant message must include a tool call") @@ -44,26 +27,26 @@ describe("getSharedToolUseSection", () => { }) it("should include native tool-calling instructions", () => { - const section = getSharedToolUseSection(TOOL_PROTOCOL.NATIVE) + const section = getSharedToolUseSection("native") expect(section).toContain("provider-native tool-calling mechanism") expect(section).toContain("Do not include XML markup or examples") }) it("should NOT include XML formatting instructions", () => { - const section = getSharedToolUseSection(TOOL_PROTOCOL.NATIVE) + const section = getSharedToolUseSection("native") - expect(section).not.toContain("XML-style tags") - expect(section).not.toContain("Always use the actual tool name as the XML tag name") + expect(section).not.toContain("") + expect(section).not.toContain("") }) }) - describe("default protocol", () => { - it("should default to XML protocol when no protocol is specified", () => { + describe("default (native-only)", () => { + it("should default to native tool calling when no mode is specified", () => { const section = getSharedToolUseSection() - - expect(section).toContain("XML-style tags") - expect(section).toContain("You must use exactly one tool per message") + expect(section).toContain("provider-native tool-calling mechanism") + // No legacy XML-tag tool-calling remnants + expect(section).not.toContain("") }) }) }) diff --git a/src/core/prompts/sections/custom-instructions.ts b/src/core/prompts/sections/custom-instructions.ts index ed33f4a1e39..8eee0a09986 100644 --- a/src/core/prompts/sections/custom-instructions.ts +++ b/src/core/prompts/sections/custom-instructions.ts @@ -6,7 +6,6 @@ import { Dirent } from "fs" import { isLanguage } from "@roo-code/types" import type { SystemPromptSettings } from "../types" -import { getEffectiveProtocol, isNativeProtocol } from "@roo-code/types" import { LANGUAGES } from "../../../shared/language" import { @@ -459,17 +458,13 @@ export async function addCustomInstructions( const joinedSections = sections.join("\n\n") - const effectiveProtocol = getEffectiveProtocol(options.settings?.toolProtocol) - 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${ - isNativeProtocol(effectiveProtocol) ? "." : " 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. ${joinedSections} ` diff --git a/src/core/prompts/sections/mcp-servers.ts b/src/core/prompts/sections/mcp-servers.ts index 3eb1569c5ad..42a6d5d4405 100644 --- a/src/core/prompts/sections/mcp-servers.ts +++ b/src/core/prompts/sections/mcp-servers.ts @@ -17,7 +17,6 @@ export async function getMcpServersSection( .getServers() .filter((server) => server.status === "connected") .map((server) => { - // Only include tool descriptions when using XML protocol const tools = includeToolDescriptions ? server.tools ?.filter((tool) => tool.enabledForPrompt !== false) @@ -56,7 +55,7 @@ export async function getMcpServersSection( // Different instructions based on protocol const toolAccessInstructions = includeToolDescriptions ? `When a server is connected, you can use the server's tools via the \`use_mcp_tool\` tool, and access the server's resources via the \`access_mcp_resource\` tool.` - : `When a server is connected, each server's tools are available as native tools with the naming pattern \`mcp_{server_name}_{tool_name}\`. For example, a tool named 'get_forecast' from a server named 'weather' would be available as \`mcp_weather_get_forecast\`. You can also access server resources using the \`access_mcp_resource\` tool.` + : `When a server is connected, you can use the server's tools via the \`use_mcp_tool\` tool, and access the server's resources via the \`access_mcp_resource\` tool.` const baseSection = `MCP SERVERS diff --git a/src/core/prompts/sections/rules.ts b/src/core/prompts/sections/rules.ts index 800fb430efb..4f6e573fa73 100644 --- a/src/core/prompts/sections/rules.ts +++ b/src/core/prompts/sections/rules.ts @@ -1,5 +1,4 @@ import type { SystemPromptSettings } from "../types" -import { getEffectiveProtocol, isNativeProtocol } from "@roo-code/types" import { getShell } from "../../../utils/shell" @@ -64,9 +63,6 @@ When asked about your creator, vendor, or company, respond with: } export function getRulesSection(cwd: string, settings?: SystemPromptSettings): string { - // Determine whether to use XML tool references based on protocol - const effectiveProtocol = getEffectiveProtocol(settings?.toolProtocol) - // Get shell-appropriate command chaining operator const chainOp = getCommandChainOperator() const chainNote = getCommandChainNote() @@ -76,7 +72,7 @@ export function getRulesSection(cwd: string, settings?: SystemPromptSettings): s 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 ${isNativeProtocol(effectiveProtocol) ? "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 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 ${chainOp} 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) ${chainOp} (command, in this case npm install)\`.${chainNote ? ` ${chainNote}` : ""} diff --git a/src/core/prompts/sections/tool-use-guidelines.ts b/src/core/prompts/sections/tool-use-guidelines.ts index a5dad2cc0b9..256e7aba5ab 100644 --- a/src/core/prompts/sections/tool-use-guidelines.ts +++ b/src/core/prompts/sections/tool-use-guidelines.ts @@ -1,12 +1,6 @@ -import { ToolProtocol, TOOL_PROTOCOL } from "@roo-code/types" -import { isNativeProtocol } from "@roo-code/types" - import { experiments, EXPERIMENT_IDS } from "../../../shared/experiments" -export function getToolUseGuidelinesSection( - protocol: ToolProtocol = TOOL_PROTOCOL.XML, - experimentFlags?: Record, -): string { +export function getToolUseGuidelinesSection(experimentFlags?: Record): string { // Build guidelines array with automatic numbering let itemNumber = 1 const guidelinesList: string[] = [] @@ -20,50 +14,43 @@ export function getToolUseGuidelinesSection( `${itemNumber++}. Choose the most appropriate tool based on the task and the tool descriptions provided. Assess if you need additional information to proceed, and which of the available tools would be most effective for gathering this information. For example using the list_files tool is more effective than running a command like \`ls\` in the terminal. It's critical that you think about each available tool and use the one that best fits the current step in the task.`, ) - // Remaining guidelines - different for native vs XML protocol - if (isNativeProtocol(protocol)) { - // Check if multiple native tool calls is enabled via experiment - const isMultipleNativeToolCallsEnabled = experiments.isEnabled( - experimentFlags ?? {}, - EXPERIMENT_IDS.MULTIPLE_NATIVE_TOOL_CALLS, - ) + // Native-only guidelines. + // Check if multiple native tool calls is enabled via experiment. + const isMultipleNativeToolCallsEnabled = experiments.isEnabled( + experimentFlags ?? {}, + EXPERIMENT_IDS.MULTIPLE_NATIVE_TOOL_CALLS, + ) - if (isMultipleNativeToolCallsEnabled) { - guidelinesList.push( - `${itemNumber++}. If multiple actions are needed, you may use multiple tools in a single message when appropriate, or use tools iteratively across messages. Each tool use should be informed by the results of previous tool uses. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result.`, - ) - } else { - guidelinesList.push( - `${itemNumber++}. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result.`, - ) - } + if (isMultipleNativeToolCallsEnabled) { + guidelinesList.push( + `${itemNumber++}. If multiple actions are needed, you may use multiple tools in a single message when appropriate, or use tools iteratively across messages. Each tool use should be informed by the results of previous tool uses. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result.`, + ) } else { guidelinesList.push( `${itemNumber++}. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result.`, ) } - - // Protocol-specific guideline - only add for XML protocol - if (!isNativeProtocol(protocol)) { - guidelinesList.push(`${itemNumber++}. Formulate your tool use using the XML format specified for each tool.`) - } - guidelinesList.push(`${itemNumber++}. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: + // Only add the per-tool confirmation guideline when NOT using multiple tool calls. + // When multiple tool calls are enabled, results may arrive batched (after all tools), + // so "after each tool use" would contradict the batching behavior. + if (!isMultipleNativeToolCallsEnabled) { + guidelinesList.push(`${itemNumber++}. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - Information about whether the tool succeeded or failed, along with any reasons for failure. - Linter errors that may have arisen due to the changes you made, which you'll need to address. - New terminal output in reaction to the changes, which you may need to consider or act upon. - Any other relevant feedback or information related to the tool use.`) + } - // Only add the "wait for confirmation" guideline for XML protocol - // Native protocol allows multiple tools per message, so waiting after each tool doesn't apply - if (!isNativeProtocol(protocol)) { + // Only add the "wait for confirmation" guideline when NOT using multiple tool calls. + // With multiple tool calls enabled, the model is expected to batch tools and get results together. + if (!isMultipleNativeToolCallsEnabled) { guidelinesList.push( `${itemNumber++}. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user.`, ) } // Join guidelines and add the footer - // For native protocol, the footer is less relevant since multiple tools can execute in one message - const footer = isNativeProtocol(protocol) + const footer = isMultipleNativeToolCallsEnabled ? `\n\nBy carefully considering the user's response after tool executions, you can react accordingly and make informed decisions about how to proceed with the task. This iterative process helps ensure the overall success and accuracy of your work.` : `\n\nIt is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: 1. Confirm the success of each step before proceeding. diff --git a/src/core/prompts/sections/tool-use.ts b/src/core/prompts/sections/tool-use.ts index c3f5e221b80..6a5fcd49809 100644 --- a/src/core/prompts/sections/tool-use.ts +++ b/src/core/prompts/sections/tool-use.ts @@ -1,44 +1,20 @@ -import { ToolProtocol, TOOL_PROTOCOL, isNativeProtocol } from "@roo-code/types" - import { experiments, EXPERIMENT_IDS } from "../../../shared/experiments" -export function getSharedToolUseSection( - protocol: ToolProtocol = TOOL_PROTOCOL.XML, - experimentFlags?: Record, -): string { - if (isNativeProtocol(protocol)) { - // Check if multiple native tool calls is enabled via experiment - const isMultipleNativeToolCallsEnabled = experiments.isEnabled( - experimentFlags ?? {}, - EXPERIMENT_IDS.MULTIPLE_NATIVE_TOOL_CALLS, - ) - - const toolUseGuidance = isMultipleNativeToolCallsEnabled - ? " You must call at least one tool per assistant response. Prefer calling as many tools as are reasonably needed in a single response to reduce back-and-forth and complete tasks faster." - : " You must use exactly one tool call per assistant response. Do not call zero tools or more than one tool in the same response." - - return `==== +export function getSharedToolUseSection(_protocol = "native", experimentFlags?: Record): string { + // Tool calling is native-only. + // Check if multiple native tool calls is enabled via experiment + const isMultipleNativeToolCallsEnabled = experiments.isEnabled( + experimentFlags ?? {}, + EXPERIMENT_IDS.MULTIPLE_NATIVE_TOOL_CALLS, + ) -TOOL USE - -You have access to a set of tools that are executed upon the user's approval. Use the provider-native tool-calling mechanism. Do not include XML markup or examples.${toolUseGuidance}` - } + const toolUseGuidance = isMultipleNativeToolCallsEnabled + ? " You must call at least one tool per assistant response. Prefer calling as many tools as are reasonably needed in a single response to reduce back-and-forth and complete tasks faster." + : " You must use exactly one tool call per assistant response. Do not call zero tools or more than one tool in the same response." return `==== TOOL USE -You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. - -# Tool Use Formatting - -Tool uses are formatted using XML-style tags. The tool name itself becomes the XML tag name. Each parameter is enclosed within its own set of tags. Here's the structure: - - -value1 -value2 -... - - -Always use the actual tool name as the XML tag name for proper parsing and execution.` +You have access to a set of tools that are executed upon the user's approval. Use the provider-native tool-calling mechanism. Do not include XML markup or examples.${toolUseGuidance}` } diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index 040d7039292..90c775e28fd 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -1,15 +1,7 @@ import * as vscode from "vscode" import * as os from "os" -import { - type ModeConfig, - type PromptComponent, - type CustomModePrompts, - type TodoItem, - getEffectiveProtocol, - isNativeProtocol, -} from "@roo-code/types" -import { customToolRegistry, formatXml } from "@roo-code/core" +import { type ModeConfig, type PromptComponent, type CustomModePrompts, type TodoItem } from "@roo-code/types" import { Mode, modes, defaultModeSlug, getModeBySlug, getGroupName, getModeSelection } from "../../shared/modes" import { DiffStrategy } from "../../shared/tools" @@ -23,7 +15,6 @@ import { SkillsManager } from "../../services/skills/SkillsManager" import { PromptVariables, loadSystemPromptFile } from "./sections/custom-system-prompt" import type { SystemPromptSettings } from "./types" -import { getToolDescriptionsForMode } from "./tools" import { getRulesSection, getSystemInfoSection, @@ -91,52 +82,19 @@ async function generatePrompt( const codeIndexManager = CodeIndexManager.getInstance(context, cwd) - // Determine the effective protocol (defaults to 'xml') - const effectiveProtocol = getEffectiveProtocol(settings?.toolProtocol) + // Tool calling is native-only. + const effectiveProtocol = "native" const [modesSection, mcpServersSection, skillsSection] = await Promise.all([ getModesSection(context), shouldIncludeMcp - ? getMcpServersSection( - mcpHub, - effectiveDiffStrategy, - enableMcpServerCreation, - !isNativeProtocol(effectiveProtocol), - ) + ? getMcpServersSection(mcpHub, effectiveDiffStrategy, enableMcpServerCreation, false) : Promise.resolve(""), getSkillsSection(skillsManager, mode as string), ]) - // Build tools catalog section only for XML protocol - const builtInToolsCatalog = isNativeProtocol(effectiveProtocol) - ? "" - : `\n\n${getToolDescriptionsForMode( - mode, - cwd, - supportsComputerUse, - codeIndexManager, - effectiveDiffStrategy, - browserViewportSize, - shouldIncludeMcp ? mcpHub : undefined, - customModeConfigs, - experiments, - partialReadsEnabled, - settings, - enableMcpServerCreation, - modelId, - )}` - - let customToolsSection = "" - - if (experiments?.customTools && !isNativeProtocol(effectiveProtocol)) { - const customTools = customToolRegistry.getAllSerialized() - - if (customTools.length > 0) { - customToolsSection = `\n\n${formatXml(customTools)}` - } - } - - const toolsCatalog = builtInToolsCatalog + customToolsSection + // Tools catalog is not included in the system prompt in native-only mode. + const toolsCatalog = "" const basePrompt = `${roleDefinition} @@ -144,7 +102,7 @@ ${markdownFormattingSection()} ${getSharedToolUseSection(effectiveProtocol, experiments)}${toolsCatalog} -${getToolUseGuidelinesSection(effectiveProtocol, experiments)} + ${getToolUseGuidelinesSection(experiments)} ${mcpServersSection} diff --git a/src/core/prompts/tools/__tests__/access-mcp-resource.spec.ts b/src/core/prompts/tools/__tests__/access-mcp-resource.spec.ts deleted file mode 100644 index 1a927937ff8..00000000000 --- a/src/core/prompts/tools/__tests__/access-mcp-resource.spec.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { getAccessMcpResourceDescription } from "../access-mcp-resource" -import { ToolArgs } from "../types" -import { McpHub } from "../../../../services/mcp/McpHub" - -describe("getAccessMcpResourceDescription", () => { - const baseArgs: Omit = { - cwd: "/test", - supportsComputerUse: false, - } - - it("should return undefined when mcpHub is not provided", () => { - const args: ToolArgs = { - ...baseArgs, - mcpHub: undefined, - } - - const result = getAccessMcpResourceDescription(args) - expect(result).toBeUndefined() - }) - - it("should return undefined when mcpHub has no servers with resources", () => { - const mockMcpHub = { - getServers: () => [ - { - name: "test-server", - resources: [], - }, - ], - } as unknown as McpHub - - const args: ToolArgs = { - ...baseArgs, - mcpHub: mockMcpHub, - } - - const result = getAccessMcpResourceDescription(args) - expect(result).toBeUndefined() - }) - - it("should return undefined when mcpHub has servers with undefined resources", () => { - const mockMcpHub = { - getServers: () => [ - { - name: "test-server", - resources: undefined, - }, - ], - } as unknown as McpHub - - const args: ToolArgs = { - ...baseArgs, - mcpHub: mockMcpHub, - } - - const result = getAccessMcpResourceDescription(args) - expect(result).toBeUndefined() - }) - - it("should return undefined when mcpHub has no servers", () => { - const mockMcpHub = { - getServers: () => [], - } as unknown as McpHub - - const args: ToolArgs = { - ...baseArgs, - mcpHub: mockMcpHub, - } - - const result = getAccessMcpResourceDescription(args) - expect(result).toBeUndefined() - }) - - it("should return description when mcpHub has servers with resources", () => { - const mockMcpHub = { - getServers: () => [ - { - name: "test-server", - resources: [{ uri: "test://resource", name: "Test Resource" }], - }, - ], - } as unknown as McpHub - - const args: ToolArgs = { - ...baseArgs, - mcpHub: mockMcpHub, - } - - const result = getAccessMcpResourceDescription(args) - expect(result).toBeDefined() - expect(result).toContain("## access_mcp_resource") - expect(result).toContain("server_name") - expect(result).toContain("uri") - }) - - it("should return description when at least one server has resources", () => { - const mockMcpHub = { - getServers: () => [ - { - name: "server-without-resources", - resources: [], - }, - { - name: "server-with-resources", - resources: [{ uri: "test://resource", name: "Test Resource" }], - }, - ], - } as unknown as McpHub - - const args: ToolArgs = { - ...baseArgs, - mcpHub: mockMcpHub, - } - - const result = getAccessMcpResourceDescription(args) - expect(result).toBeDefined() - expect(result).toContain("## access_mcp_resource") - }) -}) diff --git a/src/core/prompts/tools/__tests__/attempt-completion.spec.ts b/src/core/prompts/tools/__tests__/attempt-completion.spec.ts deleted file mode 100644 index 026d73789ff..00000000000 --- a/src/core/prompts/tools/__tests__/attempt-completion.spec.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { getAttemptCompletionDescription } from "../attempt-completion" - -describe("getAttemptCompletionDescription", () => { - it("should NOT include command parameter in the description", () => { - const args = { - cwd: "/test/path", - supportsComputerUse: false, - } - - const description = getAttemptCompletionDescription(args) - - // Check that command parameter is NOT included (permanently disabled) - expect(description).not.toContain("- command: (optional)") - expect(description).not.toContain("A CLI command to execute to show a live demo") - expect(description).not.toContain("Command to demonstrate result (optional)") - expect(description).not.toContain("open index.html") - - // But should still have the basic structure - expect(description).toContain("## attempt_completion") - expect(description).toContain("- result: (required)") - expect(description).toContain("") - expect(description).toContain("") - }) - - it("should work when no args provided", () => { - const description = getAttemptCompletionDescription() - - // Check that command parameter is NOT included (permanently disabled) - expect(description).not.toContain("- command: (optional)") - expect(description).not.toContain("A CLI command to execute to show a live demo") - expect(description).not.toContain("Command to demonstrate result (optional)") - expect(description).not.toContain("open index.html") - - // But should still have the basic structure - expect(description).toContain("## attempt_completion") - expect(description).toContain("- result: (required)") - expect(description).toContain("") - expect(description).toContain("") - }) - - it("should show example without command", () => { - const args = { - cwd: "/test/path", - supportsComputerUse: false, - } - - const description = getAttemptCompletionDescription(args) - - // Check example format - expect(description).toContain("Example: Requesting to attempt completion with a result") - expect(description).toContain("I've updated the CSS") - expect(description).not.toContain("Example: Requesting to attempt completion with a result and command") - }) - - it("should contain core functionality description", () => { - const description = getAttemptCompletionDescription() - - // Should contain core functionality - const coreText = "After each tool use, the user will respond with the result of that tool use" - expect(description).toContain(coreText) - - // Should contain the important note - const importantNote = "IMPORTANT NOTE: This tool CANNOT be used until you've confirmed" - expect(description).toContain(importantNote) - - // Should contain result parameter - expect(description).toContain("- result: (required)") - }) -}) diff --git a/src/core/prompts/tools/__tests__/fetch-instructions.spec.ts b/src/core/prompts/tools/__tests__/fetch-instructions.spec.ts deleted file mode 100644 index ef01f132f50..00000000000 --- a/src/core/prompts/tools/__tests__/fetch-instructions.spec.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { getFetchInstructionsDescription } from "../fetch-instructions" - -describe("getFetchInstructionsDescription", () => { - it("should include create_mcp_server when enableMcpServerCreation is true", () => { - const description = getFetchInstructionsDescription(true) - - expect(description).toContain("create_mcp_server") - expect(description).toContain("create_mode") - expect(description).toContain("Example: Requesting instructions to create an MCP Server") - expect(description).toContain("create_mcp_server") - }) - - it("should include create_mcp_server when enableMcpServerCreation is undefined (default behavior)", () => { - const description = getFetchInstructionsDescription() - - expect(description).toContain("create_mcp_server") - expect(description).toContain("create_mode") - expect(description).toContain("Example: Requesting instructions to create an MCP Server") - expect(description).toContain("create_mcp_server") - }) - - it("should exclude create_mcp_server when enableMcpServerCreation is false", () => { - const description = getFetchInstructionsDescription(false) - - expect(description).not.toContain("create_mcp_server") - expect(description).toContain("create_mode") - expect(description).toContain("Example: Requesting instructions to create a Mode") - expect(description).toContain("create_mode") - expect(description).not.toContain("Example: Requesting instructions to create an MCP Server") - }) - - it("should have the correct structure", () => { - const description = getFetchInstructionsDescription(true) - - expect(description).toContain("## fetch_instructions") - expect(description).toContain("Description: Request to fetch instructions to perform a task") - expect(description).toContain("Parameters:") - expect(description).toContain("- task: (required) The task to get instructions for.") - expect(description).toContain("") - expect(description).toContain("") - }) - - it("should handle null value consistently (treat as default/undefined)", () => { - const description = getFetchInstructionsDescription(null as any) - - // Should behave the same as undefined (default to true) - expect(description).toContain("create_mcp_server") - expect(description).toContain("create_mode") - expect(description).toContain("Example: Requesting instructions to create an MCP Server") - expect(description).toContain("create_mcp_server") - }) -}) diff --git a/src/core/prompts/tools/__tests__/filter-tools-for-mode.spec.ts b/src/core/prompts/tools/__tests__/filter-tools-for-mode.spec.ts deleted file mode 100644 index 5cdfe2f1e79..00000000000 --- a/src/core/prompts/tools/__tests__/filter-tools-for-mode.spec.ts +++ /dev/null @@ -1,912 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from "vitest" -import type OpenAI from "openai" -import type { ModeConfig, ModelInfo } from "@roo-code/types" -import { - filterNativeToolsForMode, - filterMcpToolsForMode, - applyModelToolCustomization, - resolveToolAlias, -} from "../filter-tools-for-mode" -import * as toolsModule from "../../../../shared/tools" - -describe("filterNativeToolsForMode", () => { - const mockNativeTools: OpenAI.Chat.ChatCompletionTool[] = [ - { - type: "function", - function: { - name: "read_file", - description: "Read files", - parameters: {}, - }, - }, - { - type: "function", - function: { - name: "write_to_file", - description: "Write files", - parameters: {}, - }, - }, - { - type: "function", - function: { - name: "apply_diff", - description: "Apply diff", - parameters: {}, - }, - }, - { - type: "function", - function: { - name: "execute_command", - description: "Execute command", - parameters: {}, - }, - }, - { - type: "function", - function: { - name: "browser_action", - description: "Browser action", - parameters: {}, - }, - }, - { - type: "function", - function: { - name: "ask_followup_question", - description: "Ask question", - parameters: {}, - }, - }, - { - type: "function", - function: { - name: "attempt_completion", - description: "Complete task", - parameters: {}, - }, - }, - ] - - it("should filter tools for architect mode (read, browser, mcp only)", () => { - const architectMode: ModeConfig = { - slug: "architect", - name: "Architect", - roleDefinition: "Test", - groups: ["read", "browser", "mcp"] as const, - } - - const filtered = filterNativeToolsForMode( - mockNativeTools, - "architect", - [architectMode], - {}, - undefined, - {}, - undefined, - ) - - const toolNames = filtered.map((t) => ("function" in t ? t.function.name : "")) - - // Should include read tools - expect(toolNames).toContain("read_file") - - // Should NOT include edit tools - expect(toolNames).not.toContain("write_to_file") - expect(toolNames).not.toContain("apply_diff") - - // Should NOT include command tools - expect(toolNames).not.toContain("execute_command") - - // Should include browser tools - expect(toolNames).toContain("browser_action") - - // Should ALWAYS include always-available tools - expect(toolNames).toContain("ask_followup_question") - expect(toolNames).toContain("attempt_completion") - }) - - it("should filter tools for code mode (all groups)", () => { - const codeMode: ModeConfig = { - slug: "code", - name: "Code", - roleDefinition: "Test", - groups: ["read", "edit", "browser", "command", "mcp"] as const, - } - - const filtered = filterNativeToolsForMode(mockNativeTools, "code", [codeMode], {}, undefined, {}, undefined) - - const toolNames = filtered.map((t) => ("function" in t ? t.function.name : "")) - - // Should include all tools (code mode has all groups) - expect(toolNames).toContain("read_file") - expect(toolNames).toContain("write_to_file") - expect(toolNames).toContain("apply_diff") - expect(toolNames).toContain("execute_command") - expect(toolNames).toContain("browser_action") - expect(toolNames).toContain("ask_followup_question") - expect(toolNames).toContain("attempt_completion") - }) - - it("should always include always-available tools regardless of mode groups", () => { - const restrictiveMode: ModeConfig = { - slug: "restrictive", - name: "Restrictive", - roleDefinition: "Test", - groups: [] as const, // No groups - } - - const filtered = filterNativeToolsForMode( - mockNativeTools, - "restrictive", - [restrictiveMode], - {}, - undefined, - {}, - undefined, - ) - - const toolNames = filtered.map((t) => ("function" in t ? t.function.name : "")) - - // Should still include always-available tools - expect(toolNames).toContain("ask_followup_question") - expect(toolNames).toContain("attempt_completion") - - // Should NOT include any other tools - expect(toolNames).not.toContain("read_file") - expect(toolNames).not.toContain("write_to_file") - expect(toolNames).not.toContain("execute_command") - }) - - it("should handle undefined mode by using default mode", () => { - const filtered = filterNativeToolsForMode(mockNativeTools, undefined, undefined, {}, undefined, {}, undefined) - - // Should return some tools (default mode is code which has all groups) - expect(filtered.length).toBeGreaterThan(0) - - const toolNames = filtered.map((t) => ("function" in t ? t.function.name : "")) - expect(toolNames).toContain("ask_followup_question") - expect(toolNames).toContain("attempt_completion") - }) - - it("should exclude codebase_search when codeIndexManager is not configured", () => { - const codeMode: ModeConfig = { - slug: "code", - name: "Code", - roleDefinition: "Test", - groups: ["read", "edit", "browser", "command", "mcp"] as const, - } - - const mockCodebaseSearchTool: OpenAI.Chat.ChatCompletionTool = { - type: "function", - function: { - name: "codebase_search", - description: "Search codebase", - parameters: {}, - }, - } - - const toolsWithCodebaseSearch = [...mockNativeTools, mockCodebaseSearchTool] - - // Without codeIndexManager - const filtered = filterNativeToolsForMode( - toolsWithCodebaseSearch, - "code", - [codeMode], - {}, - undefined, - {}, - undefined, - ) - const toolNames = filtered.map((t) => ("function" in t ? t.function.name : "")) - expect(toolNames).not.toContain("codebase_search") - }) - - it("should exclude access_mcp_resource when mcpHub is not provided", () => { - const codeMode: ModeConfig = { - slug: "code", - name: "Code", - roleDefinition: "Test", - groups: ["read", "edit", "browser", "command", "mcp"] as const, - } - - const mockAccessMcpResourceTool: OpenAI.Chat.ChatCompletionTool = { - type: "function", - function: { - name: "access_mcp_resource", - description: "Access MCP resource", - parameters: {}, - }, - } - - const toolsWithAccessMcpResource = [...mockNativeTools, mockAccessMcpResourceTool] - - // Without mcpHub - const filtered = filterNativeToolsForMode( - toolsWithAccessMcpResource, - "code", - [codeMode], - {}, - undefined, - {}, - undefined, - ) - const toolNames = filtered.map((t) => ("function" in t ? t.function.name : "")) - expect(toolNames).not.toContain("access_mcp_resource") - }) - - it("should exclude access_mcp_resource when mcpHub has no resources", () => { - const codeMode: ModeConfig = { - slug: "code", - name: "Code", - roleDefinition: "Test", - groups: ["read", "edit", "browser", "command", "mcp"] as const, - } - - const mockAccessMcpResourceTool: OpenAI.Chat.ChatCompletionTool = { - type: "function", - function: { - name: "access_mcp_resource", - description: "Access MCP resource", - parameters: {}, - }, - } - - const toolsWithAccessMcpResource = [...mockNativeTools, mockAccessMcpResourceTool] - - // Mock mcpHub with no resources - const mockMcpHub = { - getServers: () => [ - { - name: "test-server", - resources: [], - }, - ], - } as any - - const filtered = filterNativeToolsForMode( - toolsWithAccessMcpResource, - "code", - [codeMode], - {}, - undefined, - {}, - mockMcpHub, - ) - const toolNames = filtered.map((t) => ("function" in t ? t.function.name : "")) - expect(toolNames).not.toContain("access_mcp_resource") - }) - - it("should include access_mcp_resource when mcpHub has resources", () => { - const codeMode: ModeConfig = { - slug: "code", - name: "Code", - roleDefinition: "Test", - groups: ["read", "edit", "browser", "command", "mcp"] as const, - } - - const mockAccessMcpResourceTool: OpenAI.Chat.ChatCompletionTool = { - type: "function", - function: { - name: "access_mcp_resource", - description: "Access MCP resource", - parameters: {}, - }, - } - - const toolsWithAccessMcpResource = [...mockNativeTools, mockAccessMcpResourceTool] - - // Mock mcpHub with resources - const mockMcpHub = { - getServers: () => [ - { - name: "test-server", - resources: [{ uri: "test://resource", name: "Test Resource" }], - }, - ], - } as any - - const filtered = filterNativeToolsForMode( - toolsWithAccessMcpResource, - "code", - [codeMode], - {}, - undefined, - {}, - mockMcpHub, - ) - const toolNames = filtered.map((t) => ("function" in t ? t.function.name : "")) - expect(toolNames).toContain("access_mcp_resource") - }) - - it("should exclude update_todo_list when todoListEnabled is false", () => { - const codeMode: ModeConfig = { - slug: "code", - name: "Code", - roleDefinition: "Test", - groups: ["read", "edit", "browser", "command", "mcp"] as const, - } - - const mockTodoTool: OpenAI.Chat.ChatCompletionTool = { - type: "function", - function: { - name: "update_todo_list", - description: "Update todo list", - parameters: {}, - }, - } - - const toolsWithTodo = [...mockNativeTools, mockTodoTool] - - const filtered = filterNativeToolsForMode( - toolsWithTodo, - "code", - [codeMode], - {}, - undefined, - { - todoListEnabled: false, - }, - undefined, - ) - const toolNames = filtered.map((t) => ("function" in t ? t.function.name : "")) - expect(toolNames).not.toContain("update_todo_list") - }) - - it("should exclude generate_image when experiment is not enabled", () => { - const codeMode: ModeConfig = { - slug: "code", - name: "Code", - roleDefinition: "Test", - groups: ["read", "edit", "browser", "command", "mcp"] as const, - } - - const mockImageTool: OpenAI.Chat.ChatCompletionTool = { - type: "function", - function: { - name: "generate_image", - description: "Generate image", - parameters: {}, - }, - } - - const toolsWithImage = [...mockNativeTools, mockImageTool] - - const filtered = filterNativeToolsForMode( - toolsWithImage, - "code", - [codeMode], - { imageGeneration: false }, - undefined, - {}, - undefined, - ) - const toolNames = filtered.map((t) => ("function" in t ? t.function.name : "")) - expect(toolNames).not.toContain("generate_image") - }) - - it("should exclude run_slash_command when experiment is not enabled", () => { - const codeMode: ModeConfig = { - slug: "code", - name: "Code", - roleDefinition: "Test", - groups: ["read", "edit", "browser", "command", "mcp"] as const, - } - - const mockSlashCommandTool: OpenAI.Chat.ChatCompletionTool = { - type: "function", - function: { - name: "run_slash_command", - description: "Run slash command", - parameters: {}, - }, - } - - const toolsWithSlashCommand = [...mockNativeTools, mockSlashCommandTool] - - const filtered = filterNativeToolsForMode( - toolsWithSlashCommand, - "code", - [codeMode], - { runSlashCommand: false }, - undefined, - {}, - undefined, - ) - const toolNames = filtered.map((t) => ("function" in t ? t.function.name : "")) - expect(toolNames).not.toContain("run_slash_command") - }) -}) - -describe("filterMcpToolsForMode", () => { - const mockMcpTools: OpenAI.Chat.ChatCompletionTool[] = [ - { - type: "function", - function: { - name: "mcp_server1_tool1", - description: "MCP tool 1", - parameters: {}, - }, - }, - { - type: "function", - function: { - name: "mcp_server1_tool2", - description: "MCP tool 2", - parameters: {}, - }, - }, - ] - - it("should include MCP tools when mode has mcp group", () => { - const modeWithMcp: ModeConfig = { - slug: "test-with-mcp", - name: "Test", - roleDefinition: "Test", - groups: ["read", "mcp"] as const, - } - - const filtered = filterMcpToolsForMode(mockMcpTools, "test-with-mcp", [modeWithMcp], {}) - - expect(filtered).toHaveLength(2) - expect(filtered).toEqual(mockMcpTools) - }) - - it("should exclude MCP tools when mode does not have mcp group", () => { - const modeWithoutMcp: ModeConfig = { - slug: "test-no-mcp", - name: "Test", - roleDefinition: "Test", - groups: ["read", "edit"] as const, - } - - const filtered = filterMcpToolsForMode(mockMcpTools, "test-no-mcp", [modeWithoutMcp], {}) - - expect(filtered).toHaveLength(0) - }) - - it("should handle undefined mode by using default mode", () => { - // Default mode (code) has mcp group - const filtered = filterMcpToolsForMode(mockMcpTools, undefined, undefined, {}) - - // Should include MCP tools since default mode has mcp group - expect(filtered.length).toBeGreaterThan(0) - }) - - describe("applyModelToolCustomization", () => { - const codeMode: ModeConfig = { - slug: "code", - name: "Code", - roleDefinition: "Test", - groups: ["read", "edit", "browser", "command", "mcp"] as const, - } - - const architectMode: ModeConfig = { - slug: "architect", - name: "Architect", - roleDefinition: "Test", - groups: ["read", "browser", "mcp"] as const, - } - - it("should return original tools when modelInfo is undefined", () => { - const tools = new Set(["read_file", "write_to_file", "apply_diff"]) - const result = applyModelToolCustomization(tools, codeMode, undefined) - expect(result.allowedTools).toEqual(tools) - }) - - it("should exclude tools specified in excludedTools", () => { - const tools = new Set(["read_file", "write_to_file", "apply_diff"]) - const modelInfo: ModelInfo = { - contextWindow: 100000, - supportsPromptCache: false, - excludedTools: ["apply_diff"], - } - const result = applyModelToolCustomization(tools, codeMode, modelInfo) - expect(result.allowedTools.has("read_file")).toBe(true) - expect(result.allowedTools.has("write_to_file")).toBe(true) - expect(result.allowedTools.has("apply_diff")).toBe(false) - }) - - it("should exclude multiple tools", () => { - const tools = new Set(["read_file", "write_to_file", "apply_diff", "execute_command"]) - const modelInfo: ModelInfo = { - contextWindow: 100000, - supportsPromptCache: false, - excludedTools: ["apply_diff", "write_to_file"], - } - const result = applyModelToolCustomization(tools, codeMode, modelInfo) - expect(result.allowedTools.has("read_file")).toBe(true) - expect(result.allowedTools.has("execute_command")).toBe(true) - expect(result.allowedTools.has("write_to_file")).toBe(false) - expect(result.allowedTools.has("apply_diff")).toBe(false) - }) - - it("should include tools only if they belong to allowed groups", () => { - const tools = new Set(["read_file"]) - const modelInfo: ModelInfo = { - contextWindow: 100000, - supportsPromptCache: false, - includedTools: ["write_to_file", "apply_diff"], // Both in edit group - } - const result = applyModelToolCustomization(tools, codeMode, modelInfo) - expect(result.allowedTools.has("read_file")).toBe(true) - expect(result.allowedTools.has("write_to_file")).toBe(true) - expect(result.allowedTools.has("apply_diff")).toBe(true) - }) - - it("should NOT include tools from groups not allowed by mode", () => { - const tools = new Set(["read_file"]) - const modelInfo: ModelInfo = { - contextWindow: 100000, - supportsPromptCache: false, - includedTools: ["write_to_file", "apply_diff"], // Edit group tools - } - // Architect mode doesn't have edit group - const result = applyModelToolCustomization(tools, architectMode, modelInfo) - expect(result.allowedTools.has("read_file")).toBe(true) - expect(result.allowedTools.has("write_to_file")).toBe(false) // Not in allowed groups - expect(result.allowedTools.has("apply_diff")).toBe(false) // Not in allowed groups - }) - - it("should apply both exclude and include operations", () => { - const tools = new Set(["read_file", "write_to_file", "apply_diff"]) - const modelInfo: ModelInfo = { - contextWindow: 100000, - supportsPromptCache: false, - excludedTools: ["apply_diff"], - includedTools: ["search_and_replace"], // Another edit tool (customTool) - } - const result = applyModelToolCustomization(tools, codeMode, modelInfo) - expect(result.allowedTools.has("read_file")).toBe(true) - expect(result.allowedTools.has("write_to_file")).toBe(true) - expect(result.allowedTools.has("apply_diff")).toBe(false) // Excluded - expect(result.allowedTools.has("search_and_replace")).toBe(true) // Included - }) - - it("should handle empty excludedTools and includedTools arrays", () => { - const tools = new Set(["read_file", "write_to_file"]) - const modelInfo: ModelInfo = { - contextWindow: 100000, - supportsPromptCache: false, - excludedTools: [], - includedTools: [], - } - const result = applyModelToolCustomization(tools, codeMode, modelInfo) - expect(result.allowedTools).toEqual(tools) - }) - - it("should ignore excluded tools that are not in the original set", () => { - const tools = new Set(["read_file", "write_to_file"]) - const modelInfo: ModelInfo = { - contextWindow: 100000, - supportsPromptCache: false, - excludedTools: ["apply_diff", "nonexistent_tool"], - } - const result = applyModelToolCustomization(tools, codeMode, modelInfo) - expect(result.allowedTools.has("read_file")).toBe(true) - expect(result.allowedTools.has("write_to_file")).toBe(true) - expect(result.allowedTools.size).toBe(2) - }) - - it("should NOT include customTools by default", () => { - const tools = new Set(["read_file", "write_to_file"]) - // Assume 'edit' group has a customTool defined in TOOL_GROUPS - const modelInfo: ModelInfo = { - contextWindow: 100000, - supportsPromptCache: false, - // No includedTools specified - } - const result = applyModelToolCustomization(tools, codeMode, modelInfo) - // customTools should not be in the result unless explicitly included - expect(result.allowedTools.has("read_file")).toBe(true) - expect(result.allowedTools.has("write_to_file")).toBe(true) - }) - - it("should NOT include tools that are not in any TOOL_GROUPS", () => { - const tools = new Set(["read_file"]) - const modelInfo: ModelInfo = { - contextWindow: 100000, - supportsPromptCache: false, - includedTools: ["my_custom_tool"], // Not in any tool group - } - const result = applyModelToolCustomization(tools, codeMode, modelInfo) - expect(result.allowedTools.has("read_file")).toBe(true) - expect(result.allowedTools.has("my_custom_tool")).toBe(false) - }) - - it("should NOT include undefined tools even with allowed groups", () => { - const tools = new Set(["read_file"]) - const modelInfo: ModelInfo = { - contextWindow: 100000, - supportsPromptCache: false, - includedTools: ["custom_edit_tool"], // Not in any tool group - } - // Even though architect mode has read group, undefined tools are not added - const result = applyModelToolCustomization(tools, architectMode, modelInfo) - expect(result.allowedTools.has("read_file")).toBe(true) - expect(result.allowedTools.has("custom_edit_tool")).toBe(false) - }) - - describe("with customTools defined in TOOL_GROUPS", () => { - const originalToolGroups = { ...toolsModule.TOOL_GROUPS } - - beforeEach(() => { - // Add a customTool to the edit group - ;(toolsModule.TOOL_GROUPS as any).edit = { - ...originalToolGroups.edit, - customTools: ["special_edit_tool"], - } - }) - - afterEach(() => { - // Restore original TOOL_GROUPS - ;(toolsModule.TOOL_GROUPS as any).edit = originalToolGroups.edit - }) - - it("should include customTools when explicitly specified in includedTools", () => { - const tools = new Set(["read_file", "write_to_file"]) - const modelInfo: ModelInfo = { - contextWindow: 100000, - supportsPromptCache: false, - includedTools: ["special_edit_tool"], // customTool from edit group - } - const result = applyModelToolCustomization(tools, codeMode, modelInfo) - expect(result.allowedTools.has("read_file")).toBe(true) - expect(result.allowedTools.has("write_to_file")).toBe(true) - expect(result.allowedTools.has("special_edit_tool")).toBe(true) // customTool should be included - }) - - it("should NOT include customTools when not specified in includedTools", () => { - const tools = new Set(["read_file", "write_to_file"]) - const modelInfo: ModelInfo = { - contextWindow: 100000, - supportsPromptCache: false, - // No includedTools specified - } - const result = applyModelToolCustomization(tools, codeMode, modelInfo) - expect(result.allowedTools.has("read_file")).toBe(true) - expect(result.allowedTools.has("write_to_file")).toBe(true) - expect(result.allowedTools.has("special_edit_tool")).toBe(false) // customTool should NOT be included by default - }) - - it("should NOT include customTools from groups not allowed by mode", () => { - const tools = new Set(["read_file"]) - const modelInfo: ModelInfo = { - contextWindow: 100000, - supportsPromptCache: false, - includedTools: ["special_edit_tool"], // customTool from edit group - } - // Architect mode doesn't have edit group - const result = applyModelToolCustomization(tools, architectMode, modelInfo) - expect(result.allowedTools.has("read_file")).toBe(true) - expect(result.allowedTools.has("special_edit_tool")).toBe(false) // customTool should NOT be included - }) - }) - }) - - describe("filterNativeToolsForMode with model customization", () => { - const mockNativeTools: OpenAI.Chat.ChatCompletionTool[] = [ - { - type: "function", - function: { - name: "read_file", - description: "Read files", - parameters: {}, - }, - }, - { - type: "function", - function: { - name: "write_to_file", - description: "Write files", - parameters: {}, - }, - }, - { - type: "function", - function: { - name: "apply_diff", - description: "Apply diff", - parameters: {}, - }, - }, - { - type: "function", - function: { - name: "execute_command", - description: "Execute command", - parameters: {}, - }, - }, - { - type: "function", - function: { - name: "search_and_replace", - description: "Search and replace", - parameters: {}, - }, - }, - { - type: "function", - function: { - name: "edit_file", - description: "Edit file", - parameters: {}, - }, - }, - ] - - it("should exclude tools when model specifies excludedTools", () => { - const codeMode: ModeConfig = { - slug: "code", - name: "Code", - roleDefinition: "Test", - groups: ["read", "edit", "browser", "command", "mcp"] as const, - } - - const modelInfo: ModelInfo = { - contextWindow: 100000, - supportsPromptCache: false, - excludedTools: ["apply_diff"], - } - - const filtered = filterNativeToolsForMode(mockNativeTools, "code", [codeMode], {}, undefined, { - modelInfo, - }) - - const toolNames = filtered.map((t) => ("function" in t ? t.function.name : "")) - - expect(toolNames).toContain("read_file") - expect(toolNames).toContain("write_to_file") - expect(toolNames).not.toContain("apply_diff") // Excluded by model - }) - - it("should include tools when model specifies includedTools from allowed groups", () => { - const modeWithOnlyRead: ModeConfig = { - slug: "limited", - name: "Limited", - roleDefinition: "Test", - groups: ["read", "edit"] as const, - } - - const modelInfo: ModelInfo = { - contextWindow: 100000, - supportsPromptCache: false, - includedTools: ["search_and_replace"], // Edit group customTool - } - - const filtered = filterNativeToolsForMode(mockNativeTools, "limited", [modeWithOnlyRead], {}, undefined, { - modelInfo, - }) - - const toolNames = filtered.map((t) => ("function" in t ? t.function.name : "")) - - expect(toolNames).toContain("search_and_replace") // Included by model - }) - - it("should NOT include tools from groups not allowed by mode", () => { - const architectMode: ModeConfig = { - slug: "architect", - name: "Architect", - roleDefinition: "Test", - groups: ["read", "browser"] as const, // No edit group - } - - const modelInfo: ModelInfo = { - contextWindow: 100000, - supportsPromptCache: false, - includedTools: ["write_to_file", "apply_diff"], // Edit group tools - } - - const filtered = filterNativeToolsForMode(mockNativeTools, "architect", [architectMode], {}, undefined, { - modelInfo, - }) - - const toolNames = filtered.map((t) => ("function" in t ? t.function.name : "")) - - expect(toolNames).toContain("read_file") - expect(toolNames).not.toContain("write_to_file") // Not in mode's allowed groups - expect(toolNames).not.toContain("apply_diff") // Not in mode's allowed groups - }) - - it("should combine excludedTools and includedTools", () => { - const codeMode: ModeConfig = { - slug: "code", - name: "Code", - roleDefinition: "Test", - groups: ["read", "edit", "browser", "command", "mcp"] as const, - } - - const modelInfo: ModelInfo = { - contextWindow: 100000, - supportsPromptCache: false, - excludedTools: ["apply_diff"], - includedTools: ["search_and_replace"], - } - - const filtered = filterNativeToolsForMode(mockNativeTools, "code", [codeMode], {}, undefined, { - modelInfo, - }) - - const toolNames = filtered.map((t) => ("function" in t ? t.function.name : "")) - - expect(toolNames).toContain("write_to_file") - expect(toolNames).toContain("search_and_replace") // Included - expect(toolNames).not.toContain("apply_diff") // Excluded - }) - - it("should honor included aliases while respecting exclusions", () => { - const codeMode: ModeConfig = { - slug: "code", - name: "Code", - roleDefinition: "Test", - groups: ["read", "edit", "browser", "command", "mcp"] as const, - } - - const modelInfo: ModelInfo = { - contextWindow: 100000, - supportsPromptCache: false, - excludedTools: ["apply_diff"], - includedTools: ["edit_file", "write_file"], - } - - const filtered = filterNativeToolsForMode(mockNativeTools, "code", [codeMode], {}, undefined, { - modelInfo, - }) - - const toolNames = filtered.map((t) => ("function" in t ? t.function.name : "")) - - expect(toolNames).toContain("edit_file") - expect(toolNames).toContain("write_file") - expect(toolNames).not.toContain("apply_diff") - expect(toolNames).not.toContain("write_to_file") - }) - }) -}) - -describe("resolveToolAlias", () => { - it("should resolve known alias to canonical name", () => { - // write_file is an alias for write_to_file (defined in TOOL_ALIASES) - expect(resolveToolAlias("write_file")).toBe("write_to_file") - }) - - it("should return canonical name unchanged", () => { - expect(resolveToolAlias("write_to_file")).toBe("write_to_file") - expect(resolveToolAlias("read_file")).toBe("read_file") - expect(resolveToolAlias("apply_diff")).toBe("apply_diff") - }) - - it("should return unknown tool names unchanged", () => { - expect(resolveToolAlias("unknown_tool")).toBe("unknown_tool") - expect(resolveToolAlias("custom_tool_xyz")).toBe("custom_tool_xyz") - }) - - it("should ensure allowedFunctionNames are consistent with functionDeclarations", () => { - // This test documents the fix for the Gemini allowedFunctionNames issue. - // When tools are renamed via aliasRenames, the alias names must be resolved - // back to canonical names for allowedFunctionNames to match functionDeclarations. - // - // Example scenario: - // - Model specifies includedTools: ["write_file"] (an alias) - // - filterNativeToolsForMode returns tool with name "write_file" - // - But allTools (functionDeclarations) contains "write_to_file" (canonical) - // - If allowedFunctionNames contains "write_file", Gemini will error - // - Resolving aliases ensures consistency: resolveToolAlias("write_file") -> "write_to_file" - - const aliasToolName = "write_file" - const canonicalToolName = "write_to_file" - - // Simulate extracting name from a filtered tool that was renamed to alias - const extractedName = aliasToolName - - // Before the fix: allowedFunctionNames would contain alias name - // This would cause Gemini to error because "write_file" doesn't exist in functionDeclarations - - // After the fix: we resolve to canonical name - const resolvedName = resolveToolAlias(extractedName) - - // The resolved name matches what's in functionDeclarations (canonical names) - expect(resolvedName).toBe(canonicalToolName) - }) -}) diff --git a/src/core/prompts/tools/__tests__/new-task.spec.ts b/src/core/prompts/tools/__tests__/new-task.spec.ts deleted file mode 100644 index c110cffcd1b..00000000000 --- a/src/core/prompts/tools/__tests__/new-task.spec.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { getNewTaskDescription } from "../new-task" -import { ToolArgs } from "../types" - -describe("getNewTaskDescription", () => { - it("should NOT show todos parameter at all when setting is disabled", () => { - const args: ToolArgs = { - cwd: "/test", - supportsComputerUse: false, - settings: { - newTaskRequireTodos: false, - }, - } - - const description = getNewTaskDescription(args) - - // Check that todos parameter is NOT shown at all - expect(description).not.toContain("todos:") - expect(description).not.toContain("todos parameter") - expect(description).not.toContain("The initial todo list in markdown checklist format") - - // Should have a simple example without todos - expect(description).toContain("Implement a new feature for the application") - - // Should NOT have any todos tags in examples - expect(description).not.toContain("") - expect(description).not.toContain("") - - // Should still have mode and message as required - expect(description).toContain("mode: (required)") - expect(description).toContain("message: (required)") - }) - - it("should show todos as required when setting is enabled", () => { - const args: ToolArgs = { - cwd: "/test", - supportsComputerUse: false, - settings: { - newTaskRequireTodos: true, - }, - } - - const description = getNewTaskDescription(args) - - // Check that todos is marked as required - expect(description).toContain("todos: (required)") - expect(description).toContain("and initial todo list") - expect(description).toContain("The initial todo list in markdown checklist format") - - // Should not contain any mention of optional for todos - expect(description).not.toContain("todos: (optional)") - expect(description).not.toContain("optional initial todo list") - - // Should include todos in the example - expect(description).toContain("") - expect(description).toContain("") - expect(description).toContain("Set up auth middleware") - }) - - it("should NOT show todos parameter when settings is undefined", () => { - const args: ToolArgs = { - cwd: "/test", - supportsComputerUse: false, - settings: undefined, - } - - const description = getNewTaskDescription(args) - - // Check that todos parameter is NOT shown by default - expect(description).not.toContain("todos:") - expect(description).not.toContain("The initial todo list in markdown checklist format") - expect(description).not.toContain("") - expect(description).not.toContain("") - }) - - it("should NOT show todos parameter when newTaskRequireTodos is undefined", () => { - const args: ToolArgs = { - cwd: "/test", - supportsComputerUse: false, - settings: {}, - } - - const description = getNewTaskDescription(args) - - // Check that todos parameter is NOT shown by default - expect(description).not.toContain("todos:") - expect(description).not.toContain("The initial todo list in markdown checklist format") - expect(description).not.toContain("") - expect(description).not.toContain("") - }) - - it("should include todos in examples only when setting is enabled", () => { - const argsWithSettingOff: ToolArgs = { - cwd: "/test", - supportsComputerUse: false, - settings: { - newTaskRequireTodos: false, - }, - } - - const argsWithSettingOn: ToolArgs = { - cwd: "/test", - supportsComputerUse: false, - settings: { - newTaskRequireTodos: true, - }, - } - - const descriptionOff = getNewTaskDescription(argsWithSettingOff) - const descriptionOn = getNewTaskDescription(argsWithSettingOn) - - // When setting is on, should include todos in main example - expect(descriptionOn).toContain("Implement user authentication") - expect(descriptionOn).toContain("[ ] Set up auth middleware") - expect(descriptionOn).toContain("") - expect(descriptionOn).toContain("") - - // When setting is off, should NOT include any todos references - expect(descriptionOff).not.toContain("") - expect(descriptionOff).not.toContain("") - expect(descriptionOff).not.toContain("[ ] Set up auth middleware") - expect(descriptionOff).not.toContain("[ ] First task to complete") - - // When setting is off, main example should be simple - const usagePattern = /\s*.*<\/mode>\s*.*<\/message>\s*<\/new_task>/s - expect(descriptionOff).toMatch(usagePattern) - }) -}) diff --git a/src/core/prompts/tools/access-mcp-resource.ts b/src/core/prompts/tools/access-mcp-resource.ts deleted file mode 100644 index 3807aab6bd9..00000000000 --- a/src/core/prompts/tools/access-mcp-resource.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { ToolArgs } from "./types" -import { McpHub } from "../../../services/mcp/McpHub" - -/** - * Helper function to check if any MCP server has resources available - */ -function hasAnyMcpResources(mcpHub: McpHub): boolean { - const servers = mcpHub.getServers() - return servers.some((server) => server.resources && server.resources.length > 0) -} - -export function getAccessMcpResourceDescription(args: ToolArgs): string | undefined { - if (!args.mcpHub || !hasAnyMcpResources(args.mcpHub)) { - return undefined - } - return `## access_mcp_resource -Description: Request to access a resource provided by a connected MCP server. Resources represent data sources that can be used as context, such as files, API responses, or system information. -Parameters: -- server_name: (required) The name of the MCP server providing the resource -- uri: (required) The URI identifying the specific resource to access -Usage: - -server name here -resource URI here - - -Example: Requesting to access an MCP resource - - -weather-server -weather://san-francisco/current -` -} diff --git a/src/core/prompts/tools/ask-followup-question.ts b/src/core/prompts/tools/ask-followup-question.ts deleted file mode 100644 index c40684b8bc7..00000000000 --- a/src/core/prompts/tools/ask-followup-question.ts +++ /dev/null @@ -1,27 +0,0 @@ -export function getAskFollowupQuestionDescription(): string { - return `## ask_followup_question -Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. - -Parameters: -- question: (required) A clear, specific question addressing the information needed -- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) - -Usage: - -Your question here - -First suggestion -Action with mode switch - - - -Example: - -What is the path to the frontend-config.json file? - -./src/frontend-config.json -./config/frontend-config.json -./frontend-config.json - -` -} diff --git a/src/core/prompts/tools/attempt-completion.ts b/src/core/prompts/tools/attempt-completion.ts deleted file mode 100644 index 62f0827f98e..00000000000 --- a/src/core/prompts/tools/attempt-completion.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { ToolArgs } from "./types" - -export function getAttemptCompletionDescription(args?: ToolArgs): string { - return `## attempt_completion -Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. -IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must confirm that you've received successful results from the user for any previous tool uses. If not, then DO NOT use this tool. -Parameters: -- result: (required) The result of the task. Formulate this result in a way that is final and does not require further input from the user. Don't end your result with questions or offers for further assistance. -Usage: - - -Your final result description here - - - -Example: Requesting to attempt completion with a result - - -I've updated the CSS - -` -} diff --git a/src/core/prompts/tools/browser-action.ts b/src/core/prompts/tools/browser-action.ts deleted file mode 100644 index 88c7343d0a5..00000000000 --- a/src/core/prompts/tools/browser-action.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { ToolArgs } from "./types" - -export function getBrowserActionDescription(args: ToolArgs): string | undefined { - if (!args.supportsComputerUse) { - return undefined - } - return `## browser_action -Description: Request to interact with a Puppeteer-controlled browser. Every action, except \`close\`, will be responded to with a screenshot of the browser's current state, along with any new console logs. You may only perform one browser action per message, and wait for the user's response including a screenshot and logs to determine the next action. - -This tool is particularly useful for web development tasks as it allows you to launch a browser, navigate to pages, interact with elements through clicks and keyboard input, and capture the results through screenshots and console logs. Use it at key stages of web development tasks - such as after implementing new features, making substantial changes, when troubleshooting issues, or to verify the result of your work. Analyze the provided screenshots to ensure correct rendering or identify errors, and review console logs for runtime issues. - -The user may ask generic non-development tasks (such as "what's the latest news" or "look up the weather"), in which case you might use this tool to complete the task if it makes sense to do so, rather than trying to create a website or using curl to answer the question. However, if an available MCP server tool or resource can be used instead, you should prefer to use it over browser_action. - -**Browser Session Lifecycle:** -- Browser sessions **start** with \`launch\` and **end** with \`close\` -- The session remains active across multiple messages and tool uses -- You can use other tools while the browser session is active - it will stay open in the background - -Parameters: -- action: (required) The action to perform. The available actions are: - * launch: Launch a new Puppeteer-controlled browser instance at the specified URL. This **must always be the first action**. - - Use with the \`url\` parameter to provide the URL. - - Ensure the URL is valid and includes the appropriate protocol (e.g. http://localhost:3000/page, file:///path/to/file.html, etc.) - * hover: Move the cursor to a specific x,y coordinate. - - Use with the \`coordinate\` parameter to specify the location. - - Always move to the center of an element (icon, button, link, etc.) based on coordinates derived from a screenshot. - * click: Click at a specific x,y coordinate. - - Use with the \`coordinate\` parameter to specify the location. - - Always click in the center of an element (icon, button, link, etc.) based on coordinates derived from a screenshot. - * type: Type a string of text on the keyboard. You might use this after clicking on a text field to input text. - - Use with the \`text\` parameter to provide the string to type. - * press: Press a single keyboard key or key combination (e.g., Enter, Tab, Escape, Cmd+K, Shift+Enter). - - Use with the \`text\` parameter to provide the key name or combination. - - For single keys: Enter, Tab, Escape, etc. - - For key combinations: Cmd+K, Ctrl+C, Shift+Enter, Alt+F4, etc. - - Supported modifiers: Cmd/Command/Meta, Ctrl/Control, Shift, Alt/Option - - Example: Cmd+K or Shift+Enter - * resize: Resize the viewport to a specific w,h size. - - Use with the \`size\` parameter to specify the new size. - * scroll_down: Scroll down the page by one page height. - * scroll_up: Scroll up the page by one page height. - * screenshot: Take a screenshot and save it to a file. - - Use with the \`path\` parameter to specify the destination file path. - - Supported formats: .png, .jpeg, .webp - - Example: \`screenshot\` with \`screenshots/result.png\` - * close: Close the Puppeteer-controlled browser instance. This **must always be the final browser action**. - - Example: \`close\` -- url: (optional) Use this for providing the URL for the \`launch\` action. - * Example: https://example.com -- coordinate: (optional) The X and Y coordinates for the \`click\` and \`hover\` actions. - * **CRITICAL**: Screenshot dimensions are NOT the same as the browser viewport dimensions - * Format: x,y@widthxheight - * Measure x,y on the screenshot image you see in chat - * The widthxheight MUST be the EXACT pixel size of that screenshot image (never the browser viewport) - * Never use the browser viewport size for widthxheight - the viewport is only a reference and is often larger than the screenshot - * Images are often downscaled before you see them, so the screenshot's dimensions will likely be smaller than the viewport - * Example A: If the screenshot you see is 1094x1092 and you want to click (450,300) on that image, use: 450,300@1094x1092 - * Example B: If the browser viewport is 1280x800 but the screenshot is 1000x625 and you want to click (500,300) on the screenshot, use: 500,300@1000x625 -- size: (optional) The width and height for the \`resize\` action. - * Example: 1280,720 -- text: (optional) Use this for providing the text for the \`type\` action. - * Example: Hello, world! -- path: (optional) File path for the \`screenshot\` action. Path is relative to the workspace. - * Supported formats: .png, .jpeg, .webp - * Example: screenshots/my-screenshot.png -Usage: - -Action to perform (e.g., launch, click, type, press, scroll_down, scroll_up, close) -URL to launch the browser at (optional) -x,y@widthxheight coordinates (optional) -Text to type (optional) - - -Example: Requesting to launch a browser at https://example.com - -launch -https://example.com - - -Example: Requesting to click on the element at coordinates 450,300 on a 1024x768 image - -click -450,300@1024x768 - - -Example: Taking a screenshot and saving it to a file - -screenshot -screenshots/result.png -` -} diff --git a/src/core/prompts/tools/codebase-search.ts b/src/core/prompts/tools/codebase-search.ts deleted file mode 100644 index f6130392157..00000000000 --- a/src/core/prompts/tools/codebase-search.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { ToolArgs } from "./types" - -export function getCodebaseSearchDescription(args: ToolArgs): string { - return `## codebase_search -Description: Find files most relevant to the search query using semantic search. Searches based on meaning rather than exact text matches. By default searches entire workspace. Reuse the user's exact wording unless there's a clear reason not to - their phrasing often helps semantic search. Queries MUST be in English (translate if needed). - -**CRITICAL: For ANY exploration of code you haven't examined yet in this conversation, you MUST use this tool FIRST before any other search or file exploration tools.** This applies throughout the entire conversation, not just at the beginning. This tool uses semantic search to find relevant code based on meaning rather than just keywords, making it far more effective than regex-based search_files for understanding implementations. Even if you've already explored some code, any new area of exploration requires codebase_search first. - -Parameters: -- query: (required) The search query. Reuse the user's exact wording/question format unless there's a clear reason not to. -- path: (optional) Limit search to specific subdirectory (relative to the current workspace directory ${args.cwd}). Leave empty for entire workspace. - -Usage: - -Your natural language query here -Optional subdirectory path - - -Example: Searching for user authentication code - -User login and password hashing -src/auth - - -Example: Searching entire workspace - -database connection pooling - - -` -} diff --git a/src/core/prompts/tools/execute-command.ts b/src/core/prompts/tools/execute-command.ts deleted file mode 100644 index c1fc1ea3f19..00000000000 --- a/src/core/prompts/tools/execute-command.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ToolArgs } from "./types" - -export function getExecuteCommandDescription(args: ToolArgs): string | undefined { - return `## execute_command -Description: Request to execute a CLI command on the system. Use this when you need to perform system operations or run specific commands to accomplish any step in the user's task. You must tailor your command to the user's system and provide a clear explanation of what the command does. For command chaining, use the appropriate chaining syntax for the user's shell. Prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run. Prefer relative commands and paths that avoid location sensitivity for terminal consistency, e.g: \`touch ./testdata/example.file\`, \`dir ./examples/model1/data/yaml\`, or \`go test ./cmd/front --config ./cmd/front/config.yml\`. If directed by the user, you may open a terminal in a different directory by using the \`cwd\` parameter. -Parameters: -- command: (required) The CLI command to execute. This should be valid for the current operating system. Ensure the command is properly formatted and does not contain any harmful instructions. -- cwd: (optional) The working directory to execute the command in (default: ${args.cwd}) -Usage: - -Your command here -Working directory path (optional) - - -Example: Requesting to execute npm run dev - -npm run dev - - -Example: Requesting to execute ls in a specific directory if directed - -ls -la -/home/user/projects -` -} diff --git a/src/core/prompts/tools/fetch-instructions.ts b/src/core/prompts/tools/fetch-instructions.ts deleted file mode 100644 index dd9cbb80da9..00000000000 --- a/src/core/prompts/tools/fetch-instructions.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Generates the fetch_instructions tool description. - * @param enableMcpServerCreation - Whether to include MCP server creation task. - * Defaults to true when undefined. - */ -export function getFetchInstructionsDescription(enableMcpServerCreation?: boolean): string { - const tasks = - enableMcpServerCreation !== false - ? ` create_mcp_server - create_mode` - : ` create_mode` - - const example = - enableMcpServerCreation !== false - ? `Example: Requesting instructions to create an MCP Server - - -create_mcp_server -` - : `Example: Requesting instructions to create a Mode - - -create_mode -` - - return `## fetch_instructions -Description: Request to fetch instructions to perform a task -Parameters: -- task: (required) The task to get instructions for. This can take the following values: -${tasks} - -${example}` -} diff --git a/src/core/prompts/tools/filter-tools-for-mode.ts b/src/core/prompts/tools/filter-tools-for-mode.ts index f296c1b5c50..79db5d6edcb 100644 --- a/src/core/prompts/tools/filter-tools-for-mode.ts +++ b/src/core/prompts/tools/filter-tools-for-mode.ts @@ -211,7 +211,7 @@ export function applyModelToolCustomization( /** * Filters native tools based on mode restrictions and model customization. - * This ensures native tools are filtered the same way XML tools are filtered in the system prompt. + * This ensures native tools are filtered consistently with mode/tool permissions. * * @param nativeTools - Array of all available native tools * @param mode - Current mode slug diff --git a/src/core/prompts/tools/generate-image.ts b/src/core/prompts/tools/generate-image.ts deleted file mode 100644 index 458b7ae8cf3..00000000000 --- a/src/core/prompts/tools/generate-image.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { ToolArgs } from "./types" - -export function getGenerateImageDescription(args: ToolArgs): string { - return `## generate_image -Description: Request to generate or edit an image using AI models through OpenRouter API. This tool can create new images from text prompts or modify existing images based on your instructions. When an input image is provided, the AI will apply the requested edits, transformations, or enhancements to that image. -Parameters: -- prompt: (required) The text prompt describing what to generate or how to edit the image -- path: (required) The file path where the generated/edited image should be saved (relative to the current workspace directory ${args.cwd}). The tool will automatically add the appropriate image extension if not provided. -- image: (optional) The file path to an input image to edit or transform (relative to the current workspace directory ${args.cwd}). Supported formats: PNG, JPG, JPEG, GIF, WEBP. -Usage: - -Your image description here -path/to/save/image.png -path/to/input/image.jpg - - -Example: Requesting to generate a sunset image - -A beautiful sunset over mountains with vibrant orange and purple colors -images/sunset.png - - -Example: Editing an existing image - -Transform this image into a watercolor painting style -images/watercolor-output.png -images/original-photo.jpg - - -Example: Upscaling and enhancing an image - -Upscale this image to higher resolution, enhance details, improve clarity and sharpness while maintaining the original content and composition -images/enhanced-photo.png -images/low-res-photo.jpg -` -} diff --git a/src/core/prompts/tools/index.ts b/src/core/prompts/tools/index.ts deleted file mode 100644 index b75725a99b3..00000000000 --- a/src/core/prompts/tools/index.ts +++ /dev/null @@ -1,172 +0,0 @@ -import type { ToolName, ModeConfig } from "@roo-code/types" - -import { TOOL_GROUPS, ALWAYS_AVAILABLE_TOOLS, DiffStrategy } from "../../../shared/tools" -import { Mode, getModeConfig, getGroupName } from "../../../shared/modes" - -import { isToolAllowedForMode } from "../../tools/validateToolUse" - -import { McpHub } from "../../../services/mcp/McpHub" -import { CodeIndexManager } from "../../../services/code-index/manager" - -import { ToolArgs } from "./types" -import { getExecuteCommandDescription } from "./execute-command" -import { getReadFileDescription } from "./read-file" -import { getFetchInstructionsDescription } from "./fetch-instructions" -import { getWriteToFileDescription } from "./write-to-file" -import { getSearchFilesDescription } from "./search-files" -import { getListFilesDescription } from "./list-files" -import { getBrowserActionDescription } from "./browser-action" -import { getAskFollowupQuestionDescription } from "./ask-followup-question" -import { getAttemptCompletionDescription } from "./attempt-completion" -import { getUseMcpToolDescription } from "./use-mcp-tool" -import { getAccessMcpResourceDescription } from "./access-mcp-resource" -import { getSwitchModeDescription } from "./switch-mode" -import { getNewTaskDescription } from "./new-task" -import { getCodebaseSearchDescription } from "./codebase-search" -import { getUpdateTodoListDescription } from "./update-todo-list" -import { getRunSlashCommandDescription } from "./run-slash-command" -import { getGenerateImageDescription } from "./generate-image" - -// Map of tool names to their description functions -const toolDescriptionMap: Record string | undefined> = { - execute_command: (args) => getExecuteCommandDescription(args), - read_file: (args) => getReadFileDescription(args), - fetch_instructions: (args) => getFetchInstructionsDescription(args.settings?.enableMcpServerCreation), - write_to_file: (args) => getWriteToFileDescription(args), - search_files: (args) => getSearchFilesDescription(args), - list_files: (args) => getListFilesDescription(args), - browser_action: (args) => getBrowserActionDescription(args), - ask_followup_question: () => getAskFollowupQuestionDescription(), - attempt_completion: (args) => getAttemptCompletionDescription(args), - use_mcp_tool: (args) => getUseMcpToolDescription(args), - access_mcp_resource: (args) => getAccessMcpResourceDescription(args), - codebase_search: (args) => getCodebaseSearchDescription(args), - switch_mode: () => getSwitchModeDescription(), - new_task: (args) => getNewTaskDescription(args), - apply_diff: (args) => - args.diffStrategy ? args.diffStrategy.getToolDescription({ cwd: args.cwd, toolOptions: args.toolOptions }) : "", - update_todo_list: (args) => getUpdateTodoListDescription(args), - run_slash_command: () => getRunSlashCommandDescription(), - generate_image: (args) => getGenerateImageDescription(args), -} - -export function getToolDescriptionsForMode( - mode: Mode, - cwd: string, - supportsComputerUse: boolean, - codeIndexManager?: CodeIndexManager, - diffStrategy?: DiffStrategy, - browserViewportSize?: string, - mcpHub?: McpHub, - customModes?: ModeConfig[], - experiments?: Record, - partialReadsEnabled?: boolean, - settings?: Record, - enableMcpServerCreation?: boolean, - modelId?: string, -): string { - const config = getModeConfig(mode, customModes) - const args: ToolArgs = { - cwd, - supportsComputerUse, - diffStrategy, - browserViewportSize, - mcpHub, - partialReadsEnabled, - settings: { - ...settings, - enableMcpServerCreation, - modelId, - }, - experiments, - } - - const tools = new Set() - - // Add tools from mode's groups - config.groups.forEach((groupEntry) => { - const groupName = getGroupName(groupEntry) - const toolGroup = TOOL_GROUPS[groupName] - if (toolGroup) { - toolGroup.tools.forEach((tool) => { - if ( - isToolAllowedForMode( - tool as ToolName, - mode, - customModes ?? [], - undefined, - undefined, - experiments ?? {}, - ) - ) { - tools.add(tool) - } - }) - } - }) - - // Add always available tools - ALWAYS_AVAILABLE_TOOLS.forEach((tool) => tools.add(tool)) - - // Conditionally exclude codebase_search if feature is disabled or not configured - if ( - !codeIndexManager || - !(codeIndexManager.isFeatureEnabled && codeIndexManager.isFeatureConfigured && codeIndexManager.isInitialized) - ) { - tools.delete("codebase_search") - } - - // Conditionally exclude update_todo_list if disabled in settings - if (settings?.todoListEnabled === false) { - tools.delete("update_todo_list") - } - - // Conditionally exclude generate_image if experiment is not enabled - if (!experiments?.imageGeneration) { - tools.delete("generate_image") - } - - // Conditionally exclude run_slash_command if experiment is not enabled - if (!experiments?.runSlashCommand) { - tools.delete("run_slash_command") - } - - // Map tool descriptions for allowed tools - const descriptions = Array.from(tools).map((toolName) => { - const descriptionFn = toolDescriptionMap[toolName] - if (!descriptionFn) { - return undefined - } - - const description = descriptionFn({ - ...args, - toolOptions: undefined, // No tool options in group-based approach - }) - - return description - }) - - return `# Tools\n\n${descriptions.filter(Boolean).join("\n\n")}` -} - -// Export individual description functions for backward compatibility -export { - getExecuteCommandDescription, - getReadFileDescription, - getFetchInstructionsDescription, - getWriteToFileDescription, - getSearchFilesDescription, - getListFilesDescription, - getBrowserActionDescription, - getAskFollowupQuestionDescription, - getAttemptCompletionDescription, - getUseMcpToolDescription, - getAccessMcpResourceDescription, - getSwitchModeDescription, - getCodebaseSearchDescription, - getRunSlashCommandDescription, - getGenerateImageDescription, -} - -// Export native tool definitions (JSON schema format for OpenAI-compatible APIs) -export { nativeTools } from "./native-tools" diff --git a/src/core/prompts/tools/list-files.ts b/src/core/prompts/tools/list-files.ts deleted file mode 100644 index 96c43ea4a65..00000000000 --- a/src/core/prompts/tools/list-files.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { ToolArgs } from "./types" - -export function getListFilesDescription(args: ToolArgs): string { - return `## list_files -Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not. -Parameters: -- path: (required) The path of the directory to list contents for (relative to the current workspace directory ${args.cwd}) -- recursive: (optional) Whether to list files recursively. Use true for recursive listing, false or omit for top-level only. -Usage: - -Directory path here -true or false (optional) - - -Example: Requesting to list all files in the current directory - -. -false -` -} diff --git a/src/core/prompts/tools/new-task.ts b/src/core/prompts/tools/new-task.ts deleted file mode 100644 index bba6c6250f3..00000000000 --- a/src/core/prompts/tools/new-task.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { ToolArgs } from "./types" - -/** - * Prompt when todos are NOT required (default) - */ -const PROMPT_WITHOUT_TODOS = `## new_task -Description: This will let you create a new task instance in the chosen mode using your provided message. - -Parameters: -- mode: (required) The slug of the mode to start the new task in (e.g., "code", "debug", "architect"). -- message: (required) The initial user message or instructions for this new task. - -Usage: - -your-mode-slug-here -Your initial instructions here - - -Example: - -code -Implement a new feature for the application - -` - -/** - * Prompt when todos ARE required - */ -const PROMPT_WITH_TODOS = `## new_task -Description: This will let you create a new task instance in the chosen mode using your provided message and initial todo list. - -Parameters: -- mode: (required) The slug of the mode to start the new task in (e.g., "code", "debug", "architect"). -- message: (required) The initial user message or instructions for this new task. -- todos: (required) The initial todo list in markdown checklist format for the new task. - -Usage: - -your-mode-slug-here -Your initial instructions here - -[ ] First task to complete -[ ] Second task to complete -[ ] Third task to complete - - - -Example: - -code -Implement user authentication - -[ ] Set up auth middleware -[ ] Create login endpoint -[ ] Add session management -[ ] Write tests - - - -` - -export function getNewTaskDescription(args: ToolArgs): string { - const todosRequired = args.settings?.newTaskRequireTodos === true - - // Simply return the appropriate prompt based on the setting - return todosRequired ? PROMPT_WITH_TODOS : PROMPT_WITHOUT_TODOS -} diff --git a/src/core/prompts/tools/read-file.ts b/src/core/prompts/tools/read-file.ts deleted file mode 100644 index 86f4dc8c640..00000000000 --- a/src/core/prompts/tools/read-file.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { ToolArgs } from "./types" - -export function getReadFileDescription(args: ToolArgs): string { - const maxConcurrentReads = args.settings?.maxConcurrentFileReads ?? 5 - const isMultipleReadsEnabled = maxConcurrentReads > 1 - - return `## read_file -Description: Request to read the contents of ${isMultipleReadsEnabled ? "one or more files" : "a file"}. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code.${args.partialReadsEnabled ? " Use line ranges to efficiently read specific portions of large files." : ""} Supports text extraction from PDF and DOCX files, but may not handle other binary files properly. - -${isMultipleReadsEnabled ? `**IMPORTANT: You can read a maximum of ${maxConcurrentReads} files in a single request.** If you need to read more files, use multiple sequential read_file requests.` : "**IMPORTANT: Multiple file reads are currently disabled. You can only read one file at a time.**"} - -${args.partialReadsEnabled ? `By specifying line ranges, you can efficiently read specific portions of large files without loading the entire file into memory.` : ""} -Parameters: -- args: Contains one or more file elements, where each file contains: - - path: (required) File path (relative to workspace directory ${args.cwd}) - ${args.partialReadsEnabled ? `- line_range: (optional) One or more line range elements in format "start-end" (1-based, inclusive)` : ""} - -Usage: - - - - path/to/file - ${args.partialReadsEnabled ? `start-end` : ""} - - - - -Examples: - -1. Reading a single file: - - - - src/app.ts - ${args.partialReadsEnabled ? `1-1000` : ""} - - - - -${isMultipleReadsEnabled ? `2. Reading multiple files (within the ${maxConcurrentReads}-file limit):` : ""}${ - isMultipleReadsEnabled - ? ` - - - - src/app.ts - ${ - args.partialReadsEnabled - ? `1-50 - 100-150` - : "" - } - - - src/utils.ts - ${args.partialReadsEnabled ? `10-20` : ""} - - -` - : "" - } - -${isMultipleReadsEnabled ? "3. " : "2. "}Reading an entire file: - - - - config.json - - - - -IMPORTANT: You MUST use this Efficient Reading Strategy: -- ${isMultipleReadsEnabled ? `You MUST read all related files and implementations together in a single operation (up to ${maxConcurrentReads} files at once)` : "You MUST read files one at a time, as multiple file reads are currently disabled"} -- You MUST obtain all necessary context before proceeding with changes -${ - args.partialReadsEnabled - ? `- You MUST use line ranges to read specific portions of large files, rather than reading entire files when not needed -- You MUST combine adjacent line ranges (<10 lines apart) -- You MUST use multiple ranges for content separated by >10 lines -- You MUST include sufficient line context for planned modifications while keeping ranges minimal -` - : "" -} -${isMultipleReadsEnabled ? `- When you need to read more than ${maxConcurrentReads} files, prioritize the most critical files first, then use subsequent read_file requests for additional files` : ""}` -} diff --git a/src/core/prompts/tools/run-slash-command.ts b/src/core/prompts/tools/run-slash-command.ts deleted file mode 100644 index 27047dcbaaf..00000000000 --- a/src/core/prompts/tools/run-slash-command.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Generates the run_slash_command tool description. - */ -export function getRunSlashCommandDescription(): string { - return `## run_slash_command -Description: Execute a slash command to get specific instructions or content. Slash commands are predefined templates that provide detailed guidance for common tasks. - -Parameters: -- command: (required) The name of the slash command to execute (e.g., "init", "test", "deploy") -- args: (optional) Additional arguments or context to pass to the command - -Usage: - -command_name -optional arguments - - -Examples: - -1. Running the init command to analyze a codebase: - -init - - -2. Running a command with additional context: - -test -focus on integration tests - - -The command content will be returned for you to execute or follow as instructions.` -} diff --git a/src/core/prompts/tools/search-files.ts b/src/core/prompts/tools/search-files.ts deleted file mode 100644 index f0af9f8a23a..00000000000 --- a/src/core/prompts/tools/search-files.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { ToolArgs } from "./types" - -export function getSearchFilesDescription(args: ToolArgs): string { - return `## search_files -Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. - -Craft your regex patterns carefully to balance specificity and flexibility. Use this tool to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include surrounding context, so analyze the surrounding code to better understand the matches. Leverage this tool in combination with other tools for more comprehensive analysis - for example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches. - -Parameters: -- path: (required) The path of the directory to search in (relative to the current workspace directory ${args.cwd}). This directory will be recursively searched. -- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax. -- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*). - -Usage: - -Directory path here -Your regex pattern here -file pattern here (optional) - - -Example: Searching for all .ts files in the current directory - -. -.* -*.ts - - -Example: Searching for function definitions in JavaScript files - -src -function\\s+\\w+ -*.js -` -} diff --git a/src/core/prompts/tools/switch-mode.ts b/src/core/prompts/tools/switch-mode.ts deleted file mode 100644 index a8c64d1e10f..00000000000 --- a/src/core/prompts/tools/switch-mode.ts +++ /dev/null @@ -1,18 +0,0 @@ -export function getSwitchModeDescription(): string { - return `## switch_mode -Description: Request to switch to a different mode. This tool allows modes to request switching to another mode when needed, such as switching to Code mode to make code changes. The user must approve the mode switch. -Parameters: -- mode_slug: (required) The slug of the mode to switch to (e.g., "code", "ask", "architect") -- reason: (optional) The reason for switching modes -Usage: - -Mode slug here -Reason for switching here - - -Example: Requesting to switch to code mode - -code -Need to make code changes -` -} diff --git a/src/core/prompts/tools/types.ts b/src/core/prompts/tools/types.ts deleted file mode 100644 index 9471d100d7b..00000000000 --- a/src/core/prompts/tools/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { DiffStrategy } from "../../../shared/tools" -import { McpHub } from "../../../services/mcp/McpHub" - -export type ToolArgs = { - cwd: string - supportsComputerUse: boolean - diffStrategy?: DiffStrategy - browserViewportSize?: string - mcpHub?: McpHub - toolOptions?: any - partialReadsEnabled?: boolean - settings?: Record - experiments?: Record -} diff --git a/src/core/prompts/tools/update-todo-list.ts b/src/core/prompts/tools/update-todo-list.ts deleted file mode 100644 index 30100617dfb..00000000000 --- a/src/core/prompts/tools/update-todo-list.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { ToolArgs } from "./types" - -/** - * Get the description for the update_todo_list tool. - */ -export function getUpdateTodoListDescription(args?: ToolArgs): string { - return `## update_todo_list - -**Description:** -Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks. - -**Checklist Format:** -- Use a single-level markdown checklist (no nesting or subtasks). -- List todos in the intended execution order. -- Status options: - - [ ] Task description (pending) - - [x] Task description (completed) - - [-] Task description (in progress) - -**Status Rules:** -- [ ] = pending (not started) -- [x] = completed (fully finished, no unresolved issues) -- [-] = in_progress (currently being worked on) - -**Core Principles:** -- Before updating, always confirm which todos have been completed since the last update. -- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress). -- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately. -- Do not remove any unfinished todos unless explicitly instructed. -- Always retain all unfinished tasks, updating their status as needed. -- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies). -- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved. -- Remove tasks only if they are no longer relevant or if the user requests deletion. - -**Usage Example:** - - -[x] Analyze requirements -[x] Design architecture -[-] Implement core logic -[ ] Write tests -[ ] Update documentation - - - -*After completing "Implement core logic" and starting "Write tests":* - - -[x] Analyze requirements -[x] Design architecture -[x] Implement core logic -[-] Write tests -[ ] Update documentation -[ ] Add performance benchmarks - - - -**When to Use:** -- The task is complicated or involves multiple steps or requires ongoing tracking. -- You need to update the status of several todos at once. -- New actionable items are discovered during task execution. -- The user requests a todo list or provides multiple tasks. -- The task is complex and benefits from clear, stepwise progress tracking. - -**When NOT to Use:** -- There is only a single, trivial task. -- The task can be completed in one or two simple steps. -- The request is purely conversational or informational. - -**Task Management Guidelines:** -- Mark task as completed immediately after all work of the current task is done. -- Start the next task by marking it as in_progress. -- Add new todos as soon as they are identified. -- Use clear, descriptive task names. -` -} diff --git a/src/core/prompts/tools/use-mcp-tool.ts b/src/core/prompts/tools/use-mcp-tool.ts deleted file mode 100644 index ac9ef5b075d..00000000000 --- a/src/core/prompts/tools/use-mcp-tool.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { ToolArgs } from "./types" - -export function getUseMcpToolDescription(args: ToolArgs): string | undefined { - if (!args.mcpHub) { - return undefined - } - return `## use_mcp_tool -Description: Request to use a tool provided by a connected MCP server. Each MCP server can provide multiple tools with different capabilities. Tools have defined input schemas that specify required and optional parameters. -Parameters: -- server_name: (required) The name of the MCP server providing the tool -- tool_name: (required) The name of the tool to execute -- arguments: (required) A JSON object containing the tool's input parameters, following the tool's input schema -Usage: - -server name here -tool name here - -{ - "param1": "value1", - "param2": "value2" -} - - - -Example: Requesting to use an MCP tool - - -weather-server -get_forecast - -{ - "city": "San Francisco", - "days": 5 -} - -` -} diff --git a/src/core/prompts/tools/write-to-file.ts b/src/core/prompts/tools/write-to-file.ts deleted file mode 100644 index 49ca1169f12..00000000000 --- a/src/core/prompts/tools/write-to-file.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { ToolArgs } from "./types" - -export function getWriteToFileDescription(args: ToolArgs): string { - return `## write_to_file -Description: Request to write content to a file. This tool is primarily used for **creating new files** or for scenarios where a **complete rewrite of an existing file is intentionally required**. If the file exists, it will be overwritten. If it doesn't exist, it will be created. This tool will automatically create any directories needed to write the file. - -**Important:** You should prefer using other editing tools over write_to_file when making changes to existing files, since write_to_file is slower and cannot handle large files. Use write_to_file primarily for new file creation. - -When using this tool, use it directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code. - -When creating a new project, organize all new files within a dedicated project directory unless the user specifies otherwise. Structure the project logically, adhering to best practices for the specific type of project being created. - -Parameters: -- path: (required) The path of the file to write to (relative to the current workspace directory ${args.cwd}) -- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include line numbers in the content. - -Usage: - -File path here - -Your file content here - - - -Example: Writing a configuration file - -frontend-config.json - -{ - "apiEndpoint": "https://api.example.com", - "theme": { - "primaryColor": "#007bff", - "secondaryColor": "#6c757d", - "fontFamily": "Arial, sans-serif" - }, - "features": { - "darkMode": true, - "notifications": true, - "analytics": false - }, - "version": "1.0.0" -} - -` -} diff --git a/src/core/prompts/types.ts b/src/core/prompts/types.ts index 0e27910c015..d438735f279 100644 --- a/src/core/prompts/types.ts +++ b/src/core/prompts/types.ts @@ -1,5 +1,3 @@ -import { ToolProtocol } from "@roo-code/types" - /** * Settings passed to system prompt generation functions */ @@ -11,7 +9,6 @@ export interface SystemPromptSettings { /** When true, recursively discover and load .roo/rules from subdirectories */ enableSubfolderRules?: boolean newTaskRequireTodos: boolean - toolProtocol?: ToolProtocol /** When true, model should hide vendor/company identity in responses */ isStealthModel?: boolean } diff --git a/src/core/task-persistence/taskMetadata.ts b/src/core/task-persistence/taskMetadata.ts index cf8d9adb529..81ac92c9354 100644 --- a/src/core/task-persistence/taskMetadata.ts +++ b/src/core/task-persistence/taskMetadata.ts @@ -1,7 +1,7 @@ import NodeCache from "node-cache" import getFolderSize from "get-folder-size" -import type { ClineMessage, HistoryItem, ToolProtocol } from "@roo-code/types" +import type { ClineMessage, HistoryItem } from "@roo-code/types" import { combineApiRequests } from "../../shared/combineApiRequests" import { combineCommandSequences } from "../../shared/combineCommandSequences" @@ -25,11 +25,6 @@ export type TaskMetadataOptions = { apiConfigName?: string /** Initial status for the task (e.g., "active" for child tasks) */ initialStatus?: "active" | "delegated" | "completed" - /** - * The tool protocol locked to this task. Once set, the task will - * continue using this protocol even if user settings change. - */ - toolProtocol?: ToolProtocol } export async function taskMetadata({ @@ -43,7 +38,6 @@ export async function taskMetadata({ mode, apiConfigName, initialStatus, - toolProtocol, }: TaskMetadataOptions) { const taskDir = await getTaskDirectoryPath(globalStoragePath, id) @@ -99,8 +93,7 @@ export async function taskMetadata({ // initialStatus is included when provided (e.g., "active" for child tasks) // to ensure the status is set from the very first save, avoiding race conditions // where attempt_completion might run before a separate status update. - // toolProtocol is persisted to ensure tasks resume with the correct protocol - // even if user settings have changed. + // Tool calling is native-only. const historyItem: HistoryItem = { id, rootTaskId, @@ -118,7 +111,6 @@ export async function taskMetadata({ size: taskDirSize, workspace, mode, - ...(toolProtocol && { toolProtocol }), ...(typeof apiConfigName === "string" && apiConfigName.length > 0 ? { apiConfigName } : {}), ...(initialStatus && { status: initialStatus }), } diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 2ad2ca10b3c..d7971c9cf50 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -33,7 +33,6 @@ import { type HistoryItem, type CreateTaskOptions, type ModelInfo, - type ToolProtocol, type ClineApiReqCancelReason, type ClineApiReqInfo, RooCodeEventName, @@ -45,20 +44,17 @@ import { isIdleAsk, isInteractiveAsk, isResumableAsk, - isNativeProtocol, QueuedMessage, DEFAULT_CONSECUTIVE_MISTAKE_LIMIT, DEFAULT_CHECKPOINT_TIMEOUT_SECONDS, MAX_CHECKPOINT_TIMEOUT_SECONDS, MIN_CHECKPOINT_TIMEOUT_SECONDS, - TOOL_PROTOCOL, ConsecutiveMistakeError, MAX_MCP_TOOLS_THRESHOLD, countEnabledMcpTools, } from "@roo-code/types" import { TelemetryService } from "@roo-code/telemetry" import { CloudService, BridgeOrchestrator } from "@roo-code/cloud" -import { resolveToolProtocol, detectToolProtocolFromHistory } from "../../utils/resolveToolProtocol" // api import { ApiHandler, ApiHandlerCreateMessageMetadata, buildApiHandler } from "../../api" @@ -107,7 +103,6 @@ import { FileContextTracker } from "../context-tracking/FileContextTracker" import { RooIgnoreController } from "../ignore/RooIgnoreController" import { RooProtectedController } from "../protect/RooProtectedController" import { type AssistantMessageContent, presentAssistantMessage } from "../assistant-message" -import { AssistantMessageParser } from "../assistant-message/AssistantMessageParser" import { NativeToolCallParser } from "../assistant-message/NativeToolCallParser" import { manageContext, willManageContext } from "../context-management" import { ClineProvider } from "../webview/ClineProvider" @@ -210,29 +205,7 @@ export class Task extends EventEmitter implements TaskLike { */ private _taskMode: string | undefined - /** - * The tool protocol locked to this task. Once set, the task will continue - * using this protocol even if user settings change. - * - * ## Why This Matters - * When NTC (Native Tool Calling) is enabled, XML parsing does NOT occur. - * If a task previously used XML tools, resuming it with NTC enabled would - * break because the tool calls in the history would not be parseable. - * - * ## Lifecycle - * - * ### For new tasks: - * 1. Set immediately in constructor via `resolveToolProtocol()` - * 2. Locked for the lifetime of the task - * - * ### For history items: - * 1. If `historyItem.toolProtocol` exists, use it - * 2. Otherwise, detect from API history via `detectToolProtocolFromHistory()` - * 3. If no tools in history, use `resolveToolProtocol()` from current settings - * - * @private - */ - private _taskToolProtocol: ToolProtocol | undefined + // Tool calling is native-only. /** * Promise that resolves when the task mode has been initialized. @@ -389,7 +362,7 @@ export class Task extends EventEmitter implements TaskLike { /** * Push a tool_result block to userMessageContent, preventing duplicates. - * This is critical for native tool protocol where duplicate tool_use_ids cause API errors. + * Duplicate tool_use_ids cause API errors. * * @param toolResult - The tool_result block to add * @returns true if added, false if duplicate was skipped @@ -412,7 +385,8 @@ export class Task extends EventEmitter implements TaskLike { didAlreadyUseTool = false didToolFailInCurrentTurn = false didCompleteReadingStream = false - assistantMessageParser?: AssistantMessageParser + // Tool calling is native-only; no streaming parser is required. + assistantMessageParser?: undefined private providerProfileChangeListener?: (config: { name: string; provider?: string }) => void // Native tool call streaming state (track which index each tool is at) @@ -560,10 +534,6 @@ export class Task extends EventEmitter implements TaskLike { this.taskModeReady = Promise.resolve() this.taskApiConfigReady = Promise.resolve() TelemetryService.instance.captureTaskRestarted(this.taskId) - - // For history items, use the persisted tool protocol if available. - // If not available (old tasks), it will be detected in resumeTaskFromHistory. - this._taskToolProtocol = historyItem.toolProtocol } else { // For new tasks, don't set the mode/apiConfigName yet - wait for async initialization. this._taskMode = undefined @@ -571,20 +541,9 @@ export class Task extends EventEmitter implements TaskLike { this.taskModeReady = this.initializeTaskMode(provider) this.taskApiConfigReady = this.initializeTaskApiConfigName(provider) TelemetryService.instance.captureTaskCreated(this.taskId) - - // For new tasks, resolve and lock the tool protocol immediately. - // This ensures the task will continue using this protocol even if - // user settings change. - const modelInfo = this.api.getModel().info - this._taskToolProtocol = resolveToolProtocol(this.apiConfiguration, modelInfo) } - // Initialize the assistant message parser based on the locked tool protocol. - // For native protocol, tool calls come as tool_call chunks, not XML. - // For history items without a persisted protocol, we default to XML parser - // and will update it in resumeTaskFromHistory after detection. - const effectiveProtocol = this._taskToolProtocol || "xml" - this.assistantMessageParser = effectiveProtocol !== "native" ? new AssistantMessageParser() : undefined + this.assistantMessageParser = undefined this.messageQueueService = new MessageQueueService() @@ -734,8 +693,7 @@ export class Task extends EventEmitter implements TaskLike { } /** - * Sets up a listener for provider profile changes to automatically update the parser state. - * This ensures the XML/native protocol parser stays synchronized with the current model. + * Sets up a listener for provider profile changes. * * @private * @param provider - The ClineProvider instance to listen to @@ -1080,7 +1038,7 @@ export class Task extends EventEmitter implements TaskLike { /** * Flush any pending tool results to the API conversation history. * - * This is critical for native tool protocol when the task is about to be + * This is critical when the task is about to be * delegated (e.g., via new_task). Before delegation, if other tools were * called in the same turn before new_task, their tool_result blocks are * accumulated in `userMessageContent` but haven't been saved to the API @@ -1210,7 +1168,6 @@ export class Task extends EventEmitter implements TaskLike { mode: this._taskMode || defaultModeSlug, // Use the task's own mode, not the current provider mode. apiConfigName: this._taskApiConfigName, // Use the task's own provider profile, not the current provider profile. initialStatus: this.initialStatus, - toolProtocol: this._taskToolProtocol, // Persist the locked tool protocol. }) // Emit token/tool usage updates using debounced function @@ -1541,9 +1498,9 @@ export class Task extends EventEmitter implements TaskLike { } /** - * Updates the API configuration but preserves the locked tool protocol. - * The task's tool protocol is locked at creation time and should NOT change - * even when switching between models/profiles with different settings. + * Updates the API configuration and rebuilds the API handler. + * Tool calling is native-only, so there is no tool-protocol switching or + * tool parser swapping here. * * @param newApiConfiguration - The new API configuration to use */ @@ -1551,11 +1508,6 @@ export class Task extends EventEmitter implements TaskLike { // Update the configuration and rebuild the API handler this.apiConfiguration = newApiConfiguration this.api = buildApiHandler(this.apiConfiguration) - - // IMPORTANT: Do NOT change the parser based on the new configuration! - // The task's tool protocol is locked at creation time and must remain - // consistent throughout the task's lifetime to ensure history can be - // properly resumed. } public async submitUserMessage( @@ -1641,9 +1593,8 @@ export class Task extends EventEmitter implements TaskLike { const { contextTokens: prevContextTokens } = this.getTokenUsage() - // Determine if we're using native tool protocol for proper message handling - // Use the task's locked protocol, NOT the current settings (fallback to xml if not set) - const useNativeTools = isNativeProtocol(this._taskToolProtocol ?? "xml") + // Tool calling is native-only; pass through so summarization preserves tool_use/tool_result integrity. + const useNativeTools = true const { messages, @@ -1661,7 +1612,7 @@ export class Task extends EventEmitter implements TaskLike { false, // manual trigger customCondensingPrompt, // User's custom prompt condensingApiHandler, // Specific handler for condensing - useNativeTools, // Pass native tools flag for proper message handling + useNativeTools, ) if (error) { this.say( @@ -1825,10 +1776,7 @@ export class Task extends EventEmitter implements TaskLike { relPath ? ` for '${relPath.toPosix()}'` : "" } without value for required parameter '${paramName}'. Retrying...`, ) - // Use the task's locked protocol, NOT the current settings (fallback to xml if not set) - return formatResponse.toolError( - formatResponse.missingToolParameterError(paramName, this._taskToolProtocol ?? "xml"), - ) + return formatResponse.toolError(formatResponse.missingToolParameterError(paramName)) } // Lifecycle @@ -1993,30 +1941,7 @@ export class Task extends EventEmitter implements TaskLike { // the task first. this.apiConversationHistory = await this.getSavedApiConversationHistory() - // If we don't have a persisted tool protocol (old tasks before this feature), - // detect it from the API history. This ensures tasks that previously used - // XML tools will continue using XML even if NTC is now enabled. - if (!this._taskToolProtocol) { - const detectedProtocol = detectToolProtocolFromHistory(this.apiConversationHistory) - if (detectedProtocol) { - // Found tool calls in history - lock to that protocol - this._taskToolProtocol = detectedProtocol - } else { - // No tool calls in history yet - use current settings - const modelInfo = this.api.getModel().info - this._taskToolProtocol = resolveToolProtocol(this.apiConfiguration, modelInfo) - } - - // Update parser state to match the detected/resolved protocol - const shouldUseXmlParser = this._taskToolProtocol === "xml" - if (shouldUseXmlParser && !this.assistantMessageParser) { - this.assistantMessageParser = new AssistantMessageParser() - } else if (!shouldUseXmlParser && this.assistantMessageParser) { - this.assistantMessageParser.reset() - this.assistantMessageParser = undefined - } - } else { - } + // Tool calling is native-only. const lastClineMessage = this.clineMessages .slice() @@ -2047,50 +1972,7 @@ export class Task extends EventEmitter implements TaskLike { // even if it goes out of sync with cline messages. let existingApiConversationHistory: ApiMessage[] = await this.getSavedApiConversationHistory() - // v2.0 xml tags refactor caveat: since we don't use tools anymore for XML protocol, - // we need to replace all tool use blocks with a text block since the API disallows - // conversations with tool uses and no tool schema. - // For native protocol, we preserve tool_use and tool_result blocks as they're expected by the API. - // IMPORTANT: Use the task's locked protocol, NOT the current settings! - const useNative = isNativeProtocol(this._taskToolProtocol) - - // Only convert tool blocks to text for XML protocol - // For native protocol, the API expects proper tool_use/tool_result structure - if (!useNative) { - const conversationWithoutToolBlocks = existingApiConversationHistory.map((message) => { - if (Array.isArray(message.content)) { - const newContent = message.content.map((block) => { - if (block.type === "tool_use") { - // Format tool invocation based on the task's locked protocol - const params = block.input as Record - const formattedText = formatToolInvocation(block.name, params, this._taskToolProtocol) - - return { - type: "text", - text: formattedText, - } as Anthropic.Messages.TextBlockParam - } else if (block.type === "tool_result") { - // Convert block.content to text block array, removing images - const contentAsTextBlocks = Array.isArray(block.content) - ? block.content.filter((item) => item.type === "text") - : [{ type: "text", text: block.content }] - const textContent = contentAsTextBlocks.map((item) => item.text).join("\n\n") - const toolName = findToolName(block.tool_use_id, existingApiConversationHistory) - return { - type: "text", - text: `[${toolName} Result]\n\n${textContent}`, - } as Anthropic.Messages.TextBlockParam - } - return block - }) - return { ...message, content: newContent } - } - return message - }) - existingApiConversationHistory = conversationWithoutToolBlocks - } - - // FIXME: remove tool use blocks altogether + // Tool blocks are always preserved; native tool calling only. // if the last message is an assistant message, we need to check if there's tool use since every tool use has to have a tool response // if there's no tool use and only a text block, then we can just add a user message @@ -2509,8 +2391,7 @@ export class Task extends EventEmitter implements TaskLike { // the user hits max requests and denies resetting the count. break } else { - // Use the task's locked protocol, NOT the current settings (fallback to xml if not set) - nextUserContent = [{ type: "text", text: formatResponse.noToolsUsed(this._taskToolProtocol ?? "xml") }] + nextUserContent = [{ type: "text", text: formatResponse.noToolsUsed() }] } } } @@ -2634,7 +2515,7 @@ export class Task extends EventEmitter implements TaskLike { const environmentDetails = await getEnvironmentDetails(this, currentIncludeFileDetails) // Remove any existing environment_details blocks before adding fresh ones. - // This prevents duplicate environment details when resuming tasks with XML tool calls, + // This prevents duplicate environment details when resuming tasks, // where the old user message content may already contain environment details from the previous session. // We check for both opening and closing tags to ensure we're matching complete environment detail blocks, // not just mentions of the tag in regular content. @@ -2773,7 +2654,7 @@ export class Task extends EventEmitter implements TaskLike { this.didToolFailInCurrentTurn = false this.presentAssistantMessageLocked = false this.presentAssistantMessageHasPendingUpdates = false - this.assistantMessageParser?.reset() + // No legacy text-stream tool parser. this.streamingToolCallIndices.clear() // Clear any leftover streaming tool call state from previous interrupted streams NativeToolCallParser.clearAllStreamingToolCalls() @@ -2786,15 +2667,6 @@ export class Task extends EventEmitter implements TaskLike { this.cachedStreamingModel = this.api.getModel() const streamModelInfo = this.cachedStreamingModel.info const cachedModelId = this.cachedStreamingModel.id - // Use the task's locked protocol instead of resolving fresh. - // This ensures task resumption works correctly even if NTC settings changed. - // Fallback to resolving if somehow _taskToolProtocol is not set (should not happen). - const streamProtocol = resolveToolProtocol( - this.apiConfiguration, - streamModelInfo, - this._taskToolProtocol, - ) - const shouldUseXmlParser = streamProtocol === "xml" // Yields only if the first chunk is successful, otherwise will // allow the user to retry the request (most likely due to rate @@ -2973,8 +2845,8 @@ export class Task extends EventEmitter implements TaskLike { presentAssistantMessage(this) } else if (toolUseIndex !== undefined) { // finalizeStreamingToolCall returned null (malformed JSON or missing args) - // We still need to mark the tool as non-partial so it gets executed - // The tool's validation will catch any missing required parameters + // Mark the tool as non-partial so it's presented as complete, but execution + // will be short-circuited in presentAssistantMessage with a structured tool_result. const existingToolUse = this.assistantMessageContent[toolUseIndex] if (existingToolUse && existingToolUse.type === "tool_use") { existingToolUse.partial = false @@ -3028,43 +2900,20 @@ export class Task extends EventEmitter implements TaskLike { case "text": { assistantMessage += chunk.text - // Use the protocol determined at the start of streaming - // Don't rely solely on parser existence - parser might exist from previous state - if (shouldUseXmlParser && this.assistantMessageParser) { - // XML protocol: Parse raw assistant message chunk into content blocks - const prevLength = this.assistantMessageContent.length - this.assistantMessageContent = this.assistantMessageParser.processChunk(chunk.text) - - if (this.assistantMessageContent.length > prevLength) { - // New content we need to present, reset to - // false in case previous content set this to true. - this.userMessageContentReady = false - } - - // Present content to user. - presentAssistantMessage(this) + // Native tool calling: text chunks are plain text. + // Create or update a text content block directly + const lastBlock = this.assistantMessageContent[this.assistantMessageContent.length - 1] + if (lastBlock?.type === "text" && lastBlock.partial) { + lastBlock.content = assistantMessage } else { - // Native protocol: Text chunks are plain text, not XML tool calls - // Create or update a text content block directly - const lastBlock = - this.assistantMessageContent[this.assistantMessageContent.length - 1] - - if (lastBlock?.type === "text" && lastBlock.partial) { - // Update existing partial text block - lastBlock.content = assistantMessage - } else { - // Create new text block - this.assistantMessageContent.push({ - type: "text", - content: assistantMessage, - partial: true, - }) - this.userMessageContentReady = false - } - - // Present content to user - presentAssistantMessage(this) + this.assistantMessageContent.push({ + type: "text", + content: assistantMessage, + partial: true, + }) + this.userMessageContentReady = false } + presentAssistantMessage(this) break } } @@ -3406,19 +3255,11 @@ export class Task extends EventEmitter implements TaskLike { // Can't just do this b/c a tool could be in the middle of executing. // this.assistantMessageContent.forEach((e) => (e.partial = false)) - // Now that the stream is complete, finalize any remaining partial content blocks (XML protocol only) - // Use the protocol determined at the start of streaming - if (shouldUseXmlParser && this.assistantMessageParser) { - this.assistantMessageParser.finalizeContentBlocks() - const parsedBlocks = this.assistantMessageParser.getContentBlocks() - // For XML protocol: Use only parsed blocks (includes both text and tool_use parsed from XML) - this.assistantMessageContent = parsedBlocks - } + // No legacy streaming parser to finalize. - // Present any partial blocks that were just completed - // For XML protocol: includes both text and tool_use blocks parsed from the text stream - // For native protocol: tool_use blocks were already presented during streaming via - // tool_call_partial events, but we still need to present them if they exist (e.g., malformed) + // Present any partial blocks that were just completed. + // Tool calls are typically presented during streaming via tool_call_partial events, + // but we still present here if any partial blocks remain (e.g., malformed streams). if (partialBlocks.length > 0) { // If there is content to update then it will complete and // update `this.userMessageContentReady` to true, which we @@ -3448,8 +3289,7 @@ export class Task extends EventEmitter implements TaskLike { await this.saveClineMessages() await this.providerRef.deref()?.postStateToWebview() - // Reset parser after each complete conversation round (XML protocol only) - this.assistantMessageParser?.reset() + // No legacy text-stream tool parser state to reset. // Now add to apiConversationHistory. // Need to save assistant responses to file before proceeding to @@ -3596,7 +3436,7 @@ export class Task extends EventEmitter implements TaskLike { // Use the task's locked protocol for consistent behavior this.userMessageContent.push({ type: "text", - text: formatResponse.noToolsUsed(this._taskToolProtocol ?? "xml"), + text: formatResponse.noToolsUsed(), }) } else { // Reset counter when tools are used successfully @@ -3630,13 +3470,12 @@ export class Task extends EventEmitter implements TaskLike { await this.say("error", "MODEL_NO_ASSISTANT_MESSAGES") } - // IMPORTANT: For native tool protocol, we already added the user message to + // IMPORTANT: We already added the user message to // apiConversationHistory at line 1876. Since the assistant failed to respond, // we need to remove that message before retrying to avoid having two consecutive // user messages (which would cause tool_result validation errors). let state = await this.providerRef.deref()?.getState() - // Use the task's locked protocol, NOT current settings - if (isNativeProtocol(this._taskToolProtocol ?? "xml") && this.apiConversationHistory.length > 0) { + if (this.apiConversationHistory.length > 0) { const lastMessage = this.apiConversationHistory[this.apiConversationHistory.length - 1] if (lastMessage.role === "user") { // Remove the last user message that we added earlier @@ -3695,14 +3534,11 @@ export class Task extends EventEmitter implements TaskLike { continue } else { // User declined to retry - // For native protocol, re-add the user message we removed - // Use the task's locked protocol, NOT current settings - if (isNativeProtocol(this._taskToolProtocol ?? "xml")) { - await this.addToApiConversationHistory({ - role: "user", - content: currentUserContent, - }) - } + // Re-add the user message we removed. + await this.addToApiConversationHistory({ + role: "user", + content: currentUserContent, + }) await this.say( "error", @@ -3795,15 +3631,6 @@ export class Task extends EventEmitter implements TaskLike { const canUseBrowserTool = modelSupportsBrowser && modeSupportsBrowser && (browserToolEnabled ?? true) - // Use the task's locked protocol for system prompt consistency. - // This ensures the system prompt matches the protocol the task was started with, - // even if user settings have changed since then. - const toolProtocol = resolveToolProtocol( - apiConfiguration ?? this.apiConfiguration, - modelInfo, - this._taskToolProtocol, - ) - return SYSTEM_PROMPT( provider.context, this.cwd, @@ -3831,7 +3658,6 @@ export class Task extends EventEmitter implements TaskLike { newTaskRequireTodos: vscode.workspace .getConfiguration(Package.name) .get("newTaskRequireTodos", false), - toolProtocol, isStealthModel: modelInfo?.isStealthModel, }, undefined, // todoList @@ -3873,9 +3699,8 @@ export class Task extends EventEmitter implements TaskLike { `Forcing truncation to ${FORCED_CONTEXT_REDUCTION_PERCENT}% of current context.`, ) - // Determine if we're using native tool protocol for proper message handling - // Use the task's locked protocol, NOT the current settings - const useNativeTools = isNativeProtocol(this._taskToolProtocol ?? "xml") + // Tool calling is native-only. + const useNativeTools = true // Send condenseTaskContextStarted to show in-progress indicator await this.providerRef.deref()?.postMessageToWebview({ type: "condenseTaskContextStarted", text: this.taskId }) @@ -4042,9 +3867,8 @@ export class Task extends EventEmitter implements TaskLike { // Get the current profile ID using the helper method const currentProfileId = this.getCurrentProfileId(state) - // Determine if we're using native tool protocol for proper message handling - // Use the task's locked protocol, NOT the current settings - const useNativeTools = isNativeProtocol(this._taskToolProtocol ?? "xml") + // Tool calling is native-only. + const useNativeTools = true // Check if context management will likely run (threshold check) // This allows us to show an in-progress indicator to the user @@ -4168,14 +3992,9 @@ export class Task extends EventEmitter implements TaskLike { throw new Error("Auto-approval limit reached and user did not approve continuation") } - // Determine if we should include native tools based on: - // 1. Task's locked tool protocol is set to NATIVE - // 2. Model supports native tools - // CRITICAL: Use the task's locked protocol to ensure tasks that started with XML - // tools continue using XML even if NTC settings have since changed. + // Tool calling is native-only. + // Whether we include tools is determined by whether we have any tools to send. const modelInfo = this.api.getModel().info - const taskProtocol = this._taskToolProtocol ?? "xml" - const shouldIncludeTools = taskProtocol === TOOL_PROTOCOL.NATIVE && (modelInfo.supportsNativeTools ?? false) // Build complete tools array: native tools + dynamic MCP tools // When includeAllToolsWithRestrictions is true, returns all tools but provides @@ -4191,7 +4010,7 @@ export class Task extends EventEmitter implements TaskLike { // so they continue to receive only the filtered tools for the current mode. const supportsAllowedFunctionNames = apiConfiguration?.apiProvider === "gemini" - if (shouldIncludeTools) { + { const provider = this.providerRef.deref() if (!provider) { throw new Error("Provider reference lost during tool building") @@ -4215,6 +4034,8 @@ export class Task extends EventEmitter implements TaskLike { allowedFunctionNames = toolsResult.allowedFunctionNames } + const shouldIncludeTools = allTools.length > 0 + // Parallel tool calls are disabled - feature is on hold // Previously resolved from experiments.isEnabled(..., EXPERIMENT_IDS.MULTIPLE_NATIVE_TOOL_CALLS) const parallelToolCallsEnabled = false @@ -4223,12 +4044,11 @@ export class Task extends EventEmitter implements TaskLike { mode: mode, taskId: this.taskId, suppressPreviousResponseId: this.skipPrevResponseIdOnce, - // Include tools and tool protocol when using native protocol and model supports it + // Include tools whenever they are present. ...(shouldIncludeTools ? { tools: allTools, tool_choice: "auto", - toolProtocol: taskProtocol, parallelToolCalls: parallelToolCallsEnabled, // When mode restricts tools, provide allowedFunctionNames so providers // like Gemini can see all tools in history but only call allowed ones @@ -4638,15 +4458,7 @@ export class Task extends EventEmitter implements TaskLike { return this.workspacePath } - /** - * Get the tool protocol locked to this task. - * Returns undefined only if the task hasn't been fully initialized yet. - * - * @see {@link _taskToolProtocol} for lifecycle details - */ - public get taskToolProtocol() { - return this._taskToolProtocol - } + // Tool protocol removed (native-only). /** * Provides convenient access to high-level message operations. diff --git a/src/core/task/__tests__/native-tools-filtering.spec.ts b/src/core/task/__tests__/native-tools-filtering.spec.ts index 761fe6e1ec2..c9cd6a30604 100644 --- a/src/core/task/__tests__/native-tools-filtering.spec.ts +++ b/src/core/task/__tests__/native-tools-filtering.spec.ts @@ -3,9 +3,8 @@ import type { ModeConfig } from "@roo-code/types" describe("Native Tools Filtering by Mode", () => { describe("attemptApiRequest native tool filtering", () => { it("should filter native tools based on mode restrictions", async () => { - // This test verifies that when using native protocol, tools are filtered - // by mode restrictions before being sent to the API, similar to how - // XML tools are filtered in the system prompt. + // This test verifies that native tools are filtered by mode restrictions + // before being sent to the API. const architectMode: ModeConfig = { slug: "architect", diff --git a/src/core/task/__tests__/task-tool-history.spec.ts b/src/core/task/__tests__/task-tool-history.spec.ts index fc7f2fd1311..87dc282a798 100644 --- a/src/core/task/__tests__/task-tool-history.spec.ts +++ b/src/core/task/__tests__/task-tool-history.spec.ts @@ -1,7 +1,5 @@ import { describe, it, expect, beforeEach, vi } from "vitest" import { Anthropic } from "@anthropic-ai/sdk" -import { TOOL_PROTOCOL } from "@roo-code/types" -import { resolveToolProtocol } from "../../../utils/resolveToolProtocol" describe("Task Tool History Handling", () => { describe("resumeTaskFromHistory tool block preservation", () => { @@ -42,20 +40,7 @@ describe("Task Tool History Handling", () => { }, ] - // Simulate the protocol check - const mockApiConfiguration = { apiProvider: "roo" as const } - const mockModelInfo = { supportsNativeTools: true } - const mockExperiments = {} - - const protocol = TOOL_PROTOCOL.NATIVE - - // Test the logic that should NOT convert tool blocks for native protocol - const useNative = protocol === TOOL_PROTOCOL.NATIVE - - if (!useNative) { - // This block should NOT execute for native protocol - throw new Error("Should not convert tool blocks for native protocol") - } + // Tool calling is native-only; tool blocks must be preserved. // Verify tool blocks are preserved const assistantMessage = apiHistory[1] @@ -80,51 +65,6 @@ describe("Task Tool History Handling", () => { ]), ) }) - - it("should convert tool blocks to text for XML protocol", () => { - // Mock API conversation history with tool blocks - const apiHistory: any[] = [ - { - role: "assistant", - content: [ - { - type: "tool_use", - id: "toolu_123", - name: "read_file", - input: { path: "config.json" }, - }, - ], - ts: Date.now(), - }, - ] - - // Simulate XML protocol - tool blocks should be converted to text - const protocol = "xml" - const useNative = false // XML protocol is not native - - // For XML protocol, we should convert tool blocks - if (!useNative) { - const conversationWithoutToolBlocks = apiHistory.map((message) => { - if (Array.isArray(message.content)) { - const newContent = message.content.map((block: any) => { - if (block.type === "tool_use") { - return { - type: "text", - text: `\n\nconfig.json\n\n`, - } - } - return block - }) - return { ...message, content: newContent } - } - return message - }) - - // Verify tool blocks were converted to text - expect(conversationWithoutToolBlocks[0].content[0].type).toBe("text") - expect(conversationWithoutToolBlocks[0].content[0].text).toContain("") - } - }) }) describe("convertToOpenAiMessages format", () => { diff --git a/src/core/task/__tests__/task-xml-protocol-regression.spec.ts b/src/core/task/__tests__/task-xml-protocol-regression.spec.ts deleted file mode 100644 index fe39dab1c76..00000000000 --- a/src/core/task/__tests__/task-xml-protocol-regression.spec.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { describe, it, expect } from "vitest" -import { formatToolInvocation } from "../../tools/helpers/toolResultFormatting" - -/** - * Regression tests to ensure XML protocol behavior remains unchanged - * after adding native protocol support. - */ -describe("XML Protocol Regression Tests", () => { - it("should format tool invocations as XML tags for xml protocol", () => { - const result = formatToolInvocation( - "read_file", - { path: "config.json", start_line: "1", end_line: "10" }, - "xml", - ) - - expect(result).toContain("") - expect(result).toContain("") - expect(result).toContain("config.json") - expect(result).toContain("") - expect(result).toContain("") - expect(result).toContain("1") - expect(result).toContain("") - expect(result).toContain("") - }) - - it("should handle complex nested structures in XML format", () => { - const result = formatToolInvocation( - "execute_command", - { - command: "npm install", - cwd: "/home/user/project", - }, - "xml", - ) - - expect(result).toContain("") - expect(result).toContain("") - expect(result).toContain("npm install") - expect(result).toContain("") - expect(result).toContain("") - expect(result).toContain("/home/user/project") - expect(result).toContain("") - expect(result).toContain("") - }) - - it("should handle empty parameters correctly in XML format", () => { - const result = formatToolInvocation("list_files", {}, "xml") - - expect(result).toBe("\n\n") - }) - - it("should preserve XML format for tool results in conversation history", () => { - // Simulate what happens in resumeTaskFromHistory for XML protocol - const useNative = false // XML protocol - - const mockToolUse = { - type: "tool_use", - id: "toolu_123", - name: "read_file", - input: { path: "test.ts" }, - } - - if (!useNative) { - // This is the conversion logic that should happen for XML - const converted = { - type: "text", - text: formatToolInvocation(mockToolUse.name, mockToolUse.input as Record, "xml"), - } - - expect(converted.type).toBe("text") - expect(converted.text).toContain("") - expect(converted.text).toContain("") - expect(converted.text).toContain("test.ts") - } else { - throw new Error("Should not reach here for XML protocol") - } - }) -}) diff --git a/src/core/tools/ApplyDiffTool.ts b/src/core/tools/ApplyDiffTool.ts index c8024c7500b..5ca7002ff2d 100644 --- a/src/core/tools/ApplyDiffTool.ts +++ b/src/core/tools/ApplyDiffTool.ts @@ -24,15 +24,8 @@ interface ApplyDiffParams { export class ApplyDiffTool extends BaseTool<"apply_diff"> { readonly name = "apply_diff" as const - parseLegacy(params: Partial>): ApplyDiffParams { - return { - path: params.path || "", - diff: params.diff || "", - } - } - async execute(params: ApplyDiffParams, task: Task, callbacks: ToolCallbacks): Promise { - const { askApproval, handleError, pushToolResult, toolProtocol } = callbacks + const { askApproval, handleError, pushToolResult } = callbacks let { path: relPath, diff: diffContent } = params if (diffContent && !task.api.getModel().id.includes("claude")) { @@ -58,7 +51,7 @@ export class ApplyDiffTool extends BaseTool<"apply_diff"> { if (!accessAllowed) { await task.say("rooignore_error", relPath) - pushToolResult(formatResponse.rooIgnoreError(relPath, toolProtocol)) + pushToolResult(formatResponse.rooIgnoreError(relPath)) return } @@ -239,7 +232,7 @@ export class ApplyDiffTool extends BaseTool<"apply_diff"> { let partFailHint = "" if (diffResult.failParts && diffResult.failParts.length > 0) { - partFailHint = `But unable to apply all diff parts to file: ${absolutePath}, silently use tool to check newest file version and re-apply diffs\n` + partFailHint = `But unable to apply all diff parts to file: ${absolutePath}. Use the read_file tool to check the newest file version and re-apply diffs.\n` } // Get the formatted response message diff --git a/src/core/tools/ApplyPatchTool.ts b/src/core/tools/ApplyPatchTool.ts index bf4cdaa1b8c..0c3a1765f22 100644 --- a/src/core/tools/ApplyPatchTool.ts +++ b/src/core/tools/ApplyPatchTool.ts @@ -23,15 +23,9 @@ interface ApplyPatchParams { export class ApplyPatchTool extends BaseTool<"apply_patch"> { readonly name = "apply_patch" as const - parseLegacy(params: Partial>): ApplyPatchParams { - return { - patch: params.patch || "", - } - } - async execute(params: ApplyPatchParams, task: Task, callbacks: ToolCallbacks): Promise { const { patch } = params - const { askApproval, handleError, pushToolResult, toolProtocol } = callbacks + const { askApproval, handleError, pushToolResult } = callbacks try { // Validate required parameters @@ -88,7 +82,7 @@ export class ApplyPatchTool extends BaseTool<"apply_patch"> { const accessAllowed = task.rooIgnoreController?.validateAccess(relPath) if (!accessAllowed) { await task.say("rooignore_error", relPath) - pushToolResult(formatResponse.rooIgnoreError(relPath, toolProtocol)) + pushToolResult(formatResponse.rooIgnoreError(relPath)) return } diff --git a/src/core/tools/AskFollowupQuestionTool.ts b/src/core/tools/AskFollowupQuestionTool.ts index 69146a4c2e4..010a6240f1e 100644 --- a/src/core/tools/AskFollowupQuestionTool.ts +++ b/src/core/tools/AskFollowupQuestionTool.ts @@ -1,6 +1,5 @@ import { Task } from "../task/Task" import { formatResponse } from "../prompts/responses" -import { parseXml } from "../../utils/xml" import type { ToolUse } from "../../shared/tools" import { BaseTool, ToolCallbacks } from "./BaseTool" @@ -18,55 +17,9 @@ interface AskFollowupQuestionParams { export class AskFollowupQuestionTool extends BaseTool<"ask_followup_question"> { readonly name = "ask_followup_question" as const - parseLegacy(params: Partial>): AskFollowupQuestionParams { - const question = params.question || "" - const follow_up_xml = params.follow_up - - const suggestions: Suggestion[] = [] - - if (follow_up_xml) { - // Define the actual structure returned by the XML parser - type ParsedSuggestion = string | { "#text": string; "@_mode"?: string } - - try { - const parsedSuggest = parseXml(follow_up_xml, ["suggest"]) as { - suggest: ParsedSuggestion[] | ParsedSuggestion - } - - const rawSuggestions = Array.isArray(parsedSuggest?.suggest) - ? parsedSuggest.suggest - : [parsedSuggest?.suggest].filter((sug): sug is ParsedSuggestion => sug !== undefined) - - // Transform parsed XML to our Suggest format - for (const sug of rawSuggestions) { - if (typeof sug === "string") { - // Simple string suggestion (no mode attribute) - suggestions.push({ text: sug }) - } else { - // XML object with text content and optional mode attribute - const suggestion: Suggestion = { text: sug["#text"] } - if (sug["@_mode"]) { - suggestion.mode = sug["@_mode"] - } - suggestions.push(suggestion) - } - } - } catch (error) { - throw new Error( - `Failed to parse follow_up XML: ${error instanceof Error ? error.message : String(error)}`, - ) - } - } - - return { - question, - follow_up: suggestions, - } - } - async execute(params: AskFollowupQuestionParams, task: Task, callbacks: ToolCallbacks): Promise { const { question, follow_up } = params - const { handleError, pushToolResult, toolProtocol } = callbacks + const { handleError, pushToolResult } = callbacks try { if (!question) { @@ -93,14 +46,11 @@ export class AskFollowupQuestionTool extends BaseTool<"ask_followup_question"> { } override async handlePartial(task: Task, block: ToolUse<"ask_followup_question">): Promise { - // Get question from params (for XML protocol) or nativeArgs (for native protocol) - const question: string | undefined = block.params.question ?? block.nativeArgs?.question + const question: string | undefined = block.nativeArgs?.question ?? block.params.question // During partial streaming, only show the question to avoid displaying raw JSON // The full JSON with suggestions will be sent when the tool call is complete (!block.partial) - await task - .ask("followup", this.removeClosingTag("question", question, block.partial), block.partial) - .catch(() => {}) + await task.ask("followup", question ?? "", block.partial).catch(() => {}) } } diff --git a/src/core/tools/AttemptCompletionTool.ts b/src/core/tools/AttemptCompletionTool.ts index 7e8e7816280..a406a15c8b4 100644 --- a/src/core/tools/AttemptCompletionTool.ts +++ b/src/core/tools/AttemptCompletionTool.ts @@ -36,13 +36,6 @@ interface DelegationProvider { export class AttemptCompletionTool extends BaseTool<"attempt_completion"> { readonly name = "attempt_completion" as const - parseLegacy(params: Partial>): AttemptCompletionParams { - return { - result: params.result || "", - command: params.command, - } - } - async execute(params: AttemptCompletionParams, task: Task, callbacks: AttemptCompletionCallbacks): Promise { const { result } = params const { handleError, pushToolResult, askFinishSubTaskApproval } = callbacks @@ -194,16 +187,9 @@ export class AttemptCompletionTool extends BaseTool<"attempt_completion"> { if (command) { if (lastMessage && lastMessage.ask === "command") { - await task - .ask("command", this.removeClosingTag("command", command, block.partial), block.partial) - .catch(() => {}) + await task.ask("command", command ?? "", block.partial).catch(() => {}) } else { - await task.say( - "completion_result", - this.removeClosingTag("result", result, block.partial), - undefined, - false, - ) + await task.say("completion_result", result ?? "", undefined, false) // Force final token usage update before emitting TaskCompleted for consistency task.emitFinalTokenUsageUpdate() @@ -211,17 +197,10 @@ export class AttemptCompletionTool extends BaseTool<"attempt_completion"> { TelemetryService.instance.captureTaskCompleted(task.taskId) task.emit(RooCodeEventName.TaskCompleted, task.taskId, task.getTokenUsage(), task.toolUsage) - await task - .ask("command", this.removeClosingTag("command", command, block.partial), block.partial) - .catch(() => {}) + await task.ask("command", command ?? "", block.partial).catch(() => {}) } } else { - await task.say( - "completion_result", - this.removeClosingTag("result", result, block.partial), - undefined, - block.partial, - ) + await task.say("completion_result", result ?? "", undefined, block.partial) } } } diff --git a/src/core/tools/BaseTool.ts b/src/core/tools/BaseTool.ts index e18c3593e43..7d574068a97 100644 --- a/src/core/tools/BaseTool.ts +++ b/src/core/tools/BaseTool.ts @@ -1,14 +1,7 @@ -import type { ToolName, ToolProtocol } from "@roo-code/types" +import type { ToolName } from "@roo-code/types" import { Task } from "../task/Task" -import type { - ToolUse, - HandleError, - PushToolResult, - RemoveClosingTag, - AskApproval, - NativeToolArgs, -} from "../../shared/tools" +import type { ToolUse, HandleError, PushToolResult, AskApproval, NativeToolArgs } from "../../shared/tools" /** * Callbacks passed to tool execution @@ -17,8 +10,6 @@ export interface ToolCallbacks { askApproval: AskApproval handleError: HandleError pushToolResult: PushToolResult - removeClosingTag: RemoveClosingTag - toolProtocol: ToolProtocol toolCallId?: string } @@ -31,14 +22,7 @@ type ToolParams = TName extends keyof NativeToolArgs ? N /** * Abstract base class for all tools. * - * Provides a consistent architecture where: - * - XML/legacy protocol: params → parseLegacy() → typed params → execute() - * - Native protocol: nativeArgs already contain typed data → execute() - * - * Each tool extends this class and implements: - * - parseLegacy(): Convert XML/legacy string params to typed params - * - execute(): Protocol-agnostic core logic using typed params - * - handlePartial(): (optional) Handle streaming partial messages + * Tools receive typed arguments from native tool calling via `ToolUse.nativeArgs`. * * @template TName - The specific tool name, which determines native arg types */ @@ -54,24 +38,10 @@ export abstract class BaseTool { */ protected lastSeenPartialPath: string | undefined = undefined - /** - * Parse XML/legacy string-based parameters into typed parameters. - * - * For XML protocol, this converts params.args (XML string) or params.path (legacy) - * into a typed structure that execute() can use. - * - * @param params - Raw ToolUse.params from XML protocol - * @returns Typed parameters for execute() - * @throws Error if parsing fails - */ - abstract parseLegacy(params: Partial>): ToolParams - /** * Execute the tool with typed parameters. * - * This is the protocol-agnostic core logic. It receives typed parameters - * (from parseLegacy for XML, or directly from native protocol) and performs - * the tool's operation. + * Receives typed parameters from native tool calling via `ToolUse.nativeArgs`. * * @param params - Typed parameters * @param task - Task instance with state and API access @@ -93,40 +63,6 @@ export abstract class BaseTool { // Tools can override to show streaming UI updates } - /** - * Remove partial closing XML tags from text during streaming. - * - * This utility helps clean up partial XML tag artifacts that can appear - * at the end of streamed content, preventing them from being displayed to users. - * - * @param tag - The tag name to check for partial closing - * @param text - The text content to clean - * @param isPartial - Whether this is a partial message (if false, returns text as-is) - * @returns Cleaned text with partial closing tags removed - */ - protected removeClosingTag(tag: string, text: string | undefined, isPartial: boolean): string { - if (!isPartial) { - return text || "" - } - - if (!text) { - return "" - } - - // This regex dynamically constructs a pattern to match the closing tag: - // - Optionally matches whitespace before the tag - // - Matches '<' or ' `(?:${char})?`) - .join("")}$`, - "g", - ) - - return text.replace(tagRegex, "") - } - /** * Check if a path parameter has stabilized during streaming. * @@ -167,7 +103,7 @@ export abstract class BaseTool { * * Handles the complete flow: * 1. Partial message handling (if partial) - * 2. Parameter parsing (parseLegacy for XML, or use nativeArgs directly) + * 2. Parameter parsing (nativeArgs only) * 3. Core execution (execute) * * @param task - Task instance @@ -189,22 +125,34 @@ export abstract class BaseTool { return } - // Determine protocol and parse parameters accordingly + // Native-only: obtain typed parameters from `nativeArgs`. let params: ToolParams try { if (block.nativeArgs !== undefined) { - // Native protocol: typed args provided by NativeToolCallParser - // TypeScript knows nativeArgs is properly typed based on TName + // Native: typed args provided by NativeToolCallParser. params = block.nativeArgs as ToolParams } else { - // XML/legacy protocol: parse string params into typed params - params = this.parseLegacy(block.params) + // If legacy/XML markup was provided via params, surface a clear error. + const paramsText = (() => { + try { + return JSON.stringify(block.params ?? {}) + } catch { + return "" + } + })() + if (paramsText.includes("<") && paramsText.includes(">")) { + throw new Error( + "XML tool calls are no longer supported. Use native tool calling (nativeArgs) instead.", + ) + } + throw new Error("Tool call is missing native arguments (nativeArgs).") } } catch (error) { console.error(`Error parsing parameters:`, error) const errorMessage = `Failed to parse ${this.name} parameters: ${error instanceof Error ? error.message : String(error)}` await callbacks.handleError(`parsing ${this.name} args`, new Error(errorMessage)) - callbacks.pushToolResult(`${errorMessage}`) + // Note: handleError already emits a tool_result via formatResponse.toolError in the caller. + // Do NOT call pushToolResult here to avoid duplicate tool_result payloads. return } diff --git a/src/core/tools/BrowserActionTool.ts b/src/core/tools/BrowserActionTool.ts index 39a2bab3d1a..3bd584e0cb4 100644 --- a/src/core/tools/BrowserActionTool.ts +++ b/src/core/tools/BrowserActionTool.ts @@ -3,7 +3,7 @@ import { Anthropic } from "@anthropic-ai/sdk" import { BrowserAction, BrowserActionResult, browserActions, ClineSayBrowserAction } from "@roo-code/types" import { Task } from "../task/Task" -import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" +import { ToolUse, AskApproval, HandleError, PushToolResult } from "../../shared/tools" import { formatResponse } from "../prompts/responses" import { scaleCoordinate } from "../../shared/browserUtils" @@ -14,7 +14,6 @@ export async function browserActionTool( askApproval: AskApproval, handleError: HandleError, pushToolResult: PushToolResult, - removeClosingTag: RemoveClosingTag, ) { const action: BrowserAction | undefined = block.params.action as BrowserAction const url: string | undefined = block.params.url @@ -40,15 +39,15 @@ export async function browserActionTool( try { if (block.partial) { if (action === "launch") { - await cline.ask("browser_action_launch", removeClosingTag("url", url), block.partial).catch(() => {}) + await cline.ask("browser_action_launch", url ?? "", block.partial).catch(() => {}) } else { await cline.say( "browser_action", JSON.stringify({ action: action as BrowserAction, - coordinate: removeClosingTag("coordinate", coordinate), - text: removeClosingTag("text", text), - size: removeClosingTag("size", size), + coordinate: coordinate ?? "", + text: text ?? "", + size: size ?? "", } satisfies ClineSayBrowserAction), undefined, block.partial, diff --git a/src/core/tools/CodebaseSearchTool.ts b/src/core/tools/CodebaseSearchTool.ts index 96b5cb5d088..f0d906fabd8 100644 --- a/src/core/tools/CodebaseSearchTool.ts +++ b/src/core/tools/CodebaseSearchTool.ts @@ -18,22 +18,8 @@ interface CodebaseSearchParams { export class CodebaseSearchTool extends BaseTool<"codebase_search"> { readonly name = "codebase_search" as const - parseLegacy(params: Partial>): CodebaseSearchParams { - let query = params.query - let directoryPrefix = params.path - - if (directoryPrefix) { - directoryPrefix = path.normalize(directoryPrefix) - } - - return { - query: query || "", - path: directoryPrefix, - } - } - async execute(params: CodebaseSearchParams, task: Task, callbacks: ToolCallbacks): Promise { - const { askApproval, handleError, pushToolResult, toolProtocol } = callbacks + const { askApproval, handleError, pushToolResult } = callbacks const { query, path: directoryPrefix } = params const workspacePath = task.cwd && task.cwd.trim() !== "" ? task.cwd : getWorkspacePath() diff --git a/src/core/tools/EditFileTool.ts b/src/core/tools/EditFileTool.ts index f2369c76f35..2495a372bc5 100644 --- a/src/core/tools/EditFileTool.ts +++ b/src/core/tools/EditFileTool.ts @@ -136,17 +136,6 @@ export class EditFileTool extends BaseTool<"edit_file"> { private didSendPartialToolAsk = false private partialToolAskRelPath: string | undefined - parseLegacy(params: Partial>): EditFileParams { - return { - file_path: params.file_path || "", - old_string: params.old_string || "", - new_string: params.new_string || "", - expected_replacements: params.expected_replacements - ? parseInt(params.expected_replacements, 10) - : undefined, - } - } - async execute(params: EditFileParams, task: Task, callbacks: ToolCallbacks): Promise { // Coerce old_string/new_string to handle malformed native tool calls where they could be non-strings. // In native mode, malformed calls can pass numbers/objects; normalize those to "" to avoid later crashes. @@ -154,7 +143,7 @@ export class EditFileTool extends BaseTool<"edit_file"> { const old_string = typeof params.old_string === "string" ? params.old_string : "" const new_string = typeof params.new_string === "string" ? params.new_string : "" const expected_replacements = params.expected_replacements ?? 1 - const { askApproval, handleError, pushToolResult, toolProtocol } = callbacks + const { askApproval, handleError, pushToolResult } = callbacks let relPathForErrorHandling: string | undefined let operationPreviewForErrorHandling: string | undefined @@ -224,7 +213,7 @@ export class EditFileTool extends BaseTool<"edit_file"> { await finalizePartialToolAskIfNeeded(relPath) task.didToolFailInCurrentTurn = true await task.say("rooignore_error", relPath) - pushToolResult(formatResponse.rooIgnoreError(relPath, toolProtocol)) + pushToolResult(formatResponse.rooIgnoreError(relPath)) return } diff --git a/src/core/tools/ExecuteCommandTool.ts b/src/core/tools/ExecuteCommandTool.ts index 52f47433068..d3e2bbce8df 100644 --- a/src/core/tools/ExecuteCommandTool.ts +++ b/src/core/tools/ExecuteCommandTool.ts @@ -29,16 +29,9 @@ interface ExecuteCommandParams { export class ExecuteCommandTool extends BaseTool<"execute_command"> { readonly name = "execute_command" as const - parseLegacy(params: Partial>): ExecuteCommandParams { - return { - command: params.command || "", - cwd: params.cwd, - } - } - async execute(params: ExecuteCommandParams, task: Task, callbacks: ToolCallbacks): Promise { const { command, cwd: customCwd } = params - const { handleError, pushToolResult, askApproval, removeClosingTag, toolProtocol } = callbacks + const { handleError, pushToolResult, askApproval } = callbacks try { if (!command) { @@ -52,7 +45,7 @@ export class ExecuteCommandTool extends BaseTool<"execute_command"> { if (ignoredFileAttemptedToAccess) { await task.say("rooignore_error", ignoredFileAttemptedToAccess) - pushToolResult(formatResponse.rooIgnoreError(ignoredFileAttemptedToAccess, toolProtocol)) + pushToolResult(formatResponse.rooIgnoreError(ignoredFileAttemptedToAccess)) return } @@ -144,9 +137,7 @@ export class ExecuteCommandTool extends BaseTool<"execute_command"> { override async handlePartial(task: Task, block: ToolUse<"execute_command">): Promise { const command = block.params.command - await task - .ask("command", this.removeClosingTag("command", command, block.partial), block.partial) - .catch(() => {}) + await task.ask("command", command ?? "", block.partial).catch(() => {}) } } diff --git a/src/core/tools/FetchInstructionsTool.ts b/src/core/tools/FetchInstructionsTool.ts index 7749de2cb8d..f800e57fc4b 100644 --- a/src/core/tools/FetchInstructionsTool.ts +++ b/src/core/tools/FetchInstructionsTool.ts @@ -14,14 +14,8 @@ interface FetchInstructionsParams { export class FetchInstructionsTool extends BaseTool<"fetch_instructions"> { readonly name = "fetch_instructions" as const - parseLegacy(params: Partial>): FetchInstructionsParams { - return { - task: params.task || "", - } - } - async execute(params: FetchInstructionsParams, task: Task, callbacks: ToolCallbacks): Promise { - const { handleError, pushToolResult, askApproval, toolProtocol } = callbacks + const { handleError, pushToolResult, askApproval } = callbacks const { task: taskParam } = params try { diff --git a/src/core/tools/GenerateImageTool.ts b/src/core/tools/GenerateImageTool.ts index d4bbe980d63..3eaa2d84c2d 100644 --- a/src/core/tools/GenerateImageTool.ts +++ b/src/core/tools/GenerateImageTool.ts @@ -22,17 +22,9 @@ import { t } from "../../i18n" export class GenerateImageTool extends BaseTool<"generate_image"> { readonly name = "generate_image" as const - parseLegacy(params: Partial>): GenerateImageParams { - return { - prompt: params.prompt || "", - path: params.path || "", - image: params.image, - } - } - async execute(params: GenerateImageParams, task: Task, callbacks: ToolCallbacks): Promise { const { prompt, path: relPath, image: inputImagePath } = params - const { handleError, pushToolResult, askApproval, removeClosingTag, toolProtocol } = callbacks + const { handleError, pushToolResult, askApproval } = callbacks const provider = task.providerRef.deref() const state = await provider?.getState() @@ -67,7 +59,7 @@ export class GenerateImageTool extends BaseTool<"generate_image"> { const accessAllowed = task.rooIgnoreController?.validateAccess(relPath) if (!accessAllowed) { await task.say("rooignore_error", relPath) - pushToolResult(formatResponse.rooIgnoreError(relPath, toolProtocol)) + pushToolResult(formatResponse.rooIgnoreError(relPath)) return } @@ -88,7 +80,7 @@ export class GenerateImageTool extends BaseTool<"generate_image"> { const inputImageAccessAllowed = task.rooIgnoreController?.validateAccess(inputImagePath) if (!inputImageAccessAllowed) { await task.say("rooignore_error", inputImagePath) - pushToolResult(formatResponse.rooIgnoreError(inputImagePath, toolProtocol)) + pushToolResult(formatResponse.rooIgnoreError(inputImagePath)) return } @@ -171,12 +163,12 @@ export class GenerateImageTool extends BaseTool<"generate_image"> { return } - const fullPath = path.resolve(task.cwd, removeClosingTag("path", relPath)) + const fullPath = path.resolve(task.cwd, relPath) const isOutsideWorkspace = isPathOutsideWorkspace(fullPath) const sharedMessageProps = { tool: "generateImage" as const, - path: getReadablePath(task.cwd, removeClosingTag("path", relPath)), + path: getReadablePath(task.cwd, relPath), content: prompt, isOutsideWorkspace, isProtected: isWriteProtected, diff --git a/src/core/tools/ListFilesTool.ts b/src/core/tools/ListFilesTool.ts index b4128d2a851..716d7ed7848 100644 --- a/src/core/tools/ListFilesTool.ts +++ b/src/core/tools/ListFilesTool.ts @@ -19,19 +19,9 @@ interface ListFilesParams { export class ListFilesTool extends BaseTool<"list_files"> { readonly name = "list_files" as const - parseLegacy(params: Partial>): ListFilesParams { - const recursiveRaw: string | undefined = params.recursive - const recursive = recursiveRaw?.toLowerCase() === "true" - - return { - path: params.path || "", - recursive, - } - } - async execute(params: ListFilesParams, task: Task, callbacks: ToolCallbacks): Promise { const { path: relDirPath, recursive } = params - const { askApproval, handleError, pushToolResult, removeClosingTag } = callbacks + const { askApproval, handleError, pushToolResult } = callbacks try { if (!relDirPath) { @@ -88,7 +78,7 @@ export class ListFilesTool extends BaseTool<"list_files"> { const sharedMessageProps: ClineSayTool = { tool: !recursive ? "listFilesTopLevel" : "listFilesRecursive", - path: getReadablePath(task.cwd, this.removeClosingTag("path", relDirPath, block.partial)), + path: getReadablePath(task.cwd, relDirPath ?? ""), isOutsideWorkspace, } diff --git a/src/core/tools/MultiApplyDiffTool.ts b/src/core/tools/MultiApplyDiffTool.ts index af5fefa251c..642479b4f27 100644 --- a/src/core/tools/MultiApplyDiffTool.ts +++ b/src/core/tools/MultiApplyDiffTool.ts @@ -1,55 +1,6 @@ -import path from "path" -import fs from "fs/promises" - -import { type ClineSayTool, DEFAULT_WRITE_DELAY_MS, isNativeProtocol } from "@roo-code/types" -import { TelemetryService } from "@roo-code/telemetry" - -import { getReadablePath } from "../../utils/path" import { Task } from "../task/Task" -import { ToolUse, RemoveClosingTag, AskApproval, HandleError, PushToolResult } from "../../shared/tools" -import { formatResponse } from "../prompts/responses" -import { fileExistsAtPath } from "../../utils/fs" -import { RecordSource } from "../context-tracking/FileContextTrackerTypes" -import { unescapeHtmlEntities } from "../../utils/text-normalization" -import { parseXmlForDiff } from "../../utils/xml" -import { EXPERIMENT_IDS, experiments } from "../../shared/experiments" +import { ToolUse, AskApproval, HandleError, PushToolResult } from "../../shared/tools" import { applyDiffTool as applyDiffToolClass } from "./ApplyDiffTool" -import { computeDiffStats, sanitizeUnifiedDiff } from "../diff/stats" -import { resolveToolProtocol } from "../../utils/resolveToolProtocol" - -interface DiffOperation { - path: string - diff: Array<{ - content: string - startLine?: number - }> -} - -// Track operation status -interface OperationResult { - path: string - status: "pending" | "approved" | "denied" | "blocked" | "error" - error?: string - result?: string - diffItems?: Array<{ content: string; startLine?: number }> - absolutePath?: string - fileExists?: boolean -} - -// Add proper type definitions -interface ParsedFile { - path: string - diff: ParsedDiff | ParsedDiff[] -} - -interface ParsedDiff { - content: string - start_line?: string -} - -interface ParsedXmlResult { - file: ParsedFile | ParsedFile[] -} export async function applyDiffTool( cline: Task, @@ -57,708 +8,10 @@ export async function applyDiffTool( askApproval: AskApproval, handleError: HandleError, pushToolResult: PushToolResult, - removeClosingTag: RemoveClosingTag, ) { - // Check if native protocol is enabled - if so, always use single-file class-based tool - // Use the task's locked protocol for consistency throughout the task lifetime - const toolProtocol = resolveToolProtocol(cline.apiConfiguration, cline.api.getModel().info, cline.taskToolProtocol) - if (isNativeProtocol(toolProtocol)) { - return applyDiffToolClass.handle(cline, block as ToolUse<"apply_diff">, { - askApproval, - handleError, - pushToolResult, - removeClosingTag, - toolProtocol, - }) - } - - // Check if MULTI_FILE_APPLY_DIFF experiment is enabled - const provider = cline.providerRef.deref() - const state = await provider?.getState() - if (provider && state) { - const isMultiFileApplyDiffEnabled = experiments.isEnabled( - state.experiments ?? {}, - EXPERIMENT_IDS.MULTI_FILE_APPLY_DIFF, - ) - - // If experiment is disabled, use single-file class-based tool - if (!isMultiFileApplyDiffEnabled) { - return applyDiffToolClass.handle(cline, block as ToolUse<"apply_diff">, { - askApproval, - handleError, - pushToolResult, - removeClosingTag, - toolProtocol, - }) - } - } - - // Otherwise, continue with new multi-file implementation - const argsXmlTag: string | undefined = block.params.args - const legacyPath: string | undefined = block.params.path - const legacyDiffContent: string | undefined = block.params.diff - const legacyStartLineStr: string | undefined = block.params.start_line - - let operationsMap: Record = {} - let usingLegacyParams = false - let filteredOperationErrors: string[] = [] - - // Handle partial message first - if (block.partial) { - let filePath = "" - if (argsXmlTag) { - const match = argsXmlTag.match(/.*?([^<]+)<\/path>/s) - if (match) { - filePath = match[1] - } - } else if (legacyPath) { - // Use legacy path if argsXmlTag is not present for partial messages - filePath = legacyPath - } - - const sharedMessageProps: ClineSayTool = { - tool: "appliedDiff", - path: getReadablePath(cline.cwd, filePath), - } - const partialMessage = JSON.stringify(sharedMessageProps) - await cline.ask("tool", partialMessage, block.partial).catch(() => {}) - return - } - - if (argsXmlTag) { - // Parse file entries from XML (new way) - try { - // IMPORTANT: We use parseXmlForDiff here instead of parseXml to prevent HTML entity decoding - // This ensures exact character matching when comparing parsed content against original file content - // Without this, special characters like & would be decoded to & causing diff mismatches - const parsed = parseXmlForDiff(argsXmlTag, ["file.diff.content"]) as ParsedXmlResult - const files = Array.isArray(parsed.file) ? parsed.file : [parsed.file].filter(Boolean) - - for (const file of files) { - if (!file.path || !file.diff) continue - - const filePath = file.path - - // Initialize the operation in the map if it doesn't exist - if (!operationsMap[filePath]) { - operationsMap[filePath] = { - path: filePath, - diff: [], - } - } - - // Handle diff as either array or single element - const diffs = Array.isArray(file.diff) ? file.diff : [file.diff] - - for (let i = 0; i < diffs.length; i++) { - const diff = diffs[i] - let diffContent: string - let startLine: number | undefined - - // Ensure content is a string before storing it - diffContent = typeof diff.content === "string" ? diff.content : "" - startLine = diff.start_line ? parseInt(diff.start_line) : undefined - - // Only add to operations if we have valid content - if (diffContent) { - operationsMap[filePath].diff.push({ - content: diffContent, - startLine, - }) - } - } - } - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error) - const detailedError = `Failed to parse apply_diff XML. This usually means: -1. The XML structure is malformed or incomplete -2. Missing required , , or tags -3. Invalid characters or encoding in the XML - -Expected structure: - - - relative/path/to/file.ext - - diff content here - line number - - - - -Original error: ${errorMessage}` - cline.consecutiveMistakeCount++ - cline.recordToolError("apply_diff") - TelemetryService.instance.captureDiffApplicationError(cline.taskId, cline.consecutiveMistakeCount) - await cline.say("diff_error", `Failed to parse apply_diff XML: ${errorMessage}`) - pushToolResult(detailedError) - cline.processQueuedMessages() - return - } - } else if (legacyPath && typeof legacyDiffContent === "string") { - // Handle legacy parameters (old way) - usingLegacyParams = true - operationsMap[legacyPath] = { - path: legacyPath, - diff: [ - { - content: legacyDiffContent, // Unescaping will be handled later like new diffs - startLine: legacyStartLineStr ? parseInt(legacyStartLineStr) : undefined, - }, - ], - } - } else { - // Neither new XML args nor old path/diff params are sufficient - cline.consecutiveMistakeCount++ - cline.recordToolError("apply_diff") - const errorMsg = await cline.sayAndCreateMissingParamError( - "apply_diff", - "args (or legacy 'path' and 'diff' parameters)", - ) - pushToolResult(errorMsg) - cline.processQueuedMessages() - return - } - - // If no operations were extracted, bail out - if (Object.keys(operationsMap).length === 0) { - cline.consecutiveMistakeCount++ - cline.recordToolError("apply_diff") - pushToolResult( - await cline.sayAndCreateMissingParamError( - "apply_diff", - usingLegacyParams - ? "legacy 'path' and 'diff' (must be valid and non-empty)" - : "args (must contain at least one valid file element)", - ), - ) - cline.processQueuedMessages() - return - } - - // Convert map to array of operations for processing - const operations = Object.values(operationsMap) - - const operationResults: OperationResult[] = operations.map((op) => ({ - path: op.path, - status: "pending", - diffItems: op.diff, - })) - - // Function to update operation result - const updateOperationResult = (path: string, updates: Partial) => { - const index = operationResults.findIndex((result) => result.path === path) - if (index !== -1) { - operationResults[index] = { ...operationResults[index], ...updates } - } - } - - try { - // First validate all files and prepare for batch approval - const operationsToApprove: OperationResult[] = [] - const allDiffErrors: string[] = [] // Collect all diff errors - - for (const operation of operations) { - const { path: relPath, diff: diffItems } = operation - - // Verify file access is allowed - const accessAllowed = cline.rooIgnoreController?.validateAccess(relPath) - if (!accessAllowed) { - await cline.say("rooignore_error", relPath) - updateOperationResult(relPath, { - status: "blocked", - error: formatResponse.rooIgnoreError(relPath, undefined), - }) - continue - } - - // Check if file is write-protected - const isWriteProtected = cline.rooProtectedController?.isWriteProtected(relPath) || false - - // Verify file exists - const absolutePath = path.resolve(cline.cwd, relPath) - const fileExists = await fileExistsAtPath(absolutePath) - if (!fileExists) { - updateOperationResult(relPath, { - status: "blocked", - error: `File does not exist at path: ${absolutePath}`, - }) - continue - } - - // Add to operations that need approval - const opResult = operationResults.find((r) => r.path === relPath) - if (opResult) { - opResult.absolutePath = absolutePath - opResult.fileExists = fileExists - operationsToApprove.push(opResult) - } - } - - // Handle batch approval if there are multiple files - if (operationsToApprove.length > 1) { - // Check if any files are write-protected - const hasProtectedFiles = operationsToApprove.some( - (opResult) => cline.rooProtectedController?.isWriteProtected(opResult.path) || false, - ) - - // Stream batch diffs progressively for better UX - const batchDiffs: Array<{ - path: string - changeCount: number - key: string - content: string - diffStats?: { added: number; removed: number } - diffs?: Array<{ content: string; startLine?: number }> - }> = [] - - for (const opResult of operationsToApprove) { - const readablePath = getReadablePath(cline.cwd, opResult.path) - const changeCount = opResult.diffItems?.length || 0 - const changeText = changeCount === 1 ? "1 change" : `${changeCount} changes` - - let unified = "" - try { - const original = await fs.readFile(opResult.absolutePath!, "utf-8") - const processed = !cline.api.getModel().id.includes("claude") - ? (opResult.diffItems || []).map((item) => ({ - ...item, - content: item.content ? unescapeHtmlEntities(item.content) : item.content, - })) - : opResult.diffItems || [] - - const applyRes = - (await cline.diffStrategy?.applyDiff(original, processed)) ?? ({ success: false } as any) - const newContent = applyRes.success && applyRes.content ? applyRes.content : original - unified = formatResponse.createPrettyPatch(opResult.path, original, newContent) - } catch { - unified = "" - } - - const unifiedSanitized = sanitizeUnifiedDiff(unified) - const stats = computeDiffStats(unifiedSanitized) || undefined - batchDiffs.push({ - path: readablePath, - changeCount, - key: `${readablePath} (${changeText})`, - content: unifiedSanitized, - diffStats: stats, - diffs: opResult.diffItems?.map((item) => ({ - content: item.content, - startLine: item.startLine, - })), - }) - - // Send a partial update after each file preview is ready - const partialMessage = JSON.stringify({ - tool: "appliedDiff", - batchDiffs, - isProtected: hasProtectedFiles, - } satisfies ClineSayTool) - await cline.ask("tool", partialMessage, true).catch(() => {}) - } - - // Final approval message (non-partial) - const completeMessage = JSON.stringify({ - tool: "appliedDiff", - batchDiffs, - isProtected: hasProtectedFiles, - } satisfies ClineSayTool) - - const { response, text, images } = await cline.ask("tool", completeMessage, false) - - // Process batch response - if (response === "yesButtonClicked") { - // Approve all files - if (text) { - await cline.say("user_feedback", text, images) - } - operationsToApprove.forEach((opResult) => { - updateOperationResult(opResult.path, { status: "approved" }) - }) - } else if (response === "noButtonClicked") { - // Deny all files - if (text) { - await cline.say("user_feedback", text, images) - } - cline.didRejectTool = true - operationsToApprove.forEach((opResult) => { - updateOperationResult(opResult.path, { - status: "denied", - result: `Changes to ${opResult.path} were not approved by user`, - }) - }) - } else { - // Handle individual permissions from objectResponse - try { - const parsedResponse = JSON.parse(text || "{}") - // Check if this is our batch diff approval response - if (parsedResponse.action === "applyDiff" && parsedResponse.approvedFiles) { - const approvedFiles = parsedResponse.approvedFiles - let hasAnyDenial = false - - operationsToApprove.forEach((opResult) => { - const approved = approvedFiles[opResult.path] === true - - if (approved) { - updateOperationResult(opResult.path, { status: "approved" }) - } else { - hasAnyDenial = true - updateOperationResult(opResult.path, { - status: "denied", - result: `Changes to ${opResult.path} were not approved by user`, - }) - } - }) - - if (hasAnyDenial) { - cline.didRejectTool = true - } - } else { - // Legacy individual permissions format - const individualPermissions = parsedResponse - let hasAnyDenial = false - - batchDiffs.forEach((batchDiff, index) => { - const opResult = operationsToApprove[index] - const approved = individualPermissions[batchDiff.key] === true - - if (approved) { - updateOperationResult(opResult.path, { status: "approved" }) - } else { - hasAnyDenial = true - updateOperationResult(opResult.path, { - status: "denied", - result: `Changes to ${opResult.path} were not approved by user`, - }) - } - }) - - if (hasAnyDenial) { - cline.didRejectTool = true - } - } - } catch (error) { - // Fallback: if JSON parsing fails, deny all files - console.error("Failed to parse individual permissions:", error) - cline.didRejectTool = true - operationsToApprove.forEach((opResult) => { - updateOperationResult(opResult.path, { - status: "denied", - result: `Changes to ${opResult.path} were not approved by user`, - }) - }) - } - } - } else if (operationsToApprove.length === 1) { - // Single file approval - process immediately - const opResult = operationsToApprove[0] - updateOperationResult(opResult.path, { status: "approved" }) - } - - // Process approved operations - const results: string[] = [] - - for (const opResult of operationResults) { - // Skip operations that weren't approved or were blocked - if (opResult.status !== "approved") { - if (opResult.result) { - results.push(opResult.result) - } else if (opResult.error) { - results.push(opResult.error) - } - continue - } - - const relPath = opResult.path - const diffItems = opResult.diffItems || [] - const absolutePath = opResult.absolutePath! - const fileExists = opResult.fileExists! - - try { - let originalContent: string | null = await fs.readFile(absolutePath, "utf-8") - let beforeContent: string | null = originalContent - let successCount = 0 - let formattedError = "" - - // Pre-process all diff items for HTML entity unescaping if needed - const processedDiffItems = !cline.api.getModel().id.includes("claude") - ? diffItems.map((item) => ({ - ...item, - content: item.content ? unescapeHtmlEntities(item.content) : item.content, - })) - : diffItems - - // Apply all diffs at once with the array-based method - const diffResult = (await cline.diffStrategy?.applyDiff(originalContent, processedDiffItems)) ?? { - success: false, - error: "No diff strategy available - please ensure a valid diff strategy is configured", - } - - // Release the original content from memory as it's no longer needed - originalContent = null - - if (!diffResult.success) { - cline.consecutiveMistakeCount++ - const currentCount = (cline.consecutiveMistakeCountForApplyDiff.get(relPath) || 0) + 1 - cline.consecutiveMistakeCountForApplyDiff.set(relPath, currentCount) - - TelemetryService.instance.captureDiffApplicationError(cline.taskId, currentCount) - - if (diffResult.failParts && diffResult.failParts.length > 0) { - for (let i = 0; i < diffResult.failParts.length; i++) { - const failPart = diffResult.failParts[i] - if (failPart.success) { - continue - } - - // Collect error for later reporting - allDiffErrors.push(`${relPath} - Diff ${i + 1}: ${failPart.error}`) - - const errorDetails = failPart.details ? JSON.stringify(failPart.details, null, 2) : "" - formattedError += ` -Diff ${i + 1} failed for file: ${relPath} -Error: ${failPart.error} - -Suggested fixes: -1. Verify the search content exactly matches the file content (including whitespace and case) -2. Check for correct indentation and line endings -3. Use the read_file tool to verify the file's current contents -4. Consider breaking complex changes into smaller diffs -5. Ensure start_line parameter matches the actual content location -${errorDetails ? `\nDetailed error information:\n${errorDetails}\n` : ""} -\n\n` - } - } else { - const errorDetails = diffResult.details ? JSON.stringify(diffResult.details, null, 2) : "" - formattedError += ` -Unable to apply diffs to file: ${absolutePath} -Error: ${diffResult.error} - -Recovery suggestions: -1. Use the read_file tool to verify the file's current contents -2. Verify the diff format matches the expected search/replace pattern -3. Check that the search content exactly matches what's in the file -4. Consider using line numbers with start_line parameter -5. Break large changes into smaller, more specific diffs -${errorDetails ? `\nTechnical details:\n${errorDetails}\n` : ""} -\n\n` - } - } else { - // Get the content from the result and update success count - originalContent = diffResult.content || originalContent - successCount = diffItems.length - (diffResult.failParts?.length || 0) - } - - // If no diffs were successfully applied, continue to next file - if (successCount === 0) { - if (formattedError) { - const currentCount = cline.consecutiveMistakeCountForApplyDiff.get(relPath) || 0 - if (currentCount >= 2) { - await cline.say("diff_error", formattedError) - } - cline.recordToolError("apply_diff", formattedError) - results.push(formattedError) - - // For single file operations, we need to send a complete message to stop the spinner - if (operationsToApprove.length === 1) { - const sharedMessageProps: ClineSayTool = { - tool: "appliedDiff", - path: getReadablePath(cline.cwd, relPath), - diff: diffItems.map((item) => item.content).join("\n\n"), - } - // Send a complete message (partial: false) to update the UI and stop the spinner - await cline.ask("tool", JSON.stringify(sharedMessageProps), false).catch(() => {}) - } - } - continue - } - - cline.consecutiveMistakeCount = 0 - cline.consecutiveMistakeCountForApplyDiff.delete(relPath) - - // Check if preventFocusDisruption experiment is enabled - const provider = cline.providerRef.deref() - const state = await provider?.getState() - const diagnosticsEnabled = state?.diagnosticsEnabled ?? true - const writeDelayMs = state?.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS - const isPreventFocusDisruptionEnabled = experiments.isEnabled( - state?.experiments ?? {}, - EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION, - ) - - // For batch operations, we've already gotten approval - const isWriteProtected = cline.rooProtectedController?.isWriteProtected(relPath) || false - const sharedMessageProps: ClineSayTool = { - tool: "appliedDiff", - path: getReadablePath(cline.cwd, relPath), - isProtected: isWriteProtected, - } - - // If single file, handle based on PREVENT_FOCUS_DISRUPTION setting - let didApprove = true - if (operationsToApprove.length === 1) { - // Prepare common data for single file operation - const diffContents = diffItems.map((item) => item.content).join("\n\n") - const unifiedPatchRaw = formatResponse.createPrettyPatch(relPath, beforeContent!, originalContent!) - const unifiedPatch = sanitizeUnifiedDiff(unifiedPatchRaw) - const operationMessage = JSON.stringify({ - ...sharedMessageProps, - diff: diffContents, - content: unifiedPatch, - diffStats: computeDiffStats(unifiedPatch) || undefined, - } satisfies ClineSayTool) - - let toolProgressStatus - if (cline.diffStrategy && cline.diffStrategy.getProgressStatus) { - toolProgressStatus = cline.diffStrategy.getProgressStatus( - { - ...block, - params: { ...block.params, diff: diffContents }, - }, - { success: true }, - ) - } - - // Set up diff view - cline.diffViewProvider.editType = "modify" - - // Show diff view if focus disruption prevention is disabled - if (!isPreventFocusDisruptionEnabled) { - await cline.diffViewProvider.open(relPath) - await cline.diffViewProvider.update(originalContent!, true) - cline.diffViewProvider.scrollToFirstDiff() - } else { - // For direct save, we still need to set originalContent - cline.diffViewProvider.originalContent = await fs.readFile(absolutePath, "utf-8") - } - - // Ask for approval (same for both flows) - const isWriteProtected = cline.rooProtectedController?.isWriteProtected(relPath) || false - didApprove = await askApproval("tool", operationMessage, toolProgressStatus, isWriteProtected) - - if (!didApprove) { - // Revert changes if diff view was shown - if (!isPreventFocusDisruptionEnabled) { - await cline.diffViewProvider.revertChanges() - } - results.push(`Changes to ${relPath} were not approved by user`) - continue - } - - // Save the changes - if (isPreventFocusDisruptionEnabled) { - // Direct file write without diff view or opening the file - await cline.diffViewProvider.saveDirectly( - relPath, - originalContent!, - false, - diagnosticsEnabled, - writeDelayMs, - ) - } else { - // Call saveChanges to update the DiffViewProvider properties - await cline.diffViewProvider.saveChanges(diagnosticsEnabled, writeDelayMs) - } - } else { - // Batch operations - already approved above - if (isPreventFocusDisruptionEnabled) { - // Direct file write without diff view or opening the file - cline.diffViewProvider.editType = "modify" - cline.diffViewProvider.originalContent = await fs.readFile(absolutePath, "utf-8") - await cline.diffViewProvider.saveDirectly( - relPath, - originalContent!, - false, - diagnosticsEnabled, - writeDelayMs, - ) - } else { - // Original behavior with diff view - cline.diffViewProvider.editType = "modify" - await cline.diffViewProvider.open(relPath) - await cline.diffViewProvider.update(originalContent!, true) - cline.diffViewProvider.scrollToFirstDiff() - - // Call saveChanges to update the DiffViewProvider properties - await cline.diffViewProvider.saveChanges(diagnosticsEnabled, writeDelayMs) - } - } - - // Track file edit operation - await cline.fileContextTracker.trackFileContext(relPath, "roo_edited" as RecordSource) - - // Used to determine if we should wait for busy terminal to update before sending api request - cline.didEditFile = true - let partFailHint = "" - - if (successCount < diffItems.length) { - partFailHint = `Unable to apply all diff parts to file: ${absolutePath}` - } - - // Get the formatted response message - const message = await cline.diffViewProvider.pushToolWriteResult(cline, cline.cwd, !fileExists) - - if (partFailHint) { - results.push(partFailHint + "\n" + message) - } else { - results.push(message) - } - - await cline.diffViewProvider.reset() - } catch (error) { - const errorMsg = error instanceof Error ? error.message : String(error) - updateOperationResult(relPath, { - status: "error", - error: `Error processing ${relPath}: ${errorMsg}`, - }) - results.push(`Error processing ${relPath}: ${errorMsg}`) - } - } - - // Add filtered operation errors to results - if (filteredOperationErrors.length > 0) { - results.push(...filteredOperationErrors) - } - - // Report all diff errors at once if any - if (allDiffErrors.length > 0) { - await cline.say("diff_error", allDiffErrors.join("\n")) - } - - // Check for single SEARCH/REPLACE block warning - let totalSearchBlocks = 0 - for (const operation of operations) { - for (const diffItem of operation.diff) { - const searchBlocks = (diffItem.content.match(/<<<<<<< SEARCH/g) || []).length - totalSearchBlocks += searchBlocks - } - } - - // Check protocol for notice formatting - reuse the task's locked protocol - const noticeProtocol = resolveToolProtocol( - cline.apiConfiguration, - cline.api.getModel().info, - cline.taskToolProtocol, - ) - const singleBlockNotice = - totalSearchBlocks === 1 - ? isNativeProtocol(noticeProtocol) - ? "\n" + - JSON.stringify({ - notice: "Making multiple related changes in a single apply_diff is more efficient. If other changes are needed in this file, please include them as additional SEARCH/REPLACE blocks.", - }) - : "\nMaking multiple related changes in a single apply_diff is more efficient. If other changes are needed in this file, please include them as additional SEARCH/REPLACE blocks." - : "" - - // Push the final result combining all operation results - pushToolResult(results.join("\n\n") + singleBlockNotice) - cline.processQueuedMessages() - return - } catch (error) { - await handleError("applying diff", error) - await cline.diffViewProvider.reset() - cline.processQueuedMessages() - return - } + return applyDiffToolClass.handle(cline, block as ToolUse<"apply_diff">, { + askApproval, + handleError, + pushToolResult, + }) } diff --git a/src/core/tools/NewTaskTool.ts b/src/core/tools/NewTaskTool.ts index c5607d2a851..fd208128da8 100644 --- a/src/core/tools/NewTaskTool.ts +++ b/src/core/tools/NewTaskTool.ts @@ -20,17 +20,9 @@ interface NewTaskParams { export class NewTaskTool extends BaseTool<"new_task"> { readonly name = "new_task" as const - parseLegacy(params: Partial>): NewTaskParams { - return { - mode: params.mode || "", - message: params.message || "", - todos: params.todos, - } - } - async execute(params: NewTaskParams, task: Task, callbacks: ToolCallbacks): Promise { const { mode, message, todos } = params - const { askApproval, handleError, pushToolResult, toolProtocol, toolCallId } = callbacks + const { askApproval, handleError, pushToolResult } = callbacks try { // Validate required parameters. @@ -147,9 +139,9 @@ export class NewTaskTool extends BaseTool<"new_task"> { const partialMessage = JSON.stringify({ tool: "newTask", - mode: this.removeClosingTag("mode", mode, block.partial), - content: this.removeClosingTag("message", message, block.partial), - todos: this.removeClosingTag("todos", todos, block.partial), + mode: mode ?? "", + content: message ?? "", + todos: todos, }) await task.ask("tool", partialMessage, block.partial).catch(() => {}) diff --git a/src/core/tools/ReadFileTool.ts b/src/core/tools/ReadFileTool.ts index 2bba6bc6cd9..f4c4be0b4ad 100644 --- a/src/core/tools/ReadFileTool.ts +++ b/src/core/tools/ReadFileTool.ts @@ -3,7 +3,7 @@ import * as fs from "fs/promises" import { isBinaryFile } from "isbinaryfile" import type { FileEntry, LineRange } from "@roo-code/types" -import { type ClineSayTool, isNativeProtocol, ANTHROPIC_DEFAULT_MAX_TOKENS } from "@roo-code/types" +import { type ClineSayTool, ANTHROPIC_DEFAULT_MAX_TOKENS } from "@roo-code/types" import { Task } from "../task/Task" import { formatResponse } from "../prompts/responses" @@ -16,8 +16,6 @@ import { countFileLines } from "../../integrations/misc/line-counter" import { readLines } from "../../integrations/misc/read-lines" import { extractTextFromFile, addLineNumbers, getSupportedBinaryFormats } from "../../integrations/misc/extract-text" import { parseSourceCodeDefinitionsForFile } from "../../services/tree-sitter" -import { parseXml } from "../../utils/xml" -import { resolveToolProtocol } from "../../utils/resolveToolProtocol" import type { ToolUse } from "../../shared/tools" import { @@ -39,7 +37,6 @@ interface FileResult { error?: string notice?: string lineRanges?: LineRange[] - xmlContent?: string nativeContent?: string imageDataUrl?: string feedbackText?: string @@ -49,78 +46,17 @@ interface FileResult { export class ReadFileTool extends BaseTool<"read_file"> { readonly name = "read_file" as const - parseLegacy(params: Partial>): { files: FileEntry[] } { - const argsXmlTag = params.args - const legacyPath = params.path - const legacyStartLineStr = params.start_line - const legacyEndLineStr = params.end_line - - const fileEntries: FileEntry[] = [] - - // XML args format - if (argsXmlTag) { - const parsed = parseXml(argsXmlTag) as any - const files = Array.isArray(parsed.file) ? parsed.file : [parsed.file].filter(Boolean) - - for (const file of files) { - if (!file.path) continue - - const fileEntry: FileEntry = { - path: file.path, - lineRanges: [], - } - - if (file.line_range) { - const ranges = Array.isArray(file.line_range) ? file.line_range : [file.line_range] - for (const range of ranges) { - const match = String(range).match(/(\d+)-(\d+)/) - if (match) { - const [, start, end] = match.map(Number) - if (!isNaN(start) && !isNaN(end)) { - fileEntry.lineRanges?.push({ start, end }) - } - } - } - } - fileEntries.push(fileEntry) - } - - return { files: fileEntries } - } - - // Legacy single file path - if (legacyPath) { - const fileEntry: FileEntry = { - path: legacyPath, - lineRanges: [], - } - - if (legacyStartLineStr && legacyEndLineStr) { - const start = parseInt(legacyStartLineStr, 10) - const end = parseInt(legacyEndLineStr, 10) - if (!isNaN(start) && !isNaN(end) && start > 0 && end > 0) { - fileEntry.lineRanges?.push({ start, end }) - } - } - fileEntries.push(fileEntry) - } - - return { files: fileEntries } - } - async execute(params: { files: FileEntry[] }, task: Task, callbacks: ToolCallbacks): Promise { - const { handleError, pushToolResult, toolProtocol } = callbacks + const { handleError, pushToolResult } = callbacks const fileEntries = params.files const modelInfo = task.api.getModel().info - // Use the task's locked protocol for consistent output formatting throughout the task - const protocol = resolveToolProtocol(task.apiConfiguration, modelInfo, task.taskToolProtocol) - const useNative = isNativeProtocol(protocol) + const useNative = true if (!fileEntries || fileEntries.length === 0) { task.consecutiveMistakeCount++ task.recordToolError("read_file") - const errorMsg = await task.sayAndCreateMissingParamError("read_file", "args (containing valid file paths)") - const errorResult = useNative ? `Error: ${errorMsg}` : `${errorMsg}` + const errorMsg = await task.sayAndCreateMissingParamError("read_file", "files") + const errorResult = `Error: ${errorMsg}` pushToolResult(errorResult) return } @@ -132,7 +68,7 @@ export class ReadFileTool extends BaseTool<"read_file"> { task.recordToolError("read_file") const errorMsg = `Too many files requested. You attempted to read ${fileEntries.length} files, but the concurrent file reads limit is ${maxConcurrentFileReads}. Please read files in batches of ${maxConcurrentFileReads} or fewer.` await task.say("error", errorMsg) - const errorResult = useNative ? `Error: ${errorMsg}` : `${errorMsg}` + const errorResult = `Error: ${errorMsg}` pushToolResult(errorResult) return } @@ -167,7 +103,6 @@ export class ReadFileTool extends BaseTool<"read_file"> { updateFileResult(relPath, { status: "blocked", error: errorMsg, - xmlContent: `${relPath}Error reading file: ${errorMsg}`, nativeContent: `File: ${relPath}\nError: Error reading file: ${errorMsg}`, }) await task.say("error", `Error reading file ${relPath}: ${errorMsg}`) @@ -179,7 +114,6 @@ export class ReadFileTool extends BaseTool<"read_file"> { updateFileResult(relPath, { status: "blocked", error: errorMsg, - xmlContent: `${relPath}Error reading file: ${errorMsg}`, nativeContent: `File: ${relPath}\nError: Error reading file: ${errorMsg}`, }) await task.say("error", `Error reading file ${relPath}: ${errorMsg}`) @@ -198,7 +132,6 @@ export class ReadFileTool extends BaseTool<"read_file"> { updateFileResult(relPath, { status: "blocked", error: errorMsg, - xmlContent: `${relPath}${errorMsg}`, nativeContent: `File: ${relPath}\nError: ${errorMsg}`, }) continue @@ -252,7 +185,6 @@ export class ReadFileTool extends BaseTool<"read_file"> { filesToApprove.forEach((fileResult) => { updateFileResult(fileResult.path, { status: "denied", - xmlContent: `${fileResult.path}Denied by user`, nativeContent: `File: ${fileResult.path}\nStatus: Denied by user`, feedbackText: text, feedbackImages: images, @@ -273,7 +205,6 @@ export class ReadFileTool extends BaseTool<"read_file"> { hasAnyDenial = true updateFileResult(fileResult.path, { status: "denied", - xmlContent: `${fileResult.path}Denied by user`, nativeContent: `File: ${fileResult.path}\nStatus: Denied by user`, }) } @@ -286,7 +217,6 @@ export class ReadFileTool extends BaseTool<"read_file"> { filesToApprove.forEach((fileResult) => { updateFileResult(fileResult.path, { status: "denied", - xmlContent: `${fileResult.path}Denied by user`, nativeContent: `File: ${fileResult.path}\nStatus: Denied by user`, }) }) @@ -326,7 +256,6 @@ export class ReadFileTool extends BaseTool<"read_file"> { task.didRejectTool = true updateFileResult(relPath, { status: "denied", - xmlContent: `${relPath}Denied by user`, nativeContent: `File: ${relPath}\nStatus: Denied by user`, feedbackText: text, feedbackImages: images, @@ -359,7 +288,6 @@ export class ReadFileTool extends BaseTool<"read_file"> { updateFileResult(relPath, { status: "error", error: errorMsg, - xmlContent: `${relPath}Error reading file: ${errorMsg}`, nativeContent: `File: ${relPath}\nError: Error reading file: ${errorMsg}`, }) await task.say("error", `Error reading file ${relPath}: ${errorMsg}`) @@ -385,7 +313,6 @@ export class ReadFileTool extends BaseTool<"read_file"> { if (!validationResult.isValid) { await task.fileContextTracker.trackFileContext(relPath, "read_tool" as RecordSource) updateFileResult(relPath, { - xmlContent: `${relPath}\n${validationResult.notice}\n`, nativeContent: `File: ${relPath}\nNote: ${validationResult.notice}`, }) continue @@ -396,7 +323,6 @@ export class ReadFileTool extends BaseTool<"read_file"> { await task.fileContextTracker.trackFileContext(relPath, "read_tool" as RecordSource) updateFileResult(relPath, { - xmlContent: `${relPath}\n${imageResult.notice}\n`, nativeContent: `File: ${relPath}\nNote: ${imageResult.notice}`, imageDataUrl: imageResult.dataUrl, }) @@ -406,7 +332,6 @@ export class ReadFileTool extends BaseTool<"read_file"> { updateFileResult(relPath, { status: "error", error: `Error reading image file: ${errorMsg}`, - xmlContent: `${relPath}Error reading image file: ${errorMsg}`, nativeContent: `File: ${relPath}\nError: Error reading image file: ${errorMsg}`, }) await task.say("error", `Error reading image file ${relPath}: ${errorMsg}`) @@ -426,10 +351,6 @@ export class ReadFileTool extends BaseTool<"read_file"> { await task.fileContextTracker.trackFileContext(relPath, "read_tool" as RecordSource) updateFileResult(relPath, { - xmlContent: - lineCount > 0 - ? `${relPath}\n\n${numberedContent}\n` - : `${relPath}\nFile is empty\n`, nativeContent: lineCount > 0 ? `File: ${relPath}\nLines 1-${lineCount}:\n${numberedContent}` @@ -441,7 +362,6 @@ export class ReadFileTool extends BaseTool<"read_file"> { updateFileResult(relPath, { status: "error", error: `Error extracting text: ${errorMsg}`, - xmlContent: `${relPath}Error extracting text: ${errorMsg}`, nativeContent: `File: ${relPath}\nError: Error extracting text: ${errorMsg}`, }) await task.say("error", `Error extracting text from ${relPath}: ${errorMsg}`) @@ -451,7 +371,6 @@ export class ReadFileTool extends BaseTool<"read_file"> { const fileFormat = fileExtension.slice(1) || "bin" updateFileResult(relPath, { notice: `Binary file format: ${fileFormat}`, - xmlContent: `${relPath}\nBinary file - content not displayed\n`, nativeContent: `File: ${relPath}\nBinary file (${fileFormat}) - content not displayed`, }) continue @@ -473,7 +392,6 @@ export class ReadFileTool extends BaseTool<"read_file"> { } updateFileResult(relPath, { - xmlContent: `${relPath}\n${rangeResults.join("\n")}\n`, nativeContent: `File: ${relPath}\n${nativeRangeResults.join("\n\n")}`, }) continue @@ -488,7 +406,6 @@ export class ReadFileTool extends BaseTool<"read_file"> { if (defResult) { const notice = `Showing only ${maxReadFileLine} of ${totalLines} total lines. Use line_range if you need to read more lines` updateFileResult(relPath, { - xmlContent: `${relPath}\n${defResult}\n${notice}\n`, nativeContent: `File: ${relPath}\nCode Definitions:\n${defResult}\n\nNote: ${notice}`, }) } @@ -526,7 +443,6 @@ export class ReadFileTool extends BaseTool<"read_file"> { nativeInfo += `\nNote: ${notice}` updateFileResult(relPath, { - xmlContent: `${relPath}\n${xmlInfo}`, nativeContent: `File: ${relPath}\n${nativeInfo}`, }) } catch (error) { @@ -606,7 +522,6 @@ export class ReadFileTool extends BaseTool<"read_file"> { await task.fileContextTracker.trackFileContext(relPath, "read_tool" as RecordSource) updateFileResult(relPath, { - xmlContent: `${relPath}\n${xmlInfo}`, nativeContent: `File: ${relPath}\n${nativeInfo}`, }) } catch (error) { @@ -614,7 +529,6 @@ export class ReadFileTool extends BaseTool<"read_file"> { updateFileResult(relPath, { status: "error", error: `Error reading file: ${errorMsg}`, - xmlContent: `${relPath}Error reading file: ${errorMsg}`, nativeContent: `File: ${relPath}\nError: Error reading file: ${errorMsg}`, }) await task.say("error", `Error reading file ${relPath}: ${errorMsg}`) @@ -627,17 +541,11 @@ export class ReadFileTool extends BaseTool<"read_file"> { task.didToolFailInCurrentTurn = true } - // Build final result based on protocol - let finalResult: string - if (useNative) { - const nativeResults = fileResults - .filter((result) => result.nativeContent) - .map((result) => result.nativeContent) - finalResult = nativeResults.join("\n\n---\n\n") - } else { - const xmlResults = fileResults.filter((result) => result.xmlContent).map((result) => result.xmlContent) - finalResult = `\n${xmlResults.join("\n")}\n` - } + // Build final result (native-only) + const finalResult = fileResults + .filter((result) => result.nativeContent) + .map((result) => result.nativeContent) + .join("\n\n---\n\n") const fileImageUrls = fileResults .filter((result) => result.imageDataUrl) @@ -700,7 +608,6 @@ export class ReadFileTool extends BaseTool<"read_file"> { updateFileResult(relPath, { status: "error", error: `Error reading file: ${errorMsg}`, - xmlContent: `${relPath}Error reading file: ${errorMsg}`, nativeContent: `File: ${relPath}\nError: Error reading file: ${errorMsg}`, }) } @@ -710,17 +617,10 @@ export class ReadFileTool extends BaseTool<"read_file"> { // Mark that a tool failed in this turn task.didToolFailInCurrentTurn = true - // Build final error result based on protocol - let errorResult: string - if (useNative) { - const nativeResults = fileResults - .filter((result) => result.nativeContent) - .map((result) => result.nativeContent) - errorResult = nativeResults.join("\n\n---\n\n") - } else { - const xmlResults = fileResults.filter((result) => result.xmlContent).map((result) => result.xmlContent) - errorResult = `\n${xmlResults.join("\n")}\n` - } + const errorResult = fileResults + .filter((result) => result.nativeContent) + .map((result) => result.nativeContent) + .join("\n\n---\n\n") pushToolResult(errorResult) } @@ -744,69 +644,16 @@ export class ReadFileTool extends BaseTool<"read_file"> { } } - // Fallback to legacy/XML or synthesized params const blockParams = second as any - - if (blockParams?.args) { - try { - const parsed = parseXml(blockParams.args) as any - const files = Array.isArray(parsed.file) ? parsed.file : [parsed.file].filter(Boolean) - const paths = files.map((f: any) => f?.path).filter(Boolean) as string[] - - if (paths.length === 0) { - return `[${blockName} with no valid paths]` - } else if (paths.length === 1) { - return `[${blockName} for '${paths[0]}'. Reading multiple files at once is more efficient for the LLM. If other files are relevant to your current task, please read them simultaneously.]` - } else if (paths.length <= 3) { - const pathList = paths.map((p) => `'${p}'`).join(", ") - return `[${blockName} for ${pathList}]` - } else { - return `[${blockName} for ${paths.length} files]` - } - } catch (error) { - console.error("Failed to parse read_file args XML for description:", error) - return `[${blockName} with unparsable args]` - } - } else if (blockParams?.path) { + if (blockParams?.path) { return `[${blockName} for '${blockParams.path}'. Reading multiple files at once is more efficient for the LLM. If other files are relevant to your current task, please read them simultaneously.]` - } else if (blockParams?.files) { - // Back-compat: some paths may still synthesize params.files; try to parse if present - try { - const files = JSON.parse(blockParams.files) - if (Array.isArray(files) && files.length > 0) { - const paths = files.map((f: any) => f?.path).filter(Boolean) as string[] - if (paths.length === 1) { - return `[${blockName} for '${paths[0]}'. Reading multiple files at once is more efficient for the LLM. If other files are relevant to your current task, please read them simultaneously.]` - } else if (paths.length <= 3) { - const pathList = paths.map((p) => `'${p}'`).join(", ") - return `[${blockName} for ${pathList}]` - } else { - return `[${blockName} for ${paths.length} files]` - } - } - } catch (error) { - console.error("Failed to parse native files JSON for description:", error) - return `[${blockName} with unparsable files]` - } } - - return `[${blockName} with missing path/args/files]` + return `[${blockName} with missing files]` } override async handlePartial(task: Task, block: ToolUse<"read_file">): Promise { - const argsXmlTag = block.params.args - const legacyPath = block.params.path - let filePath = "" - if (argsXmlTag) { - const match = argsXmlTag.match(/.*?([^<]+)<\/path>/s) - if (match) filePath = match[1] - } - if (!filePath && legacyPath) { - filePath = legacyPath - } - - if (!filePath && block.nativeArgs && "files" in block.nativeArgs && Array.isArray(block.nativeArgs.files)) { + if (block.nativeArgs && "files" in block.nativeArgs && Array.isArray(block.nativeArgs.files)) { const files = block.nativeArgs.files if (files.length > 0 && files[0]?.path) { filePath = files[0].path diff --git a/src/core/tools/RunSlashCommandTool.ts b/src/core/tools/RunSlashCommandTool.ts index 69cb9dde95b..0bcf970226f 100644 --- a/src/core/tools/RunSlashCommandTool.ts +++ b/src/core/tools/RunSlashCommandTool.ts @@ -14,16 +14,9 @@ interface RunSlashCommandParams { export class RunSlashCommandTool extends BaseTool<"run_slash_command"> { readonly name = "run_slash_command" as const - parseLegacy(params: Partial>): RunSlashCommandParams { - return { - command: params.command || "", - args: params.args, - } - } - async execute(params: RunSlashCommandParams, task: Task, callbacks: ToolCallbacks): Promise { const { command: commandName, args } = params - const { askApproval, handleError, pushToolResult, toolProtocol } = callbacks + const { askApproval, handleError, pushToolResult } = callbacks // Check if run slash command experiment is enabled const provider = task.providerRef.deref() @@ -128,8 +121,8 @@ export class RunSlashCommandTool extends BaseTool<"run_slash_command"> { const partialMessage = JSON.stringify({ tool: "runSlashCommand", - command: this.removeClosingTag("command", commandName, block.partial), - args: this.removeClosingTag("args", args, block.partial), + command: commandName, + args: args, }) await task.ask("tool", partialMessage, block.partial).catch(() => {}) diff --git a/src/core/tools/SearchAndReplaceTool.ts b/src/core/tools/SearchAndReplaceTool.ts index 724f2d08229..93c3b4533b7 100644 --- a/src/core/tools/SearchAndReplaceTool.ts +++ b/src/core/tools/SearchAndReplaceTool.ts @@ -28,26 +28,9 @@ interface SearchAndReplaceParams { export class SearchAndReplaceTool extends BaseTool<"search_and_replace"> { readonly name = "search_and_replace" as const - parseLegacy(params: Partial>): SearchAndReplaceParams { - // Parse operations from JSON string if provided - let operations: SearchReplaceOperation[] = [] - if (params.operations) { - try { - operations = JSON.parse(params.operations) - } catch { - operations = [] - } - } - - return { - path: params.path || "", - operations, - } - } - async execute(params: SearchAndReplaceParams, task: Task, callbacks: ToolCallbacks): Promise { const { path: relPath, operations } = params - const { askApproval, handleError, pushToolResult, toolProtocol } = callbacks + const { askApproval, handleError, pushToolResult } = callbacks try { // Validate required parameters @@ -90,7 +73,7 @@ export class SearchAndReplaceTool extends BaseTool<"search_and_replace"> { if (!accessAllowed) { await task.say("rooignore_error", relPath) - pushToolResult(formatResponse.rooIgnoreError(relPath, toolProtocol)) + pushToolResult(formatResponse.rooIgnoreError(relPath)) return } diff --git a/src/core/tools/SearchFilesTool.ts b/src/core/tools/SearchFilesTool.ts index ad1ea22b8fc..3230c043e04 100644 --- a/src/core/tools/SearchFilesTool.ts +++ b/src/core/tools/SearchFilesTool.ts @@ -19,14 +19,6 @@ interface SearchFilesParams { export class SearchFilesTool extends BaseTool<"search_files"> { readonly name = "search_files" as const - parseLegacy(params: Partial>): SearchFilesParams { - return { - path: params.path || "", - regex: params.regex || "", - file_pattern: params.file_pattern || undefined, - } - } - async execute(params: SearchFilesParams, task: Task, callbacks: ToolCallbacks): Promise { const { askApproval, handleError, pushToolResult } = callbacks @@ -89,9 +81,9 @@ export class SearchFilesTool extends BaseTool<"search_files"> { const sharedMessageProps: ClineSayTool = { tool: "searchFiles", - path: getReadablePath(task.cwd, this.removeClosingTag("path", relDirPath, block.partial)), - regex: this.removeClosingTag("regex", regex, block.partial), - filePattern: this.removeClosingTag("file_pattern", filePattern, block.partial), + path: getReadablePath(task.cwd, relDirPath ?? ""), + regex: regex ?? "", + filePattern: filePattern ?? "", isOutsideWorkspace, } diff --git a/src/core/tools/SearchReplaceTool.ts b/src/core/tools/SearchReplaceTool.ts index e95427bde73..2d8817364ff 100644 --- a/src/core/tools/SearchReplaceTool.ts +++ b/src/core/tools/SearchReplaceTool.ts @@ -24,17 +24,9 @@ interface SearchReplaceParams { export class SearchReplaceTool extends BaseTool<"search_replace"> { readonly name = "search_replace" as const - parseLegacy(params: Partial>): SearchReplaceParams { - return { - file_path: params.file_path || "", - old_string: params.old_string || "", - new_string: params.new_string || "", - } - } - async execute(params: SearchReplaceParams, task: Task, callbacks: ToolCallbacks): Promise { const { file_path, old_string, new_string } = params - const { askApproval, handleError, pushToolResult, toolProtocol } = callbacks + const { askApproval, handleError, pushToolResult } = callbacks try { // Validate required parameters @@ -64,10 +56,7 @@ export class SearchReplaceTool extends BaseTool<"search_replace"> { task.consecutiveMistakeCount++ task.recordToolError("search_replace") pushToolResult( - formatResponse.toolError( - "The 'old_string' and 'new_string' parameters must be different.", - toolProtocol, - ), + formatResponse.toolError("The 'old_string' and 'new_string' parameters must be different."), ) return } @@ -84,7 +73,7 @@ export class SearchReplaceTool extends BaseTool<"search_replace"> { if (!accessAllowed) { await task.say("rooignore_error", relPath) - pushToolResult(formatResponse.rooIgnoreError(relPath, toolProtocol)) + pushToolResult(formatResponse.rooIgnoreError(relPath)) return } @@ -99,7 +88,7 @@ export class SearchReplaceTool extends BaseTool<"search_replace"> { task.recordToolError("search_replace") const errorMessage = `File not found: ${relPath}. Cannot perform search and replace on a non-existent file.` await task.say("error", errorMessage) - pushToolResult(formatResponse.toolError(errorMessage, toolProtocol)) + pushToolResult(formatResponse.toolError(errorMessage)) return } @@ -113,7 +102,7 @@ export class SearchReplaceTool extends BaseTool<"search_replace"> { task.recordToolError("search_replace") const errorMessage = `Failed to read file '${relPath}'. Please verify file permissions and try again.` await task.say("error", errorMessage) - pushToolResult(formatResponse.toolError(errorMessage, toolProtocol)) + pushToolResult(formatResponse.toolError(errorMessage)) return } @@ -130,7 +119,6 @@ export class SearchReplaceTool extends BaseTool<"search_replace"> { pushToolResult( formatResponse.toolError( `No match found for the specified 'old_string'. Please ensure it matches the file contents exactly, including whitespace and indentation.`, - toolProtocol, ), ) return @@ -142,7 +130,6 @@ export class SearchReplaceTool extends BaseTool<"search_replace"> { pushToolResult( formatResponse.toolError( `Found ${matchCount} matches for the specified 'old_string'. This tool can only replace ONE occurrence at a time. Please provide more context (3-5 lines before and after) to uniquely identify the specific instance you want to change.`, - toolProtocol, ), ) return diff --git a/src/core/tools/SwitchModeTool.ts b/src/core/tools/SwitchModeTool.ts index c5fedaedcf4..a60ce63bded 100644 --- a/src/core/tools/SwitchModeTool.ts +++ b/src/core/tools/SwitchModeTool.ts @@ -14,16 +14,9 @@ interface SwitchModeParams { export class SwitchModeTool extends BaseTool<"switch_mode"> { readonly name = "switch_mode" as const - parseLegacy(params: Partial>): SwitchModeParams { - return { - mode_slug: params.mode_slug || "", - reason: params.reason || "", - } - } - async execute(params: SwitchModeParams, task: Task, callbacks: ToolCallbacks): Promise { const { mode_slug, reason } = params - const { askApproval, handleError, pushToolResult, toolProtocol } = callbacks + const { askApproval, handleError, pushToolResult } = callbacks try { if (!mode_slug) { @@ -83,8 +76,8 @@ export class SwitchModeTool extends BaseTool<"switch_mode"> { const partialMessage = JSON.stringify({ tool: "switchMode", - mode: this.removeClosingTag("mode_slug", mode_slug, block.partial), - reason: this.removeClosingTag("reason", reason, block.partial), + mode: mode_slug ?? "", + reason: reason ?? "", }) await task.ask("tool", partialMessage, block.partial).catch(() => {}) diff --git a/src/core/tools/UpdateTodoListTool.ts b/src/core/tools/UpdateTodoListTool.ts index f8b3653b9a3..7414b713cf4 100644 --- a/src/core/tools/UpdateTodoListTool.ts +++ b/src/core/tools/UpdateTodoListTool.ts @@ -16,14 +16,8 @@ let approvedTodoList: TodoItem[] | undefined = undefined export class UpdateTodoListTool extends BaseTool<"update_todo_list"> { readonly name = "update_todo_list" as const - parseLegacy(params: Partial>): UpdateTodoListParams { - return { - todos: params.todos || "", - } - } - async execute(params: UpdateTodoListParams, task: Task, callbacks: ToolCallbacks): Promise { - const { pushToolResult, handleError, askApproval, toolProtocol } = callbacks + const { pushToolResult, handleError, askApproval } = callbacks try { const todosRaw = params.todos diff --git a/src/core/tools/UseMcpToolTool.ts b/src/core/tools/UseMcpToolTool.ts index e7ed744c78c..34763e24af2 100644 --- a/src/core/tools/UseMcpToolTool.ts +++ b/src/core/tools/UseMcpToolTool.ts @@ -25,18 +25,8 @@ type ValidationResult = export class UseMcpToolTool extends BaseTool<"use_mcp_tool"> { readonly name = "use_mcp_tool" as const - parseLegacy(params: Partial>): UseMcpToolParams { - // For legacy params, arguments come as a JSON string that needs parsing - // We don't parse here - let validateParams handle parsing and errors - return { - server_name: params.server_name || "", - tool_name: params.tool_name || "", - arguments: params.arguments as any, // Keep as string for validation to handle - } - } - async execute(params: UseMcpToolParams, task: Task, callbacks: ToolCallbacks): Promise { - const { askApproval, handleError, pushToolResult, toolProtocol } = callbacks + const { askApproval, handleError, pushToolResult } = callbacks try { // Validate parameters @@ -89,9 +79,9 @@ export class UseMcpToolTool extends BaseTool<"use_mcp_tool"> { const params = block.params const partialMessage = JSON.stringify({ type: "use_mcp_tool", - serverName: this.removeClosingTag("server_name", params.server_name, block.partial), - toolName: this.removeClosingTag("tool_name", params.tool_name, block.partial), - arguments: this.removeClosingTag("arguments", params.arguments, block.partial), + serverName: params.server_name ?? "", + toolName: params.tool_name ?? "", + arguments: params.arguments, } satisfies ClineAskUseMcpServer) await task.ask("use_mcp_server", partialMessage, true).catch(() => {}) @@ -116,31 +106,22 @@ export class UseMcpToolTool extends BaseTool<"use_mcp_tool"> { return { isValid: false } } - // Parse arguments if provided + // Native-only: arguments are already a structured object. let parsedArguments: Record | undefined - - if (params.arguments) { - // If arguments is already an object (from native protocol), use it - if (typeof params.arguments === "object") { - parsedArguments = params.arguments - } else if (typeof params.arguments === "string") { - // If arguments is a string (from legacy/XML protocol), parse it - try { - parsedArguments = JSON.parse(params.arguments) - } catch (error) { - task.consecutiveMistakeCount++ - task.recordToolError("use_mcp_tool") - await task.say("error", t("mcp:errors.invalidJsonArgument", { toolName: params.tool_name })) - task.didToolFailInCurrentTurn = true - - pushToolResult( - formatResponse.toolError( - formatResponse.invalidMcpToolArgumentError(params.server_name, params.tool_name), - ), - ) - return { isValid: false } - } + if (params.arguments !== undefined) { + if (typeof params.arguments !== "object" || params.arguments === null || Array.isArray(params.arguments)) { + task.consecutiveMistakeCount++ + task.recordToolError("use_mcp_tool") + await task.say("error", t("mcp:errors.invalidJsonArgument", { toolName: params.tool_name })) + task.didToolFailInCurrentTurn = true + pushToolResult( + formatResponse.toolError( + formatResponse.invalidMcpToolArgumentError(params.server_name, params.tool_name), + ), + ) + return { isValid: false } } + parsedArguments = params.arguments } return { diff --git a/src/core/tools/WriteToFileTool.ts b/src/core/tools/WriteToFileTool.ts index 11247ec03d6..c8455ef3d97 100644 --- a/src/core/tools/WriteToFileTool.ts +++ b/src/core/tools/WriteToFileTool.ts @@ -26,15 +26,8 @@ interface WriteToFileParams { export class WriteToFileTool extends BaseTool<"write_to_file"> { readonly name = "write_to_file" as const - parseLegacy(params: Partial>): WriteToFileParams { - return { - path: params.path || "", - content: params.content || "", - } - } - async execute(params: WriteToFileParams, task: Task, callbacks: ToolCallbacks): Promise { - const { pushToolResult, handleError, askApproval, removeClosingTag } = callbacks + const { pushToolResult, handleError, askApproval } = callbacks const relPath = params.path let newContent = params.content @@ -92,12 +85,12 @@ export class WriteToFileTool extends BaseTool<"write_to_file"> { newContent = unescapeHtmlEntities(newContent) } - const fullPath = relPath ? path.resolve(task.cwd, removeClosingTag("path", relPath)) : "" + const fullPath = relPath ? path.resolve(task.cwd, relPath) : "" const isOutsideWorkspace = isPathOutsideWorkspace(fullPath) const sharedMessageProps: ClineSayTool = { tool: fileExists ? "editedExistingFile" : "newFileCreated", - path: getReadablePath(task.cwd, removeClosingTag("path", relPath)), + path: getReadablePath(task.cwd, relPath), content: newContent, isOutsideWorkspace, isProtected: isWriteProtected, diff --git a/src/core/tools/__tests__/applyDiffTool.experiment.spec.ts b/src/core/tools/__tests__/applyDiffTool.experiment.spec.ts index 65d7cb67749..42e6e04cadf 100644 --- a/src/core/tools/__tests__/applyDiffTool.experiment.spec.ts +++ b/src/core/tools/__tests__/applyDiffTool.experiment.spec.ts @@ -1,5 +1,4 @@ import { EXPERIMENT_IDS } from "../../../shared/experiments" -import { TOOL_PROTOCOL } from "@roo-code/types" // Mock vscode vi.mock("vscode", () => ({ @@ -25,16 +24,15 @@ describe("applyDiffTool experiment routing", () => { let mockAskApproval: any let mockHandleError: any let mockPushToolResult: any - let mockRemoveClosingTag: any let mockProvider: any beforeEach(async () => { vi.clearAllMocks() - // Reset vscode mock to default behavior (XML protocol) + // Reset vscode mock to default behavior const vscode = await import("vscode") vi.mocked(vscode.workspace.getConfiguration).mockReturnValue({ - get: vi.fn().mockReturnValue(TOOL_PROTOCOL.XML), + get: vi.fn().mockReturnValue(undefined), } as any) mockProvider = { @@ -63,7 +61,6 @@ describe("applyDiffTool experiment routing", () => { maxTokens: 4096, contextWindow: 128000, supportsPromptCache: false, - supportsNativeTools: false, }, }), }, @@ -81,10 +78,9 @@ describe("applyDiffTool experiment routing", () => { mockAskApproval = vi.fn() mockHandleError = vi.fn() mockPushToolResult = vi.fn() - mockRemoveClosingTag = vi.fn((tag, value) => value) }) - it("should always use class-based tool with native protocol (XML deprecated)", async () => { + it("should always use class-based tool with native protocol", async () => { mockProvider.getState.mockResolvedValue({ experiments: { [EXPERIMENT_IDS.MULTI_FILE_APPLY_DIFF]: false, @@ -94,22 +90,13 @@ describe("applyDiffTool experiment routing", () => { // Mock the class-based tool to resolve successfully ;(applyDiffToolClass.handle as any).mockResolvedValue(undefined) - await multiApplyDiffTool( - mockCline, - mockBlock, - mockAskApproval, - mockHandleError, - mockPushToolResult, - mockRemoveClosingTag, - ) + await multiApplyDiffTool(mockCline, mockBlock, mockAskApproval, mockHandleError, mockPushToolResult) - // Always uses native protocol now (XML deprecated) + // MultiApplyDiffTool always delegates to the class-based tool. expect(applyDiffToolClass.handle).toHaveBeenCalledWith(mockCline, mockBlock, { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "native", }) }) @@ -119,26 +106,17 @@ describe("applyDiffTool experiment routing", () => { // Mock the class-based tool to resolve successfully ;(applyDiffToolClass.handle as any).mockResolvedValue(undefined) - await multiApplyDiffTool( - mockCline, - mockBlock, - mockAskApproval, - mockHandleError, - mockPushToolResult, - mockRemoveClosingTag, - ) + await multiApplyDiffTool(mockCline, mockBlock, mockAskApproval, mockHandleError, mockPushToolResult) - // Always uses native protocol now (XML deprecated) + // MultiApplyDiffTool always delegates to the class-based tool. expect(applyDiffToolClass.handle).toHaveBeenCalledWith(mockCline, mockBlock, { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "native", }) }) - it("should use class-based tool when MULTI_FILE_APPLY_DIFF experiment is enabled (native protocol always used)", async () => { + it("should use class-based tool when MULTI_FILE_APPLY_DIFF experiment is enabled", async () => { mockProvider.getState.mockResolvedValue({ experiments: { [EXPERIMENT_IDS.MULTI_FILE_APPLY_DIFF]: true, @@ -148,61 +126,15 @@ describe("applyDiffTool experiment routing", () => { // Mock the class-based tool to resolve successfully ;(applyDiffToolClass.handle as any).mockResolvedValue(undefined) - await multiApplyDiffTool( - mockCline, - mockBlock, - mockAskApproval, - mockHandleError, - mockPushToolResult, - mockRemoveClosingTag, - ) + await multiApplyDiffTool(mockCline, mockBlock, mockAskApproval, mockHandleError, mockPushToolResult) - // Native protocol is always used now, so class-based tool is always called + // MultiApplyDiffTool always delegates to the class-based tool. expect(applyDiffToolClass.handle).toHaveBeenCalledWith(mockCline, mockBlock, { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "native", }) }) - it("should use class-based tool when model defaults to native protocol", async () => { - // Update model to support native tools and default to native protocol - mockCline.api.getModel = vi.fn().mockReturnValue({ - id: "test-model", - info: { - maxTokens: 4096, - contextWindow: 128000, - supportsPromptCache: false, - supportsNativeTools: true, // Model supports native tools - defaultToolProtocol: "native", // Model defaults to native protocol - }, - }) - - mockProvider.getState.mockResolvedValue({ - experiments: { - [EXPERIMENT_IDS.MULTI_FILE_APPLY_DIFF]: true, - }, - }) - ;(applyDiffToolClass.handle as any).mockResolvedValue(undefined) - - await multiApplyDiffTool( - mockCline, - mockBlock, - mockAskApproval, - mockHandleError, - mockPushToolResult, - mockRemoveClosingTag, - ) - - // When native protocol is used, should always use class-based tool - expect(applyDiffToolClass.handle).toHaveBeenCalledWith(mockCline, mockBlock, { - askApproval: mockAskApproval, - handleError: mockHandleError, - pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "native", - }) - }) + // MultiApplyDiffTool always delegates to the class-based tool. }) diff --git a/src/core/tools/__tests__/askFollowupQuestionTool.spec.ts b/src/core/tools/__tests__/askFollowupQuestionTool.spec.ts index 074617130c9..e13f639ba00 100644 --- a/src/core/tools/__tests__/askFollowupQuestionTool.spec.ts +++ b/src/core/tools/__tests__/askFollowupQuestionTool.spec.ts @@ -27,7 +27,10 @@ describe("askFollowupQuestionTool", () => { name: "ask_followup_question", params: { question: "What would you like to do?", - follow_up: "Option 1Option 2", + }, + nativeArgs: { + question: "What would you like to do?", + follow_up: [{ text: "Option 1" }, { text: "Option 2" }], }, partial: false, } @@ -36,8 +39,6 @@ describe("askFollowupQuestionTool", () => { askApproval: vi.fn(), handleError: vi.fn(), pushToolResult: mockPushToolResult, - removeClosingTag: vi.fn((tag, content) => content), - toolProtocol: "xml", }) expect(mockCline.ask).toHaveBeenCalledWith( @@ -53,7 +54,13 @@ describe("askFollowupQuestionTool", () => { name: "ask_followup_question", params: { question: "What would you like to do?", - follow_up: 'Write codeDebug issue', + }, + nativeArgs: { + question: "What would you like to do?", + follow_up: [ + { text: "Write code", mode: "code" }, + { text: "Debug issue", mode: "debug" }, + ], }, partial: false, } @@ -62,8 +69,6 @@ describe("askFollowupQuestionTool", () => { askApproval: vi.fn(), handleError: vi.fn(), pushToolResult: mockPushToolResult, - removeClosingTag: vi.fn((tag, content) => content), - toolProtocol: "xml", }) expect(mockCline.ask).toHaveBeenCalledWith( @@ -81,7 +86,10 @@ describe("askFollowupQuestionTool", () => { name: "ask_followup_question", params: { question: "What would you like to do?", - follow_up: 'Regular optionPlan architecture', + }, + nativeArgs: { + question: "What would you like to do?", + follow_up: [{ text: "Regular option" }, { text: "Plan architecture", mode: "architect" }], }, partial: false, } @@ -90,8 +98,6 @@ describe("askFollowupQuestionTool", () => { askApproval: vi.fn(), handleError: vi.fn(), pushToolResult: mockPushToolResult, - removeClosingTag: vi.fn((tag, content) => content), - toolProtocol: "xml", }) expect(mockCline.ask).toHaveBeenCalledWith( @@ -122,8 +128,6 @@ describe("askFollowupQuestionTool", () => { askApproval: vi.fn(), handleError: vi.fn(), pushToolResult: mockPushToolResult, - removeClosingTag: vi.fn((tag, content) => content || ""), - toolProtocol: "native", }) // During partial streaming, only the question should be sent (not JSON with suggestions) @@ -144,8 +148,6 @@ describe("askFollowupQuestionTool", () => { askApproval: vi.fn(), handleError: vi.fn(), pushToolResult: mockPushToolResult, - removeClosingTag: vi.fn((tag, content) => content || ""), - toolProtocol: "xml", }) expect(mockCline.ask).toHaveBeenCalledWith("followup", "Choose wisely", true) diff --git a/src/core/tools/__tests__/attemptCompletionTool.spec.ts b/src/core/tools/__tests__/attemptCompletionTool.spec.ts index 3950e3ead71..9aac6296c6c 100644 --- a/src/core/tools/__tests__/attemptCompletionTool.spec.ts +++ b/src/core/tools/__tests__/attemptCompletionTool.spec.ts @@ -34,7 +34,6 @@ describe("attemptCompletionTool", () => { let mockPushToolResult: ReturnType let mockAskApproval: ReturnType let mockHandleError: ReturnType - let mockRemoveClosingTag: ReturnType let mockToolDescription: ReturnType let mockAskFinishSubTaskApproval: ReturnType let mockGetConfiguration: ReturnType @@ -43,7 +42,6 @@ describe("attemptCompletionTool", () => { mockPushToolResult = vi.fn() mockAskApproval = vi.fn() mockHandleError = vi.fn() - mockRemoveClosingTag = vi.fn() mockToolDescription = vi.fn() mockAskFinishSubTaskApproval = vi.fn() mockGetConfiguration = vi.fn(() => ({ @@ -62,6 +60,15 @@ describe("attemptCompletionTool", () => { consecutiveMistakeCount: 0, recordToolError: vi.fn(), todoList: undefined, + say: vi.fn().mockResolvedValue(undefined), + ask: vi.fn().mockResolvedValue({ response: "yesButtonClicked", text: "", images: [] }), + emitFinalTokenUsageUpdate: vi.fn(), + emit: vi.fn(), + getTokenUsage: vi.fn().mockReturnValue({}), + toolUsage: {}, + taskId: "task_1", + apiConfiguration: { apiProvider: "test" } as any, + api: { getModel: vi.fn().mockReturnValue({ id: "test-model", info: {} }) } as any, } }) @@ -71,6 +78,7 @@ describe("attemptCompletionTool", () => { type: "tool_use", name: "attempt_completion", params: { result: "Task completed successfully" }, + nativeArgs: { result: "Task completed successfully" }, partial: false, } @@ -80,10 +88,8 @@ describe("attemptCompletionTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, askFinishSubTaskApproval: mockAskFinishSubTaskApproval, toolDescription: mockToolDescription, - toolProtocol: "xml", } await attemptCompletionTool.handle(mockTask as Task, block, callbacks) @@ -97,6 +103,7 @@ describe("attemptCompletionTool", () => { type: "tool_use", name: "attempt_completion", params: { result: "Task completed successfully" }, + nativeArgs: { result: "Task completed successfully" }, partial: false, } @@ -106,10 +113,8 @@ describe("attemptCompletionTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, askFinishSubTaskApproval: mockAskFinishSubTaskApproval, toolDescription: mockToolDescription, - toolProtocol: "xml", } await attemptCompletionTool.handle(mockTask as Task, block, callbacks) @@ -122,6 +127,7 @@ describe("attemptCompletionTool", () => { type: "tool_use", name: "attempt_completion", params: { result: "Task completed successfully" }, + nativeArgs: { result: "Task completed successfully" }, partial: false, } @@ -136,10 +142,8 @@ describe("attemptCompletionTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, askFinishSubTaskApproval: mockAskFinishSubTaskApproval, toolDescription: mockToolDescription, - toolProtocol: "xml", } await attemptCompletionTool.handle(mockTask as Task, block, callbacks) @@ -152,6 +156,7 @@ describe("attemptCompletionTool", () => { type: "tool_use", name: "attempt_completion", params: { result: "Task completed successfully" }, + nativeArgs: { result: "Task completed successfully" }, partial: false, } @@ -176,10 +181,8 @@ describe("attemptCompletionTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, askFinishSubTaskApproval: mockAskFinishSubTaskApproval, toolDescription: mockToolDescription, - toolProtocol: "xml", } await attemptCompletionTool.handle(mockTask as Task, block, callbacks) @@ -195,6 +198,7 @@ describe("attemptCompletionTool", () => { type: "tool_use", name: "attempt_completion", params: { result: "Task completed successfully" }, + nativeArgs: { result: "Task completed successfully" }, partial: false, } @@ -219,10 +223,8 @@ describe("attemptCompletionTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, askFinishSubTaskApproval: mockAskFinishSubTaskApproval, toolDescription: mockToolDescription, - toolProtocol: "xml", } await attemptCompletionTool.handle(mockTask as Task, block, callbacks) @@ -238,6 +240,7 @@ describe("attemptCompletionTool", () => { type: "tool_use", name: "attempt_completion", params: { result: "Task completed successfully" }, + nativeArgs: { result: "Task completed successfully" }, partial: false, } @@ -263,10 +266,8 @@ describe("attemptCompletionTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, askFinishSubTaskApproval: mockAskFinishSubTaskApproval, toolDescription: mockToolDescription, - toolProtocol: "xml", } await attemptCompletionTool.handle(mockTask as Task, block, callbacks) @@ -282,6 +283,7 @@ describe("attemptCompletionTool", () => { type: "tool_use", name: "attempt_completion", params: { result: "Task completed successfully" }, + nativeArgs: { result: "Task completed successfully" }, partial: false, } @@ -306,10 +308,8 @@ describe("attemptCompletionTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, askFinishSubTaskApproval: mockAskFinishSubTaskApproval, toolDescription: mockToolDescription, - toolProtocol: "xml", } await attemptCompletionTool.handle(mockTask as Task, block, callbacks) @@ -326,6 +326,7 @@ describe("attemptCompletionTool", () => { type: "tool_use", name: "attempt_completion", params: { result: "Task completed successfully" }, + nativeArgs: { result: "Task completed successfully" }, partial: false, } @@ -350,10 +351,8 @@ describe("attemptCompletionTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, askFinishSubTaskApproval: mockAskFinishSubTaskApproval, toolDescription: mockToolDescription, - toolProtocol: "xml", } await attemptCompletionTool.handle(mockTask as Task, block, callbacks) @@ -370,6 +369,7 @@ describe("attemptCompletionTool", () => { type: "tool_use", name: "attempt_completion", params: { result: "Task completed successfully" }, + nativeArgs: { result: "Task completed successfully" }, partial: false, } @@ -394,10 +394,8 @@ describe("attemptCompletionTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, askFinishSubTaskApproval: mockAskFinishSubTaskApproval, toolDescription: mockToolDescription, - toolProtocol: "xml", } await attemptCompletionTool.handle(mockTask as Task, block, callbacks) @@ -415,6 +413,7 @@ describe("attemptCompletionTool", () => { type: "tool_use", name: "attempt_completion", params: { result: "Task completed successfully" }, + nativeArgs: { result: "Task completed successfully" }, partial: false, } @@ -425,10 +424,8 @@ describe("attemptCompletionTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, askFinishSubTaskApproval: mockAskFinishSubTaskApproval, toolDescription: mockToolDescription, - toolProtocol: "xml", } const mockSay = vi.fn() @@ -450,6 +447,7 @@ describe("attemptCompletionTool", () => { type: "tool_use", name: "attempt_completion", params: { result: "Task completed successfully" }, + nativeArgs: { result: "Task completed successfully" }, partial: false, } @@ -460,10 +458,8 @@ describe("attemptCompletionTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, askFinishSubTaskApproval: mockAskFinishSubTaskApproval, toolDescription: mockToolDescription, - toolProtocol: "xml", } await attemptCompletionTool.handle(mockTask as Task, block, callbacks) diff --git a/src/core/tools/__tests__/editFileTool.spec.ts b/src/core/tools/__tests__/editFileTool.spec.ts index 96ca18c5d3f..80d431edab2 100644 --- a/src/core/tools/__tests__/editFileTool.spec.ts +++ b/src/core/tools/__tests__/editFileTool.spec.ts @@ -91,7 +91,6 @@ describe("editFileTool", () => { let mockAskApproval: ReturnType let mockHandleError: ReturnType let mockPushToolResult: ReturnType - let mockRemoveClosingTag: ReturnType let toolResult: ToolResponse | undefined beforeEach(() => { @@ -153,7 +152,6 @@ describe("editFileTool", () => { mockAskApproval = vi.fn().mockResolvedValue(true) mockHandleError = vi.fn().mockResolvedValue(undefined) - mockRemoveClosingTag = vi.fn((tag, content) => content) toolResult = undefined }) @@ -179,6 +177,19 @@ describe("editFileTool", () => { mockedFsReadFile.mockResolvedValue(fileContent) mockTask.rooIgnoreController.validateAccess.mockReturnValue(accessAllowed) + const nativeArgs: Record = { + file_path: testFilePath, + old_string: testOldString, + new_string: testNewString, + } + for (const [key, value] of Object.entries(params)) { + nativeArgs[key] = value + } + // Keep expected_replacements numeric in native args when provided. + if (typeof nativeArgs.expected_replacements === "string") { + nativeArgs.expected_replacements = Number(nativeArgs.expected_replacements) + } + const toolUse: ToolUse = { type: "tool_use", name: "edit_file", @@ -188,6 +199,7 @@ describe("editFileTool", () => { new_string: testNewString, ...params, }, + nativeArgs: nativeArgs as any, partial: isPartial, } @@ -199,8 +211,6 @@ describe("editFileTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "native", }) return toolResult @@ -278,8 +288,6 @@ describe("editFileTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: localPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "native", }) return capturedResult @@ -476,7 +484,10 @@ describe("editFileTool", () => { ) expect(mockTask.consecutiveMistakeCountForEditFile.get(testFilePath)).toBe(2) - expect(mockTask.say).toHaveBeenCalledWith("diff_error", expect.stringContaining("Occurrence count mismatch")) + expect(mockTask.say).toHaveBeenCalledWith( + "diff_error", + expect.stringContaining("Occurrence count mismatch"), + ) }) it("resets consecutive error counter on successful edit", async () => { @@ -629,6 +640,11 @@ describe("editFileTool", () => { old_string: testOldString, new_string: testNewString, }, + nativeArgs: { + file_path: testFilePath, + old_string: testOldString, + new_string: testNewString, + }, partial: false, } @@ -641,8 +657,6 @@ describe("editFileTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: localPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "native", }) expect(capturedResult).toContain("Failed to read file") diff --git a/src/core/tools/__tests__/executeCommandTimeout.integration.spec.ts b/src/core/tools/__tests__/executeCommandTimeout.integration.spec.ts index f93a29caaf4..6036755f061 100644 --- a/src/core/tools/__tests__/executeCommandTimeout.integration.spec.ts +++ b/src/core/tools/__tests__/executeCommandTimeout.integration.spec.ts @@ -206,7 +206,6 @@ describe("Command Execution Timeout Integration", () => { let mockAskApproval: any let mockHandleError: any let mockPushToolResult: any - let mockRemoveClosingTag: any beforeEach(() => { // Reset mocks for allowlist tests @@ -214,19 +213,24 @@ describe("Command Execution Timeout Integration", () => { ;(fs.access as any).mockResolvedValue(undefined) ;(TerminalRegistry.getOrCreateTerminal as any).mockResolvedValue(mockTerminal) - // Mock the executeCommandTool parameters + // Mock the executeCommandTool parameters (native-only) mockBlock = { + type: "tool_use", + name: "execute_command", params: { command: "", cwd: undefined, }, + nativeArgs: { + command: "", + cwd: undefined, + }, partial: false, } mockAskApproval = vitest.fn().mockResolvedValue(true) // Always approve mockHandleError = vitest.fn() mockPushToolResult = vitest.fn() - mockRemoveClosingTag = vitest.fn() // Mock task with additional properties needed by executeCommandTool mockTask = { @@ -266,6 +270,7 @@ describe("Command Execution Timeout Integration", () => { ;(vscode.workspace.getConfiguration as any).mockReturnValue(mockGetConfiguration()) mockBlock.params.command = "npm install" + mockBlock.nativeArgs.command = "npm install" // Create a process that would timeout if not allowlisted const longRunningProcess = new Promise((resolve) => { @@ -277,8 +282,6 @@ describe("Command Execution Timeout Integration", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) // Should complete successfully without timeout because "npm" is in allowlist @@ -299,6 +302,7 @@ describe("Command Execution Timeout Integration", () => { ;(vscode.workspace.getConfiguration as any).mockReturnValue(mockGetConfiguration()) mockBlock.params.command = "sleep 10" // Not in allowlist + mockBlock.nativeArgs.command = "sleep 10" // Create a process that never resolves const neverResolvingProcess = new Promise(() => {}) @@ -309,8 +313,6 @@ describe("Command Execution Timeout Integration", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) // Should timeout because "sleep" is not in allowlist @@ -331,6 +333,7 @@ describe("Command Execution Timeout Integration", () => { ;(vscode.workspace.getConfiguration as any).mockReturnValue(mockGetConfiguration()) mockBlock.params.command = "npm install" + mockBlock.nativeArgs.command = "npm install" // Create a process that never resolves const neverResolvingProcess = new Promise(() => {}) @@ -341,8 +344,6 @@ describe("Command Execution Timeout Integration", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) // Should timeout because allowlist is empty @@ -370,14 +371,13 @@ describe("Command Execution Timeout Integration", () => { // Test exact prefix match - should not timeout mockBlock.params.command = "git log --oneline" + mockBlock.nativeArgs.command = "git log --oneline" mockTerminal.runCommand.mockReturnValueOnce(longRunningProcess) await executeCommandTool.handle(mockTask as Task, mockBlock, { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) expect(mockPushToolResult).toHaveBeenCalled() @@ -389,14 +389,13 @@ describe("Command Execution Timeout Integration", () => { // Test partial prefix match (should not match) - should timeout mockBlock.params.command = "git status" // "git" alone is not in allowlist, only "git log" + mockBlock.nativeArgs.command = "git status" mockTerminal.runCommand.mockReturnValueOnce(neverResolvingProcess) await executeCommandTool.handle(mockTask as Task, mockBlock, { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) expect(mockPushToolResult).toHaveBeenCalled() diff --git a/src/core/tools/__tests__/executeCommandTool.spec.ts b/src/core/tools/__tests__/executeCommandTool.spec.ts index 0406a83d2a2..89b2575288b 100644 --- a/src/core/tools/__tests__/executeCommandTool.spec.ts +++ b/src/core/tools/__tests__/executeCommandTool.spec.ts @@ -5,7 +5,7 @@ import * as vscode from "vscode" import { Task } from "../../task/Task" import { formatResponse } from "../../prompts/responses" -import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../../shared/tools" +import { ToolUse, AskApproval, HandleError, PushToolResult } from "../../../shared/tools" import { unescapeHtmlEntities } from "../../../utils/text-normalization" // Mock dependencies @@ -47,7 +47,6 @@ describe("executeCommandTool", () => { let mockAskApproval: any let mockHandleError: any let mockPushToolResult: any - let mockRemoveClosingTag: any let mockToolUse: ToolUse<"execute_command"> beforeEach(() => { @@ -86,7 +85,6 @@ describe("executeCommandTool", () => { mockAskApproval = vitest.fn().mockResolvedValue(true) mockHandleError = vitest.fn().mockResolvedValue(undefined) mockPushToolResult = vitest.fn() - mockRemoveClosingTag = vitest.fn().mockReturnValue("command") // Setup vscode config mock const mockConfig = { @@ -101,6 +99,9 @@ describe("executeCommandTool", () => { params: { command: "echo test", }, + nativeArgs: { + command: "echo test", + }, partial: false, } }) @@ -140,14 +141,13 @@ describe("executeCommandTool", () => { it("should execute a command normally", async () => { // Setup mockToolUse.params.command = "echo test" + mockToolUse.nativeArgs = { command: "echo test" } // Execute using the class-based handle method await executeCommandTool.handle(mockCline as unknown as Task, mockToolUse, { askApproval: mockAskApproval as unknown as AskApproval, handleError: mockHandleError as unknown as HandleError, pushToolResult: mockPushToolResult as unknown as PushToolResult, - removeClosingTag: mockRemoveClosingTag as unknown as RemoveClosingTag, - toolProtocol: "xml", }) // Verify @@ -162,14 +162,13 @@ describe("executeCommandTool", () => { // Setup mockToolUse.params.command = "echo test" mockToolUse.params.cwd = "/custom/path" + mockToolUse.nativeArgs = { command: "echo test", cwd: "/custom/path" } // Execute await executeCommandTool.handle(mockCline as unknown as Task, mockToolUse, { askApproval: mockAskApproval as unknown as AskApproval, handleError: mockHandleError as unknown as HandleError, pushToolResult: mockPushToolResult as unknown as PushToolResult, - removeClosingTag: mockRemoveClosingTag as unknown as RemoveClosingTag, - toolProtocol: "xml", }) // Verify - confirm the command was approved and result was pushed @@ -185,14 +184,14 @@ describe("executeCommandTool", () => { it("should handle missing command parameter", async () => { // Setup mockToolUse.params.command = undefined + // Native tool calls must still supply a value; simulate a missing value with an empty string. + mockToolUse.nativeArgs = { command: "" } // Execute await executeCommandTool.handle(mockCline as unknown as Task, mockToolUse, { askApproval: mockAskApproval as unknown as AskApproval, handleError: mockHandleError as unknown as HandleError, pushToolResult: mockPushToolResult as unknown as PushToolResult, - removeClosingTag: mockRemoveClosingTag as unknown as RemoveClosingTag, - toolProtocol: "xml", }) // Verify @@ -207,14 +206,13 @@ describe("executeCommandTool", () => { // Setup mockToolUse.params.command = "echo test" mockAskApproval.mockResolvedValue(false) + mockToolUse.nativeArgs = { command: "echo test" } // Execute await executeCommandTool.handle(mockCline as unknown as Task, mockToolUse, { askApproval: mockAskApproval as unknown as AskApproval, handleError: mockHandleError as unknown as HandleError, pushToolResult: mockPushToolResult as unknown as PushToolResult, - removeClosingTag: mockRemoveClosingTag as unknown as RemoveClosingTag, - toolProtocol: "xml", }) // Verify @@ -226,6 +224,7 @@ describe("executeCommandTool", () => { it("should handle rooignore validation failures", async () => { // Setup mockToolUse.params.command = "cat .env" + mockToolUse.nativeArgs = { command: "cat .env" } // Override the validateCommand mock to return a filename const validateCommandMock = vitest.fn().mockReturnValue(".env") mockCline.rooIgnoreController = { @@ -240,14 +239,12 @@ describe("executeCommandTool", () => { askApproval: mockAskApproval as unknown as AskApproval, handleError: mockHandleError as unknown as HandleError, pushToolResult: mockPushToolResult as unknown as PushToolResult, - removeClosingTag: mockRemoveClosingTag as unknown as RemoveClosingTag, - toolProtocol: "xml", }) // Verify expect(validateCommandMock).toHaveBeenCalledWith("cat .env") expect(mockCline.say).toHaveBeenCalledWith("rooignore_error", ".env") - expect(formatResponse.rooIgnoreError).toHaveBeenCalledWith(".env", "xml") + expect(formatResponse.rooIgnoreError).toHaveBeenCalledWith(".env") expect(mockPushToolResult).toHaveBeenCalledWith(mockRooIgnoreError) expect(mockAskApproval).not.toHaveBeenCalled() // executeCommandInTerminal should not be called since rooignore blocked it diff --git a/src/core/tools/__tests__/generateImageTool.test.ts b/src/core/tools/__tests__/generateImageTool.test.ts index 483533e34dd..9acd654537f 100644 --- a/src/core/tools/__tests__/generateImageTool.test.ts +++ b/src/core/tools/__tests__/generateImageTool.test.ts @@ -21,7 +21,6 @@ describe("generateImageTool", () => { let mockAskApproval: any let mockHandleError: any let mockPushToolResult: any - let mockRemoveClosingTag: any beforeEach(() => { vi.clearAllMocks() @@ -60,7 +59,6 @@ describe("generateImageTool", () => { mockAskApproval = vi.fn().mockResolvedValue(true) mockHandleError = vi.fn() mockPushToolResult = vi.fn() - mockRemoveClosingTag = vi.fn((tag, content) => content || "") // Mock file system operations vi.mocked(fileUtils.fileExistsAtPath).mockResolvedValue(true) @@ -79,6 +77,10 @@ describe("generateImageTool", () => { prompt: "Generate a test image", path: "test-image.png", }, + nativeArgs: { + prompt: "Generate a test image", + path: "test-image.png", + }, partial: true, } @@ -86,8 +88,6 @@ describe("generateImageTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) // Should not process anything when partial @@ -105,6 +105,11 @@ describe("generateImageTool", () => { path: "upscaled-image.png", image: "source-image.png", }, + nativeArgs: { + prompt: "Upscale this image", + path: "upscaled-image.png", + image: "source-image.png", + }, partial: true, } @@ -112,8 +117,6 @@ describe("generateImageTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) // Should not process anything when partial @@ -131,6 +134,10 @@ describe("generateImageTool", () => { prompt: "Generate a test image", path: "test-image.png", }, + nativeArgs: { + prompt: "Generate a test image", + path: "test-image.png", + }, partial: false, } @@ -151,8 +158,6 @@ describe("generateImageTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) // Should process the complete block @@ -169,6 +174,10 @@ describe("generateImageTool", () => { prompt: "Generate a test image", path: "test-image.png", }, + nativeArgs: { + prompt: "Generate a test image", + path: "test-image.png", + }, partial: false, } @@ -193,8 +202,6 @@ describe("generateImageTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) // Check that cline.say was called with image data containing cache-busting parameter @@ -223,6 +230,9 @@ describe("generateImageTool", () => { params: { path: "test-image.png", }, + nativeArgs: { + path: "test-image.png", + } as any, partial: false, } @@ -230,8 +240,6 @@ describe("generateImageTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) expect(mockCline.consecutiveMistakeCount).toBe(1) @@ -247,6 +255,9 @@ describe("generateImageTool", () => { params: { prompt: "Generate a test image", }, + nativeArgs: { + prompt: "Generate a test image", + } as any, partial: false, } @@ -254,8 +265,6 @@ describe("generateImageTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) expect(mockCline.consecutiveMistakeCount).toBe(1) @@ -281,6 +290,10 @@ describe("generateImageTool", () => { prompt: "Generate a test image", path: "test-image.png", }, + nativeArgs: { + prompt: "Generate a test image", + path: "test-image.png", + }, partial: false, } @@ -288,8 +301,6 @@ describe("generateImageTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) expect(mockPushToolResult).toHaveBeenCalledWith( @@ -312,6 +323,11 @@ describe("generateImageTool", () => { path: "upscaled.png", image: "non-existent.png", }, + nativeArgs: { + prompt: "Upscale this image", + path: "upscaled.png", + image: "non-existent.png", + }, partial: false, } @@ -319,8 +335,6 @@ describe("generateImageTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) expect(mockCline.say).toHaveBeenCalledWith("error", expect.stringContaining("Input image not found")) @@ -336,6 +350,11 @@ describe("generateImageTool", () => { path: "upscaled.png", image: "test.bmp", // Unsupported format }, + nativeArgs: { + prompt: "Upscale this image", + path: "upscaled.png", + image: "test.bmp", + }, partial: false, } @@ -343,8 +362,6 @@ describe("generateImageTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) expect(mockCline.say).toHaveBeenCalledWith("error", expect.stringContaining("Unsupported image format")) diff --git a/src/core/tools/__tests__/multiApplyDiffTool.spec.ts b/src/core/tools/__tests__/multiApplyDiffTool.spec.ts index 0910550dd82..009022d9c09 100644 --- a/src/core/tools/__tests__/multiApplyDiffTool.spec.ts +++ b/src/core/tools/__tests__/multiApplyDiffTool.spec.ts @@ -9,7 +9,6 @@ import * as pathUtils from "../../../utils/path" vi.mock("fs/promises") vi.mock("../../../utils/fs") vi.mock("../../../utils/path") -vi.mock("../../../utils/xml") // Mock the ApplyDiffTool class-based tool that MultiApplyDiffTool delegates to for native protocol vi.mock("../ApplyDiffTool", () => ({ @@ -93,7 +92,6 @@ describe("multiApplyDiffTool", () => { maxTokens: 4096, contextWindow: 128000, supportsPromptCache: false, - supportsNativeTools: false, }, }), }, @@ -122,8 +120,10 @@ describe("multiApplyDiffTool", () => { }) describe("Native protocol delegation", () => { - it("should delegate to applyDiffToolClass.handle for XML args format", async () => { + it("delegates XML args format to the class-based tool (no XML parsing in wrapper)", async () => { mockBlock = { + type: "tool_use", + name: "apply_diff", params: { args: ` test.ts @@ -135,28 +135,13 @@ describe("multiApplyDiffTool", () => { partial: false, } - await applyDiffTool( - mockCline, - mockBlock, - mockAskApproval, - mockHandleError, - mockPushToolResult, - mockRemoveClosingTag, - ) + await applyDiffTool(mockCline, mockBlock, mockAskApproval, mockHandleError, mockPushToolResult) - // Should delegate to the class-based tool - expect(applyDiffToolClass.handle).toHaveBeenCalled() - expect(applyDiffToolClass.handle).toHaveBeenCalledWith( - mockCline, - mockBlock, - expect.objectContaining({ - askApproval: mockAskApproval, - handleError: mockHandleError, - pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "native", - }), - ) + expect(applyDiffToolClass.handle).toHaveBeenCalledWith(mockCline, mockBlock, { + askApproval: mockAskApproval, + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + }) }) it("should delegate to applyDiffToolClass.handle for legacy path/diff params", async () => { @@ -168,28 +153,14 @@ describe("multiApplyDiffTool", () => { partial: false, } - await applyDiffTool( - mockCline, - mockBlock, - mockAskApproval, - mockHandleError, - mockPushToolResult, - mockRemoveClosingTag, - ) + await applyDiffTool(mockCline, mockBlock, mockAskApproval, mockHandleError, mockPushToolResult) // Should delegate to the class-based tool - expect(applyDiffToolClass.handle).toHaveBeenCalled() - expect(applyDiffToolClass.handle).toHaveBeenCalledWith( - mockCline, - mockBlock, - expect.objectContaining({ - askApproval: mockAskApproval, - handleError: mockHandleError, - pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "native", - }), - ) + expect(applyDiffToolClass.handle).toHaveBeenCalledWith(mockCline, mockBlock, { + askApproval: mockAskApproval, + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + }) }) it("should handle undefined diff content by delegating to class-based tool", async () => { @@ -201,14 +172,7 @@ describe("multiApplyDiffTool", () => { partial: false, } - await applyDiffTool( - mockCline, - mockBlock, - mockAskApproval, - mockHandleError, - mockPushToolResult, - mockRemoveClosingTag, - ) + await applyDiffTool(mockCline, mockBlock, mockAskApproval, mockHandleError, mockPushToolResult) // Should delegate to the class-based tool (which will handle the error) expect(applyDiffToolClass.handle).toHaveBeenCalled() @@ -227,14 +191,7 @@ describe("multiApplyDiffTool", () => { partial: false, } - await applyDiffTool( - mockCline, - mockBlock, - mockAskApproval, - mockHandleError, - mockPushToolResult, - mockRemoveClosingTag, - ) + await applyDiffTool(mockCline, mockBlock, mockAskApproval, mockHandleError, mockPushToolResult) // Should delegate to the class-based tool expect(applyDiffToolClass.handle).toHaveBeenCalled() @@ -261,14 +218,7 @@ another new content partial: false, } - await applyDiffTool( - mockCline, - mockBlock, - mockAskApproval, - mockHandleError, - mockPushToolResult, - mockRemoveClosingTag, - ) + await applyDiffTool(mockCline, mockBlock, mockAskApproval, mockHandleError, mockPushToolResult) // Should delegate to the class-based tool expect(applyDiffToolClass.handle).toHaveBeenCalled() @@ -289,14 +239,7 @@ new content partial: false, } - await applyDiffTool( - mockCline, - mockBlock, - mockAskApproval, - mockHandleError, - mockPushToolResult, - mockRemoveClosingTag, - ) + await applyDiffTool(mockCline, mockBlock, mockAskApproval, mockHandleError, mockPushToolResult) // Should delegate to the class-based tool expect(applyDiffToolClass.handle).toHaveBeenCalled() @@ -315,14 +258,7 @@ new content partial: false, } - await applyDiffTool( - mockCline, - mockBlock, - mockAskApproval, - mockHandleError, - mockPushToolResult, - mockRemoveClosingTag, - ) + await applyDiffTool(mockCline, mockBlock, mockAskApproval, mockHandleError, mockPushToolResult) // Should delegate to the class-based tool expect(applyDiffToolClass.handle).toHaveBeenCalled() @@ -342,14 +278,7 @@ new content partial: false, } - await applyDiffTool( - mockCline, - mockBlock, - mockAskApproval, - mockHandleError, - mockPushToolResult, - mockRemoveClosingTag, - ) + await applyDiffTool(mockCline, mockBlock, mockAskApproval, mockHandleError, mockPushToolResult) // Should delegate to the class-based tool expect(applyDiffToolClass.handle).toHaveBeenCalled() diff --git a/src/core/tools/__tests__/newTaskTool.spec.ts b/src/core/tools/__tests__/newTaskTool.spec.ts index 975f754ee56..fc383c13eef 100644 --- a/src/core/tools/__tests__/newTaskTool.spec.ts +++ b/src/core/tools/__tests__/newTaskTool.spec.ts @@ -1,6 +1,6 @@ // npx vitest core/tools/__tests__/newTaskTool.spec.ts -import type { AskApproval, HandleError } from "../../../shared/tools" +import type { AskApproval, HandleError, NativeToolArgs, ToolUse } from "../../../shared/tools" // Mock vscode module vi.mock("vscode", () => ({ @@ -67,7 +67,6 @@ type MockClineInstance = { taskId: string } const mockAskApproval = vi.fn() const mockHandleError = vi.fn() const mockPushToolResult = vi.fn() -const mockRemoveClosingTag = vi.fn((_name: string, value: string | undefined) => value ?? "") const mockEmit = vi.fn() const mockRecordToolError = vi.fn() const mockSayAndCreateMissingParamError = vi.fn() @@ -109,10 +108,21 @@ const mockCline = { // Import the class to test AFTER mocks are set up import { newTaskTool } from "../NewTaskTool" -import type { ToolUse } from "../../../shared/tools" import { getModeBySlug } from "../../../shared/modes" import * as vscode from "vscode" +const withNativeArgs = (block: ToolUse<"new_task">): ToolUse<"new_task"> => ({ + ...block, + // Native tool calling: `nativeArgs` is the source of truth for tool execution. + // These tests intentionally exercise missing-param behavior, so we allow undefined + // values and let the tool's runtime validation handle it. + nativeArgs: { + mode: block.params.mode, + message: block.params.message, + todos: block.params.todos, + } as unknown as NativeToolArgs["new_task"], +}) + describe("newTaskTool", () => { beforeEach(() => { // Reset mocks before each test @@ -134,7 +144,7 @@ describe("newTaskTool", () => { }) it("should correctly un-escape \\\\@ to \\@ in the message passed to the new task", async () => { - const block: ToolUse = { + const block: ToolUse<"new_task"> = { type: "tool_use", // Add required 'type' property name: "new_task", // Correct property name params: { @@ -145,12 +155,10 @@ describe("newTaskTool", () => { partial: false, } - await newTaskTool.handle(mockCline as any, block as ToolUse<"new_task">, { + await newTaskTool.handle(mockCline as any, withNativeArgs(block), { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) // Verify askApproval was called @@ -171,7 +179,7 @@ describe("newTaskTool", () => { }) it("should not un-escape single escaped \@", async () => { - const block: ToolUse = { + const block: ToolUse<"new_task"> = { type: "tool_use", // Add required 'type' property name: "new_task", // Correct property name params: { @@ -182,12 +190,10 @@ describe("newTaskTool", () => { partial: false, } - await newTaskTool.handle(mockCline as any, block as ToolUse<"new_task">, { + await newTaskTool.handle(mockCline as any, withNativeArgs(block), { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) expect(mockStartSubtask).toHaveBeenCalledWith( @@ -198,7 +204,7 @@ describe("newTaskTool", () => { }) it("should not un-escape non-escaped @", async () => { - const block: ToolUse = { + const block: ToolUse<"new_task"> = { type: "tool_use", // Add required 'type' property name: "new_task", // Correct property name params: { @@ -209,12 +215,10 @@ describe("newTaskTool", () => { partial: false, } - await newTaskTool.handle(mockCline as any, block as ToolUse<"new_task">, { + await newTaskTool.handle(mockCline as any, withNativeArgs(block), { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) expect(mockStartSubtask).toHaveBeenCalledWith( @@ -225,7 +229,7 @@ describe("newTaskTool", () => { }) it("should handle mixed escaping scenarios", async () => { - const block: ToolUse = { + const block: ToolUse<"new_task"> = { type: "tool_use", // Add required 'type' property name: "new_task", // Correct property name params: { @@ -236,12 +240,10 @@ describe("newTaskTool", () => { partial: false, } - await newTaskTool.handle(mockCline as any, block as ToolUse<"new_task">, { + await newTaskTool.handle(mockCline as any, withNativeArgs(block), { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) expect(mockStartSubtask).toHaveBeenCalledWith( @@ -252,7 +254,7 @@ describe("newTaskTool", () => { }) it("should handle missing todos parameter gracefully (backward compatibility)", async () => { - const block: ToolUse = { + const block: ToolUse<"new_task"> = { type: "tool_use", name: "new_task", params: { @@ -263,12 +265,10 @@ describe("newTaskTool", () => { partial: false, } - await newTaskTool.handle(mockCline as any, block as ToolUse<"new_task">, { + await newTaskTool.handle(mockCline as any, withNativeArgs(block), { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) // Should NOT error when todos is missing @@ -284,7 +284,7 @@ describe("newTaskTool", () => { }) it("should work with todos parameter when provided", async () => { - const block: ToolUse = { + const block: ToolUse<"new_task"> = { type: "tool_use", name: "new_task", params: { @@ -295,12 +295,10 @@ describe("newTaskTool", () => { partial: false, } - await newTaskTool.handle(mockCline as any, block as ToolUse<"new_task">, { + await newTaskTool.handle(mockCline as any, withNativeArgs(block), { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) // Should parse and include todos when provided @@ -317,7 +315,7 @@ describe("newTaskTool", () => { }) it("should error when mode parameter is missing", async () => { - const block: ToolUse = { + const block: ToolUse<"new_task"> = { type: "tool_use", name: "new_task", params: { @@ -328,12 +326,10 @@ describe("newTaskTool", () => { partial: false, } - await newTaskTool.handle(mockCline as any, block as ToolUse<"new_task">, { + await newTaskTool.handle(mockCline as any, withNativeArgs(block), { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) expect(mockSayAndCreateMissingParamError).toHaveBeenCalledWith("new_task", "mode") @@ -342,7 +338,7 @@ describe("newTaskTool", () => { }) it("should error when message parameter is missing", async () => { - const block: ToolUse = { + const block: ToolUse<"new_task"> = { type: "tool_use", name: "new_task", params: { @@ -353,12 +349,10 @@ describe("newTaskTool", () => { partial: false, } - await newTaskTool.handle(mockCline as any, block as ToolUse<"new_task">, { + await newTaskTool.handle(mockCline as any, withNativeArgs(block), { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) expect(mockSayAndCreateMissingParamError).toHaveBeenCalledWith("new_task", "message") @@ -367,7 +361,7 @@ describe("newTaskTool", () => { }) it("should parse todos with different statuses correctly", async () => { - const block: ToolUse = { + const block: ToolUse<"new_task"> = { type: "tool_use", name: "new_task", params: { @@ -378,12 +372,10 @@ describe("newTaskTool", () => { partial: false, } - await newTaskTool.handle(mockCline as any, block as ToolUse<"new_task">, { + await newTaskTool.handle(mockCline as any, withNativeArgs(block), { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) expect(mockStartSubtask).toHaveBeenCalledWith( @@ -405,7 +397,7 @@ describe("newTaskTool", () => { get: mockGet, } as any) - const block: ToolUse = { + const block: ToolUse<"new_task"> = { type: "tool_use", name: "new_task", params: { @@ -416,12 +408,10 @@ describe("newTaskTool", () => { partial: false, } - await newTaskTool.handle(mockCline as any, block as ToolUse<"new_task">, { + await newTaskTool.handle(mockCline as any, withNativeArgs(block), { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) // Should NOT error when todos is missing and setting is disabled @@ -443,7 +433,7 @@ describe("newTaskTool", () => { get: mockGet, } as any) - const block: ToolUse = { + const block: ToolUse<"new_task"> = { type: "tool_use", name: "new_task", params: { @@ -454,12 +444,10 @@ describe("newTaskTool", () => { partial: false, } - await newTaskTool.handle(mockCline as any, block as ToolUse<"new_task">, { + await newTaskTool.handle(mockCline as any, withNativeArgs(block), { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) // Should error when todos is missing and setting is enabled @@ -481,7 +469,7 @@ describe("newTaskTool", () => { get: mockGet, } as any) - const block: ToolUse = { + const block: ToolUse<"new_task"> = { type: "tool_use", name: "new_task", params: { @@ -492,12 +480,10 @@ describe("newTaskTool", () => { partial: false, } - await newTaskTool.handle(mockCline as any, block as ToolUse<"new_task">, { + await newTaskTool.handle(mockCline as any, withNativeArgs(block), { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) // Should NOT error when todos is provided and setting is enabled @@ -525,7 +511,7 @@ describe("newTaskTool", () => { get: mockGet, } as any) - const block: ToolUse = { + const block: ToolUse<"new_task"> = { type: "tool_use", name: "new_task", params: { @@ -536,12 +522,10 @@ describe("newTaskTool", () => { partial: false, } - await newTaskTool.handle(mockCline as any, block as ToolUse<"new_task">, { + await newTaskTool.handle(mockCline as any, withNativeArgs(block), { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) // Should NOT error when todos is empty string and setting is enabled @@ -562,7 +546,7 @@ describe("newTaskTool", () => { } as any) vi.mocked(vscode.workspace.getConfiguration).mockImplementation(mockGetConfiguration) - const block: ToolUse = { + const block: ToolUse<"new_task"> = { type: "tool_use", name: "new_task", params: { @@ -572,12 +556,10 @@ describe("newTaskTool", () => { partial: false, } - await newTaskTool.handle(mockCline as any, block as ToolUse<"new_task">, { + await newTaskTool.handle(mockCline as any, withNativeArgs(block), { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) // Verify that VSCode configuration was accessed with Package.name @@ -597,7 +579,7 @@ describe("newTaskTool", () => { const pkg = await import("../../../shared/package") ;(pkg.Package as any).name = "roo-code-nightly" - const block: ToolUse = { + const block: ToolUse<"new_task"> = { type: "tool_use", name: "new_task", params: { @@ -607,12 +589,10 @@ describe("newTaskTool", () => { partial: false, } - await newTaskTool.handle(mockCline as any, block as ToolUse<"new_task">, { + await newTaskTool.handle(mockCline as any, withNativeArgs(block), { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) // Assert: configuration was read using the dynamic nightly namespace @@ -656,7 +636,7 @@ describe("newTaskTool delegation flow", () => { }, } - const block: ToolUse = { + const block: ToolUse<"new_task"> = { type: "tool_use", name: "new_task", params: { @@ -668,12 +648,10 @@ describe("newTaskTool delegation flow", () => { } // Act - await newTaskTool.handle(localCline as any, block as ToolUse<"new_task">, { + await newTaskTool.handle(localCline as any, withNativeArgs(block), { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) // Assert: provider method called with correct params diff --git a/src/core/tools/__tests__/readFileTool.spec.ts b/src/core/tools/__tests__/readFileTool.spec.ts index 0ab89c4f95d..e74b41ad465 100644 --- a/src/core/tools/__tests__/readFileTool.spec.ts +++ b/src/core/tools/__tests__/readFileTool.spec.ts @@ -7,7 +7,7 @@ import { readLines } from "../../../integrations/misc/read-lines" import { extractTextFromFile } from "../../../integrations/misc/extract-text" import { parseSourceCodeDefinitionsForFile } from "../../../services/tree-sitter" import { isBinaryFile } from "isbinaryfile" -import { ReadFileToolUse, ToolParamName, ToolResponse } from "../../../shared/tools" +import { ReadFileToolUse, ToolResponse } from "../../../shared/tools" import { readFileTool } from "../ReadFileTool" vi.mock("path", async () => { @@ -222,7 +222,7 @@ function createMockCline(): any { presentAssistantMessage: vi.fn(), handleError: vi.fn().mockResolvedValue(undefined), pushToolResult: vi.fn(), - removeClosingTag: vi.fn((tag, content) => content), + // Tool calling is native-only; tests should not depend on legacy tag-stripping helpers. fileContextTracker: { trackFileContext: vi.fn().mockResolvedValue(undefined), }, @@ -244,7 +244,7 @@ function createMockCline(): any { contextWindow: 200000, maxTokens: 4096, supportsPromptCache: false, - supportsNativeTools: false, + // (native tool support is determined at request-time; no model flag) }, }), }, @@ -351,19 +351,30 @@ describe("read_file tool with maxReadFileLine setting", () => { // Reset the spy before each test addLineNumbersMock.mockClear() - // Format args string based on params - let argsContent = `${options.path || testFilePath}` - if (options.start_line && options.end_line) { - argsContent += `${options.start_line}-${options.end_line}` - } - argsContent += `` + const lineRanges = + options.start_line && options.end_line + ? [ + { + start: Number(options.start_line), + end: Number(options.end_line), + }, + ] + : [] // Create a tool use object const toolUse: ReadFileToolUse = { type: "tool_use", name: "read_file", - params: { args: argsContent, ...params }, + params: { ...params }, partial: false, + nativeArgs: { + files: [ + { + path: options.path || testFilePath, + lineRanges, + }, + ], + }, } await readFileTool.handle(mockCline, toolUse, { @@ -372,8 +383,6 @@ describe("read_file tool with maxReadFileLine setting", () => { pushToolResult: (result: ToolResponse) => { toolResult = result }, - removeClosingTag: (_: ToolParamName, content?: string) => content ?? "", - toolProtocol: "xml", }) return toolResult @@ -588,7 +597,7 @@ describe("read_file tool with maxReadFileLine setting", () => { }) describe("read_file tool output structure", () => { - // Test basic XML structure + // Test basic native structure const testFilePath = "test/file.txt" const absoluteFilePath = "/test/file.txt" const fileContent = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5" @@ -655,14 +664,12 @@ describe("read_file tool output structure", () => { }) async function executeReadFileTool( - params: { - args?: string - } = {}, options: { totalLines?: number maxReadFileLine?: number isBinary?: boolean validateAccess?: boolean + filePath?: string } = {}, ): Promise { // Configure mocks based on test scenario @@ -675,15 +682,17 @@ describe("read_file tool output structure", () => { mockedCountFileLines.mockResolvedValue(totalLines) mockedIsBinaryFile.mockResolvedValue(isBinary) mockCline.rooIgnoreController.validateAccess = vi.fn().mockReturnValue(validateAccess) - - let argsContent = `${testFilePath}` + const filePath = options.filePath ?? testFilePath // Create a tool use object const toolUse: ReadFileToolUse = { type: "tool_use", name: "read_file", - params: { args: argsContent, ...params }, + params: {}, partial: false, + nativeArgs: { + files: [{ path: filePath, lineRanges: [] }], + }, } // Execute the tool @@ -693,8 +702,6 @@ describe("read_file tool output structure", () => { pushToolResult: (result: ToolResponse) => { toolResult = result }, - removeClosingTag: (param: ToolParamName, content?: string) => content ?? "", - toolProtocol: "xml", }) return toolResult @@ -730,7 +737,7 @@ describe("read_file tool output structure", () => { // Setup mockInputContent = fileContent // Execute - const result = await executeReadFileTool({}, { maxReadFileLine: -1 }) + const result = await executeReadFileTool({ maxReadFileLine: -1 }) // Verify using regex to check native structure const nativeStructureRegex = new RegExp(`^File: ${testFilePath}\\nLines 1-5:\\n.*$`, "s") @@ -756,7 +763,7 @@ describe("read_file tool output structure", () => { }) // Allow up to 20MB per image and total size // Execute - const result = await executeReadFileTool({}, { totalLines: 0 }) + const result = await executeReadFileTool({ totalLines: 0 }) // Verify native format for empty file expect(result).toBe(`File: ${testFilePath}\nNote: File is empty`) @@ -785,15 +792,14 @@ describe("read_file tool output structure", () => { // Ensure image support is enabled before calling the tool setImageSupport(mockCline, true) - // Create args content for multiple files - const filesXml = imagePaths.map((path) => `${path}`).join("") - const argsContent = filesXml - const toolUse: ReadFileToolUse = { type: "tool_use", name: "read_file", - params: { args: argsContent }, + params: {}, partial: false, + nativeArgs: { + files: imagePaths.map((p) => ({ path: p, lineRanges: [] })), + }, } let localResult: ToolResponse | undefined @@ -803,8 +809,6 @@ describe("read_file tool output structure", () => { pushToolResult: (result: ToolResponse) => { localResult = result }, - removeClosingTag: (_: ToolParamName, content?: string) => content ?? "", - toolProtocol: "xml", }) // In multi-image scenarios, the result is pushed to pushToolResult, not returned directly. // We need to check the mock's calls to get the result. @@ -845,7 +849,6 @@ describe("read_file tool output structure", () => { mockCline.presentAssistantMessage = vi.fn() mockCline.handleError = vi.fn().mockResolvedValue(undefined) mockCline.pushToolResult = vi.fn() - mockCline.removeClosingTag = vi.fn((tag, content) => content) mockCline.fileContextTracker = { trackFileContext: vi.fn().mockResolvedValue(undefined), } @@ -918,7 +921,6 @@ describe("read_file tool output structure", () => { mockCline.presentAssistantMessage = vi.fn() mockCline.handleError = vi.fn().mockResolvedValue(undefined) mockCline.pushToolResult = vi.fn() - mockCline.removeClosingTag = vi.fn((tag, content) => content) mockCline.fileContextTracker = { trackFileContext: vi.fn().mockResolvedValue(undefined), } @@ -1004,7 +1006,6 @@ describe("read_file tool output structure", () => { mockCline.presentAssistantMessage = vi.fn() mockCline.handleError = vi.fn().mockResolvedValue(undefined) mockCline.pushToolResult = vi.fn() - mockCline.removeClosingTag = vi.fn((tag, content) => content) mockCline.fileContextTracker = { trackFileContext: vi.fn().mockResolvedValue(undefined), } @@ -1077,7 +1078,6 @@ describe("read_file tool output structure", () => { mockCline.presentAssistantMessage = vi.fn() mockCline.handleError = vi.fn().mockResolvedValue(undefined) mockCline.pushToolResult = vi.fn() - mockCline.removeClosingTag = vi.fn((tag, content) => content) mockCline.fileContextTracker = { trackFileContext: vi.fn().mockResolvedValue(undefined), } @@ -1202,7 +1202,6 @@ describe("read_file tool output structure", () => { mockCline.presentAssistantMessage = vi.fn() mockCline.handleError = vi.fn().mockResolvedValue(undefined) mockCline.pushToolResult = vi.fn() - mockCline.removeClosingTag = vi.fn((tag, content) => content) mockCline.fileContextTracker = { trackFileContext: vi.fn().mockResolvedValue(undefined), } @@ -1249,7 +1248,6 @@ describe("read_file tool output structure", () => { mockCline.presentAssistantMessage = vi.fn() mockCline.handleError = vi.fn().mockResolvedValue(undefined) mockCline.pushToolResult = vi.fn() - mockCline.removeClosingTag = vi.fn((tag, content) => content) mockCline.fileContextTracker = { trackFileContext: vi.fn().mockResolvedValue(undefined), } @@ -1415,6 +1413,9 @@ describe("read_file tool output structure", () => { name: "read_file", params: {}, partial: false, + nativeArgs: { + files: [], + }, } // Execute the tool @@ -1424,8 +1425,6 @@ describe("read_file tool output structure", () => { pushToolResult: (result: ToolResponse) => { toolResult = result }, - removeClosingTag: (param: ToolParamName, content?: string) => content ?? "", - toolProtocol: "xml", }) // Verify - native format for error @@ -1434,7 +1433,7 @@ describe("read_file tool output structure", () => { it("should include error for RooIgnore error", async () => { // Execute - skip addLineNumbers check as it returns early with an error - const result = await executeReadFileTool({}, { validateAccess: false }) + const result = await executeReadFileTool({ validateAccess: false }) // Verify - native format for error expect(result).toBe( @@ -1460,7 +1459,7 @@ describe("read_file tool output structure", () => { mockedIsBinaryFile.mockResolvedValue(false) // Execute - const result = await executeReadFileTool({ args: `${dirPath}` }) + const result = await executeReadFileTool({ filePath: dirPath }) // Verify - native format for error expect(result).toContain(`File: ${dirPath}`) @@ -1530,12 +1529,14 @@ describe("read_file tool with image support", () => { }) async function executeReadImageTool(imagePath: string = testImagePath): Promise { - const argsContent = `${imagePath}` const toolUse: ReadFileToolUse = { type: "tool_use", name: "read_file", - params: { args: argsContent }, + params: {}, partial: false, + nativeArgs: { + files: [{ path: imagePath, lineRanges: [] }], + }, } // Debug: Check if mock is working @@ -1548,8 +1549,6 @@ describe("read_file tool with image support", () => { pushToolResult: (result: ToolResponse) => { toolResult = result }, - removeClosingTag: (_: ToolParamName, content?: string) => content ?? "", - toolProtocol: "xml", }) console.log("Result type:", Array.isArray(toolResult) ? "array" : typeof toolResult) @@ -1710,12 +1709,14 @@ describe("read_file tool with image support", () => { mockedFsReadFile.mockRejectedValue(new Error("Failed to read image")) // Execute - const argsContent = `${testImagePath}` const toolUse: ReadFileToolUse = { type: "tool_use", name: "read_file", - params: { args: argsContent }, + params: {}, partial: false, + nativeArgs: { + files: [{ path: testImagePath, lineRanges: [] }], + }, } await readFileTool.handle(localMockCline, toolUse, { @@ -1724,8 +1725,6 @@ describe("read_file tool with image support", () => { pushToolResult: (result: ToolResponse) => { toolResult = result }, - removeClosingTag: (_: ToolParamName, content?: string) => content ?? "", - toolProtocol: "xml", }) // Verify error handling - native format @@ -1871,15 +1870,14 @@ describe("read_file tool concurrent file reads limit", () => { maxTotalImageSize: 20, }) - // Create args with the specified number of files - const files = Array.from({ length: fileCount }, (_, i) => `file${i + 1}.txt`) - const argsContent = files.join("") - const toolUse: ReadFileToolUse = { type: "tool_use", name: "read_file", - params: { args: argsContent }, + params: {}, partial: false, + nativeArgs: { + files: Array.from({ length: fileCount }, (_, i) => ({ path: `file${i + 1}.txt`, lineRanges: [] })), + }, } // Configure mocks for successful file reads @@ -1896,8 +1894,6 @@ describe("read_file tool concurrent file reads limit", () => { pushToolResult: (result: ToolResponse) => { toolResult = result }, - removeClosingTag: (_: ToolParamName, content?: string) => content ?? "", - toolProtocol: "xml", }) return toolResult @@ -1978,15 +1974,14 @@ describe("read_file tool concurrent file reads limit", () => { maxTotalImageSize: 20, }) - // Create args with 6 files - const files = Array.from({ length: 6 }, (_, i) => `file${i + 1}.txt`) - const argsContent = files.join("") - const toolUse: ReadFileToolUse = { type: "tool_use", name: "read_file", - params: { args: argsContent }, + params: {}, partial: false, + nativeArgs: { + files: Array.from({ length: 6 }, (_, i) => ({ path: `file${i + 1}.txt`, lineRanges: [] })), + }, } mockReadFileWithTokenBudget.mockResolvedValue({ @@ -2002,8 +1997,6 @@ describe("read_file tool concurrent file reads limit", () => { pushToolResult: (result: ToolResponse) => { toolResult = result }, - removeClosingTag: (_: ToolParamName, content?: string) => content ?? "", - toolProtocol: "xml", }) // Should use default limit of 5 and reject 6 files diff --git a/src/core/tools/__tests__/runSlashCommandTool.spec.ts b/src/core/tools/__tests__/runSlashCommandTool.spec.ts index eef6259deb5..9aa7970b99b 100644 --- a/src/core/tools/__tests__/runSlashCommandTool.spec.ts +++ b/src/core/tools/__tests__/runSlashCommandTool.spec.ts @@ -39,7 +39,6 @@ describe("runSlashCommandTool", () => { askApproval: vi.fn().mockResolvedValue(true), handleError: vi.fn(), pushToolResult: vi.fn(), - removeClosingTag: vi.fn((tag, text) => text || ""), } }) @@ -49,6 +48,9 @@ describe("runSlashCommandTool", () => { name: "run_slash_command" as const, params: {}, partial: false, + nativeArgs: { + command: "", + }, } await runSlashCommandTool.handle(mockTask as Task, block, mockCallbacks) @@ -63,10 +65,11 @@ describe("runSlashCommandTool", () => { const block: ToolUse<"run_slash_command"> = { type: "tool_use" as const, name: "run_slash_command" as const, - params: { + params: {}, + partial: false, + nativeArgs: { command: "nonexistent", }, - partial: false, } vi.mocked(getCommand).mockResolvedValue(undefined) @@ -84,10 +87,11 @@ describe("runSlashCommandTool", () => { const block: ToolUse<"run_slash_command"> = { type: "tool_use" as const, name: "run_slash_command" as const, - params: { + params: {}, + partial: false, + nativeArgs: { command: "init", }, - partial: false, } const mockCommand = { @@ -111,10 +115,11 @@ describe("runSlashCommandTool", () => { const block: ToolUse<"run_slash_command"> = { type: "tool_use" as const, name: "run_slash_command" as const, - params: { + params: {}, + partial: false, + nativeArgs: { command: "init", }, - partial: false, } const mockCommand = { @@ -155,11 +160,12 @@ Initialize project content here`, const block: ToolUse<"run_slash_command"> = { type: "tool_use" as const, name: "run_slash_command" as const, - params: { + params: {}, + partial: false, + nativeArgs: { command: "test", args: "focus on unit tests", }, - partial: false, } const mockCommand = { @@ -192,10 +198,11 @@ Run tests with specific focus`, const block: ToolUse<"run_slash_command"> = { type: "tool_use" as const, name: "run_slash_command" as const, - params: { + params: {}, + partial: false, + nativeArgs: { command: "deploy", }, - partial: false, } const mockCommand = { @@ -225,6 +232,7 @@ Deploy application to production`, name: "run_slash_command" as const, params: { command: "init", + args: "", }, partial: true, } @@ -248,10 +256,11 @@ Deploy application to production`, const block: ToolUse<"run_slash_command"> = { type: "tool_use" as const, name: "run_slash_command" as const, - params: { + params: {}, + partial: false, + nativeArgs: { command: "init", }, - partial: false, } const error = new Error("Test error") @@ -266,10 +275,11 @@ Deploy application to production`, const block: ToolUse<"run_slash_command"> = { type: "tool_use" as const, name: "run_slash_command" as const, - params: { + params: {}, + partial: false, + nativeArgs: { command: "nonexistent", }, - partial: false, } vi.mocked(getCommand).mockResolvedValue(undefined) @@ -286,10 +296,11 @@ Deploy application to production`, const block: ToolUse<"run_slash_command"> = { type: "tool_use" as const, name: "run_slash_command" as const, - params: { + params: {}, + partial: false, + nativeArgs: { command: "init", }, - partial: false, } mockTask.consecutiveMistakeCount = 5 @@ -313,10 +324,11 @@ Deploy application to production`, const block: ToolUse<"run_slash_command"> = { type: "tool_use" as const, name: "run_slash_command" as const, - params: { + params: {}, + partial: false, + nativeArgs: { command: "debug-app", }, - partial: false, } const mockCommand = { @@ -360,10 +372,11 @@ Start debugging the application`, const block: ToolUse<"run_slash_command"> = { type: "tool_use" as const, name: "run_slash_command" as const, - params: { + params: {}, + partial: false, + nativeArgs: { command: "test", }, - partial: false, } const mockCommand = { @@ -395,10 +408,11 @@ Start debugging the application`, const block: ToolUse<"run_slash_command"> = { type: "tool_use" as const, name: "run_slash_command" as const, - params: { + params: {}, + partial: false, + nativeArgs: { command: "debug-app", }, - partial: false, } const mockCommand = { diff --git a/src/core/tools/__tests__/searchAndReplaceTool.spec.ts b/src/core/tools/__tests__/searchAndReplaceTool.spec.ts index 4566ca202e5..241d7b67b0d 100644 --- a/src/core/tools/__tests__/searchAndReplaceTool.spec.ts +++ b/src/core/tools/__tests__/searchAndReplaceTool.spec.ts @@ -89,7 +89,6 @@ describe("searchAndReplaceTool", () => { let mockAskApproval: ReturnType let mockHandleError: ReturnType let mockPushToolResult: ReturnType - let mockRemoveClosingTag: ReturnType let toolResult: ToolResponse | undefined beforeEach(() => { @@ -149,7 +148,6 @@ describe("searchAndReplaceTool", () => { mockAskApproval = vi.fn().mockResolvedValue(true) mockHandleError = vi.fn().mockResolvedValue(undefined) - mockRemoveClosingTag = vi.fn((tag, content) => content) toolResult = undefined }) @@ -175,14 +173,22 @@ describe("searchAndReplaceTool", () => { mockedFsReadFile.mockResolvedValue(fileContent) mockTask.rooIgnoreController.validateAccess.mockReturnValue(accessAllowed) + const baseParams: Record = { + path: testFilePath, + operations: JSON.stringify([{ search: "Line 2", replace: "Modified Line 2" }]), + } + const fullParams: Record = { ...baseParams, ...params } + const nativeArgs: Record = { + path: fullParams.path, + operations: + typeof fullParams.operations === "string" ? JSON.parse(fullParams.operations) : fullParams.operations, + } + const toolUse: ToolUse = { type: "tool_use", name: "search_and_replace", - params: { - path: testFilePath, - operations: JSON.stringify([{ search: "Line 2", replace: "Modified Line 2" }]), - ...params, - }, + params: fullParams as any, + nativeArgs: nativeArgs as any, partial: isPartial, } @@ -194,8 +200,6 @@ describe("searchAndReplaceTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "native", }) return toolResult @@ -367,6 +371,10 @@ describe("searchAndReplaceTool", () => { path: testFilePath, operations: JSON.stringify([{ search: "Line 2", replace: "Modified" }]), }, + nativeArgs: { + path: testFilePath, + operations: [{ search: "Line 2", replace: "Modified" }], + }, partial: false, } @@ -379,8 +387,6 @@ describe("searchAndReplaceTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: localPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "native", }) expect(capturedResult).toContain("Error:") diff --git a/src/core/tools/__tests__/searchReplaceTool.spec.ts b/src/core/tools/__tests__/searchReplaceTool.spec.ts index 4f69e8e8591..1b1f78a1288 100644 --- a/src/core/tools/__tests__/searchReplaceTool.spec.ts +++ b/src/core/tools/__tests__/searchReplaceTool.spec.ts @@ -91,7 +91,6 @@ describe("searchReplaceTool", () => { let mockAskApproval: ReturnType let mockHandleError: ReturnType let mockPushToolResult: ReturnType - let mockRemoveClosingTag: ReturnType let toolResult: ToolResponse | undefined beforeEach(() => { @@ -151,7 +150,6 @@ describe("searchReplaceTool", () => { mockAskApproval = vi.fn().mockResolvedValue(true) mockHandleError = vi.fn().mockResolvedValue(undefined) - mockRemoveClosingTag = vi.fn((tag, content) => content) toolResult = undefined }) @@ -177,6 +175,15 @@ describe("searchReplaceTool", () => { mockedFsReadFile.mockResolvedValue(fileContent) mockCline.rooIgnoreController.validateAccess.mockReturnValue(accessAllowed) + const nativeArgs: Record = { + file_path: testFilePath, + old_string: testOldString, + new_string: testNewString, + } + for (const [key, value] of Object.entries(params)) { + nativeArgs[key] = value + } + const toolUse: ToolUse = { type: "tool_use", name: "search_replace", @@ -186,6 +193,7 @@ describe("searchReplaceTool", () => { new_string: testNewString, ...params, }, + nativeArgs: nativeArgs as any, partial: isPartial, } @@ -197,8 +205,6 @@ describe("searchReplaceTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "native", }) return toolResult @@ -344,6 +350,11 @@ describe("searchReplaceTool", () => { old_string: testOldString, new_string: testNewString, }, + nativeArgs: { + file_path: testFilePath, + old_string: testOldString, + new_string: testNewString, + }, partial: false, } @@ -356,8 +367,6 @@ describe("searchReplaceTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: localPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "native", }) expect(capturedResult).toContain("Error:") diff --git a/src/core/tools/__tests__/useMcpToolTool.spec.ts b/src/core/tools/__tests__/useMcpToolTool.spec.ts index 130047ae15b..e6d1e13e3f4 100644 --- a/src/core/tools/__tests__/useMcpToolTool.spec.ts +++ b/src/core/tools/__tests__/useMcpToolTool.spec.ts @@ -80,6 +80,11 @@ describe("useMcpToolTool", () => { tool_name: "test_tool", arguments: "{}", }, + nativeArgs: { + server_name: "", + tool_name: "test_tool", + arguments: {}, + }, partial: false, } @@ -89,8 +94,6 @@ describe("useMcpToolTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) expect(mockTask.consecutiveMistakeCount).toBe(1) @@ -107,6 +110,11 @@ describe("useMcpToolTool", () => { server_name: "test_server", arguments: "{}", }, + nativeArgs: { + server_name: "test_server", + tool_name: "", + arguments: {}, + }, partial: false, } @@ -116,8 +124,6 @@ describe("useMcpToolTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) expect(mockTask.consecutiveMistakeCount).toBe(1) @@ -126,7 +132,7 @@ describe("useMcpToolTool", () => { expect(mockPushToolResult).toHaveBeenCalledWith("Missing tool_name error") }) - it("should handle invalid JSON arguments", async () => { + it("should handle invalid arguments type", async () => { const block: ToolUse = { type: "tool_use", name: "use_mcp_tool", @@ -135,6 +141,12 @@ describe("useMcpToolTool", () => { tool_name: "test_tool", arguments: "invalid json", }, + nativeArgs: { + server_name: "test_server", + tool_name: "test_tool", + // Native-only: invalid arguments are rejected unless they are an object. + arguments: [] as unknown as any, + }, partial: false, } @@ -158,8 +170,6 @@ describe("useMcpToolTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) expect(mockTask.consecutiveMistakeCount).toBe(1) @@ -188,8 +198,6 @@ describe("useMcpToolTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) expect(mockTask.ask).toHaveBeenCalledWith("use_mcp_server", expect.stringContaining("use_mcp_tool"), true) @@ -206,6 +214,11 @@ describe("useMcpToolTool", () => { tool_name: "test_tool", arguments: '{"param": "value"}', }, + nativeArgs: { + server_name: "test_server", + tool_name: "test_tool", + arguments: { param: "value" }, + }, partial: false, } @@ -227,8 +240,6 @@ describe("useMcpToolTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) expect(mockTask.consecutiveMistakeCount).toBe(0) @@ -247,12 +258,26 @@ describe("useMcpToolTool", () => { tool_name: "test_tool", arguments: "{}", }, + nativeArgs: { + server_name: "test_server", + tool_name: "test_tool", + arguments: {}, + }, partial: false, } - // Ensure validation does not fail due to unknown server by returning no provider once - // This makes validateToolExists return isValid: true and proceed to askApproval - mockProviderRef.deref.mockReturnValueOnce(undefined as any) + // Ensure server/tool validation passes so we actually reach askApproval. + mockProviderRef.deref.mockReturnValueOnce({ + getMcpHub: () => ({ + getAllServers: vi + .fn() + .mockReturnValue([ + { name: "test_server", tools: [{ name: "test_tool", description: "desc" }] }, + ]), + callTool: vi.fn(), + }), + postMessageToWebview: vi.fn(), + }) mockAskApproval.mockResolvedValue(false) @@ -260,12 +285,11 @@ describe("useMcpToolTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) expect(mockTask.say).not.toHaveBeenCalledWith("mcp_server_request_started") - expect(mockPushToolResult).not.toHaveBeenCalled() + expect(mockAskApproval).toHaveBeenCalled() + expect(mockPushToolResult).not.toHaveBeenCalledWith(expect.stringContaining("Tool result:")) }) }) @@ -278,6 +302,10 @@ describe("useMcpToolTool", () => { server_name: "test_server", tool_name: "test_tool", }, + nativeArgs: { + server_name: "test_server", + tool_name: "test_tool", + }, partial: false, } @@ -301,8 +329,6 @@ describe("useMcpToolTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) expect(mockHandleError).toHaveBeenCalledWith("executing MCP tool", error) @@ -338,6 +364,11 @@ describe("useMcpToolTool", () => { tool_name: "non-existing-tool", arguments: JSON.stringify({ test: "data" }), }, + nativeArgs: { + server_name: "test-server", + tool_name: "non-existing-tool", + arguments: { test: "data" }, + }, partial: false, } @@ -345,8 +376,6 @@ describe("useMcpToolTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) expect(mockTask.consecutiveMistakeCount).toBe(1) @@ -384,6 +413,11 @@ describe("useMcpToolTool", () => { tool_name: "any-tool", arguments: JSON.stringify({ test: "data" }), }, + nativeArgs: { + server_name: "test-server", + tool_name: "any-tool", + arguments: { test: "data" }, + }, partial: false, } @@ -391,8 +425,6 @@ describe("useMcpToolTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) expect(mockTask.consecutiveMistakeCount).toBe(1) @@ -432,6 +464,11 @@ describe("useMcpToolTool", () => { tool_name: "valid-tool", arguments: JSON.stringify({ test: "data" }), }, + nativeArgs: { + server_name: "test-server", + tool_name: "valid-tool", + arguments: { test: "data" }, + }, partial: false, } @@ -441,8 +478,6 @@ describe("useMcpToolTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) expect(mockTask.consecutiveMistakeCount).toBe(0) @@ -474,6 +509,11 @@ describe("useMcpToolTool", () => { tool_name: "any-tool", arguments: "{}", }, + nativeArgs: { + server_name: "unknown", + tool_name: "any-tool", + arguments: {}, + }, partial: false, } @@ -482,8 +522,6 @@ describe("useMcpToolTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) // Assert @@ -516,6 +554,11 @@ describe("useMcpToolTool", () => { tool_name: "any-tool", arguments: "{}", }, + nativeArgs: { + server_name: "unknown", + tool_name: "any-tool", + arguments: {}, + }, partial: false, } @@ -524,8 +567,6 @@ describe("useMcpToolTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) // Assert diff --git a/src/core/tools/__tests__/writeToFileTool.spec.ts b/src/core/tools/__tests__/writeToFileTool.spec.ts index fd791729b4d..6c63387ee10 100644 --- a/src/core/tools/__tests__/writeToFileTool.spec.ts +++ b/src/core/tools/__tests__/writeToFileTool.spec.ts @@ -106,7 +106,6 @@ describe("writeToFileTool", () => { let mockAskApproval: ReturnType let mockHandleError: ReturnType let mockPushToolResult: ReturnType - let mockRemoveClosingTag: ReturnType let toolResult: ToolResponse | undefined beforeEach(() => { @@ -184,7 +183,6 @@ describe("writeToFileTool", () => { mockAskApproval = vi.fn().mockResolvedValue(true) mockHandleError = vi.fn().mockResolvedValue(undefined) - mockRemoveClosingTag = vi.fn((tag, content) => content) toolResult = undefined }) @@ -217,6 +215,10 @@ describe("writeToFileTool", () => { content: testContent, ...params, }, + nativeArgs: { + path: (params.path ?? testFilePath) as any, + content: (params.content ?? testContent) as any, + }, partial: isPartial, } @@ -228,8 +230,6 @@ describe("writeToFileTool", () => { askApproval: mockAskApproval, handleError: mockHandleError, pushToolResult: mockPushToolResult, - removeClosingTag: mockRemoveClosingTag, - toolProtocol: "xml", }) return toolResult diff --git a/src/core/tools/accessMcpResourceTool.ts b/src/core/tools/accessMcpResourceTool.ts index 65b0e410782..9df3b2256c5 100644 --- a/src/core/tools/accessMcpResourceTool.ts +++ b/src/core/tools/accessMcpResourceTool.ts @@ -14,15 +14,8 @@ interface AccessMcpResourceParams { export class AccessMcpResourceTool extends BaseTool<"access_mcp_resource"> { readonly name = "access_mcp_resource" as const - parseLegacy(params: Partial>): AccessMcpResourceParams { - return { - server_name: params.server_name || "", - uri: params.uri || "", - } - } - async execute(params: AccessMcpResourceParams, task: Task, callbacks: ToolCallbacks): Promise { - const { askApproval, handleError, pushToolResult, toolProtocol } = callbacks + const { askApproval, handleError, pushToolResult } = callbacks const { server_name, uri } = params try { @@ -51,7 +44,7 @@ export class AccessMcpResourceTool extends BaseTool<"access_mcp_resource"> { const didApprove = await askApproval("use_mcp_server", completeMessage) if (!didApprove) { - pushToolResult(formatResponse.toolDenied(toolProtocol)) + pushToolResult(formatResponse.toolDenied()) return } @@ -91,8 +84,8 @@ export class AccessMcpResourceTool extends BaseTool<"access_mcp_resource"> { } override async handlePartial(task: Task, block: ToolUse<"access_mcp_resource">): Promise { - const server_name = this.removeClosingTag("server_name", block.params.server_name, true) - const uri = this.removeClosingTag("uri", block.params.uri, true) + const server_name = block.params.server_name ?? "" + const uri = block.params.uri ?? "" const partialMessage = JSON.stringify({ type: "access_mcp_resource", diff --git a/src/core/tools/helpers/__tests__/toolResultFormatting.spec.ts b/src/core/tools/helpers/__tests__/toolResultFormatting.spec.ts index 8f83381f17e..7e953de959c 100644 --- a/src/core/tools/helpers/__tests__/toolResultFormatting.spec.ts +++ b/src/core/tools/helpers/__tests__/toolResultFormatting.spec.ts @@ -1,82 +1,17 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from "vitest" -import * as vscode from "vscode" -import { TOOL_PROTOCOL, isNativeProtocol } from "@roo-code/types" -import { formatToolInvocation, getCurrentToolProtocol } from "../toolResultFormatting" - -vi.mock("vscode", () => ({ - workspace: { - getConfiguration: vi.fn(), - }, -})) +import { describe, it, expect } from "vitest" +import { formatToolInvocation } from "../toolResultFormatting" describe("toolResultFormatting", () => { - let mockGetConfiguration: ReturnType - - beforeEach(() => { - mockGetConfiguration = vi.fn() - ;(vscode.workspace.getConfiguration as any).mockReturnValue({ - get: mockGetConfiguration, - }) - }) - - afterEach(() => { - vi.clearAllMocks() - }) - - describe("getCurrentToolProtocol", () => { - it("should return configured protocol", () => { - mockGetConfiguration.mockReturnValue(TOOL_PROTOCOL.NATIVE) - expect(getCurrentToolProtocol()).toBe(TOOL_PROTOCOL.NATIVE) - }) - - it("should default to xml when config is not set", () => { - mockGetConfiguration.mockReturnValue("xml") - expect(getCurrentToolProtocol()).toBe("xml") - }) - }) - - describe("isNativeProtocol", () => { - it("should return true for native protocol", () => { - expect(isNativeProtocol(TOOL_PROTOCOL.NATIVE)).toBe(true) - }) - - it("should return false for XML protocol", () => { - expect(isNativeProtocol("xml")).toBe(false) - }) - }) - describe("formatToolInvocation", () => { - it("should format for XML protocol", () => { - const result = formatToolInvocation("read_file", { path: "test.ts" }, "xml") - - expect(result).toContain("") - expect(result).toContain("") - expect(result).toContain("test.ts") - expect(result).toContain("") - expect(result).toContain("") - }) - - it("should format for native protocol", () => { - const result = formatToolInvocation("read_file", { path: "test.ts" }, TOOL_PROTOCOL.NATIVE) + it("should format", () => { + const result = formatToolInvocation("read_file", { path: "test.ts" }) expect(result).toBe("Called read_file with path: test.ts") expect(result).not.toContain("<") }) - it("should handle multiple parameters for XML", () => { - const result = formatToolInvocation( - "read_file", - { path: "test.ts", start_line: "1", end_line: "10" }, - "xml", - ) - - expect(result).toContain("\ntest.ts\n") - expect(result).toContain("\n1\n") - expect(result).toContain("\n10\n") - }) - - it("should handle multiple parameters for native", () => { - const result = formatToolInvocation("read_file", { path: "test.ts", start_line: "1" }, TOOL_PROTOCOL.NATIVE) + it("should handle multiple parameters", () => { + const result = formatToolInvocation("read_file", { path: "test.ts", start_line: "1" }) expect(result).toContain("Called read_file with") expect(result).toContain("path: test.ts") @@ -84,14 +19,8 @@ describe("toolResultFormatting", () => { }) it("should handle empty parameters", () => { - const result = formatToolInvocation("list_files", {}, TOOL_PROTOCOL.NATIVE) + const result = formatToolInvocation("list_files", {}) expect(result).toBe("Called list_files") }) - - it("should use config when protocol not specified", () => { - mockGetConfiguration.mockReturnValue(TOOL_PROTOCOL.NATIVE) - const result = formatToolInvocation("read_file", { path: "test.ts" }) - expect(result).toBe("Called read_file with path: test.ts") - }) }) }) diff --git a/src/core/tools/helpers/toolResultFormatting.ts b/src/core/tools/helpers/toolResultFormatting.ts index d4c77798c5d..a0c809ea84e 100644 --- a/src/core/tools/helpers/toolResultFormatting.ts +++ b/src/core/tools/helpers/toolResultFormatting.ts @@ -1,31 +1,10 @@ -import * as vscode from "vscode" -import { Package } from "../../../shared/package" -import { TOOL_PROTOCOL, ToolProtocol, isNativeProtocol } from "@roo-code/types" - /** - * Gets the current tool protocol from workspace configuration. + * Formats tool invocation parameters for display. */ -export function getCurrentToolProtocol(): ToolProtocol { - return vscode.workspace.getConfiguration(Package.name).get("toolProtocol", "xml") -} - -/** - * Formats tool invocation parameters for display based on protocol. - * Used for legacy conversation history conversion. - */ -export function formatToolInvocation(toolName: string, params: Record, protocol?: ToolProtocol): string { - const effectiveProtocol = protocol ?? getCurrentToolProtocol() - if (isNativeProtocol(effectiveProtocol)) { - // Native protocol: readable format - const paramsList = Object.entries(params) - .map(([key, value]) => `${key}: ${typeof value === "string" ? value : JSON.stringify(value)}`) - .join(", ") - return `Called ${toolName}${paramsList ? ` with ${paramsList}` : ""}` - } else { - // XML protocol: preserve XML format - const paramsXml = Object.entries(params) - .map(([key, value]) => `<${key}>\n${value}\n`) - .join("\n") - return `<${toolName}>\n${paramsXml}\n` - } +export function formatToolInvocation(toolName: string, params: Record): string { + // Native-only: readable format + const paramsList = Object.entries(params) + .map(([key, value]) => `${key}: ${typeof value === "string" ? value : JSON.stringify(value)}`) + .join(", ") + return `Called ${toolName}${paramsList ? ` with ${paramsList}` : ""}` } diff --git a/src/core/tools/validateToolUse.ts b/src/core/tools/validateToolUse.ts index 751d164fd26..0e56bb6e65f 100644 --- a/src/core/tools/validateToolUse.ts +++ b/src/core/tools/validateToolUse.ts @@ -164,41 +164,7 @@ export function isToolAllowedForMode( throw new FileRestrictionError(mode.name, options.fileRegex, options.description, filePath, tool) } - // Handle XML args parameter (used by MULTI_FILE_APPLY_DIFF experiment) - if (toolParams?.args && typeof toolParams.args === "string") { - // Extract file paths from XML args with improved validation - try { - const filePathMatches = toolParams.args.match(/([^<]+)<\/path>/g) - if (filePathMatches) { - for (const match of filePathMatches) { - // More robust path extraction with validation - const pathMatch = match.match(/([^<]+)<\/path>/) - if (pathMatch && pathMatch[1]) { - const extractedPath = pathMatch[1].trim() - // Validate that the path is not empty and doesn't contain invalid characters - if (extractedPath && !extractedPath.includes("<") && !extractedPath.includes(">")) { - if (!doesFileMatchRegex(extractedPath, options.fileRegex)) { - throw new FileRestrictionError( - mode.name, - options.fileRegex, - options.description, - extractedPath, - tool, - ) - } - } - } - } - } - } catch (error) { - // Re-throw FileRestrictionError as it's an expected validation error - if (error instanceof FileRestrictionError) { - throw error - } - // If XML parsing fails, log the error but don't block the operation - console.warn(`Failed to parse XML args for file restriction validation: ${error}`) - } - } + // Native-only: multi-file edits provide structured params; no legacy XML args parsing. } return true diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 33fa12ca78c..d2f909f6ff0 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1391,21 +1391,13 @@ export class ClineProvider const prevConfig = task.apiConfiguration const prevProvider = prevConfig?.apiProvider const prevModelId = prevConfig ? getModelId(prevConfig) : undefined - const prevToolProtocol = prevConfig?.toolProtocol const newProvider = providerSettings.apiProvider const newModelId = getModelId(providerSettings) - const newToolProtocol = providerSettings.toolProtocol - const needsRebuild = - forceRebuild || - prevProvider !== newProvider || - prevModelId !== newModelId || - prevToolProtocol !== newToolProtocol + const needsRebuild = forceRebuild || prevProvider !== newProvider || prevModelId !== newModelId if (needsRebuild) { // Use updateApiConfiguration which handles both API handler rebuild and parser sync. - // This is important when toolProtocol changes - the assistantMessageParser needs to be - // created/destroyed to match the new protocol (XML vs native). // Note: updateApiConfiguration is declared async but has no actual async operations, // so we can safely call it without awaiting. task.updateApiConfiguration(providerSettings) @@ -3121,7 +3113,7 @@ export class ClineProvider ) } // 2) Flush pending tool results to API history BEFORE disposing the parent. - // This is critical for native tool protocol: when tools are called before new_task, + // This is critical: when tools are called before new_task, // their tool_result blocks are in userMessageContent but not yet saved to API history. // If we don't flush them, the parent's API conversation will be incomplete and // cause 400 errors when resumed (missing tool_result for tool_use blocks). @@ -3272,9 +3264,9 @@ export class ClineProvider } } - // The API expects: user → assistant (with tool_use) → user (with tool_result) - // We need to add a NEW user message with the tool_result AFTER the assistant's tool_use - // NOT add it to an existing user message + // Preferred: if the parent history contains the native tool_use for new_task, + // inject a matching tool_result for the Anthropic message contract: + // user → assistant (tool_use) → user (tool_result) if (toolUseId) { // Check if the last message is already a user message with a tool_result for this tool_use_id // (in case this is a retry or the history was already updated) @@ -3305,14 +3297,23 @@ export class ClineProvider ts, }) } + + // Validate the newly injected tool_result against the preceding assistant message. + // This ensures the tool_result's tool_use_id matches a tool_use in the immediately + // preceding assistant message (Anthropic API requirement). + const lastMessage = parentApiMessages[parentApiMessages.length - 1] + if (lastMessage?.role === "user") { + const validatedMessage = validateAndFixToolResultIds(lastMessage, parentApiMessages.slice(0, -1)) + parentApiMessages[parentApiMessages.length - 1] = validatedMessage + } } else { - // Fallback for XML protocol or when toolUseId couldn't be found: - // Add a text block (not ideal but maintains backward compatibility) + // If there is no corresponding tool_use in the parent API history, we cannot emit a + // tool_result. Fall back to a plain user text note so the parent can still resume. parentApiMessages.push({ role: "user", content: [ { - type: "text", + type: "text" as const, text: `Subtask ${childTaskId} completed.\n\nResult:\n${completionResultSummary}`, }, ], @@ -3320,15 +3321,6 @@ export class ClineProvider }) } - // Validate the newly injected tool_result against the preceding assistant message. - // This ensures the tool_result's tool_use_id matches a tool_use in the immediately - // preceding assistant message (Anthropic API requirement). - const lastMessage = parentApiMessages[parentApiMessages.length - 1] - if (lastMessage?.role === "user") { - const validatedMessage = validateAndFixToolResultIds(lastMessage, parentApiMessages.slice(0, -1)) - parentApiMessages[parentApiMessages.length - 1] = validatedMessage - } - await saveApiMessages({ messages: parentApiMessages as any, taskId: parentTaskId, globalStoragePath }) // 3) Update child metadata to "completed" status diff --git a/src/core/webview/generateSystemPrompt.ts b/src/core/webview/generateSystemPrompt.ts index 341ba484517..d8f39386f5d 100644 --- a/src/core/webview/generateSystemPrompt.ts +++ b/src/core/webview/generateSystemPrompt.ts @@ -8,7 +8,6 @@ import { SYSTEM_PROMPT } from "../prompts/system" import { MultiSearchReplaceDiffStrategy } from "../diff/strategies/multi-search-replace" import { MultiFileSearchReplaceDiffStrategy } from "../diff/strategies/multi-file-search-replace" import { Package } from "../../shared/package" -import { resolveToolProtocol } from "../../utils/resolveToolProtocol" import { ClineProvider } from "./ClineProvider" @@ -70,9 +69,6 @@ export const generateSystemPrompt = async (provider: ClineProvider, message: Web // and browser tools are enabled in settings const canUseBrowserTool = modelSupportsBrowser && modeSupportsBrowser && (browserToolEnabled ?? true) - // Resolve tool protocol for system prompt generation - const toolProtocol = resolveToolProtocol(apiConfiguration, modelInfo) - const systemPrompt = await SYSTEM_PROMPT( provider.context, cwd, @@ -98,7 +94,6 @@ export const generateSystemPrompt = async (provider: ClineProvider, message: Web newTaskRequireTodos: vscode.workspace .getConfiguration(Package.name) .get("newTaskRequireTodos", false), - toolProtocol, isStealthModel: modelInfo?.isStealthModel, }, undefined, // todoList diff --git a/src/integrations/editor/DiffViewProvider.ts b/src/integrations/editor/DiffViewProvider.ts index 3645c1e153c..94a483706e6 100644 --- a/src/integrations/editor/DiffViewProvider.ts +++ b/src/integrations/editor/DiffViewProvider.ts @@ -3,17 +3,15 @@ import * as path from "path" import * as fs from "fs/promises" import * as diff from "diff" import stripBom from "strip-bom" -import { XMLBuilder } from "fast-xml-parser" import delay from "delay" -import { type ClineSayTool, DEFAULT_WRITE_DELAY_MS, isNativeProtocol } from "@roo-code/types" +import { type ClineSayTool, DEFAULT_WRITE_DELAY_MS } from "@roo-code/types" import { createDirectoriesForFile } from "../../utils/fs" import { arePathsEqual, getReadablePath } from "../../utils/path" import { formatResponse } from "../../core/prompts/responses" import { diagnosticsToProblemsString, getNewDiagnostics } from "../diagnostics" import { Task } from "../../core/task/Task" -import { resolveToolProtocol } from "../../utils/resolveToolProtocol" import { DecorationController } from "./DecorationController" @@ -306,7 +304,7 @@ export class DiffViewProvider { * @param task Task instance to get protocol info * @param cwd Current working directory for path resolution * @param isNewFile Whether this is a new file or an existing file being modified - * @returns Formatted message (JSON for native protocol, XML for legacy) + * @returns Formatted message (JSON) */ async pushToolWriteResult(task: Task, cwd: string, isNewFile: boolean): Promise { if (!this.relPath) { @@ -326,10 +324,6 @@ export class DiffViewProvider { await task.say("user_feedback_diff", JSON.stringify(say)) } - // Check which protocol we're using - use the task's locked protocol for consistency - const toolProtocol = resolveToolProtocol(task.apiConfiguration, task.api.getModel().info, task.taskToolProtocol) - const useNative = isNativeProtocol(toolProtocol) - // Build notices array const notices = [ "You do not need to re-read the file, as you have seen all changes", @@ -341,60 +335,27 @@ export class DiffViewProvider { : []), ] - if (useNative) { - // Return JSON for native protocol - const result: any = { - path: this.relPath, - operation: isNewFile ? "created" : "modified", - notice: notices.join(" "), - } - - if (this.userEdits) { - result.user_edits = this.userEdits - } - - if (this.newProblemsMessage) { - result.problems = this.newProblemsMessage - } - - return JSON.stringify(result) - } else { - // Build XML response for legacy protocol - const xmlObj = { - file_write_result: { - path: this.relPath, - operation: isNewFile ? "created" : "modified", - user_edits: this.userEdits ? this.userEdits : undefined, - problems: this.newProblemsMessage || undefined, - notice: { - i: notices, - }, - }, - } + const result: { + path: string + operation: "created" | "modified" + notice: string + user_edits?: string + problems?: string + } = { + path: this.relPath, + operation: isNewFile ? "created" : "modified", + notice: notices.join(" "), + } - const builder = new XMLBuilder({ - format: true, - indentBy: "", - suppressEmptyNode: true, - processEntities: false, - tagValueProcessor: (name, value) => { - if (typeof value === "string") { - // Only escape <, >, and & characters - return value.replace(/&/g, "&").replace(//g, ">") - } - return value - }, - attributeValueProcessor: (name, value) => { - if (typeof value === "string") { - // Only escape <, >, and & characters - return value.replace(/&/g, "&").replace(//g, ">") - } - return value - }, - }) + if (this.userEdits) { + result.user_edits = this.userEdits + } - return builder.build(xmlObj) + if (this.newProblemsMessage) { + result.problems = this.newProblemsMessage } + + return JSON.stringify(result) } async revertChanges(): Promise { diff --git a/src/services/tree-sitter/__tests__/fixtures/sample-c.ts b/src/services/tree-sitter/__tests__/fixtures/sample-c.ts index 41ea927de9c..dc03ac025d1 100644 --- a/src/services/tree-sitter/__tests__/fixtures/sample-c.ts +++ b/src/services/tree-sitter/__tests__/fixtures/sample-c.ts @@ -120,7 +120,6 @@ void void_param_prototype( void /* Explicit void parameter */ ); - // Testing function prototype with function pointer parameter void function_pointer_prototype( void (*callback)(void*), diff --git a/src/services/tree-sitter/queries/kotlin.ts b/src/services/tree-sitter/queries/kotlin.ts index fd70f1891ed..a67096fc2e4 100644 --- a/src/services/tree-sitter/queries/kotlin.ts +++ b/src/services/tree-sitter/queries/kotlin.ts @@ -54,7 +54,6 @@ export default ` (simple_identifier) @name.definition.function ) @definition.function - ; Suspend function declarations (function_declaration (modifiers @@ -70,8 +69,6 @@ export default ` ; Companion object declarations (companion_object) @definition.companion_object - - ; Annotation class declarations (class_declaration (modifiers diff --git a/src/shared/__tests__/modes.spec.ts b/src/shared/__tests__/modes.spec.ts index a00abde7879..1c74c25e13e 100644 --- a/src/shared/__tests__/modes.spec.ts +++ b/src/shared/__tests__/modes.spec.ts @@ -248,55 +248,28 @@ describe("isToolAllowedForMode", () => { }) it("applies restrictions to apply_diff with concurrent file edits (MULTI_FILE_APPLY_DIFF experiment)", () => { - // Test apply_diff with args parameter (used when MULTI_FILE_APPLY_DIFF experiment is enabled) - // This simulates concurrent/batch file editing - const xmlArgs = - "test.md- old content\\n+ new content" + // Native-only: file restrictions for apply_diff are enforced against the top-level `path`. + // (Legacy XML args parsing has been removed.) // Should allow markdown files in architect mode expect( isToolAllowedForMode("apply_diff", "architect", [], undefined, { - args: xmlArgs, - }), - ).toBe(true) - - // Test with non-markdown file - should throw error - const xmlArgsNonMd = - "test.py- old content\\n+ new content" - - expect(() => - isToolAllowedForMode("apply_diff", "architect", [], undefined, { - args: xmlArgsNonMd, - }), - ).toThrow(FileRestrictionError) - expect(() => - isToolAllowedForMode("apply_diff", "architect", [], undefined, { - args: xmlArgsNonMd, - }), - ).toThrow(/Markdown files only/) - - // Test with multiple files - should allow only markdown files - const xmlArgsMultiple = - "readme.md- old content\\n+ new contentdocs.md- old content\\n+ new content" - - expect( - isToolAllowedForMode("apply_diff", "architect", [], undefined, { - args: xmlArgsMultiple, + path: "test.md", + diff: "- old content\n+ new content", }), ).toBe(true) - // Test with mixed file types - should throw error for non-markdown - const xmlArgsMixed = - "readme.md- old content\\n+ new contentscript.py- old content\\n+ new content" - + // Non-markdown file should throw expect(() => isToolAllowedForMode("apply_diff", "architect", [], undefined, { - args: xmlArgsMixed, + path: "test.py", + diff: "- old content\n+ new content", }), ).toThrow(FileRestrictionError) expect(() => isToolAllowedForMode("apply_diff", "architect", [], undefined, { - args: xmlArgsMixed, + path: "test.py", + diff: "- old content\n+ new content", }), ).toThrow(/Markdown files only/) }) diff --git a/src/shared/tools.ts b/src/shared/tools.ts index f893a3d332e..01632b27460 100644 --- a/src/shared/tools.ts +++ b/src/shared/tools.ts @@ -23,8 +23,6 @@ export type HandleError = (action: string, error: Error) => Promise export type PushToolResult = (content: ToolResponse) => void -export type RemoveClosingTag = (tag: ToolParamName, content?: string) => string - export type AskFinishSubTaskApproval = () => Promise export type ToolDescription = () => string @@ -80,8 +78,6 @@ export const toolParamNames = [ export type ToolParamName = (typeof toolParamNames)[number] -export type ToolProtocol = "xml" | "native" - /** * Type map defining the native (typed) argument structure for each tool. * Tools not listed here will fall back to `any` for backward compatibility. @@ -96,6 +92,8 @@ export type NativeToolArgs = { search_replace: { file_path: string; old_string: string; new_string: string } edit_file: { file_path: string; old_string: string; new_string: string; expected_replacements?: number } apply_patch: { patch: string } + list_files: { path: string; recursive?: boolean } + new_task: { mode: string; message: string; todos?: string } ask_followup_question: { question: string follow_up: Array<{ text: string; mode?: string }> diff --git a/src/utils/__tests__/resolveToolProtocol.spec.ts b/src/utils/__tests__/resolveToolProtocol.spec.ts deleted file mode 100644 index 513a7eaa35e..00000000000 --- a/src/utils/__tests__/resolveToolProtocol.spec.ts +++ /dev/null @@ -1,378 +0,0 @@ -import { describe, it, expect } from "vitest" -import { resolveToolProtocol, detectToolProtocolFromHistory } from "../resolveToolProtocol" -import { TOOL_PROTOCOL, openAiModelInfoSaneDefaults } from "@roo-code/types" -import type { ProviderSettings, ModelInfo } from "@roo-code/types" -import type { Anthropic } from "@anthropic-ai/sdk" - -describe("resolveToolProtocol", () => { - /** - * XML Protocol Deprecation: - * - * XML tool protocol has been fully deprecated. All models now use Native - * tool calling. User preferences and model defaults are ignored. - * - * Precedence: - * 1. Locked Protocol (for resumed tasks that used XML) - * 2. Native (always, for all new tasks) - */ - - describe("Locked Protocol (Precedence Level 0 - Highest Priority)", () => { - it("should return lockedProtocol when provided", () => { - const settings: ProviderSettings = { - toolProtocol: "xml", // Ignored - apiProvider: "openai-native", - } - // lockedProtocol overrides everything - const result = resolveToolProtocol(settings, undefined, "native") - expect(result).toBe(TOOL_PROTOCOL.NATIVE) - }) - - it("should return XML lockedProtocol for resumed tasks that used XML", () => { - const settings: ProviderSettings = { - toolProtocol: "native", // Ignored - apiProvider: "anthropic", - } - // lockedProtocol forces XML for backward compatibility - const result = resolveToolProtocol(settings, undefined, "xml") - expect(result).toBe(TOOL_PROTOCOL.XML) - }) - - it("should fall through to Native when lockedProtocol is undefined", () => { - const settings: ProviderSettings = { - toolProtocol: "xml", // Ignored - apiProvider: "anthropic", - } - // undefined lockedProtocol should return native - const result = resolveToolProtocol(settings, undefined, undefined) - expect(result).toBe(TOOL_PROTOCOL.NATIVE) - }) - }) - - describe("Native Protocol Always Used For New Tasks", () => { - it("should always use native for new tasks", () => { - const settings: ProviderSettings = { - apiProvider: "anthropic", - } - const result = resolveToolProtocol(settings) - expect(result).toBe(TOOL_PROTOCOL.NATIVE) - }) - - it("should use native even when user preference is XML (user prefs ignored)", () => { - const settings: ProviderSettings = { - toolProtocol: "xml", // User wants XML - ignored - apiProvider: "openai-native", - } - const result = resolveToolProtocol(settings) - expect(result).toBe(TOOL_PROTOCOL.NATIVE) - }) - - it("should use native for OpenAI compatible provider", () => { - const settings: ProviderSettings = { - apiProvider: "openai", - } - const result = resolveToolProtocol(settings, openAiModelInfoSaneDefaults) - expect(result).toBe(TOOL_PROTOCOL.NATIVE) - }) - }) - - describe("Edge Cases", () => { - it("should handle missing provider name gracefully", () => { - const settings: ProviderSettings = {} - const result = resolveToolProtocol(settings) - expect(result).toBe(TOOL_PROTOCOL.NATIVE) // Always native now - }) - - it("should handle undefined model info gracefully", () => { - const settings: ProviderSettings = { - apiProvider: "openai-native", - } - const result = resolveToolProtocol(settings, undefined) - expect(result).toBe(TOOL_PROTOCOL.NATIVE) // Always native now - }) - - it("should handle empty settings", () => { - const settings: ProviderSettings = {} - const result = resolveToolProtocol(settings) - expect(result).toBe(TOOL_PROTOCOL.NATIVE) // Always native now - }) - }) - - describe("Real-world Scenarios", () => { - it("should use Native for OpenAI models", () => { - const settings: ProviderSettings = { - apiProvider: "openai-native", - } - const modelInfo: ModelInfo = { - maxTokens: 4096, - contextWindow: 128000, - supportsPromptCache: false, - supportsNativeTools: true, - } - const result = resolveToolProtocol(settings, modelInfo) - expect(result).toBe(TOOL_PROTOCOL.NATIVE) - }) - - it("should use Native for Claude models", () => { - const settings: ProviderSettings = { - apiProvider: "anthropic", - } - const modelInfo: ModelInfo = { - maxTokens: 8192, - contextWindow: 200000, - supportsPromptCache: true, - supportsNativeTools: true, - } - const result = resolveToolProtocol(settings, modelInfo) - expect(result).toBe(TOOL_PROTOCOL.NATIVE) - }) - - it("should honor locked protocol for resumed tasks that used XML", () => { - const settings: ProviderSettings = { - apiProvider: "anthropic", - } - // Task was started when XML was used, so it's locked to XML - const result = resolveToolProtocol(settings, undefined, "xml") - expect(result).toBe(TOOL_PROTOCOL.XML) - }) - }) - - describe("Backward Compatibility - User Preferences Ignored", () => { - it("should ignore user preference for XML", () => { - const settings: ProviderSettings = { - toolProtocol: "xml", // User explicitly wants XML - ignored - apiProvider: "openai-native", - } - const result = resolveToolProtocol(settings) - expect(result).toBe(TOOL_PROTOCOL.NATIVE) // Native is always used - }) - - it("should return native regardless of user preference", () => { - const settings: ProviderSettings = { - toolProtocol: "native", // User preference - ignored but happens to match - apiProvider: "anthropic", - } - const result = resolveToolProtocol(settings) - expect(result).toBe(TOOL_PROTOCOL.NATIVE) - }) - }) -}) - -describe("detectToolProtocolFromHistory", () => { - // Helper type for API messages in tests - type ApiMessageForTest = Anthropic.MessageParam & { ts?: number } - - describe("Native Protocol Detection", () => { - it("should detect native protocol when tool_use block has an id", () => { - const messages: ApiMessageForTest[] = [ - { role: "user", content: "Hello" }, - { - role: "assistant", - content: [ - { - type: "tool_use", - id: "toolu_01abc123", // Native protocol always has an ID - name: "read_file", - input: { path: "test.ts" }, - }, - ], - }, - ] - const result = detectToolProtocolFromHistory(messages) - expect(result).toBe(TOOL_PROTOCOL.NATIVE) - }) - - it("should detect native protocol from the first tool_use block found", () => { - const messages: ApiMessageForTest[] = [ - { role: "user", content: "First message" }, - { role: "assistant", content: "Let me help you" }, - { role: "user", content: "Second message" }, - { - role: "assistant", - content: [ - { - type: "tool_use", - id: "toolu_first", - name: "read_file", - input: { path: "first.ts" }, - }, - ], - }, - { role: "user", content: "Third message" }, - { - role: "assistant", - content: [ - { - type: "tool_use", - id: "toolu_second", - name: "write_to_file", - input: { path: "second.ts", content: "test" }, - }, - ], - }, - ] - const result = detectToolProtocolFromHistory(messages) - expect(result).toBe(TOOL_PROTOCOL.NATIVE) - }) - }) - - describe("XML Protocol Detection", () => { - it("should detect XML protocol when tool_use block has no id", () => { - const messages: ApiMessageForTest[] = [ - { role: "user", content: "Hello" }, - { - role: "assistant", - content: [ - { - type: "tool_use", - // No id field - XML protocol tool calls never have an ID - name: "read_file", - input: { path: "test.ts" }, - } as Anthropic.ToolUseBlock, // Cast to bypass type check for missing id - ], - }, - ] - const result = detectToolProtocolFromHistory(messages) - expect(result).toBe(TOOL_PROTOCOL.XML) - }) - - it("should detect XML protocol when id is empty string", () => { - const messages: ApiMessageForTest[] = [ - { role: "user", content: "Hello" }, - { - role: "assistant", - content: [ - { - type: "tool_use", - id: "", // Empty string should be treated as no id - name: "read_file", - input: { path: "test.ts" }, - }, - ], - }, - ] - const result = detectToolProtocolFromHistory(messages) - expect(result).toBe(TOOL_PROTOCOL.XML) - }) - }) - - describe("No Tool Calls", () => { - it("should return undefined when no messages", () => { - const messages: ApiMessageForTest[] = [] - const result = detectToolProtocolFromHistory(messages) - expect(result).toBeUndefined() - }) - - it("should return undefined when only user messages", () => { - const messages: ApiMessageForTest[] = [ - { role: "user", content: "Hello" }, - { role: "user", content: "How are you?" }, - ] - const result = detectToolProtocolFromHistory(messages) - expect(result).toBeUndefined() - }) - - it("should return undefined when assistant messages have no tool_use", () => { - const messages: ApiMessageForTest[] = [ - { role: "user", content: "Hello" }, - { role: "assistant", content: "Hi! How can I help?" }, - { role: "user", content: "What's the weather?" }, - { - role: "assistant", - content: [{ type: "text", text: "I don't have access to weather data." }], - }, - ] - const result = detectToolProtocolFromHistory(messages) - expect(result).toBeUndefined() - }) - - it("should return undefined when content is string", () => { - const messages: ApiMessageForTest[] = [ - { role: "user", content: "Hello" }, - { role: "assistant", content: "Hi there!" }, - ] - const result = detectToolProtocolFromHistory(messages) - expect(result).toBeUndefined() - }) - }) - - describe("Mixed Content", () => { - it("should detect protocol from tool_use even with mixed content", () => { - const messages: ApiMessageForTest[] = [ - { role: "user", content: "Read this file" }, - { - role: "assistant", - content: [ - { type: "text", text: "I'll read that file for you." }, - { - type: "tool_use", - id: "toolu_mixed", - name: "read_file", - input: { path: "test.ts" }, - }, - ], - }, - ] - const result = detectToolProtocolFromHistory(messages) - expect(result).toBe(TOOL_PROTOCOL.NATIVE) - }) - - it("should skip user messages and only check assistant messages", () => { - const messages: ApiMessageForTest[] = [ - { - role: "user", - content: [ - { - type: "tool_result", - tool_use_id: "toolu_user", - content: "result", - }, - ], - }, - { - role: "assistant", - content: [ - { - type: "tool_use", - id: "toolu_assistant", - name: "write_to_file", - input: { path: "out.ts", content: "test" }, - }, - ], - }, - ] - const result = detectToolProtocolFromHistory(messages) - expect(result).toBe(TOOL_PROTOCOL.NATIVE) - }) - }) - - describe("Edge Cases", () => { - it("should handle messages with empty content array", () => { - const messages: ApiMessageForTest[] = [ - { role: "user", content: "Hello" }, - { role: "assistant", content: [] }, - ] - const result = detectToolProtocolFromHistory(messages) - expect(result).toBeUndefined() - }) - - it("should handle messages with ts field (ApiMessage format)", () => { - const messages: ApiMessageForTest[] = [ - { role: "user", content: "Hello", ts: Date.now() }, - { - role: "assistant", - content: [ - { - type: "tool_use", - id: "toolu_with_ts", - name: "read_file", - input: { path: "test.ts" }, - }, - ], - ts: Date.now(), - }, - ] - const result = detectToolProtocolFromHistory(messages) - expect(result).toBe(TOOL_PROTOCOL.NATIVE) - }) - }) -}) diff --git a/src/utils/__tests__/xml-matcher.spec.ts b/src/utils/__tests__/xml-matcher.spec.ts deleted file mode 100644 index 033084ee47a..00000000000 --- a/src/utils/__tests__/xml-matcher.spec.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { XmlMatcher } from "../xml-matcher" - -describe("XmlMatcher", () => { - it("only match at position 0", () => { - const matcher = new XmlMatcher("think") - const chunks = [...matcher.update("data"), ...matcher.final()] - expect(chunks).toHaveLength(1) - expect(chunks).toEqual([ - { - matched: true, - data: "data", - }, - ]) - }) - it("tag with space", () => { - const matcher = new XmlMatcher("think") - const chunks = [...matcher.update("< think >data"), ...matcher.final()] - expect(chunks).toHaveLength(1) - expect(chunks).toEqual([ - { - matched: true, - data: "data", - }, - ]) - }) - - it("invalid tag", () => { - const matcher = new XmlMatcher("think") - const chunks = [...matcher.update("< think 1>data"), ...matcher.final()] - expect(chunks).toHaveLength(1) - expect(chunks).toEqual([ - { - matched: false, - data: "< think 1>data", - }, - ]) - }) - - it("anonymous tag", () => { - const matcher = new XmlMatcher("think") - const chunks = [...matcher.update("<>data"), ...matcher.final()] - expect(chunks).toHaveLength(1) - expect(chunks).toEqual([ - { - matched: false, - data: "<>data", - }, - ]) - }) - - it("streaming push", () => { - const matcher = new XmlMatcher("think") - const chunks = [ - ...matcher.update("dat"), - ...matcher.update("a"), - ] - expect(chunks).toHaveLength(2) - expect(chunks).toEqual([ - { - matched: true, - data: "dat", - }, - { - matched: true, - data: "a", - }, - ]) - }) - - it("nested tag", () => { - const matcher = new XmlMatcher("think") - const chunks = [...matcher.update("XYZ"), ...matcher.final()] - expect(chunks).toHaveLength(1) - expect(chunks).toEqual([ - { - matched: true, - data: "XYZ", - }, - ]) - }) - - it("nested invalid tag", () => { - const matcher = new XmlMatcher("think") - const chunks = [...matcher.update("XYZ"), ...matcher.final()] - expect(chunks).toHaveLength(2) - expect(chunks).toEqual([ - { - matched: true, - data: "XYZ", - }, - { - matched: true, - data: "", - }, - ]) - }) - - it("Wrong matching position", () => { - const matcher = new XmlMatcher("think") - const chunks = [...matcher.update("1data"), ...matcher.final()] - expect(chunks).toHaveLength(1) - expect(chunks).toEqual([ - { - matched: false, - data: "1data", - }, - ]) - }) - - it("Unclosed tag", () => { - const matcher = new XmlMatcher("think") - const chunks = [...matcher.update("data"), ...matcher.final()] - expect(chunks).toHaveLength(1) - expect(chunks).toEqual([ - { - matched: true, - data: "data", - }, - ]) - }) -}) diff --git a/src/utils/__tests__/xml.spec.ts b/src/utils/__tests__/xml.spec.ts deleted file mode 100644 index f7a282b0c0c..00000000000 --- a/src/utils/__tests__/xml.spec.ts +++ /dev/null @@ -1,240 +0,0 @@ -import { parseXml, parseXmlForDiff } from "../xml" - -describe("parseXml", () => { - describe("type conversion", () => { - // Test the main change from the commit: no automatic type conversion - it("should not convert string numbers to numbers", () => { - const xml = ` - - 123 - -456 - 123.456 - - ` - - const result = parseXml(xml) as any - - // Ensure these remain as strings and are not converted to numbers - expect(typeof result.root.numericString).toBe("string") - expect(result.root.numericString).toBe("123") - - expect(typeof result.root.negativeNumericString).toBe("string") - expect(result.root.negativeNumericString).toBe("-456") - - expect(typeof result.root.floatNumericString).toBe("string") - expect(result.root.floatNumericString).toBe("123.456") - }) - - it("should not convert string booleans to booleans", () => { - const xml = ` - - true - false - - ` - - const result = parseXml(xml) as any - - // Ensure these remain as strings and are not converted to booleans - expect(typeof result.root.boolTrue).toBe("string") - expect(result.root.boolTrue).toBe("true") - - expect(typeof result.root.boolFalse).toBe("string") - expect(result.root.boolFalse).toBe("false") - }) - - it("should not convert attribute values to their respective types", () => { - const xml = ` - - - - ` - - const result = parseXml(xml) as any - const attributes = result.root.node - - // Check that attributes remain as strings - expect(typeof attributes["@_id"]).toBe("string") - expect(attributes["@_id"]).toBe("123") - - expect(typeof attributes["@_enabled"]).toBe("string") - expect(attributes["@_enabled"]).toBe("true") - - expect(typeof attributes["@_disabled"]).toBe("string") - expect(attributes["@_disabled"]).toBe("false") - - expect(typeof attributes["@_float"]).toBe("string") - expect(attributes["@_float"]).toBe("3.14") - }) - }) - - describe("basic functionality", () => { - it("should correctly parse a simple XML string", () => { - const xml = ` - - Test Name - Some description - - ` - - const result = parseXml(xml) as any - - expect(result).toHaveProperty("root") - expect(result.root).toHaveProperty("name", "Test Name") - expect(result.root).toHaveProperty("description", "Some description") - }) - - it("should handle attributes correctly", () => { - const xml = ` - - Item content - - ` - - const result = parseXml(xml) as any - - expect(result.root.item).toHaveProperty("@_id", "1") - expect(result.root.item).toHaveProperty("@_category", "test") - expect(result.root.item).toHaveProperty("#text", "Item content") - }) - - it("should support stopNodes parameter", () => { - const xml = ` - - - Should not parse this - - - ` - - const result = parseXml(xml, ["nestedXml"]) as any - - // With stopNodes, the parser still parses the structure but stops at the specified node - expect(result.root.data.nestedXml).toBeTruthy() - expect(result.root.data.nestedXml).toHaveProperty("item", "Should not parse this") - }) - }) -}) - -describe("parseXmlForDiff", () => { - describe("HTML entity handling", () => { - it("should NOT decode HTML entities like &", () => { - const xml = ` - - Team Identity & Project Positioning - - ` - - const result = parseXmlForDiff(xml) as any - - // The & should remain as-is, not be decoded to & - expect(result.root.content).toBe("Team Identity & Project Positioning") - }) - - it("should preserve & character without encoding", () => { - const xml = ` - - Team Identity & Project Positioning - - ` - - const result = parseXmlForDiff(xml) as any - - // The & should remain as-is - expect(result.root.content).toBe("Team Identity & Project Positioning") - }) - - it("should NOT decode other HTML entities", () => { - const xml = ` - - <div> "Hello" 'World' - - ` - - const result = parseXmlForDiff(xml) as any - - // All HTML entities should remain as-is - expect(result.root.content).toBe("<div> "Hello" 'World'") - }) - - it("should handle mixed content with entities correctly", () => { - const xml = ` - - if (a < b && c > d) { return "test"; } - - ` - - const result = parseXmlForDiff(xml) as any - - // All entities should remain unchanged - expect(result.root.code).toBe("if (a < b && c > d) { return "test"; }") - }) - }) - - describe("basic functionality (same as parseXml)", () => { - it("should correctly parse a simple XML string", () => { - const xml = ` - - Test Name - Some description - - ` - - const result = parseXmlForDiff(xml) as any - - expect(result).toHaveProperty("root") - expect(result.root).toHaveProperty("name", "Test Name") - expect(result.root).toHaveProperty("description", "Some description") - }) - - it("should handle attributes correctly", () => { - const xml = ` - - Item content - - ` - - const result = parseXmlForDiff(xml) as any - - expect(result.root.item).toHaveProperty("@_id", "1") - expect(result.root.item).toHaveProperty("@_category", "test") - expect(result.root.item).toHaveProperty("#text", "Item content") - }) - - it("should support stopNodes parameter", () => { - const xml = ` - - - Should not parse this - - - ` - - const result = parseXmlForDiff(xml, ["nestedXml"]) as any - - expect(result.root.data.nestedXml).toBeTruthy() - expect(result.root.data.nestedXml).toHaveProperty("item", "Should not parse this") - }) - }) - - describe("diff-specific use case", () => { - it("should preserve exact content for diff matching", () => { - // This simulates the actual use case from the issue - const xml = ` - - - ./doc.md - - Team Identity & Project Positioning - - - - ` - - const result = parseXmlForDiff(xml, ["file.diff.content"]) as any - - // The & should remain as-is for exact matching with file content - expect(result.args.file.diff.content).toBe("Team Identity & Project Positioning") - }) - }) -}) diff --git a/src/utils/resolveToolProtocol.ts b/src/utils/resolveToolProtocol.ts deleted file mode 100644 index 92041fbeaf2..00000000000 --- a/src/utils/resolveToolProtocol.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { ToolProtocol, TOOL_PROTOCOL } from "@roo-code/types" -import type { ProviderSettings } from "@roo-code/types" -import type { Anthropic } from "@anthropic-ai/sdk" -import { findLast, findLastIndex } from "../shared/array" - -/** - * Represents an API message in the conversation history. - * This is a minimal type definition for the detection function. - */ -type ApiMessageForDetection = Anthropic.MessageParam & { - ts?: number -} - -/** - * Resolve the effective tool protocol. - * - * **Deprecation Note (XML Protocol):** - * XML tool protocol has been deprecated. All models now use Native tool calling. - * User/profile preferences (`providerSettings.toolProtocol`) and model defaults - * (`modelInfo.defaultToolProtocol`) are ignored. - * - * Precedence: - * 1. Locked Protocol (task-level lock for resumed tasks - highest priority) - * 2. Native (always, for all new tasks) - * - * @param _providerSettings - The provider settings (toolProtocol field is ignored) - * @param _modelInfo - Unused, kept for API compatibility - * @param lockedProtocol - Optional task-locked protocol that takes absolute precedence - * @returns The resolved tool protocol (either "xml" or "native") - */ -export function resolveToolProtocol( - _providerSettings: ProviderSettings, - _modelInfo?: unknown, - lockedProtocol?: ToolProtocol, -): ToolProtocol { - // 1. Locked Protocol - task-level lock takes absolute precedence - // This ensures resumed tasks continue using their original protocol - if (lockedProtocol) { - return lockedProtocol - } - - // 2. Always return Native protocol for new tasks - // All models now support native tools; XML is deprecated - return TOOL_PROTOCOL.NATIVE -} - -/** - * Detect the tool protocol used in an existing conversation history. - * - * This function scans the API conversation history for tool_use blocks - * and determines which protocol was used based on their structure: - * - * - Native protocol: tool_use blocks ALWAYS have an `id` field - * - XML protocol: tool_use blocks NEVER have an `id` field - * - * This is critical for task resumption: if a task previously used tools - * with a specific protocol, we must continue using that protocol even - * if the user's NTC settings have changed. - * - * The function searches from the most recent message backwards to find - * the last tool call, which represents the task's current protocol state. - * - * @param messages - The API conversation history to scan - * @returns The detected protocol, or undefined if no tool calls were found - */ -export function detectToolProtocolFromHistory(messages: ApiMessageForDetection[]): ToolProtocol | undefined { - // Find the last assistant message that contains a tool_use block - const lastAssistantWithTool = findLast(messages, (message) => { - if (message.role !== "assistant") { - return false - } - const content = message.content - if (!Array.isArray(content)) { - return false - } - return content.some((block) => block.type === "tool_use") - }) - - if (!lastAssistantWithTool) { - return undefined - } - - // Find the last tool_use block in that message's content - const content = lastAssistantWithTool.content as Anthropic.ContentBlock[] - const lastToolUseIndex = findLastIndex(content, (block) => block.type === "tool_use") - - if (lastToolUseIndex === -1) { - return undefined - } - - const lastToolUse = content[lastToolUseIndex] - - // The presence or absence of `id` determines the protocol: - // - 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) - // This pattern is used in presentAssistantMessage.ts:497-500 - const hasId = "id" in lastToolUse && !!lastToolUse.id - return hasId ? TOOL_PROTOCOL.NATIVE : TOOL_PROTOCOL.XML -} diff --git a/src/utils/xml-matcher.ts b/src/utils/tag-matcher.ts similarity index 84% rename from src/utils/xml-matcher.ts rename to src/utils/tag-matcher.ts index bde14b26b3c..38d99a2904d 100644 --- a/src/utils/xml-matcher.ts +++ b/src/utils/tag-matcher.ts @@ -1,10 +1,17 @@ -export interface XmlMatcherResult { +export interface TagMatcherResult { matched: boolean data: string } -export class XmlMatcher { + +/** + * Streaming matcher for lightweight tag-delimited regions. + * + * Used to separate content inside `...` from surrounding text. + * This is used for reasoning tags like `...` in provider streams. + */ +export class TagMatcher { index = 0 - chunks: XmlMatcherResult[] = [] + chunks: TagMatcherResult[] = [] cached: string[] = [] matched: boolean = false state: "TEXT" | "TAG_OPEN" | "TAG_CLOSE" = "TEXT" @@ -12,7 +19,7 @@ export class XmlMatcher { pointer = 0 constructor( readonly tagName: string, - readonly transform?: (chunks: XmlMatcherResult) => Result, + readonly transform?: (chunks: TagMatcherResult) => Result, readonly position = 0, ) {} private collect() { diff --git a/src/utils/xml.ts b/src/utils/xml.ts deleted file mode 100644 index f183309d498..00000000000 --- a/src/utils/xml.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { XMLParser } from "fast-xml-parser" - -/** - * Options for XML parsing - */ -interface ParseXmlOptions { - /** - * Whether to process HTML entities (e.g., & to &). - * Default: true for general parsing, false for diff operations - */ - processEntities?: boolean -} - -/** - * Parses an XML string into a JavaScript object - * @param xmlString The XML string to parse - * @param stopNodes Optional array of node names to stop parsing at - * @param options Optional parsing options - * @returns Parsed JavaScript object representation of the XML - * @throws Error if the XML is invalid or parsing fails - */ -export function parseXml(xmlString: string, stopNodes?: string[], options?: ParseXmlOptions): unknown { - const _stopNodes = stopNodes ?? [] - const processEntities = options?.processEntities ?? true - - try { - const parser = new XMLParser({ - ignoreAttributes: false, - attributeNamePrefix: "@_", - parseAttributeValue: false, - parseTagValue: false, - trimValues: true, - processEntities, - stopNodes: _stopNodes, - }) - - return parser.parse(xmlString) - } catch (error) { - // Enhance error message for better debugging - const errorMessage = error instanceof Error ? error.message : "Unknown error" - throw new Error(`Failed to parse XML: ${errorMessage}`) - } -} - -/** - * Parses an XML string for diffing purposes, ensuring no HTML entities are decoded. - * This is a specialized version of parseXml to be used exclusively by diffing tools - * to prevent mismatches caused by entity processing. - * - * Use this instead of parseXml when: - * - Comparing parsed content against original file content - * - Performing diff operations where exact character matching is required - * - Processing XML that will be used in search/replace operations - * - * @param xmlString The XML string to parse - * @param stopNodes Optional array of node names to stop parsing at - * @returns Parsed JavaScript object representation of the XML - * @throws Error if the XML is invalid or parsing fails - */ -export function parseXmlForDiff(xmlString: string, stopNodes?: string[]): unknown { - // Delegate to parseXml with processEntities disabled - return parseXml(xmlString, stopNodes, { processEntities: false }) -} diff --git a/webview-ui/src/components/chat/ErrorRow.tsx b/webview-ui/src/components/chat/ErrorRow.tsx index 70253504247..4ee1a1d1297 100644 --- a/webview-ui/src/components/chat/ErrorRow.tsx +++ b/webview-ui/src/components/chat/ErrorRow.tsx @@ -222,7 +222,7 @@ export const ErrorRow = memo( {isExpanded && (
- +
)} diff --git a/webview-ui/src/components/ui/hooks/__tests__/useSelectedModel.spec.ts b/webview-ui/src/components/ui/hooks/__tests__/useSelectedModel.spec.ts index 4dca874ee20..1d42856fad0 100644 --- a/webview-ui/src/components/ui/hooks/__tests__/useSelectedModel.spec.ts +++ b/webview-ui/src/components/ui/hooks/__tests__/useSelectedModel.spec.ts @@ -582,7 +582,6 @@ describe("useSelectedModel", () => { expect(result.current.id).toBe("claude-3-7-sonnet-20250219") // Should use litellmDefaultModelInfo as fallback expect(result.current.info).toEqual(litellmDefaultModelInfo) - expect(result.current.info?.supportsNativeTools).toBe(true) }) it("should use litellmDefaultModelInfo when selected model not found in routerModels", () => { @@ -597,7 +596,6 @@ describe("useSelectedModel", () => { contextWindow: 8192, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: true, }, }, "io-intelligence": {}, @@ -619,16 +617,14 @@ describe("useSelectedModel", () => { expect(result.current.id).toBe("claude-3-7-sonnet-20250219") // Should use litellmDefaultModelInfo as fallback since default model also not in router models expect(result.current.info).toEqual(litellmDefaultModelInfo) - expect(result.current.info?.supportsNativeTools).toBe(true) }) - it("should merge only native tool defaults with routerModels when model exists", () => { + it("should return routerModels info when model exists", () => { const customModelInfo: ModelInfo = { maxTokens: 16384, contextWindow: 128000, supportsImages: true, supportsPromptCache: true, - supportsNativeTools: true, description: "Custom LiteLLM model", } @@ -656,15 +652,7 @@ describe("useSelectedModel", () => { expect(result.current.provider).toBe("litellm") expect(result.current.id).toBe("custom-model") - // Should only merge native tool defaults, not prices or other model-specific info - // Router model values override the defaults - const nativeToolDefaults = { - supportsNativeTools: litellmDefaultModelInfo.supportsNativeTools, - defaultToolProtocol: litellmDefaultModelInfo.defaultToolProtocol, - } - expect(result.current.info).toEqual({ ...nativeToolDefaults, ...customModelInfo }) - expect(result.current.info?.supportsNativeTools).toBe(true) - expect(result.current.info?.defaultToolProtocol).toBe("native") + expect(result.current.info).toEqual(customModelInfo) }) }) @@ -701,11 +689,9 @@ describe("useSelectedModel", () => { expect(result.current.provider).toBe("openai") expect(result.current.id).toBe("gpt-4o") expect(result.current.info).toEqual(openAiModelInfoSaneDefaults) - expect(result.current.info?.supportsNativeTools).toBe(true) - expect(result.current.info?.defaultToolProtocol).toBe("native") }) - it("should merge native tool defaults with custom model info", () => { + it("should return custom model info when provided", () => { const customModelInfo: ModelInfo = { maxTokens: 16384, contextWindow: 128000, @@ -727,24 +713,15 @@ describe("useSelectedModel", () => { expect(result.current.provider).toBe("openai") expect(result.current.id).toBe("custom-model") - // Should merge native tool defaults with custom model info - const nativeToolDefaults = { - supportsNativeTools: openAiModelInfoSaneDefaults.supportsNativeTools, - defaultToolProtocol: openAiModelInfoSaneDefaults.defaultToolProtocol, - } - expect(result.current.info).toEqual({ ...nativeToolDefaults, ...customModelInfo }) - expect(result.current.info?.supportsNativeTools).toBe(true) - expect(result.current.info?.defaultToolProtocol).toBe("native") + expect(result.current.info).toEqual(customModelInfo) }) - it("should allow custom model info to override native tool defaults", () => { + it("should return custom model info as-is", () => { const customModelInfo: ModelInfo = { maxTokens: 8192, contextWindow: 32000, supportsImages: false, supportsPromptCache: false, - supportsNativeTools: false, // Explicitly disable - defaultToolProtocol: "xml", // Override default to use XML instead of native } const apiConfiguration: ProviderSettings = { @@ -758,9 +735,7 @@ describe("useSelectedModel", () => { expect(result.current.provider).toBe("openai") expect(result.current.id).toBe("custom-model-no-tools") - // Custom model info should override the native tool defaults - expect(result.current.info?.supportsNativeTools).toBe(false) - expect(result.current.info?.defaultToolProtocol).toBe("xml") + expect(result.current.info).toEqual(customModelInfo) }) }) }) diff --git a/webview-ui/src/components/ui/hooks/useSelectedModel.ts b/webview-ui/src/components/ui/hooks/useSelectedModel.ts index 5788d38d912..471409a8876 100644 --- a/webview-ui/src/components/ui/hooks/useSelectedModel.ts +++ b/webview-ui/src/components/ui/hooks/useSelectedModel.ts @@ -36,7 +36,6 @@ import { BEDROCK_1M_CONTEXT_MODEL_IDS, isDynamicProvider, getProviderDefaultModelId, - NATIVE_TOOL_DEFAULTS, } from "@roo-code/types" import { useRouterModels } from "./useRouterModels" @@ -160,23 +159,17 @@ function getSelectedModel({ case "requesty": { const id = getValidatedModelId(apiConfiguration.requestyModelId, routerModels.requesty, defaultModelId) const routerInfo = routerModels.requesty?.[id] - // Merge native tool defaults for cached models that may lack these fields - const info = routerInfo ? { ...NATIVE_TOOL_DEFAULTS, ...routerInfo } : undefined - return { id, info } + return { id, info: routerInfo } } case "unbound": { const id = getValidatedModelId(apiConfiguration.unboundModelId, routerModels.unbound, defaultModelId) const routerInfo = routerModels.unbound?.[id] - // Merge native tool defaults for cached models that may lack these fields - const info = routerInfo ? { ...NATIVE_TOOL_DEFAULTS, ...routerInfo } : undefined - return { id, info } + return { id, info: routerInfo } } case "litellm": { const id = getValidatedModelId(apiConfiguration.litellmModelId, routerModels.litellm, defaultModelId) const routerInfo = routerModels.litellm?.[id] - // Merge native tool defaults for cached models that may lack these fields - const info = routerInfo ? { ...NATIVE_TOOL_DEFAULTS, ...routerInfo } : litellmDefaultModelInfo - return { id, info } + return { id, info: routerInfo ?? litellmDefaultModelInfo } } case "xai": { const id = apiConfiguration.apiModelId ?? defaultModelId @@ -283,12 +276,7 @@ function getSelectedModel({ case "openai": { const id = apiConfiguration.openAiModelId ?? "" const customInfo = apiConfiguration?.openAiCustomModelInfo - // Only merge native tool call defaults, not prices or other model-specific info - const nativeToolDefaults = { - supportsNativeTools: openAiModelInfoSaneDefaults.supportsNativeTools, - defaultToolProtocol: openAiModelInfoSaneDefaults.defaultToolProtocol, - } - const info = customInfo ? { ...nativeToolDefaults, ...customInfo } : openAiModelInfoSaneDefaults + const info = customInfo ?? openAiModelInfoSaneDefaults return { id, info } } case "ollama": { @@ -310,15 +298,9 @@ function getSelectedModel({ case "lmstudio": { const id = apiConfiguration.lmStudioModelId ?? "" const modelInfo = lmStudioModels && lmStudioModels[apiConfiguration.lmStudioModelId!] - // Only merge native tool call defaults, not prices or other model-specific info - const nativeToolDefaults = { - supportsNativeTools: lMStudioDefaultModelInfo.supportsNativeTools, - defaultToolProtocol: lMStudioDefaultModelInfo.defaultToolProtocol, - } - const info = modelInfo ? { ...nativeToolDefaults, ...modelInfo } : undefined return { id, - info, + info: modelInfo ? { ...lMStudioDefaultModelInfo, ...modelInfo } : undefined, } } case "deepinfra": { diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 5e37dc1a7df..16d2683cd17 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -767,14 +767,6 @@ "advancedSettings": { "title": "Configuració avançada" }, - "toolProtocol": { - "label": "Protocol de crida d'eines", - "description": "Trieu com es comunica en Roo amb l'API. Natiu utilitza l'API de crida de funcions del proveïdor, mentre que XML utilitza definicions d'eines amb format XML.", - "default": "Per defecte", - "xml": "XML", - "native": "Natiu", - "currentDefault": "Per defecte: {{protocol}}" - }, "advanced": { "diff": { "label": "Habilitar edició mitjançant diffs", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 5b9568f30e2..d7dedaa5e60 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -767,14 +767,6 @@ "advancedSettings": { "title": "Erweiterte Einstellungen" }, - "toolProtocol": { - "label": "Tool-Aufruf-Protokoll", - "description": "Wähle, wie Roo mit der API kommuniziert. Nativ verwendet die Funktionsaufruf-API des Anbieters, während XML XML-formatierte Werkzeugdefinitionen verwendet.", - "default": "Standard", - "xml": "XML", - "native": "Nativ", - "currentDefault": "Standard: {{protocol}}" - }, "advanced": { "diff": { "label": "Bearbeitung durch Diffs aktivieren", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index bce0ccfbd93..9bf14366066 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -776,14 +776,6 @@ "advancedSettings": { "title": "Advanced settings" }, - "toolProtocol": { - "label": "Tool Call Protocol", - "description": "Choose how Roo communicates with the API. Native uses the provider's function calling API, while XML uses XML-formatted tool definitions.", - "default": "Default", - "xml": "XML", - "native": "Native", - "currentDefault": "Default: {{protocol}}" - }, "advanced": { "diff": { "label": "Enable editing through diffs", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index a7a1bb71c60..f97a26bef41 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -767,14 +767,6 @@ "advancedSettings": { "title": "Configuración avanzada" }, - "toolProtocol": { - "label": "Protocolo de llamada a herramientas", - "description": "Elija cómo Roo se comunica con la API. Nativo utiliza la API de llamada a funciones del proveedor, mientras que XML utiliza definiciones de herramientas con formato XML.", - "default": "Predeterminado", - "xml": "XML", - "native": "Nativo", - "currentDefault": "Predeterminado: {{protocol}}" - }, "advanced": { "diff": { "label": "Habilitar edición a través de diffs", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 4982b67030c..d87805079c3 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -767,14 +767,6 @@ "advancedSettings": { "title": "Paramètres avancés" }, - "toolProtocol": { - "label": "Protocole d'appel d'outil", - "description": "Choisissez comment Roo communique avec l'API. Natif utilise l'API d'appel de fonction du fournisseur, tandis que XML utilise des définitions d'outils au format XML.", - "default": "Défaut", - "xml": "XML", - "native": "Natif", - "currentDefault": "Défaut: {{protocol}}" - }, "advanced": { "diff": { "label": "Activer l'édition via des diffs", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 030e920a03f..fa23a6a0443 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -768,14 +768,6 @@ "advancedSettings": { "title": "उन्नत सेटिंग्स" }, - "toolProtocol": { - "label": "टूल कॉल प्रोटोकॉल", - "description": "चुनें कि रू एपीआई के साथ कैसे संचार करता है। नेटिव प्रदाता के फ़ंक्शन कॉलिंग एपीआई का उपयोग करता है, जबकि एक्सएमएल एक्सएमएल-स्वरूपित टूल परिभाषाओं का उपयोग करता है।", - "default": "डिफ़ॉल्ट", - "xml": "एक्सएमएल", - "native": "नेटिव", - "currentDefault": "डिफ़ॉल्ट: {{protocol}}" - }, "advanced": { "diff": { "label": "diffs के माध्यम से संपादन सक्षम करें", diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index 1ee8bdd64c0..741d1854077 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -772,14 +772,6 @@ "advancedSettings": { "title": "Pengaturan lanjutan" }, - "toolProtocol": { - "label": "Protokol Panggilan Alat", - "description": "Pilih bagaimana Roo berkomunikasi dengan API. Asli menggunakan API panggilan fungsi penyedia, sedangkan XML menggunakan definisi alat berformat XML.", - "default": "Default", - "xml": "XML", - "native": "Asli", - "currentDefault": "Default: {{protocol}}" - }, "advanced": { "diff": { "label": "Aktifkan editing melalui diff", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 9fb92674447..93845477e2f 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -768,14 +768,6 @@ "advancedSettings": { "title": "Impostazioni avanzate" }, - "toolProtocol": { - "label": "Protocollo di chiamata dello strumento", - "description": "Scegli come Roo comunica con l'API. Nativo utilizza l'API di chiamata di funzione del provider, mentre XML utilizza definizioni di strumenti in formato XML.", - "default": "Predefinito", - "xml": "XML", - "native": "Nativo", - "currentDefault": "Predefinito: {{protocol}}" - }, "advanced": { "diff": { "label": "Abilita modifica tramite diff", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 999bb640d0c..c9e2883d5ae 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -768,14 +768,6 @@ "advancedSettings": { "title": "詳細設定" }, - "toolProtocol": { - "label": "ツールコールプロトコル", - "description": "Roo が API と通信する方法を選択します。ネイティブはプロバイダーの関数呼び出し API を使用し、XML は XML 形式のツール定義を使用します。", - "default": "デフォルト", - "xml": "XML", - "native": "ネイティブ", - "currentDefault": "デフォルト: {{protocol}}" - }, "advanced": { "diff": { "label": "diff経由の編集を有効化", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 428461b75ae..7a4fd179fe2 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -768,14 +768,6 @@ "advancedSettings": { "title": "고급 설정" }, - "toolProtocol": { - "label": "도구 호출 프로토콜", - "description": "Roo가 API와 통신하는 방법을 선택합니다. 네이티브는 공급자의 함수 호출 API를 사용하고 XML은 XML 형식의 도구 정의를 사용합니다.", - "default": "기본값", - "xml": "XML", - "native": "네이티브", - "currentDefault": "기본값: {{protocol}}" - }, "advanced": { "diff": { "label": "diff를 통한 편집 활성화", diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index b1bb0b80dcc..c02db25e9ca 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -768,14 +768,6 @@ "advancedSettings": { "title": "Geavanceerde instellingen" }, - "toolProtocol": { - "label": "Tool Call Protocol", - "description": "Kies hoe Roo communiceert met de API. Native gebruikt de functie-aanroep API van de provider, terwijl XML gebruikmaakt van XML-geformatteerde tooldefinities.", - "default": "Standaard", - "xml": "XML", - "native": "Native", - "currentDefault": "Standaard: {{protocol}}" - }, "advanced": { "diff": { "label": "Bewerken via diffs inschakelen", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 910f116c143..59ab07f7525 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -768,14 +768,6 @@ "advancedSettings": { "title": "Ustawienia zaawansowane" }, - "toolProtocol": { - "label": "Protokół wywołania narzędzia", - "description": "Wybierz, jak Roo komunikuje się z API. Natywny używa API wywołania funkcji dostawcy, podczas gdy XML używa definicji narzędzi w formacie XML.", - "default": "Domyślny", - "xml": "XML", - "native": "Natywny", - "currentDefault": "Domyślny: {{protocol}}" - }, "advanced": { "diff": { "label": "Włącz edycję przez różnice", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 7a47c0f5923..9985677e385 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -768,14 +768,6 @@ "advancedSettings": { "title": "Configurações avançadas" }, - "toolProtocol": { - "label": "Protocolo de Chamada de Ferramenta", - "description": "Escolha como o Roo se comunica com a API. Nativo usa a API de chamada de função do provedor, enquanto XML usa definições de ferramentas formatadas em XML.", - "default": "Padrão", - "xml": "XML", - "native": "Nativo", - "currentDefault": "Padrão: {{protocol}}" - }, "advanced": { "diff": { "label": "Ativar edição através de diffs", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index a1141bc9d6a..4c3073b6b93 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -768,14 +768,6 @@ "advancedSettings": { "title": "Дополнительные настройки" }, - "toolProtocol": { - "label": "Протокол вызова инструментов", - "description": "Выберите, как Roo будет взаимодействовать с API. Нативный использует API вызова функций провайдера, а XML — определения инструментов в формате XML.", - "default": "По умолчанию", - "xml": "XML", - "native": "Нативный", - "currentDefault": "По умолчанию: {{protocol}}" - }, "advanced": { "diff": { "label": "Включить редактирование через диффы", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index 51f4e01cdad..eda1ee6fc83 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -768,14 +768,6 @@ "advancedSettings": { "title": "Gelişmiş ayarlar" }, - "toolProtocol": { - "label": "Araç Çağrı Protokolü", - "description": "Roo'nun API ile nasıl iletişim kuracağını seçin. Yerel, sağlayıcının işlev çağırma API'sini kullanırken, XML, XML biçimli araç tanımlarını kullanır.", - "default": "Varsayılan", - "xml": "XML", - "native": "Yerel", - "currentDefault": "Varsayılan: {{protocol}}" - }, "advanced": { "diff": { "label": "Diff'ler aracılığıyla düzenlemeyi etkinleştir", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index b2761fec8a4..7dc80edfdd7 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -768,14 +768,6 @@ "advancedSettings": { "title": "Cài đặt nâng cao" }, - "toolProtocol": { - "label": "Giao thức gọi công cụ", - "description": "Chọn cách Roo giao tiếp với API. Native sử dụng API gọi hàm của nhà cung cấp, trong khi XML sử dụng định nghĩa công cụ định dạng XML.", - "default": "Mặc định", - "xml": "XML", - "native": "Native", - "currentDefault": "Mặc định: {{protocol}}" - }, "advanced": { "diff": { "label": "Bật chỉnh sửa qua diff", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 7dce71a42d7..8c45e887eee 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -768,14 +768,6 @@ "advancedSettings": { "title": "高级设置" }, - "toolProtocol": { - "label": "工具调用协议", - "description": "选择 Roo 如何与 API 通信。原生使用提供商的函数调用 API,而 XML 使用 XML 格式的工具定义。", - "default": "默认", - "xml": "XML", - "native": "原生", - "currentDefault": "默认: {{protocol}}" - }, "advanced": { "diff": { "label": "启用diff更新", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index c40be1d11a8..1ad8148f9eb 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -768,14 +768,6 @@ "advancedSettings": { "title": "進階設定" }, - "toolProtocol": { - "label": "工具呼叫協議", - "description": "選擇 Roo 如何與 API 通信。原生使用提供者的函數呼叫 API,而 XML 使用 XML 格式的工具定義。", - "default": "預設", - "xml": "XML", - "native": "原生", - "currentDefault": "預設: {{protocol}}" - }, "advanced": { "diff": { "label": "透過差異比對編輯",