From d08b0d300d41681741528306bd2da5888a2010e4 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Sun, 15 Jun 2025 15:14:11 +0700 Subject: [PATCH] Rename ToolUse to ToolDirective --- .../strategies/multi-file-search-replace.ts | 5 +- .../diff/strategies/multi-search-replace.ts | 9 +- src/core/message-parsing/FallbackParser.ts | 7 +- .../DirectiveStreamingParser.spec.ts | 9 +- .../DirectiveStreamingParser.test.ts | 9 +- .../__tests__/parseAssistantMessage.test.ts | 180 +++++++++--------- .../message-parsing/directives/Directive.ts | 5 + .../directives/ToolDirective.ts | 10 + src/core/message-parsing/directives/index.ts | 12 +- .../handlers/ToolDirectiveHandler.ts | 33 ++-- .../parseAssistantMessageV2.ts | 58 +++--- src/core/task/Task.ts | 5 +- src/core/tools/ToolRepetitionDetector.ts | 22 +-- .../__tests__/ToolRepetitionDetector.spec.ts | 107 ++++++----- .../__tests__/executeCommandTool.spec.ts | 29 +-- src/core/tools/__tests__/newTaskTool.test.ts | 10 +- src/core/tools/__tests__/readFileTool.test.ts | 14 +- .../tools/__tests__/useMcpToolTool.test.ts | 16 +- .../tools/__tests__/writeToFileTool.test.ts | 9 +- src/core/tools/accessMcpResourceTool.ts | 5 +- src/core/tools/applyDiffTool.ts | 5 +- src/core/tools/askFollowupQuestionTool.ts | 5 +- src/core/tools/attemptCompletionTool.ts | 4 +- src/core/tools/browserActionTool.ts | 5 +- src/core/tools/codebaseSearchTool.ts | 5 +- src/core/tools/executeCommandTool.ts | 5 +- src/core/tools/fetchInstructionsTool.ts | 5 +- src/core/tools/insertContentTool.ts | 5 +- src/core/tools/listCodeDefinitionNamesTool.ts | 5 +- src/core/tools/listFilesTool.ts | 5 +- src/core/tools/multiApplyDiffTool.ts | 6 +- src/core/tools/newTaskTool.ts | 7 +- src/core/tools/readFileTool.ts | 5 +- src/core/tools/searchAndReplaceTool.ts | 5 +- src/core/tools/searchFilesTool.ts | 5 +- src/core/tools/switchModeTool.ts | 5 +- src/core/tools/useMcpToolTool.ts | 5 +- src/core/tools/writeToFileTool.ts | 5 +- src/shared/tools.ts | 45 ++--- 39 files changed, 355 insertions(+), 336 deletions(-) create mode 100644 src/core/message-parsing/directives/Directive.ts create mode 100644 src/core/message-parsing/directives/ToolDirective.ts diff --git a/src/core/diff/strategies/multi-file-search-replace.ts b/src/core/diff/strategies/multi-file-search-replace.ts index 57503da5f4..ef7fbcf846 100644 --- a/src/core/diff/strategies/multi-file-search-replace.ts +++ b/src/core/diff/strategies/multi-file-search-replace.ts @@ -2,7 +2,8 @@ import { distance } from "fastest-levenshtein" import { ToolProgressStatus } from "@roo-code/types" import { addLineNumbers, everyLineHasLineNumbers, stripLineNumbers } from "../../../integrations/misc/extract-text" -import { ToolUse, DiffStrategy, DiffResult } from "../../../shared/tools" +import { DiffStrategy, DiffResult } from "../../../shared/tools" +import { ToolDirective } from "../../message-parsing/directives/" import { normalizeString } from "../../../utils/text-normalization" const BUFFER_LINES = 40 // Number of extra context lines to show before and after matches @@ -707,7 +708,7 @@ Each file requires its own path, start_line, and diff elements. } } - getProgressStatus(toolUse: ToolUse, result?: DiffResult): ToolProgressStatus { + getProgressStatus(toolUse: ToolDirective, result?: DiffResult): ToolProgressStatus { const diffContent = toolUse.params.diff if (diffContent) { const icon = "diff-multiple" diff --git a/src/core/diff/strategies/multi-search-replace.ts b/src/core/diff/strategies/multi-search-replace.ts index 9e740a6571..36c86f7531 100644 --- a/src/core/diff/strategies/multi-search-replace.ts +++ b/src/core/diff/strategies/multi-search-replace.ts @@ -5,7 +5,8 @@ import { distance } from "fastest-levenshtein" import { ToolProgressStatus } from "@roo-code/types" import { addLineNumbers, everyLineHasLineNumbers, stripLineNumbers } from "../../../integrations/misc/extract-text" -import { ToolUse, DiffStrategy, DiffResult } from "../../../shared/tools" +import { DiffStrategy, DiffResult } from "../../../shared/tools" +import { ToolDirective } from "../../message-parsing/directives/" import { normalizeString } from "../../../utils/text-normalization" const BUFFER_LINES = 40 // Number of extra context lines to show before and after matches @@ -609,11 +610,11 @@ Only use a single line of '=======' between search and replacement content, beca } } - getProgressStatus(toolUse: ToolUse, result?: DiffResult): ToolProgressStatus { - const diffContent = toolUse.params.diff + getProgressStatus(ToolDirective: ToolDirective, result?: DiffResult): ToolProgressStatus { + const diffContent = ToolDirective.params.diff if (diffContent) { const icon = "diff-multiple" - if (toolUse.partial) { + if (ToolDirective.partial) { if (Math.floor(diffContent.length / 10) % 10 === 0) { const searchBlockCount = (diffContent.match(/SEARCH/g) || []).length return { icon, text: `${searchBlockCount}` } diff --git a/src/core/message-parsing/FallbackParser.ts b/src/core/message-parsing/FallbackParser.ts index 59f94d6d95..d281640b15 100644 --- a/src/core/message-parsing/FallbackParser.ts +++ b/src/core/message-parsing/FallbackParser.ts @@ -1,7 +1,6 @@ -import { Directive } from "./directives" +import { Directive, ToolDirective } from "./directives" import { TextDirective, LogDirective } from "./directives" import { toolNames } from "@roo-code/types" -import { ToolUse } from "../../shared/tools" export class FallbackParser { static parse(assistantMessage: string): Directive[] { @@ -76,14 +75,14 @@ export class FallbackParser { } } - const toolUse: ToolUse = { + const ToolDirective: ToolDirective = { type: "tool_use", name: toolName as any, params, partial: !assistantMessage.includes(``), } - contentBlocks.push(toolUse) + contentBlocks.push(ToolDirective) return contentBlocks } } diff --git a/src/core/message-parsing/__tests__/DirectiveStreamingParser.spec.ts b/src/core/message-parsing/__tests__/DirectiveStreamingParser.spec.ts index d7ead213f9..2f3f21cd9a 100644 --- a/src/core/message-parsing/__tests__/DirectiveStreamingParser.spec.ts +++ b/src/core/message-parsing/__tests__/DirectiveStreamingParser.spec.ts @@ -1,7 +1,6 @@ import { suite, test, expect } from "vitest" import { DirectiveStreamingParser } from "../DirectiveStreamingParser" -import { TextDirective, LogDirective } from "../directives" -import { ToolUse } from "../../../shared/tools" +import { ToolDirective, TextDirective, LogDirective } from "../directives" suite("DirectiveStreamingParser", () => { test("should parse plain text content", () => { @@ -51,7 +50,7 @@ suite("DirectiveStreamingParser", () => { name: "read_file", params: { path: "src/app.ts" }, partial: false, - } as ToolUse, + } as ToolDirective, ]) }) @@ -70,7 +69,7 @@ suite("DirectiveStreamingParser", () => { name: "apply_diff", params: { path: "src/file.ts", diff: "diff content" }, partial: false, - } as ToolUse, + } as ToolDirective, { type: "text", content: "More text", @@ -88,7 +87,7 @@ suite("DirectiveStreamingParser", () => { name: "write_to_file", params: { path: "src/newfile.ts", content: "Some content" }, partial: true, - } as ToolUse, + } as ToolDirective, ]) }) }) diff --git a/src/core/message-parsing/__tests__/DirectiveStreamingParser.test.ts b/src/core/message-parsing/__tests__/DirectiveStreamingParser.test.ts index d7ead213f9..2f3f21cd9a 100644 --- a/src/core/message-parsing/__tests__/DirectiveStreamingParser.test.ts +++ b/src/core/message-parsing/__tests__/DirectiveStreamingParser.test.ts @@ -1,7 +1,6 @@ import { suite, test, expect } from "vitest" import { DirectiveStreamingParser } from "../DirectiveStreamingParser" -import { TextDirective, LogDirective } from "../directives" -import { ToolUse } from "../../../shared/tools" +import { ToolDirective, TextDirective, LogDirective } from "../directives" suite("DirectiveStreamingParser", () => { test("should parse plain text content", () => { @@ -51,7 +50,7 @@ suite("DirectiveStreamingParser", () => { name: "read_file", params: { path: "src/app.ts" }, partial: false, - } as ToolUse, + } as ToolDirective, ]) }) @@ -70,7 +69,7 @@ suite("DirectiveStreamingParser", () => { name: "apply_diff", params: { path: "src/file.ts", diff: "diff content" }, partial: false, - } as ToolUse, + } as ToolDirective, { type: "text", content: "More text", @@ -88,7 +87,7 @@ suite("DirectiveStreamingParser", () => { name: "write_to_file", params: { path: "src/newfile.ts", content: "Some content" }, partial: true, - } as ToolUse, + } as ToolDirective, ]) }) }) diff --git a/src/core/message-parsing/__tests__/parseAssistantMessage.test.ts b/src/core/message-parsing/__tests__/parseAssistantMessage.test.ts index def4d21eb6..12bd04c3f9 100644 --- a/src/core/message-parsing/__tests__/parseAssistantMessage.test.ts +++ b/src/core/message-parsing/__tests__/parseAssistantMessage.test.ts @@ -1,8 +1,6 @@ // npx jest src/core/assistant-message/__tests__/parseAssistantMessage.test.ts -import { TextDirective } from "../directives" -import { ToolUse } from "../../../shared/tools" - +import { TextDirective, ToolDirective } from "../directives" import { AssistantMessageContent, parseAssistantMessage as parseAssistantMessageV1 } from "../parseAssistantMessage" import { parseAssistantMessageV2 } from "../parseAssistantMessageV2" @@ -55,11 +53,11 @@ const isEmptyTextContent = (block: AssistantMessageContent) => 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) + const ToolDirective = result[0] as ToolDirective + expect(ToolDirective.type).toBe("tool_use") + expect(ToolDirective.name).toBe("read_file") + expect(ToolDirective.params.path).toBe("src/file.ts") + expect(ToolDirective.partial).toBe(false) }) it("should parse a tool use with multiple parameters", () => { @@ -68,13 +66,13 @@ const isEmptyTextContent = (block: AssistantMessageContent) => 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) + const ToolDirective = result[0] as ToolDirective + expect(ToolDirective.type).toBe("tool_use") + expect(ToolDirective.name).toBe("read_file") + expect(ToolDirective.params.path).toBe("src/file.ts") + expect(ToolDirective.params.start_line).toBe("10") + expect(ToolDirective.params.end_line).toBe("20") + expect(ToolDirective.partial).toBe(false) }) it("should mark tool use as partial when it's not closed", () => { @@ -82,11 +80,11 @@ const isEmptyTextContent = (block: AssistantMessageContent) => 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) + const ToolDirective = result[0] as ToolDirective + expect(ToolDirective.type).toBe("tool_use") + expect(ToolDirective.name).toBe("read_file") + expect(ToolDirective.params.path).toBe("src/file.ts") + expect(ToolDirective.partial).toBe(true) }) it("should handle a partial parameter in a tool use", () => { @@ -94,11 +92,11 @@ const isEmptyTextContent = (block: AssistantMessageContent) => 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) + const ToolDirective = result[0] as ToolDirective + expect(ToolDirective.type).toBe("tool_use") + expect(ToolDirective.name).toBe("read_file") + expect(ToolDirective.params.path).toBe("src/file.ts") + expect(ToolDirective.partial).toBe(true) }) }) @@ -114,11 +112,11 @@ const isEmptyTextContent = (block: AssistantMessageContent) => 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) + const ToolDirective = result[1] as ToolDirective + expect(ToolDirective.type).toBe("tool_use") + expect(ToolDirective.name).toBe("read_file") + expect(ToolDirective.params.path).toBe("src/file.ts") + expect(ToolDirective.partial).toBe(false) }) it("should parse a tool use followed by text", () => { @@ -127,11 +125,11 @@ const isEmptyTextContent = (block: AssistantMessageContent) => 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 ToolDirective = result[0] as ToolDirective + expect(ToolDirective.type).toBe("tool_use") + expect(ToolDirective.name).toBe("read_file") + expect(ToolDirective.params.path).toBe("src/file.ts") + expect(ToolDirective.partial).toBe(false) const textContent = result[1] as TextDirective expect(textContent.type).toBe("text") @@ -150,15 +148,15 @@ const isEmptyTextContent = (block: AssistantMessageContent) => expect((result[0] as TextDirective).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[1] as ToolDirective).name).toBe("read_file") + expect((result[1] as ToolDirective).params.path).toBe("src/file1.ts") expect(result[2].type).toBe("text") expect((result[2] as TextDirective).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") + expect((result[3] as ToolDirective).name).toBe("read_file") + expect((result[3] as ToolDirective).params.path).toBe("src/file2.ts") }) }) @@ -174,15 +172,15 @@ const isEmptyTextContent = (block: AssistantMessageContent) => 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.line_count).toBe("5") - 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) + const ToolDirective = result[0] as ToolDirective + expect(ToolDirective.type).toBe("tool_use") + expect(ToolDirective.name).toBe("write_to_file") + expect(ToolDirective.params.path).toBe("src/file.ts") + expect(ToolDirective.params.line_count).toBe("5") + expect(ToolDirective.params.content).toContain("function example()") + expect(ToolDirective.params.content).toContain("// This has XML-like content: ") + expect(ToolDirective.params.content).toContain("return true;") + expect(ToolDirective.partial).toBe(false) }) it("should handle empty messages", () => { @@ -206,11 +204,11 @@ const isEmptyTextContent = (block: AssistantMessageContent) => 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) + const ToolDirective = result[0] as ToolDirective + expect(ToolDirective.type).toBe("tool_use") + expect(ToolDirective.name).toBe("browser_action") + expect(Object.keys(ToolDirective.params).length).toBe(0) + expect(ToolDirective.partial).toBe(false) }) it("should handle nested tool tags that aren't actually nested", () => { @@ -220,11 +218,11 @@ const isEmptyTextContent = (block: AssistantMessageContent) => 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) + const ToolDirective = result[0] as ToolDirective + expect(ToolDirective.type).toBe("tool_use") + expect(ToolDirective.name).toBe("execute_command") + expect(ToolDirective.params.command).toBe("echo 'test.txt'") + expect(ToolDirective.partial).toBe(false) }) it("should handle a tool use with a parameter containing XML-like content", () => { @@ -232,12 +230,12 @@ const isEmptyTextContent = (block: AssistantMessageContent) => 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) + const ToolDirective = result[0] as ToolDirective + expect(ToolDirective.type).toBe("tool_use") + expect(ToolDirective.name).toBe("search_files") + expect(ToolDirective.params.regex).toBe("
.*
") + expect(ToolDirective.params.path).toBe("src") + expect(ToolDirective.partial).toBe(false) }) it("should handle consecutive tool uses without text in between", () => { @@ -247,17 +245,17 @@ const isEmptyTextContent = (block: AssistantMessageContent) => 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 ToolDirective1 = result[0] as ToolDirective + expect(ToolDirective1.type).toBe("tool_use") + expect(ToolDirective1.name).toBe("read_file") + expect(ToolDirective1.params.path).toBe("file1.ts") + expect(ToolDirective1.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) + const ToolDirective2 = result[1] as ToolDirective + expect(ToolDirective2.type).toBe("tool_use") + expect(ToolDirective2.name).toBe("read_file") + expect(ToolDirective2.params.path).toBe("file2.ts") + expect(ToolDirective2.partial).toBe(false) }) it("should handle whitespace in parameters", () => { @@ -265,11 +263,11 @@ const isEmptyTextContent = (block: AssistantMessageContent) => 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) + const ToolDirective = result[0] as ToolDirective + expect(ToolDirective.type).toBe("tool_use") + expect(ToolDirective.name).toBe("read_file") + expect(ToolDirective.params.path).toBe("src/file.ts") + expect(ToolDirective.partial).toBe(false) }) it("should handle multi-line parameters", () => { @@ -281,15 +279,15 @@ const isEmptyTextContent = (block: AssistantMessageContent) => 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.params.line_count).toBe("3") - expect(toolUse.partial).toBe(false) + const ToolDirective = result[0] as ToolDirective + expect(ToolDirective.type).toBe("tool_use") + expect(ToolDirective.name).toBe("write_to_file") + expect(ToolDirective.params.path).toBe("file.ts") + expect(ToolDirective.params.content).toContain("line 1") + expect(ToolDirective.params.content).toContain("line 2") + expect(ToolDirective.params.content).toContain("line 3") + expect(ToolDirective.params.line_count).toBe("3") + expect(ToolDirective.partial).toBe(false) }) it("should handle a complex message with multiple content types", () => { @@ -318,7 +316,7 @@ const isEmptyTextContent = (block: AssistantMessageContent) => // First tool use (read_file) expect(result[1].type).toBe("tool_use") - expect((result[1] as ToolUse).name).toBe("read_file") + expect((result[1] as ToolDirective).name).toBe("read_file") // Second text block expect(result[2].type).toBe("text") @@ -326,7 +324,7 @@ const isEmptyTextContent = (block: AssistantMessageContent) => // Second tool use (write_to_file) expect(result[3].type).toBe("tool_use") - expect((result[3] as ToolUse).name).toBe("write_to_file") + expect((result[3] as ToolDirective).name).toBe("write_to_file") // Third text block expect(result[4].type).toBe("text") @@ -334,7 +332,7 @@ const isEmptyTextContent = (block: AssistantMessageContent) => // Third tool use (execute_command) expect(result[5].type).toBe("tool_use") - expect((result[5] as ToolUse).name).toBe("execute_command") + expect((result[5] as ToolDirective).name).toBe("execute_command") }) }) }) diff --git a/src/core/message-parsing/directives/Directive.ts b/src/core/message-parsing/directives/Directive.ts new file mode 100644 index 0000000000..113ae64cac --- /dev/null +++ b/src/core/message-parsing/directives/Directive.ts @@ -0,0 +1,5 @@ +import { LogDirective } from "./LogDirective" +import { TextDirective } from "./TextDirective" +import { ToolDirective } from "./ToolDirective" + +export type Directive = TextDirective | ToolDirective | LogDirective diff --git a/src/core/message-parsing/directives/ToolDirective.ts b/src/core/message-parsing/directives/ToolDirective.ts new file mode 100644 index 0000000000..6a9437e67c --- /dev/null +++ b/src/core/message-parsing/directives/ToolDirective.ts @@ -0,0 +1,10 @@ +import { ToolName } from "@roo-code/types" +import { ToolParamName } from "../../../shared/tools" + +export interface ToolDirective { + type: "tool_use" + name: ToolName + // params is a partial record, allowing only some or none of the possible parameters to be used + params: Partial> + partial: boolean +} diff --git a/src/core/message-parsing/directives/index.ts b/src/core/message-parsing/directives/index.ts index 62397abe18..36f7aa9260 100644 --- a/src/core/message-parsing/directives/index.ts +++ b/src/core/message-parsing/directives/index.ts @@ -1,8 +1,4 @@ -import { TextDirective, LogDirective } from "../directives" -import { ToolUse } from "../../../shared/tools" - -export * from "./LogDirective" -export * from "./TextDirective" - -export type ToolDirective = ToolUse -export type Directive = TextDirective | ToolDirective | LogDirective +export type { Directive } from "./Directive" +export type { TextDirective } from "./TextDirective" +export type { ToolDirective } from "./ToolDirective" +export type { LogDirective } from "./LogDirective" diff --git a/src/core/message-parsing/handlers/ToolDirectiveHandler.ts b/src/core/message-parsing/handlers/ToolDirectiveHandler.ts index 505941f677..a480aebbb5 100644 --- a/src/core/message-parsing/handlers/ToolDirectiveHandler.ts +++ b/src/core/message-parsing/handlers/ToolDirectiveHandler.ts @@ -1,11 +1,12 @@ import * as sax from "sax" import { BaseDirectiveHandler } from "./BaseDirectiveHandler" import { ParseContext } from "../ParseContext" -import { ToolUse, ToolParamName } from "../../../shared/tools" +import { ToolParamName } from "../../../shared/tools" +import { ToolDirective } from "../directives" export class ToolDirectiveHandler extends BaseDirectiveHandler { readonly tagName: string - private currentToolUse?: ToolUse + private currentToolDirective?: ToolDirective private currentParamName?: ToolParamName private currentParamValue = "" private currentContext: "param" | "none" = "none" @@ -18,14 +19,14 @@ export class ToolDirectiveHandler extends BaseDirectiveHandler { override onOpenTag(node: sax.Tag, context: ParseContext): void { if (node.name === this.tagName) { this.flushCurrentText(context) - this.currentToolUse = { + this.currentToolDirective = { type: "tool_use", name: this.tagName as any, params: {}, partial: true, } this.currentContext = "none" - } else if (this.currentToolUse) { + } else if (this.currentToolDirective) { this.currentParamName = node.name as ToolParamName this.currentParamValue = "" this.currentContext = "param" @@ -33,13 +34,13 @@ export class ToolDirectiveHandler extends BaseDirectiveHandler { } override onCloseTag(tagName: string, context: ParseContext): void { - if (tagName === this.tagName && this.currentToolUse) { - this.currentToolUse.partial = - context.hasIncompleteXml || Object.keys(this.currentToolUse.params).length === 0 - context.contentBlocks.push(this.currentToolUse) - this.currentToolUse = undefined - } else if (this.currentToolUse && this.currentParamName && tagName === this.currentParamName) { - ;(this.currentToolUse.params as Record)[this.currentParamName] = + if (tagName === this.tagName && this.currentToolDirective) { + this.currentToolDirective.partial = + context.hasIncompleteXml || Object.keys(this.currentToolDirective.params).length === 0 + context.contentBlocks.push(this.currentToolDirective) + this.currentToolDirective = undefined + } else if (this.currentToolDirective && this.currentParamName && tagName === this.currentParamName) { + ;(this.currentToolDirective.params as Record)[this.currentParamName] = this.currentParamValue.trim() this.currentParamName = undefined this.currentParamValue = "" @@ -48,19 +49,19 @@ export class ToolDirectiveHandler extends BaseDirectiveHandler { } override onText(text: string, context: ParseContext): void { - if (this.currentContext === "param" && this.currentParamName && this.currentToolUse) { + if (this.currentContext === "param" && this.currentParamName && this.currentToolDirective) { this.currentParamValue += text } } override onEnd(context: ParseContext): void { - if (this.currentToolUse) { + if (this.currentToolDirective) { if (this.currentParamName && this.currentParamValue) { - ;(this.currentToolUse.params as Record)[this.currentParamName] = + ;(this.currentToolDirective.params as Record)[this.currentParamName] = this.currentParamValue.trim() } - this.currentToolUse.partial = true - context.contentBlocks.push(this.currentToolUse) + this.currentToolDirective.partial = true + context.contentBlocks.push(this.currentToolDirective) } } } diff --git a/src/core/message-parsing/parseAssistantMessageV2.ts b/src/core/message-parsing/parseAssistantMessageV2.ts index 7c8299e50b..3d18994dc1 100644 --- a/src/core/message-parsing/parseAssistantMessageV2.ts +++ b/src/core/message-parsing/parseAssistantMessageV2.ts @@ -1,8 +1,8 @@ import { type ToolName, toolNames } from "@roo-code/types" -import { TextDirective } from "./directives" -import { ToolUse, ToolParamName, toolParamNames } from "../../shared/tools" +import { TextDirective, ToolDirective } from "./directives" +import { ToolParamName, toolParamNames } from "../../shared/tools" -export type AssistantMessageContent = TextDirective | ToolUse +export type AssistantMessageContent = TextDirective | ToolDirective /** * Parses an assistant message string potentially containing mixed text and tool @@ -14,10 +14,10 @@ export type AssistantMessageContent = TextDirective | ToolUse * position, it checks if the substring *ending* at `i` matches any known * opening or closing tags for tools or parameters using `startsWith` with an * offset. - * It uses pre-computed Maps (`toolUseOpenTags`, `toolParamOpenTags`) for quick + * It uses pre-computed Maps (`ToolDirectiveOpenTags`, `toolParamOpenTags`) for quick * tag lookups. * State is managed using indices (`currentTextContentStart`, - * `currentToolUseStart`, `currentParamValueStart`) pointing to the start of the + * `currentToolDirectiveStart`, `currentParamValueStart`) pointing to the start of the * current block within the original `assistantMessage` string. * * Slicing is used to extract content only when a block (text, parameter, or @@ -32,7 +32,7 @@ export type AssistantMessageContent = TextDirective | ToolUse * * @param assistantMessage The raw string output from the assistant. * @returns An array of `AssistantMessageContent` objects, which can be - * `TextContent` or `ToolUse`. Blocks that were not fully closed by the + * `TextContent` or `ToolDirective`. Blocks that were not fully closed by the * end of the input string will have their `partial` flag set to * `true`. */ @@ -42,17 +42,17 @@ export function parseAssistantMessageV2(assistantMessage: string): AssistantMess let currentTextContentStart = 0 // Index where the current text block started. let currentTextContent: TextDirective | undefined = undefined - let currentToolUseStart = 0 // Index *after* the opening tag of the current tool use. - let currentToolUse: ToolUse | undefined = undefined + let currentToolDirectiveStart = 0 // Index *after* the opening tag of the current tool use. + let currentToolDirective: ToolDirective | undefined = undefined let currentParamValueStart = 0 // Index *after* the opening tag of the current param. let currentParamName: ToolParamName | undefined = undefined // Precompute tags for faster lookups. - const toolUseOpenTags = new Map() + const ToolDirectiveOpenTags = new Map() const toolParamOpenTags = new Map() for (const name of toolNames) { - toolUseOpenTags.set(`<${name}>`, name) + ToolDirectiveOpenTags.set(`<${name}>`, name) } for (const name of toolParamNames) { @@ -65,7 +65,7 @@ export function parseAssistantMessageV2(assistantMessage: string): AssistantMess const currentCharIndex = i // Parsing a tool parameter - if (currentToolUse && currentParamName) { + if (currentToolDirective && currentParamName) { const closeTag = `` // Check if the string *ending* at index `i` matches the closing tag if ( @@ -82,7 +82,7 @@ export function parseAssistantMessageV2(assistantMessage: string): AssistantMess currentCharIndex - closeTag.length + 1, // End before the closing tag. ) .trim() - currentToolUse.params[currentParamName] = value + currentToolDirective.params[currentParamName] = value 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 { @@ -91,7 +91,7 @@ export function parseAssistantMessageV2(assistantMessage: string): AssistantMess } // Parsing a tool use (but not a specific parameter). - if (currentToolUse && !currentParamName) { + if (currentToolDirective && !currentParamName) { // Ensure we are not inside a parameter already. // Check if starting a new parameter. let startedNewParam = false @@ -113,7 +113,7 @@ export function parseAssistantMessageV2(assistantMessage: string): AssistantMess } // Check if closing the current tool use. - const toolCloseTag = `` + const toolCloseTag = `` if ( currentCharIndex >= toolCloseTag.length - 1 && @@ -123,7 +123,7 @@ export function parseAssistantMessageV2(assistantMessage: string): AssistantMess // Special handling for content params *before* finalizing the // tool. const toolContentSlice = assistantMessage.slice( - currentToolUseStart, // From after the tool opening tag. + currentToolDirectiveStart, // From after the tool opening tag. currentCharIndex - toolCloseTag.length + 1, // To before the tool closing tag. ) @@ -134,8 +134,8 @@ export function parseAssistantMessageV2(assistantMessage: string): AssistantMess // 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. + currentToolDirective.name === "write_to_file" /* || currentToolDirective.name === "new_rule" */ && + // !(contentParamName in currentToolDirective.params) && // Only if not already parsed. toolContentSlice.includes(`<${contentParamName}>`) // Check if tag exists. ) { const contentStartTag = `<${contentParamName}>` @@ -150,13 +150,13 @@ export function parseAssistantMessageV2(assistantMessage: string): AssistantMess .slice(contentStart + contentStartTag.length, contentEnd) .trim() - currentToolUse.params[contentParamName] = contentValue + currentToolDirective.params[contentParamName] = contentValue } } - currentToolUse.partial = false // Mark as complete. - contentBlocks.push(currentToolUse) - currentToolUse = undefined // Reset state. + currentToolDirective.partial = false // Mark as complete. + contentBlocks.push(currentToolDirective) + currentToolDirective = undefined // Reset state. currentTextContentStart = currentCharIndex + 1 // Potential text starts after this tag. continue // Move to next char. } @@ -167,11 +167,11 @@ export function parseAssistantMessageV2(assistantMessage: string): AssistantMess } // Parsing text / looking for tool start. - if (!currentToolUse) { + if (!currentToolDirective) { // Check if starting a new tool use. let startedNewTool = false - for (const [tag, toolName] of toolUseOpenTags.entries()) { + for (const [tag, toolName] of ToolDirectiveOpenTags.entries()) { if ( currentCharIndex >= tag.length - 1 && assistantMessage.startsWith(tag, currentCharIndex - tag.length + 1) @@ -211,14 +211,14 @@ export function parseAssistantMessageV2(assistantMessage: string): AssistantMess } // Start the new tool use. - currentToolUse = { + currentToolDirective = { 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. + currentToolDirectiveStart = currentCharIndex + 1 // Tool content starts after the opening tag. startedNewTool = true break @@ -250,17 +250,17 @@ export function parseAssistantMessageV2(assistantMessage: string): AssistantMess } // Finalize any open parameter within an open tool use. - if (currentToolUse && currentParamName) { - currentToolUse.params[currentParamName] = assistantMessage + if (currentToolDirective && currentParamName) { + currentToolDirective.params[currentParamName] = assistantMessage .slice(currentParamValueStart) // From param start to end of string. .trim() // Tool use remains partial. } // Finalize any open tool use (which might contain the finalized partial param). - if (currentToolUse) { + if (currentToolDirective) { // Tool use is partial because the loop finished before its closing tag. - contentBlocks.push(currentToolUse) + contentBlocks.push(currentToolDirective) } // Finalize any trailing text content. // Only possible if a tool use wasn't open at the very end. diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 4d4bab3179..d4bddd9394 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -65,7 +65,7 @@ import { SYSTEM_PROMPT } from "../prompts/system" import { ToolRepetitionDetector } from "../tools/ToolRepetitionDetector" import { FileContextTracker } from "../context-tracking/FileContextTracker" import { RooIgnoreController } from "../ignore/RooIgnoreController" -import { presentAssistantMessage } from "../assistant-message" +import { presentAssistantMessage } from "../message-parsing" import { truncateConversationIfNeeded } from "../sliding-window" import { ClineProvider } from "../webview/ClineProvider" import { MultiSearchReplaceDiffStrategy } from "../diff/strategies/multi-search-replace" @@ -85,8 +85,7 @@ import { ApiMessage } from "../task-persistence/apiMessages" import { getMessagesSinceLastSummary, summarizeConversation } from "../condense" import { maybeRemoveImageBlocks } from "../../api/transform/image-cleaning" import { LogManager } from "../logging" -import { Directive } from "../assistant-message" -import { DirectiveStreamingParser } from "../assistant-message" +import { Directive, DirectiveStreamingParser } from "../message-parsing" export type ClineEvents = { message: [{ action: "created" | "updated"; message: ClineMessage }] diff --git a/src/core/tools/ToolRepetitionDetector.ts b/src/core/tools/ToolRepetitionDetector.ts index a82574ba0e..ebdaa13d1c 100644 --- a/src/core/tools/ToolRepetitionDetector.ts +++ b/src/core/tools/ToolRepetitionDetector.ts @@ -1,5 +1,5 @@ -import { ToolUse } from "../../shared/tools" import { t } from "../../i18n" +import { ToolDirective } from "../message-parsing/directives" /** * Class for detecting consecutive identical tool calls @@ -22,10 +22,10 @@ export class ToolRepetitionDetector { * Checks if the current tool call is identical to the previous one * and determines if execution should be allowed * - * @param currentToolCallBlock ToolUse object representing the current tool call + * @param currentToolCallBlock ToolDirective object representing the current tool call * @returns Object indicating if execution is allowed and a message to show if not */ - public check(currentToolCallBlock: ToolUse): { + public check(currentToolCallBlock: ToolDirective): { allowExecution: boolean askUser?: { messageKey: string @@ -33,7 +33,7 @@ export class ToolRepetitionDetector { } } { // Serialize the block to a canonical JSON string for comparison - const currentToolCallJson = this.serializeToolUse(currentToolCallBlock) + const currentToolCallJson = this.serializeToolDirective(currentToolCallBlock) // Compare with previous tool call if (this.previousToolCallJson === currentToolCallJson) { @@ -64,28 +64,28 @@ export class ToolRepetitionDetector { } /** - * Serializes a ToolUse object into a canonical JSON string for comparison + * Serializes a ToolDirective object into a canonical JSON string for comparison * - * @param toolUse The ToolUse object to serialize + * @param ToolDirective The ToolDirective object to serialize * @returns JSON string representation of the tool use with sorted parameter keys */ - private serializeToolUse(toolUse: ToolUse): string { + private serializeToolDirective(ToolDirective: ToolDirective): string { // Create a new parameters object with alphabetically sorted keys const sortedParams: Record = {} // Get parameter keys and sort them alphabetically - const sortedKeys = Object.keys(toolUse.params).sort() + const sortedKeys = Object.keys(ToolDirective.params).sort() // Populate the sorted parameters object in a type-safe way for (const key of sortedKeys) { - if (Object.prototype.hasOwnProperty.call(toolUse.params, key)) { - sortedParams[key] = toolUse.params[key as keyof typeof toolUse.params] + if (Object.prototype.hasOwnProperty.call(ToolDirective.params, key)) { + sortedParams[key] = ToolDirective.params[key as keyof typeof ToolDirective.params] } } // Create the object with the tool name and sorted parameters const toolObject = { - name: toolUse.name, + name: ToolDirective.name, parameters: sortedParams, } diff --git a/src/core/tools/__tests__/ToolRepetitionDetector.spec.ts b/src/core/tools/__tests__/ToolRepetitionDetector.spec.ts index 8dd5cd562e..e32e4223e8 100644 --- a/src/core/tools/__tests__/ToolRepetitionDetector.spec.ts +++ b/src/core/tools/__tests__/ToolRepetitionDetector.spec.ts @@ -3,9 +3,8 @@ import { vitest, describe, it, expect } from "vitest" import type { ToolName } from "@roo-code/types" -import type { ToolUse } from "../../../shared/tools" - import { ToolRepetitionDetector } from "../ToolRepetitionDetector" +import { ToolDirective } from "../../message-parsing/directives" vitest.mock("../../../i18n", () => ({ t: vitest.fn((key, options) => { @@ -17,7 +16,7 @@ vitest.mock("../../../i18n", () => ({ }), })) -function createToolUse(name: string, displayName?: string, params: Record = {}): ToolUse { +function createToolDirective(name: string, displayName?: string, params: Record = {}): ToolDirective { return { type: "tool_use", name: (displayName || name) as ToolName, @@ -34,15 +33,15 @@ describe("ToolRepetitionDetector", () => { // We'll verify this through behavior in subsequent tests // First call (counter = 1) - const result1 = detector.check(createToolUse("test", "test-tool")) + const result1 = detector.check(createToolDirective("test", "test-tool")) expect(result1.allowExecution).toBe(true) // Second identical call (counter = 2) - const result2 = detector.check(createToolUse("test", "test-tool")) + const result2 = detector.check(createToolDirective("test", "test-tool")) expect(result2.allowExecution).toBe(true) // Third identical call (counter = 3) reaches the default limit - const result3 = detector.check(createToolUse("test", "test-tool")) + const result3 = detector.check(createToolDirective("test", "test-tool")) expect(result3.allowExecution).toBe(false) }) @@ -51,11 +50,11 @@ describe("ToolRepetitionDetector", () => { const detector = new ToolRepetitionDetector(customLimit) // First call (counter = 1) - const result1 = detector.check(createToolUse("test", "test-tool")) + const result1 = detector.check(createToolDirective("test", "test-tool")) expect(result1.allowExecution).toBe(true) // Second identical call (counter = 2) reaches the custom limit - const result2 = detector.check(createToolUse("test", "test-tool")) + const result2 = detector.check(createToolDirective("test", "test-tool")) expect(result2.allowExecution).toBe(false) }) }) @@ -65,15 +64,15 @@ describe("ToolRepetitionDetector", () => { it("should allow execution for different tool calls", () => { const detector = new ToolRepetitionDetector() - const result1 = detector.check(createToolUse("first", "first-tool")) + const result1 = detector.check(createToolDirective("first", "first-tool")) expect(result1.allowExecution).toBe(true) expect(result1.askUser).toBeUndefined() - const result2 = detector.check(createToolUse("second", "second-tool")) + const result2 = detector.check(createToolDirective("second", "second-tool")) expect(result2.allowExecution).toBe(true) expect(result2.askUser).toBeUndefined() - const result3 = detector.check(createToolUse("third", "third-tool")) + const result3 = detector.check(createToolDirective("third", "third-tool")) expect(result3.allowExecution).toBe(true) expect(result3.askUser).toBeUndefined() }) @@ -82,13 +81,13 @@ describe("ToolRepetitionDetector", () => { const detector = new ToolRepetitionDetector(2) // First call - detector.check(createToolUse("same", "same-tool")) + detector.check(createToolDirective("same", "same-tool")) // Second identical call would reach limit of 2, but we'll make a different call - detector.check(createToolUse("different", "different-tool")) + detector.check(createToolDirective("different", "different-tool")) // Back to the first tool - should be allowed since counter was reset - const result = detector.check(createToolUse("same", "same-tool")) + const result = detector.check(createToolDirective("same", "same-tool")) expect(result.allowExecution).toBe(true) }) }) @@ -99,15 +98,15 @@ describe("ToolRepetitionDetector", () => { const detector = new ToolRepetitionDetector(3) // First call (counter = 1) - const result1 = detector.check(createToolUse("repeat", "repeat-tool")) + const result1 = detector.check(createToolDirective("repeat", "repeat-tool")) expect(result1.allowExecution).toBe(true) // Second identical call (counter = 2) - const result2 = detector.check(createToolUse("repeat", "repeat-tool")) + const result2 = detector.check(createToolDirective("repeat", "repeat-tool")) expect(result2.allowExecution).toBe(true) // Third identical call (counter = 3) reaches limit - const result3 = detector.check(createToolUse("repeat", "repeat-tool")) + const result3 = detector.check(createToolDirective("repeat", "repeat-tool")) expect(result3.allowExecution).toBe(false) }) }) @@ -118,13 +117,13 @@ describe("ToolRepetitionDetector", () => { const detector = new ToolRepetitionDetector(3) // First call (counter = 1) - detector.check(createToolUse("repeat", "repeat-tool")) + detector.check(createToolDirective("repeat", "repeat-tool")) // Second identical call (counter = 2) - detector.check(createToolUse("repeat", "repeat-tool")) + detector.check(createToolDirective("repeat", "repeat-tool")) // Third identical call (counter = 3) - should reach limit - const result = detector.check(createToolUse("repeat", "repeat-tool")) + const result = detector.check(createToolDirective("repeat", "repeat-tool")) expect(result.allowExecution).toBe(false) expect(result.askUser).toBeDefined() @@ -136,12 +135,12 @@ describe("ToolRepetitionDetector", () => { const detector = new ToolRepetitionDetector(2) // Reach the limit - detector.check(createToolUse("repeat", "repeat-tool")) - const limitResult = detector.check(createToolUse("repeat", "repeat-tool")) // This reaches limit + detector.check(createToolDirective("repeat", "repeat-tool")) + const limitResult = detector.check(createToolDirective("repeat", "repeat-tool")) // This reaches limit expect(limitResult.allowExecution).toBe(false) // Use a new tool call - should be allowed since state was reset - const result = detector.check(createToolUse("new", "new-tool")) + const result = detector.check(createToolDirective("new", "new-tool")) expect(result.allowExecution).toBe(true) }) }) @@ -152,12 +151,12 @@ describe("ToolRepetitionDetector", () => { const detector = new ToolRepetitionDetector(2) // Reach the limit with a specific tool - detector.check(createToolUse("problem", "problem-tool")) - const limitResult = detector.check(createToolUse("problem", "problem-tool")) // This reaches limit + detector.check(createToolDirective("problem", "problem-tool")) + const limitResult = detector.check(createToolDirective("problem", "problem-tool")) // This reaches limit expect(limitResult.allowExecution).toBe(false) // The same tool that previously caused problems should now be allowed - const result = detector.check(createToolUse("problem", "problem-tool")) + const result = detector.check(createToolDirective("problem", "problem-tool")) expect(result.allowExecution).toBe(true) }) @@ -165,15 +164,15 @@ describe("ToolRepetitionDetector", () => { const detector = new ToolRepetitionDetector(2) // Reach the limit - detector.check(createToolUse("repeat", "repeat-tool")) - const limitResult = detector.check(createToolUse("repeat", "repeat-tool")) // This reaches limit + detector.check(createToolDirective("repeat", "repeat-tool")) + const limitResult = detector.check(createToolDirective("repeat", "repeat-tool")) // This reaches limit expect(limitResult.allowExecution).toBe(false) // First call after reset - detector.check(createToolUse("repeat", "repeat-tool")) + detector.check(createToolDirective("repeat", "repeat-tool")) // Second identical call (counter = 2) should reach limit again - const result = detector.check(createToolUse("repeat", "repeat-tool")) + const result = detector.check(createToolDirective("repeat", "repeat-tool")) expect(result.allowExecution).toBe(false) expect(result.askUser).toBeDefined() }) @@ -186,8 +185,8 @@ describe("ToolRepetitionDetector", () => { const toolName = "special-tool-name" // Reach the limit - detector.check(createToolUse("test", toolName)) - const result = detector.check(createToolUse("test", toolName)) + detector.check(createToolDirective("test", toolName)) + const result = detector.check(createToolDirective("test", toolName)) expect(result.allowExecution).toBe(false) expect(result.askUser?.messageDetail).toContain(toolName) @@ -201,8 +200,8 @@ describe("ToolRepetitionDetector", () => { // Create an empty tool call - a tool with no parameters // Use the empty tool directly in the check calls - detector.check(createToolUse("empty-tool", "empty-tool")) - const result = detector.check(createToolUse("empty-tool")) + detector.check(createToolDirective("empty-tool", "empty-tool")) + const result = detector.check(createToolDirective("empty-tool")) expect(result.allowExecution).toBe(false) expect(result.askUser).toBeDefined() @@ -212,28 +211,28 @@ describe("ToolRepetitionDetector", () => { const detector = new ToolRepetitionDetector(2) // First, call with tool-name-1 twice to set up the counter - const toolUse1 = createToolUse("tool-name-1", "tool-name-1", { param: "value" }) - detector.check(toolUse1) + const ToolDirective1 = createToolDirective("tool-name-1", "tool-name-1", { param: "value" }) + detector.check(ToolDirective1) - // Create a tool that will serialize to the same JSON as toolUse1 - // We need to mock the serializeToolUse method to return the same value - const toolUse2 = createToolUse("tool-name-2", "tool-name-2", { param: "value" }) + // Create a tool that will serialize to the same JSON as ToolDirective1 + // We need to mock the serializeToolDirective method to return the same value + const ToolDirective2 = createToolDirective("tool-name-2", "tool-name-2", { param: "value" }) // Override the private method to force identical serialization - const originalSerialize = (detector as any).serializeToolUse - ;(detector as any).serializeToolUse = (tool: ToolUse) => { + const originalSerialize = (detector as any).serializeToolDirective + ;(detector as any).serializeToolDirective = (tool: ToolDirective) => { // Use string comparison for the name since it's technically an enum if (String(tool.name) === "tool-name-2") { - return (detector as any).serializeToolUse(toolUse1) // Return the same JSON as toolUse1 + return (detector as any).serializeToolDirective(ToolDirective1) // Return the same JSON as ToolDirective1 } return originalSerialize(tool) } // This should detect as a repetition now - const result = detector.check(toolUse2) + const result = detector.check(ToolDirective2) // Restore the original method - ;(detector as any).serializeToolUse = originalSerialize + ;(detector as any).serializeToolDirective = originalSerialize // Since we're directly manipulating the internal state for testing, // we still expect it to consider this a repetition @@ -245,14 +244,14 @@ describe("ToolRepetitionDetector", () => { const detector = new ToolRepetitionDetector(2) // First call with parameters in one order - const toolUse1 = createToolUse("same-tool", "same-tool", { a: "1", b: "2", c: "3" }) - detector.check(toolUse1) + const ToolDirective1 = createToolDirective("same-tool", "same-tool", { a: "1", b: "2", c: "3" }) + detector.check(ToolDirective1) // Create tool with same parameters but in different order - const toolUse2 = createToolUse("same-tool", "same-tool", { c: "3", a: "1", b: "2" }) + const ToolDirective2 = createToolDirective("same-tool", "same-tool", { c: "3", a: "1", b: "2" }) // This should still detect as a repetition due to canonical JSON with sorted keys - const result = detector.check(toolUse2) + const result = detector.check(ToolDirective2) // Since parameters are sorted alphabetically in the serialized JSON, // these should be considered identical @@ -267,7 +266,7 @@ describe("ToolRepetitionDetector", () => { const detector = new ToolRepetitionDetector(1) // First call (counter = 1) should be blocked - const result = detector.check(createToolUse("tool", "tool-name")) + const result = detector.check(createToolDirective("tool", "tool-name")) expect(result.allowExecution).toBe(false) expect(result.askUser).toBeDefined() @@ -277,11 +276,11 @@ describe("ToolRepetitionDetector", () => { const detector = new ToolRepetitionDetector(2) // First call (counter = 1) - const result1 = detector.check(createToolUse("tool", "tool-name")) + const result1 = detector.check(createToolDirective("tool", "tool-name")) expect(result1.allowExecution).toBe(true) // Second call (counter = 2) should be blocked - const result2 = detector.check(createToolUse("tool", "tool-name")) + const result2 = detector.check(createToolDirective("tool", "tool-name")) expect(result2.allowExecution).toBe(false) expect(result2.askUser).toBeDefined() }) @@ -290,15 +289,15 @@ describe("ToolRepetitionDetector", () => { const detector = new ToolRepetitionDetector(3) // First call (counter = 1) - const result1 = detector.check(createToolUse("tool", "tool-name")) + const result1 = detector.check(createToolDirective("tool", "tool-name")) expect(result1.allowExecution).toBe(true) // Second call (counter = 2) - const result2 = detector.check(createToolUse("tool", "tool-name")) + const result2 = detector.check(createToolDirective("tool", "tool-name")) expect(result2.allowExecution).toBe(true) // Third call (counter = 3) should be blocked - const result3 = detector.check(createToolUse("tool", "tool-name")) + const result3 = detector.check(createToolDirective("tool", "tool-name")) expect(result3.allowExecution).toBe(false) expect(result3.askUser).toBeDefined() }) diff --git a/src/core/tools/__tests__/executeCommandTool.spec.ts b/src/core/tools/__tests__/executeCommandTool.spec.ts index 042fc263cf..8eb374299b 100644 --- a/src/core/tools/__tests__/executeCommandTool.spec.ts +++ b/src/core/tools/__tests__/executeCommandTool.spec.ts @@ -6,7 +6,7 @@ import type { ToolUsage } from "@roo-code/types" import { Task } from "../../task/Task" import { formatResponse } from "../../prompts/responses" -import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../../shared/tools" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../../shared/tools" import { unescapeHtmlEntities } from "../../../utils/text-normalization" // Mock dependencies @@ -27,6 +27,7 @@ vitest.mock("../executeCommandTool") // Import after mocking import { executeCommandTool } from "../executeCommandTool" +import { ToolDirective } from "../../message-parsing/directives" // Now manually restore and mock the functions beforeEach(() => { @@ -79,7 +80,7 @@ describe("executeCommandTool", () => { let mockHandleError: any let mockPushToolResult: any let mockRemoveClosingTag: any - let mockToolUse: ToolUse + let mockToolDirective: ToolDirective beforeEach(() => { // Reset mocks @@ -106,7 +107,7 @@ describe("executeCommandTool", () => { mockRemoveClosingTag = vitest.fn().mockReturnValue("command") // Create a mock tool use object - mockToolUse = { + mockToolDirective = { type: "tool_use", name: "execute_command", params: { @@ -150,12 +151,12 @@ describe("executeCommandTool", () => { describe("Basic functionality", () => { it("should execute a command normally", async () => { // Setup - mockToolUse.params.command = "echo test" + mockToolDirective.params.command = "echo test" // Execute await executeCommandTool( mockCline as unknown as Task, - mockToolUse, + mockToolDirective, mockAskApproval as unknown as AskApproval, mockHandleError as unknown as HandleError, mockPushToolResult as unknown as PushToolResult, @@ -170,13 +171,13 @@ describe("executeCommandTool", () => { it("should pass along custom working directory if provided", async () => { // Setup - mockToolUse.params.command = "echo test" - mockToolUse.params.cwd = "/custom/path" + mockToolDirective.params.command = "echo test" + mockToolDirective.params.cwd = "/custom/path" // Execute await executeCommandTool( mockCline as unknown as Task, - mockToolUse, + mockToolDirective, mockAskApproval as unknown as AskApproval, mockHandleError as unknown as HandleError, mockPushToolResult as unknown as PushToolResult, @@ -194,12 +195,12 @@ describe("executeCommandTool", () => { describe("Error handling", () => { it("should handle missing command parameter", async () => { // Setup - mockToolUse.params.command = undefined + mockToolDirective.params.command = undefined // Execute await executeCommandTool( mockCline as unknown as Task, - mockToolUse, + mockToolDirective, mockAskApproval as unknown as AskApproval, mockHandleError as unknown as HandleError, mockPushToolResult as unknown as PushToolResult, @@ -216,13 +217,13 @@ describe("executeCommandTool", () => { it("should handle command rejection", async () => { // Setup - mockToolUse.params.command = "echo test" + mockToolDirective.params.command = "echo test" mockAskApproval.mockResolvedValue(false) // Execute await executeCommandTool( mockCline as unknown as Task, - mockToolUse, + mockToolDirective, mockAskApproval as unknown as AskApproval, mockHandleError as unknown as HandleError, mockPushToolResult as unknown as PushToolResult, @@ -237,7 +238,7 @@ describe("executeCommandTool", () => { it("should handle rooignore validation failures", async () => { // Setup - mockToolUse.params.command = "cat .env" + mockToolDirective.params.command = "cat .env" // Override the validateCommand mock to return a filename const validateCommandMock = vitest.fn().mockReturnValue(".env") mockCline.rooIgnoreController = { @@ -251,7 +252,7 @@ describe("executeCommandTool", () => { // Execute await executeCommandTool( mockCline as unknown as Task, - mockToolUse, + mockToolDirective, mockAskApproval as unknown as AskApproval, mockHandleError as unknown as HandleError, mockPushToolResult as unknown as PushToolResult, diff --git a/src/core/tools/__tests__/newTaskTool.test.ts b/src/core/tools/__tests__/newTaskTool.test.ts index 1a9e497df3..fa07b6cb72 100644 --- a/src/core/tools/__tests__/newTaskTool.test.ts +++ b/src/core/tools/__tests__/newTaskTool.test.ts @@ -52,7 +52,7 @@ jest.mock("../../prompts/responses", () => ({ // Import the function to test AFTER mocks are set up import { newTaskTool } from "../newTaskTool" -import type { ToolUse } from "../../../shared/tools" +import { ToolDirective } from "../../message-parsing/directives" describe("newTaskTool", () => { beforeEach(() => { @@ -65,7 +65,7 @@ describe("newTaskTool", () => { }) it("should correctly un-escape \\\\@ to \\@ in the message passed to the new task", async () => { - const block: ToolUse = { + const block: ToolDirective = { type: "tool_use", // Add required 'type' property name: "new_task", // Correct property name params: { @@ -102,7 +102,7 @@ describe("newTaskTool", () => { }) it("should not un-escape single escaped \@", async () => { - const block: ToolUse = { + const block: ToolDirective = { type: "tool_use", // Add required 'type' property name: "new_task", // Correct property name params: { @@ -129,7 +129,7 @@ describe("newTaskTool", () => { }) it("should not un-escape non-escaped @", async () => { - const block: ToolUse = { + const block: ToolDirective = { type: "tool_use", // Add required 'type' property name: "new_task", // Correct property name params: { @@ -156,7 +156,7 @@ describe("newTaskTool", () => { }) it("should handle mixed escaping scenarios", async () => { - const block: ToolUse = { + const block: ToolDirective = { type: "tool_use", // Add required 'type' property name: "new_task", // Correct property name params: { diff --git a/src/core/tools/__tests__/readFileTool.test.ts b/src/core/tools/__tests__/readFileTool.test.ts index 3ed5cbe3f1..1a1b2da0a8 100644 --- a/src/core/tools/__tests__/readFileTool.test.ts +++ b/src/core/tools/__tests__/readFileTool.test.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 { ReadFileToolDirective, ToolParamName, ToolResponse } from "../../../shared/tools" import { readFileTool } from "../readFileTool" import { formatResponse } from "../../prompts/responses" @@ -144,7 +144,7 @@ describe("read_file tool with maxReadFileLine setting", () => { * Helper function to execute the read file tool with different maxReadFileLine settings */ async function executeReadFileTool( - params: Partial = {}, + params: Partial = {}, options: { maxReadFileLine?: number totalLines?: number @@ -172,7 +172,7 @@ describe("read_file tool with maxReadFileLine setting", () => { argsContent += `` // Create a tool use object - const toolUse: ReadFileToolUse = { + const toolUse: ReadFileToolDirective = { type: "tool_use", name: "read_file", params: { args: argsContent, ...params }, @@ -484,7 +484,7 @@ describe("read_file tool XML output structure", () => { argsContent += `` // Create a tool use object - const toolUse: ReadFileToolUse = { + const toolUse: ReadFileToolDirective = { type: "tool_use", name: "read_file", params: { args: argsContent, ...params }, @@ -898,7 +898,7 @@ describe("read_file tool XML output structure", () => { it("should include error tag for invalid path", async () => { // Setup - missing path parameter - const toolUse: ReadFileToolUse = { + const toolUse: ReadFileToolDirective = { type: "tool_use", name: "read_file", params: {}, @@ -1167,7 +1167,7 @@ describe("read_file tool XML output structure", () => { const mockRemoveClosingTag = jest.fn((tag, content) => content) // Create a tool use object directly - const toolUse: ReadFileToolUse = { + const toolUse: ReadFileToolDirective = { type: "tool_use", name: "read_file", params: { @@ -1207,7 +1207,7 @@ describe("read_file tool XML output structure", () => { const mockRemoveClosingTag = jest.fn((tag, content) => content) // Create a tool use object directly - const toolUse: ReadFileToolUse = { + const toolUse: ReadFileToolDirective = { type: "tool_use", name: "read_file", params: { diff --git a/src/core/tools/__tests__/useMcpToolTool.test.ts b/src/core/tools/__tests__/useMcpToolTool.test.ts index 24fa2540c0..dba754d8db 100644 --- a/src/core/tools/__tests__/useMcpToolTool.test.ts +++ b/src/core/tools/__tests__/useMcpToolTool.test.ts @@ -1,7 +1,7 @@ import { useMcpToolTool } from "../useMcpToolTool" import { Task } from "../../task/Task" -import { ToolUse } from "../../../shared/tools" import { formatResponse } from "../../prompts/responses" +import { ToolDirective } from "../../message-parsing/directives" // Mock dependencies jest.mock("../../prompts/responses", () => ({ @@ -57,7 +57,7 @@ describe("useMcpToolTool", () => { describe("parameter validation", () => { it("should handle missing server_name", async () => { - const block: ToolUse = { + const block: ToolDirective = { type: "tool_use", name: "use_mcp_tool", params: { @@ -85,7 +85,7 @@ describe("useMcpToolTool", () => { }) it("should handle missing tool_name", async () => { - const block: ToolUse = { + const block: ToolDirective = { type: "tool_use", name: "use_mcp_tool", params: { @@ -113,7 +113,7 @@ describe("useMcpToolTool", () => { }) it("should handle invalid JSON arguments", async () => { - const block: ToolUse = { + const block: ToolDirective = { type: "tool_use", name: "use_mcp_tool", params: { @@ -142,7 +142,7 @@ describe("useMcpToolTool", () => { describe("partial requests", () => { it("should handle partial requests", async () => { - const block: ToolUse = { + const block: ToolDirective = { type: "tool_use", name: "use_mcp_tool", params: { @@ -170,7 +170,7 @@ describe("useMcpToolTool", () => { describe("successful execution", () => { it("should execute tool successfully with valid parameters", async () => { - const block: ToolUse = { + const block: ToolDirective = { type: "tool_use", name: "use_mcp_tool", params: { @@ -212,7 +212,7 @@ describe("useMcpToolTool", () => { }) it("should handle user rejection", async () => { - const block: ToolUse = { + const block: ToolDirective = { type: "tool_use", name: "use_mcp_tool", params: { @@ -241,7 +241,7 @@ describe("useMcpToolTool", () => { describe("error handling", () => { it("should handle unexpected errors", async () => { - const block: ToolUse = { + const block: ToolDirective = { type: "tool_use", name: "use_mcp_tool", params: { diff --git a/src/core/tools/__tests__/writeToFileTool.test.ts b/src/core/tools/__tests__/writeToFileTool.test.ts index e0789f766c..a8b942bb95 100644 --- a/src/core/tools/__tests__/writeToFileTool.test.ts +++ b/src/core/tools/__tests__/writeToFileTool.test.ts @@ -6,8 +6,9 @@ import { isPathOutsideWorkspace } from "../../../utils/pathUtils" import { getReadablePath } from "../../../utils/path" import { unescapeHtmlEntities } from "../../../utils/text-normalization" import { everyLineHasLineNumbers, stripLineNumbers } from "../../../integrations/misc/extract-text" -import { ToolUse, ToolResponse } from "../../../shared/tools" +import { ToolResponse } from "../../../shared/tools" import { writeToFileTool } from "../writeToFileTool" +import { ToolDirective } from "../../message-parsing/directives" jest.mock("path", () => { const originalPath = jest.requireActual("path") @@ -183,7 +184,7 @@ describe("writeToFileTool", () => { * Helper function to execute the write file tool with different parameters */ async function executeWriteFileTool( - params: Partial = {}, + params: Partial = {}, options: { fileExists?: boolean isPartial?: boolean @@ -199,7 +200,7 @@ describe("writeToFileTool", () => { mockCline.rooIgnoreController.validateAccess.mockReturnValue(accessAllowed) // Create a tool use object - const toolUse: ToolUse = { + const ToolDirective: ToolDirective = { type: "tool_use", name: "write_to_file", params: { @@ -213,7 +214,7 @@ describe("writeToFileTool", () => { await writeToFileTool( mockCline, - toolUse, + ToolDirective, mockAskApproval, mockHandleError, (result: ToolResponse) => { diff --git a/src/core/tools/accessMcpResourceTool.ts b/src/core/tools/accessMcpResourceTool.ts index 22b1aba909..198d05948a 100644 --- a/src/core/tools/accessMcpResourceTool.ts +++ b/src/core/tools/accessMcpResourceTool.ts @@ -1,11 +1,12 @@ import { ClineAskUseMcpServer } from "../../shared/ExtensionMessage" -import { ToolUse, RemoveClosingTag, AskApproval, HandleError, PushToolResult } from "../../shared/tools" +import { RemoveClosingTag, AskApproval, HandleError, PushToolResult } from "../../shared/tools" import { Task } from "../task/Task" import { formatResponse } from "../prompts/responses" +import { ToolDirective } from "../message-parsing/directives" export async function accessMcpResourceTool( cline: Task, - block: ToolUse, + block: ToolDirective, askApproval: AskApproval, handleError: HandleError, pushToolResult: PushToolResult, diff --git a/src/core/tools/applyDiffTool.ts b/src/core/tools/applyDiffTool.ts index d4f7fd883f..93f00ee1b9 100644 --- a/src/core/tools/applyDiffTool.ts +++ b/src/core/tools/applyDiffTool.ts @@ -6,15 +6,16 @@ import { TelemetryService } from "@roo-code/telemetry" import { ClineSayTool } from "../../shared/ExtensionMessage" import { getReadablePath } from "../../utils/path" import { Task } from "../task/Task" -import { ToolUse, RemoveClosingTag, AskApproval, HandleError, PushToolResult } from "../../shared/tools" +import { 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 { ToolDirective } from "../message-parsing/directives" export async function applyDiffToolLegacy( cline: Task, - block: ToolUse, + block: ToolDirective, askApproval: AskApproval, handleError: HandleError, pushToolResult: PushToolResult, diff --git a/src/core/tools/askFollowupQuestionTool.ts b/src/core/tools/askFollowupQuestionTool.ts index d5adf6d4cd..f5eb10ac7f 100644 --- a/src/core/tools/askFollowupQuestionTool.ts +++ b/src/core/tools/askFollowupQuestionTool.ts @@ -1,11 +1,12 @@ import { Task } from "../task/Task" -import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" import { formatResponse } from "../prompts/responses" import { parseXml } from "../../utils/xml" +import { ToolDirective } from "../message-parsing/directives" export async function askFollowupQuestionTool( cline: Task, - block: ToolUse, + block: ToolDirective, askApproval: AskApproval, handleError: HandleError, pushToolResult: PushToolResult, diff --git a/src/core/tools/attemptCompletionTool.ts b/src/core/tools/attemptCompletionTool.ts index d45ce0f65c..523380405f 100644 --- a/src/core/tools/attemptCompletionTool.ts +++ b/src/core/tools/attemptCompletionTool.ts @@ -5,7 +5,6 @@ import { TelemetryService } from "@roo-code/telemetry" import { Task } from "../task/Task" import { ToolResponse, - ToolUse, AskApproval, HandleError, PushToolResult, @@ -16,10 +15,11 @@ import { import { formatResponse } from "../prompts/responses" import { type ExecuteCommandOptions, executeCommand } from "./executeCommandTool" import { EXPERIMENT_IDS, experiments, experimentDefault } from "../../shared/experiments" +import { ToolDirective } from "../message-parsing/directives" export async function attemptCompletionTool( cline: Task, - block: ToolUse, + block: ToolDirective, askApproval: AskApproval, handleError: HandleError, pushToolResult: PushToolResult, diff --git a/src/core/tools/browserActionTool.ts b/src/core/tools/browserActionTool.ts index 13cb9b0ec2..d012183000 100644 --- a/src/core/tools/browserActionTool.ts +++ b/src/core/tools/browserActionTool.ts @@ -1,5 +1,5 @@ import { Task } from "../task/Task" -import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" import { BrowserAction, BrowserActionResult, @@ -7,10 +7,11 @@ import { ClineSayBrowserAction, } from "../../shared/ExtensionMessage" import { formatResponse } from "../prompts/responses" +import { ToolDirective } from "../message-parsing/directives" export async function browserActionTool( cline: Task, - block: ToolUse, + block: ToolDirective, askApproval: AskApproval, handleError: HandleError, pushToolResult: PushToolResult, diff --git a/src/core/tools/codebaseSearchTool.ts b/src/core/tools/codebaseSearchTool.ts index 236b066306..6e204948a3 100644 --- a/src/core/tools/codebaseSearchTool.ts +++ b/src/core/tools/codebaseSearchTool.ts @@ -5,12 +5,13 @@ import { CodeIndexManager } from "../../services/code-index/manager" import { getWorkspacePath } from "../../utils/path" import { formatResponse } from "../prompts/responses" import { VectorStoreSearchResult } from "../../services/code-index/interfaces" -import { AskApproval, HandleError, PushToolResult, RemoveClosingTag, ToolUse } from "../../shared/tools" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" +import { ToolDirective } from "../message-parsing/directives" import path from "path" export async function codebaseSearchTool( cline: Task, - block: ToolUse, + block: ToolDirective, askApproval: AskApproval, handleError: HandleError, pushToolResult: PushToolResult, diff --git a/src/core/tools/executeCommandTool.ts b/src/core/tools/executeCommandTool.ts index e38d3c74f6..3dda433d11 100644 --- a/src/core/tools/executeCommandTool.ts +++ b/src/core/tools/executeCommandTool.ts @@ -8,18 +8,19 @@ import { TelemetryService } from "@roo-code/telemetry" import { Task } from "../task/Task" -import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag, ToolResponse } from "../../shared/tools" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag, ToolResponse } from "../../shared/tools" import { formatResponse } from "../prompts/responses" import { unescapeHtmlEntities } from "../../utils/text-normalization" import { ExitCodeDetails, RooTerminalCallbacks, RooTerminalProcess } from "../../integrations/terminal/types" import { TerminalRegistry } from "../../integrations/terminal/TerminalRegistry" import { Terminal } from "../../integrations/terminal/Terminal" +import { ToolDirective } from "../message-parsing/directives" class ShellIntegrationError extends Error {} export async function executeCommandTool( cline: Task, - block: ToolUse, + block: ToolDirective, askApproval: AskApproval, handleError: HandleError, pushToolResult: PushToolResult, diff --git a/src/core/tools/fetchInstructionsTool.ts b/src/core/tools/fetchInstructionsTool.ts index 5325f98fbf..67a22ad460 100644 --- a/src/core/tools/fetchInstructionsTool.ts +++ b/src/core/tools/fetchInstructionsTool.ts @@ -2,11 +2,12 @@ import { Task } from "../task/Task" import { fetchInstructions } from "../prompts/instructions/instructions" import { ClineSayTool } from "../../shared/ExtensionMessage" import { formatResponse } from "../prompts/responses" -import { ToolUse, AskApproval, HandleError, PushToolResult } from "../../shared/tools" +import { AskApproval, HandleError, PushToolResult } from "../../shared/tools" +import { ToolDirective } from "../message-parsing/directives" export async function fetchInstructionsTool( cline: Task, - block: ToolUse, + block: ToolDirective, askApproval: AskApproval, handleError: HandleError, pushToolResult: PushToolResult, diff --git a/src/core/tools/insertContentTool.ts b/src/core/tools/insertContentTool.ts index 0963bc78cc..f2a62998e6 100644 --- a/src/core/tools/insertContentTool.ts +++ b/src/core/tools/insertContentTool.ts @@ -4,16 +4,17 @@ import path from "path" import { getReadablePath } from "../../utils/path" import { Task } from "../task/Task" -import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" import { formatResponse } from "../prompts/responses" import { ClineSayTool } from "../../shared/ExtensionMessage" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" import { fileExistsAtPath } from "../../utils/fs" import { insertGroups } from "../diff/insert-groups" +import { ToolDirective } from "../message-parsing/directives" export async function insertContentTool( cline: Task, - block: ToolUse, + block: ToolDirective, askApproval: AskApproval, handleError: HandleError, pushToolResult: PushToolResult, diff --git a/src/core/tools/listCodeDefinitionNamesTool.ts b/src/core/tools/listCodeDefinitionNamesTool.ts index f0cdde01eb..504c554b58 100644 --- a/src/core/tools/listCodeDefinitionNamesTool.ts +++ b/src/core/tools/listCodeDefinitionNamesTool.ts @@ -1,16 +1,17 @@ import path from "path" import fs from "fs/promises" -import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" import { Task } from "../task/Task" import { ClineSayTool } from "../../shared/ExtensionMessage" import { getReadablePath } from "../../utils/path" import { parseSourceCodeForDefinitionsTopLevel, parseSourceCodeDefinitionsForFile } from "../../services/tree-sitter" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" +import { ToolDirective } from "../message-parsing/directives" export async function listCodeDefinitionNamesTool( cline: Task, - block: ToolUse, + block: ToolDirective, askApproval: AskApproval, handleError: HandleError, pushToolResult: PushToolResult, diff --git a/src/core/tools/listFilesTool.ts b/src/core/tools/listFilesTool.ts index 6d40de711c..accd13869c 100644 --- a/src/core/tools/listFilesTool.ts +++ b/src/core/tools/listFilesTool.ts @@ -5,7 +5,8 @@ import { ClineSayTool } from "../../shared/ExtensionMessage" import { formatResponse } from "../prompts/responses" import { listFiles } from "../../services/glob/list-files" import { getReadablePath } from "../../utils/path" -import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" +import { ToolDirective } from "../message-parsing/directives" /** * Implements the list_files tool. @@ -24,7 +25,7 @@ import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } f export async function listFilesTool( cline: Task, - block: ToolUse, + block: ToolDirective, askApproval: AskApproval, handleError: HandleError, pushToolResult: PushToolResult, diff --git a/src/core/tools/multiApplyDiffTool.ts b/src/core/tools/multiApplyDiffTool.ts index ba36cd3759..e507b94d47 100644 --- a/src/core/tools/multiApplyDiffTool.ts +++ b/src/core/tools/multiApplyDiffTool.ts @@ -6,7 +6,7 @@ import { TelemetryService } from "@roo-code/telemetry" import { ClineSayTool } from "../../shared/ExtensionMessage" import { getReadablePath } from "../../utils/path" import { Task } from "../task/Task" -import { ToolUse, RemoveClosingTag, AskApproval, HandleError, PushToolResult } from "../../shared/tools" +import { RemoveClosingTag, AskApproval, HandleError, PushToolResult } from "../../shared/tools" import { formatResponse } from "../prompts/responses" import { fileExistsAtPath } from "../../utils/fs" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" @@ -14,6 +14,7 @@ import { unescapeHtmlEntities } from "../../utils/text-normalization" import { parseXml } from "../../utils/xml" import { EXPERIMENT_IDS, experiments } from "../../shared/experiments" import { applyDiffToolLegacy } from "./applyDiffTool" +import { ToolDirective } from "../message-parsing/directives" interface DiffOperation { path: string @@ -51,7 +52,7 @@ interface ParsedXmlResult { export async function applyDiffTool( cline: Task, - block: ToolUse, + block: ToolDirective, askApproval: AskApproval, handleError: HandleError, pushToolResult: PushToolResult, @@ -161,7 +162,6 @@ Expected structure: Original error: ${errorMessage}` throw new Error(detailedError) } - } else if (legacyPath && typeof legacyDiffContent === "string") { // Handle legacy parameters (old way) usingLegacyParams = true diff --git a/src/core/tools/newTaskTool.ts b/src/core/tools/newTaskTool.ts index 25d5766d5d..f3eda79e95 100644 --- a/src/core/tools/newTaskTool.ts +++ b/src/core/tools/newTaskTool.ts @@ -1,13 +1,14 @@ import delay from "delay" -import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" import { Task } from "../task/Task" import { defaultModeSlug, getModeBySlug } from "../../shared/modes" import { formatResponse } from "../prompts/responses" +import { ToolDirective } from "../message-parsing/directives" export async function newTaskTool( cline: Task, - block: ToolUse, + block: ToolDirective, askApproval: AskApproval, handleError: HandleError, pushToolResult: PushToolResult, @@ -43,7 +44,7 @@ export async function newTaskTool( cline.consecutiveMistakeCount = 0 // Un-escape one level of backslashes before '@' for hierarchical subtasks -// Un-escape one level: \\@ -> \@ (removes one backslash for hierarchical subtasks) + // Un-escape one level: \\@ -> \@ (removes one backslash for hierarchical subtasks) const unescapedMessage = message.replace(/\\\\@/g, "\\@") // Verify the mode exists diff --git a/src/core/tools/readFileTool.ts b/src/core/tools/readFileTool.ts index e49ac43d7b..897c249d4c 100644 --- a/src/core/tools/readFileTool.ts +++ b/src/core/tools/readFileTool.ts @@ -5,7 +5,7 @@ import { Task } from "../task/Task" import { ClineSayTool } from "../../shared/ExtensionMessage" import { formatResponse } from "../prompts/responses" import { t } from "../../i18n" -import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" import { isPathOutsideWorkspace } from "../../utils/pathUtils" import { getReadablePath } from "../../utils/path" @@ -14,6 +14,7 @@ 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 { ToolDirective } from "../message-parsing/directives" export function getReadFileToolDescription(blockName: string, blockParams: any): string { // Handle both single path and multiple files via args @@ -72,7 +73,7 @@ interface FileResult { export async function readFileTool( cline: Task, - block: ToolUse, + block: ToolDirective, askApproval: AskApproval, handleError: HandleError, pushToolResult: PushToolResult, diff --git a/src/core/tools/searchAndReplaceTool.ts b/src/core/tools/searchAndReplaceTool.ts index 58d246b133..4ec0865245 100644 --- a/src/core/tools/searchAndReplaceTool.ts +++ b/src/core/tools/searchAndReplaceTool.ts @@ -5,12 +5,13 @@ import delay from "delay" // Internal imports import { Task } from "../task/Task" -import { AskApproval, HandleError, PushToolResult, RemoveClosingTag, ToolUse } from "../../shared/tools" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" import { formatResponse } from "../prompts/responses" import { ClineSayTool } from "../../shared/ExtensionMessage" import { getReadablePath } from "../../utils/path" import { fileExistsAtPath } from "../../utils/fs" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" +import { ToolDirective } from "../message-parsing/directives" /** * Tool for performing search and replace operations on files @@ -62,7 +63,7 @@ async function validateParams( */ export async function searchAndReplaceTool( cline: Task, - block: ToolUse, + block: ToolDirective, askApproval: AskApproval, handleError: HandleError, pushToolResult: PushToolResult, diff --git a/src/core/tools/searchFilesTool.ts b/src/core/tools/searchFilesTool.ts index 6528f20d54..dd6b0aa336 100644 --- a/src/core/tools/searchFilesTool.ts +++ b/src/core/tools/searchFilesTool.ts @@ -1,14 +1,15 @@ import path from "path" import { Task } from "../task/Task" -import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" import { ClineSayTool } from "../../shared/ExtensionMessage" import { getReadablePath } from "../../utils/path" import { regexSearchFiles } from "../../services/ripgrep" +import { ToolDirective } from "../message-parsing/directives" export async function searchFilesTool( cline: Task, - block: ToolUse, + block: ToolDirective, askApproval: AskApproval, handleError: HandleError, pushToolResult: PushToolResult, diff --git a/src/core/tools/switchModeTool.ts b/src/core/tools/switchModeTool.ts index 8ce906b41f..63d8a17ab8 100644 --- a/src/core/tools/switchModeTool.ts +++ b/src/core/tools/switchModeTool.ts @@ -1,13 +1,14 @@ import delay from "delay" import { Task } from "../task/Task" -import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" import { formatResponse } from "../prompts/responses" import { defaultModeSlug, getModeBySlug } from "../../shared/modes" +import { ToolDirective } from "../message-parsing/directives" export async function switchModeTool( cline: Task, - block: ToolUse, + block: ToolDirective, askApproval: AskApproval, handleError: HandleError, pushToolResult: PushToolResult, diff --git a/src/core/tools/useMcpToolTool.ts b/src/core/tools/useMcpToolTool.ts index 30dff5ce4f..630299f5e2 100644 --- a/src/core/tools/useMcpToolTool.ts +++ b/src/core/tools/useMcpToolTool.ts @@ -1,9 +1,10 @@ import { Task } from "../task/Task" -import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" import { formatResponse } from "../prompts/responses" import { ClineAskUseMcpServer } from "../../shared/ExtensionMessage" import { McpExecutionStatus } from "@roo-code/types" import { t } from "../../i18n" +import { ToolDirective } from "../message-parsing/directives" interface McpToolParams { server_name?: string @@ -166,7 +167,7 @@ async function executeToolAndProcessResult( export async function useMcpToolTool( cline: Task, - block: ToolUse, + block: ToolDirective, askApproval: AskApproval, handleError: HandleError, pushToolResult: PushToolResult, diff --git a/src/core/tools/writeToFileTool.ts b/src/core/tools/writeToFileTool.ts index f7543d6d8b..6f306e8b13 100644 --- a/src/core/tools/writeToFileTool.ts +++ b/src/core/tools/writeToFileTool.ts @@ -5,7 +5,7 @@ import * as vscode from "vscode" import { Task } from "../task/Task" import { ClineSayTool } from "../../shared/ExtensionMessage" import { formatResponse } from "../prompts/responses" -import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" import { fileExistsAtPath } from "../../utils/fs" import { stripLineNumbers, everyLineHasLineNumbers } from "../../integrations/misc/extract-text" @@ -13,10 +13,11 @@ import { getReadablePath } from "../../utils/path" import { isPathOutsideWorkspace } from "../../utils/pathUtils" import { detectCodeOmission } from "../../integrations/editor/detect-omission" import { unescapeHtmlEntities } from "../../utils/text-normalization" +import { ToolDirective } from "../message-parsing/directives" export async function writeToFileTool( cline: Task, - block: ToolUse, + block: ToolDirective, askApproval: AskApproval, handleError: HandleError, pushToolResult: PushToolResult, diff --git a/src/shared/tools.ts b/src/shared/tools.ts index 7525f85088..bea13447f6 100644 --- a/src/shared/tools.ts +++ b/src/shared/tools.ts @@ -1,6 +1,7 @@ import { Anthropic } from "@anthropic-ai/sdk" import type { ClineAsk, ToolProgressStatus, ToolGroup, ToolName } from "@roo-code/types" +import { ToolDirective } from "../core/message-parsing/directives/" export type ToolResponse = string | Array @@ -61,96 +62,88 @@ export const toolParamNames = [ export type ToolParamName = (typeof toolParamNames)[number] -export interface ToolUse { - type: "tool_use" - name: ToolName - // params is a partial record, allowing only some or none of the possible parameters to be used - params: Partial> - partial: boolean -} - -export interface ExecuteCommandToolUse extends ToolUse { +export interface ExecuteCommandToolDirective extends ToolDirective { name: "execute_command" // Pick, "command"> makes "command" required, but Partial<> makes it optional params: Partial, "command" | "cwd">> } -export interface ReadFileToolUse extends ToolUse { +export interface ReadFileToolDirective extends ToolDirective { name: "read_file" params: Partial, "args" | "path" | "start_line" | "end_line">> } -export interface FetchInstructionsToolUse extends ToolUse { +export interface FetchInstructionsToolDirective extends ToolDirective { name: "fetch_instructions" params: Partial, "task">> } -export interface WriteToFileToolUse extends ToolUse { +export interface WriteToFileToolDirective extends ToolDirective { name: "write_to_file" params: Partial, "path" | "content" | "line_count">> } -export interface InsertCodeBlockToolUse extends ToolUse { +export interface InsertCodeBlockToolDirective extends ToolDirective { name: "insert_content" params: Partial, "path" | "line" | "content">> } -export interface CodebaseSearchToolUse extends ToolUse { +export interface CodebaseSearchToolDirective extends ToolDirective { name: "codebase_search" params: Partial, "query" | "path">> } -export interface SearchFilesToolUse extends ToolUse { +export interface SearchFilesToolDirective extends ToolDirective { name: "search_files" params: Partial, "path" | "regex" | "file_pattern">> } -export interface ListFilesToolUse extends ToolUse { +export interface ListFilesToolDirective extends ToolDirective { name: "list_files" params: Partial, "path" | "recursive">> } -export interface ListCodeDefinitionNamesToolUse extends ToolUse { +export interface ListCodeDefinitionNamesToolDirective extends ToolDirective { name: "list_code_definition_names" params: Partial, "path">> } -export interface BrowserActionToolUse extends ToolUse { +export interface BrowserActionToolDirective extends ToolDirective { name: "browser_action" params: Partial, "action" | "url" | "coordinate" | "text" | "size">> } -export interface UseMcpToolToolUse extends ToolUse { +export interface UseMcpToolToolDirective extends ToolDirective { name: "use_mcp_tool" params: Partial, "server_name" | "tool_name" | "arguments">> } -export interface AccessMcpResourceToolUse extends ToolUse { +export interface AccessMcpResourceToolDirective extends ToolDirective { name: "access_mcp_resource" params: Partial, "server_name" | "uri">> } -export interface AskFollowupQuestionToolUse extends ToolUse { +export interface AskFollowupQuestionToolDirective extends ToolDirective { name: "ask_followup_question" params: Partial, "question" | "follow_up">> } -export interface AttemptCompletionToolUse extends ToolUse { +export interface AttemptCompletionToolDirective extends ToolDirective { name: "attempt_completion" params: Partial, "result" | "command">> } -export interface SwitchModeToolUse extends ToolUse { +export interface SwitchModeToolDirective extends ToolDirective { name: "switch_mode" params: Partial, "mode_slug" | "reason">> } -export interface NewTaskToolUse extends ToolUse { +export interface NewTaskToolDirective extends ToolDirective { name: "new_task" params: Partial, "mode" | "message">> } -export interface SearchAndReplaceToolUse extends ToolUse { +export interface SearchAndReplaceToolDirective extends ToolDirective { name: "search_and_replace" params: Required, "path" | "search" | "replace">> & Partial, "use_regex" | "ignore_case" | "start_line" | "end_line">> @@ -270,5 +263,5 @@ export interface DiffStrategy { endLine?: number, ): Promise - getProgressStatus?(toolUse: ToolUse, result?: any): ToolProgressStatus + getProgressStatus?(ToolDirective: ToolDirective, result?: any): ToolProgressStatus }