Roo-Code/src/shared/tools.ts

400 lines
13 KiB
TypeScript

import { Anthropic } from "@anthropic-ai/sdk"
import type {
ClineAsk,
ToolProgressStatus,
ToolGroup,
ToolName,
BrowserActionParams,
GenerateImageParams,
FollowUpQuestion,
} from "@roo-code/types"
export type ToolResponse = string | Array<Anthropic.TextBlockParam | Anthropic.ImageBlockParam>
export type AskApproval = (
type: ClineAsk,
partialMessage?: string,
progressStatus?: ToolProgressStatus,
forceApproval?: boolean,
) => Promise<boolean>
export type HandleError = (action: string, error: Error) => Promise<void>
export type PushToolResult = (content: ToolResponse) => void
export type AskFinishSubTaskApproval = () => Promise<boolean>
export interface TextContent {
type: "text"
content: string
partial: boolean
}
export const toolParamNames = [
"command",
"path",
"content",
"regex",
"file_pattern",
"recursive",
"action",
"url",
"coordinate",
"text",
"server_name",
"tool_name",
"arguments",
"uri",
"question",
"result",
"diff",
"mode_slug",
"reason",
"line",
"mode",
"message",
"cwd",
"follow_up",
"task",
"size",
"query",
"args",
"skill", // skill tool parameter
"start_line",
"end_line",
"todos",
"prompt",
"image",
// read_file parameters (native protocol)
"operations", // search_and_replace parameter for multiple operations
"patch", // apply_patch parameter
"file_path", // search_replace and edit_file parameter
"old_string", // search_replace and edit_file parameter
"new_string", // search_replace and edit_file parameter
"expected_replacements", // edit_file parameter for multiple occurrences
"expected_replacements", // edit_file parameter for multiple occurrences
"questions", // ask_followup_question parameter for multiple questions
"artifact_id", // read_command_output parameter
"search", // read_command_output parameter for grep-like search
"offset", // read_command_output and read_file parameter
"limit", // read_command_output and read_file parameter
// read_file indentation mode parameters
"indentation",
"anchor_line",
"max_levels",
"include_siblings",
"include_header",
"max_lines",
// read_file legacy format parameter (backward compatibility)
"files",
"line_ranges",
] as const
export type ToolParamName = (typeof toolParamNames)[number]
/**
* Type map defining the native (typed) argument structure for each tool.
* Tools not listed here will fall back to `any` for backward compatibility.
*/
export type NativeToolArgs = {
access_mcp_resource: { server_name: string; uri: string }
read_file: import("@roo-code/types").ReadFileToolParams
read_command_output: { artifact_id: string; search?: string; offset?: number; limit?: number }
attempt_completion: { result: string }
execute_command: { command: string; cwd?: string }
apply_diff: { path: string; diff: string }
search_and_replace: { path: string; operations: Array<{ search: string; replace: string }> }
search_replace: { file_path: string; old_string: string; new_string: string }
edit_file: { file_path: string; old_string: string; new_string: string; expected_replacements?: number }
apply_patch: { patch: string }
list_files: { path: string; recursive?: boolean }
new_task: { mode: string; message: string; todos?: string }
ask_followup_question: {
questions: FollowUpQuestion[]
follow_up?: Array<{ text: string; mode?: string }>
}
browser_action: BrowserActionParams
codebase_search: { query: string; path?: string }
generate_image: GenerateImageParams
run_slash_command: { command: string; args?: string }
skill: { skill: string; args?: string | null }
search_files: { path: string; regex: string; file_pattern?: string | null }
switch_mode: { mode_slug: string; reason: string }
update_todo_list: { todos: string }
use_mcp_tool: { server_name: string; tool_name: string; arguments?: Record<string, unknown> }
write_to_file: { path: string; content: string }
// Add more tools as they are migrated to native protocol
}
/**
* Generic ToolUse interface that provides proper typing for both protocols.
*
* @template TName - The specific tool name, which determines the nativeArgs type
*/
export interface ToolUse<TName extends ToolName = ToolName> {
type: "tool_use"
id?: string // Optional ID to track tool calls
name: TName
/**
* The original tool name as called by the model (e.g. an alias like "edit_file"),
* if it differs from the canonical tool name used for execution.
* Used to preserve tool names in API conversation history.
*/
originalName?: string
// params is a partial record, allowing only some or none of the possible parameters to be used
params: Partial<Record<ToolParamName, string>>
partial: boolean
// nativeArgs is properly typed based on TName if it's in NativeToolArgs, otherwise never
nativeArgs?: TName extends keyof NativeToolArgs ? NativeToolArgs[TName] : never
/**
* Flag indicating whether the tool call used a legacy/deprecated format.
* Used for telemetry tracking to monitor migration from old formats.
*/
usedLegacyFormat?: boolean
}
/**
* Represents a native MCP tool call from the model.
* In native mode, MCP tools are called directly with their prefixed name (e.g., "mcp_serverName_toolName")
* rather than through the use_mcp_tool wrapper. This type preserves the original tool name
* so it appears correctly in API conversation history.
*/
export interface McpToolUse {
type: "mcp_tool_use"
id?: string // Tool call ID from the API
/** The original tool name from the API (e.g., "mcp_serverName_toolName") */
name: string
/** Extracted server name from the tool name */
serverName: string
/** Extracted tool name from the tool name */
toolName: string
/** Arguments passed to the MCP tool */
arguments: Record<string, unknown>
partial: boolean
}
export interface ExecuteCommandToolUse extends ToolUse<"execute_command"> {
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<"read_file"> {
name: "read_file"
params: Partial<
Pick<
Record<ToolParamName, string>,
| "args"
| "path"
| "start_line"
| "end_line"
| "mode"
| "offset"
| "limit"
| "indentation"
| "anchor_line"
| "max_levels"
| "include_siblings"
| "include_header"
>
>
}
export interface WriteToFileToolUse extends ToolUse<"write_to_file"> {
name: "write_to_file"
params: Partial<Pick<Record<ToolParamName, string>, "path" | "content">>
}
export interface CodebaseSearchToolUse extends ToolUse<"codebase_search"> {
name: "codebase_search"
params: Partial<Pick<Record<ToolParamName, string>, "query" | "path">>
}
export interface SearchFilesToolUse extends ToolUse<"search_files"> {
name: "search_files"
params: Partial<Pick<Record<ToolParamName, string>, "path" | "regex" | "file_pattern">>
}
export interface ListFilesToolUse extends ToolUse<"list_files"> {
name: "list_files"
params: Partial<Pick<Record<ToolParamName, string>, "path" | "recursive">>
}
export interface BrowserActionToolUse extends ToolUse<"browser_action"> {
name: "browser_action"
params: Partial<Pick<Record<ToolParamName, string>, "action" | "url" | "coordinate" | "text" | "size" | "path">>
}
export interface UseMcpToolToolUse extends ToolUse<"use_mcp_tool"> {
name: "use_mcp_tool"
params: Partial<Pick<Record<ToolParamName, string>, "server_name" | "tool_name" | "arguments">>
}
export interface AccessMcpResourceToolUse extends ToolUse<"access_mcp_resource"> {
name: "access_mcp_resource"
params: Partial<Pick<Record<ToolParamName, string>, "server_name" | "uri">>
}
export interface AskFollowupQuestionToolUse extends ToolUse<"ask_followup_question"> {
name: "ask_followup_question"
params: Partial<Pick<Record<ToolParamName, string>, "question" | "follow_up" | "questions">>
}
export interface AttemptCompletionToolUse extends ToolUse<"attempt_completion"> {
name: "attempt_completion"
params: Partial<Pick<Record<ToolParamName, string>, "result">>
}
export interface SwitchModeToolUse extends ToolUse<"switch_mode"> {
name: "switch_mode"
params: Partial<Pick<Record<ToolParamName, string>, "mode_slug" | "reason">>
}
export interface NewTaskToolUse extends ToolUse<"new_task"> {
name: "new_task"
params: Partial<Pick<Record<ToolParamName, string>, "mode" | "message" | "todos">>
}
export interface RunSlashCommandToolUse extends ToolUse<"run_slash_command"> {
name: "run_slash_command"
params: Partial<Pick<Record<ToolParamName, string>, "command" | "args">>
}
export interface SkillToolUse extends ToolUse<"skill"> {
name: "skill"
params: Partial<Pick<Record<ToolParamName, string>, "skill" | "args">>
}
export interface GenerateImageToolUse extends ToolUse<"generate_image"> {
name: "generate_image"
params: Partial<Pick<Record<ToolParamName, string>, "prompt" | "path" | "image">>
}
// Define tool group configuration
export type ToolGroupConfig = {
tools: readonly string[]
alwaysAvailable?: boolean // Whether this group is always available and shouldn't show in prompts view
customTools?: readonly string[] // Opt-in only tools - only available when explicitly included via model's includedTools
}
export const TOOL_DISPLAY_NAMES: Record<ToolName, string> = {
execute_command: "run commands",
read_file: "read files",
read_command_output: "read command output",
write_to_file: "write files",
apply_diff: "apply changes",
search_and_replace: "apply changes using search and replace",
search_replace: "apply single search and replace",
edit_file: "edit files using search and replace",
apply_patch: "apply patches using codex format",
search_files: "search files",
list_files: "list files",
browser_action: "use a browser",
use_mcp_tool: "use mcp tools",
access_mcp_resource: "access mcp resources",
ask_followup_question: "ask questions",
attempt_completion: "complete tasks",
switch_mode: "switch modes",
new_task: "create new task",
codebase_search: "codebase search",
update_todo_list: "update todo list",
run_slash_command: "run slash command",
skill: "load skill",
generate_image: "generate images",
custom_tool: "use custom tools",
} as const
// Define available tool groups.
export const TOOL_GROUPS: Record<ToolGroup, ToolGroupConfig> = {
read: {
tools: ["read_file", "search_files", "list_files", "codebase_search"],
},
edit: {
tools: ["apply_diff", "write_to_file", "generate_image"],
customTools: ["search_and_replace", "search_replace", "edit_file", "apply_patch"],
},
browser: {
tools: ["browser_action"],
},
command: {
tools: ["execute_command", "read_command_output"],
},
mcp: {
tools: ["use_mcp_tool", "access_mcp_resource"],
},
modes: {
tools: ["switch_mode", "new_task"],
alwaysAvailable: true,
},
}
// Tools that are always available to all modes.
export const ALWAYS_AVAILABLE_TOOLS: ToolName[] = [
"ask_followup_question",
"attempt_completion",
"switch_mode",
"new_task",
"update_todo_list",
"run_slash_command",
"skill",
] as const
/**
* Central registry of tool aliases.
* Maps alias name -> canonical tool name.
*
* This allows models to use alternative names for tools (e.g., "edit_file" instead of "apply_diff").
* When a model calls a tool by its alias, the system resolves it to the canonical name for execution,
* but preserves the alias in API conversation history for consistency.
*
* To add a new alias, simply add an entry here. No other files need to be modified.
*/
export const TOOL_ALIASES: Record<string, ToolName> = {
write_file: "write_to_file",
} as const
export type DiffResult =
| { success: true; content: string; failParts?: DiffResult[] }
| ({
success: false
error?: string
details?: {
similarity?: number
threshold?: number
matchedRange?: { start: number; end: number }
searchContent?: string
bestMatch?: string
}
failParts?: DiffResult[]
} & ({ error: string } | { failParts: DiffResult[] }))
export interface DiffItem {
content: string
startLine?: number
}
export interface DiffStrategy {
/**
* Get the name of this diff strategy for analytics and debugging
* @returns The name of the diff strategy
*/
getName(): string
/**
* Apply a diff to the original content
* @param originalContent The original file content
* @param diffContent The diff content in the strategy's format (string for legacy, DiffItem[] for new)
* @param startLine Optional line number where the search block starts. If not provided, searches the entire file.
* @param endLine Optional line number where the search block ends. If not provided, searches the entire file.
* @returns A DiffResult object containing either the successful result or error details
*/
applyDiff(
originalContent: string,
diffContent: string | DiffItem[],
startLine?: number,
endLine?: number,
): Promise<DiffResult>
getProgressStatus?(toolUse: ToolUse, result?: any): ToolProgressStatus
}