Rename ToolUse to ToolDirective

This commit is contained in:
Steven T. Cramer 2025-06-15 15:14:11 +07:00
parent e6333785f3
commit d08b0d300d
39 changed files with 355 additions and 336 deletions

View file

@ -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"

View file

@ -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}` }

View file

@ -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(`</${toolName}>`),
}
contentBlocks.push(toolUse)
contentBlocks.push(ToolDirective)
return contentBlocks
}
}

View file

@ -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,
])
})
})

View file

@ -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,
])
})
})

View file

@ -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: </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: </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 '<read_file><path>test.txt</path></read_file>'")
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 '<read_file><path>test.txt</path></read_file>'")
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("<div>.*</div>")
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("<div>.*</div>")
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")
})
})
})

View file

@ -0,0 +1,5 @@
import { LogDirective } from "./LogDirective"
import { TextDirective } from "./TextDirective"
import { ToolDirective } from "./ToolDirective"
export type Directive = TextDirective | ToolDirective | LogDirective

View file

@ -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<Record<ToolParamName, string>>
partial: boolean
}

View file

@ -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"

View file

@ -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<string, string>)[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<string, string>)[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<string, string>)[this.currentParamName] =
;(this.currentToolDirective.params as Record<string, string>)[this.currentParamName] =
this.currentParamValue.trim()
}
this.currentToolUse.partial = true
context.contentBlocks.push(this.currentToolUse)
this.currentToolDirective.partial = true
context.contentBlocks.push(this.currentToolDirective)
}
}
}

View file

@ -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<string, ToolName>()
const ToolDirectiveOpenTags = new Map<string, ToolName>()
const toolParamOpenTags = new Map<string, ToolParamName>()
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 = `</${currentParamName}>`
// 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 = `</${currentToolUse.name}>`
const toolCloseTag = `</${currentToolDirective.name}>`
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.

View file

@ -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 }]

View file

@ -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<string, unknown> = {}
// 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,
}

View file

@ -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<string, string> = {}): ToolUse {
function createToolDirective(name: string, displayName?: string, params: Record<string, string> = {}): 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()
})

View file

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

View file

@ -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: {

View file

@ -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<ReadFileToolUse["params"]> = {},
params: Partial<ReadFileToolDirective["params"]> = {},
options: {
maxReadFileLine?: number
totalLines?: number
@ -172,7 +172,7 @@ describe("read_file tool with maxReadFileLine setting", () => {
argsContent += `</file>`
// 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 += `</file>`
// 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: {

View file

@ -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: {

View file

@ -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<ToolUse["params"]> = {},
params: Partial<ToolDirective["params"]> = {},
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) => {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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

View file

@ -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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<Anthropic.TextBlockParam | Anthropic.ImageBlockParam>
@ -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<Record<ToolParamName, string>>
partial: boolean
}
export interface ExecuteCommandToolUse extends ToolUse {
export interface ExecuteCommandToolDirective extends ToolDirective {
name: "execute_command"
// Pick<Record<ToolParamName, string>, "command"> makes "command" required, but Partial<> makes it optional
params: Partial<Pick<Record<ToolParamName, string>, "command" | "cwd">>
}
export interface ReadFileToolUse extends ToolUse {
export interface ReadFileToolDirective extends ToolDirective {
name: "read_file"
params: Partial<Pick<Record<ToolParamName, string>, "args" | "path" | "start_line" | "end_line">>
}
export interface FetchInstructionsToolUse extends ToolUse {
export interface FetchInstructionsToolDirective extends ToolDirective {
name: "fetch_instructions"
params: Partial<Pick<Record<ToolParamName, string>, "task">>
}
export interface WriteToFileToolUse extends ToolUse {
export interface WriteToFileToolDirective extends ToolDirective {
name: "write_to_file"
params: Partial<Pick<Record<ToolParamName, string>, "path" | "content" | "line_count">>
}
export interface InsertCodeBlockToolUse extends ToolUse {
export interface InsertCodeBlockToolDirective extends ToolDirective {
name: "insert_content"
params: Partial<Pick<Record<ToolParamName, string>, "path" | "line" | "content">>
}
export interface CodebaseSearchToolUse extends ToolUse {
export interface CodebaseSearchToolDirective extends ToolDirective {
name: "codebase_search"
params: Partial<Pick<Record<ToolParamName, string>, "query" | "path">>
}
export interface SearchFilesToolUse extends ToolUse {
export interface SearchFilesToolDirective extends ToolDirective {
name: "search_files"
params: Partial<Pick<Record<ToolParamName, string>, "path" | "regex" | "file_pattern">>
}
export interface ListFilesToolUse extends ToolUse {
export interface ListFilesToolDirective extends ToolDirective {
name: "list_files"
params: Partial<Pick<Record<ToolParamName, string>, "path" | "recursive">>
}
export interface ListCodeDefinitionNamesToolUse extends ToolUse {
export interface ListCodeDefinitionNamesToolDirective extends ToolDirective {
name: "list_code_definition_names"
params: Partial<Pick<Record<ToolParamName, string>, "path">>
}
export interface BrowserActionToolUse extends ToolUse {
export interface BrowserActionToolDirective extends ToolDirective {
name: "browser_action"
params: Partial<Pick<Record<ToolParamName, string>, "action" | "url" | "coordinate" | "text" | "size">>
}
export interface UseMcpToolToolUse extends ToolUse {
export interface UseMcpToolToolDirective extends ToolDirective {
name: "use_mcp_tool"
params: Partial<Pick<Record<ToolParamName, string>, "server_name" | "tool_name" | "arguments">>
}
export interface AccessMcpResourceToolUse extends ToolUse {
export interface AccessMcpResourceToolDirective extends ToolDirective {
name: "access_mcp_resource"
params: Partial<Pick<Record<ToolParamName, string>, "server_name" | "uri">>
}
export interface AskFollowupQuestionToolUse extends ToolUse {
export interface AskFollowupQuestionToolDirective extends ToolDirective {
name: "ask_followup_question"
params: Partial<Pick<Record<ToolParamName, string>, "question" | "follow_up">>
}
export interface AttemptCompletionToolUse extends ToolUse {
export interface AttemptCompletionToolDirective extends ToolDirective {
name: "attempt_completion"
params: Partial<Pick<Record<ToolParamName, string>, "result" | "command">>
}
export interface SwitchModeToolUse extends ToolUse {
export interface SwitchModeToolDirective extends ToolDirective {
name: "switch_mode"
params: Partial<Pick<Record<ToolParamName, string>, "mode_slug" | "reason">>
}
export interface NewTaskToolUse extends ToolUse {
export interface NewTaskToolDirective extends ToolDirective {
name: "new_task"
params: Partial<Pick<Record<ToolParamName, string>, "mode" | "message">>
}
export interface SearchAndReplaceToolUse extends ToolUse {
export interface SearchAndReplaceToolDirective extends ToolDirective {
name: "search_and_replace"
params: Required<Pick<Record<ToolParamName, string>, "path" | "search" | "replace">> &
Partial<Pick<Record<ToolParamName, string>, "use_regex" | "ignore_case" | "start_line" | "end_line">>
@ -270,5 +263,5 @@ export interface DiffStrategy {
endLine?: number,
): Promise<DiffResult>
getProgressStatus?(toolUse: ToolUse, result?: any): ToolProgressStatus
getProgressStatus?(ToolDirective: ToolDirective, result?: any): ToolProgressStatus
}