Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
231 changes: 231 additions & 0 deletions src/core/tools/ToolAdapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
import { toolRegistry } from "./ToolRegistry"
import { ToolName } from "@roo-code/types"
import { Task } from "../task/Task"
import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools"

/**
* ToolAdapter - Provides backward compatibility with existing tool invocation code
* This adapter allows the existing codebase to work with the new RooTool classes
* without requiring immediate changes to all tool invocations.
*
* This is a transitional component that can be removed once all code
* has been updated to use the ToolRegistry directly.
*/
export class ToolAdapter {
/**
* Execute a tool by name using the new RooTool system
* This method provides the same interface as the old tool functions
* but delegates to the new RooTool implementations
*/
static async executeTool(
toolName: ToolName,
cline: Task,
block: ToolUse,
askApproval: AskApproval,
handleError: HandleError,
pushToolResult: PushToolResult,
removeClosingTag: RemoveClosingTag,
): Promise<void> {
const tool = toolRegistry.getTool(toolName)

if (!tool) {
// Fall back to legacy implementation if tool hasn't been migrated yet
return ToolAdapter.executeLegacyTool(
toolName,
cline,
block,
askApproval,
handleError,
pushToolResult,
removeClosingTag,
)
}

// Execute using the new RooTool implementation
return tool.execute(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
}

/**
* Execute a legacy tool that hasn't been migrated yet
* This allows for gradual migration of tools
*/
private static async executeLegacyTool(
toolName: ToolName,
cline: Task,
block: ToolUse,
askApproval: AskApproval,
handleError: HandleError,
pushToolResult: PushToolResult,
removeClosingTag: RemoveClosingTag,
): Promise<void> {
// Import and execute legacy tool implementations
// These imports will be removed as tools are migrated
switch (toolName) {
case "write_to_file": {
// This is already migrated, but keeping as example
const { writeToFileTool } = await import("../tools/writeToFileTool")
return writeToFileTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
}
case "read_file": {
const { readFileTool } = await import("../tools/readFileTool")
return readFileTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
}
case "execute_command": {
const { executeCommandTool } = await import("../tools/executeCommandTool")
return executeCommandTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
}
case "apply_diff": {
// Use the legacy apply diff for now
const { applyDiffToolLegacy } = await import("../tools/applyDiffTool")
return applyDiffToolLegacy(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
}
case "search_files": {
const { searchFilesTool } = await import("../tools/searchFilesTool")
return searchFilesTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
}
case "list_files": {
const { listFilesTool } = await import("../tools/listFilesTool")
return listFilesTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
}
case "list_code_definition_names": {
const { listCodeDefinitionNamesTool } = await import("../tools/listCodeDefinitionNamesTool")
return listCodeDefinitionNamesTool(
cline,
block,
askApproval,
handleError,
pushToolResult,
removeClosingTag,
)
}
case "browser_action": {
const { browserActionTool } = await import("../tools/browserActionTool")
return browserActionTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
}
case "use_mcp_tool": {
const { useMcpToolTool } = await import("../tools/useMcpToolTool")
return useMcpToolTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
}
case "access_mcp_resource": {
const { accessMcpResourceTool } = await import("../tools/accessMcpResourceTool")
return accessMcpResourceTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
}
case "ask_followup_question": {
const { askFollowupQuestionTool } = await import("../tools/askFollowupQuestionTool")
return askFollowupQuestionTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
}
case "attempt_completion": {
const { attemptCompletionTool } = await import("../tools/attemptCompletionTool")
// attemptCompletionTool requires additional parameters
const toolDescription = () => `[${block.name}]`
const askFinishSubTaskApproval = async () => {
const toolMessage = JSON.stringify({ tool: "finishTask" })
return await askApproval("tool", toolMessage)
}
return attemptCompletionTool(
cline,
block,
askApproval,
handleError,
pushToolResult,
removeClosingTag,
toolDescription,
askFinishSubTaskApproval,
)
}
case "switch_mode": {
const { switchModeTool } = await import("../tools/switchModeTool")
return switchModeTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
}
case "new_task": {
const { newTaskTool } = await import("../tools/newTaskTool")
return newTaskTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
}
case "insert_content": {
const { insertContentTool } = await import("../tools/insertContentTool")
return insertContentTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
}
case "search_and_replace": {
const { searchAndReplaceTool } = await import("../tools/searchAndReplaceTool")
return searchAndReplaceTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
}
case "codebase_search": {
const { codebaseSearchTool } = await import("../tools/codebaseSearchTool")
return codebaseSearchTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
}
case "update_todo_list": {
const { updateTodoListTool } = await import("../tools/updateTodoListTool")
return updateTodoListTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
}
case "run_slash_command": {
const { runSlashCommandTool } = await import("../tools/runSlashCommandTool")
return runSlashCommandTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
}
case "generate_image": {
const { generateImageTool } = await import("../tools/generateImageTool")
return generateImageTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
}
case "fetch_instructions": {
const { fetchInstructionsTool } = await import("../tools/fetchInstructionsTool")
return fetchInstructionsTool(cline, block, askApproval, handleError, pushToolResult)
}
default:
throw new Error(`Unknown tool: ${toolName}`)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we create a specific error class like UnknownToolError for better error handling and debugging? Generic errors make it harder to track down issues in production.

}
}

/**
* Get tool usage description using the new system
* Falls back to legacy implementation if needed
*/
static getToolUsageDescription(block: ToolUse): string {
const tool = toolRegistry.getTool(block.name as ToolName)

if (tool) {
return tool.getToolUsageDescription(block)
}

// Fall back to legacy description logic
// This is copied from presentAssistantMessage.ts and can be removed
// once all tools are migrated
switch (block.name) {
case "execute_command":
return `[${block.name} for '${block.params.command}']`
case "write_to_file":
return `[${block.name} to '${block.params.path}']`
case "read_file":
// This would use the getReadFileToolDescription function
return `[${block.name} for '${block.params.path || block.params.args}']`
case "list_files":
return `[${block.name} for '${block.params.path}']`
case "list_code_definition_names":
return `[${block.name} for '${block.params.path}']`
case "search_files":
return `[${block.name} for '${block.params.regex}' in '${block.params.path}']`
case "browser_action":
return `[${block.name} for '${block.params.action}']`
case "use_mcp_tool":
return `[${block.name} for '${block.params.server_name}']`
case "access_mcp_resource":
return `[${block.name} for '${block.params.server_name}']`
case "ask_followup_question":
return `[${block.name} for '${block.params.question}']`
case "attempt_completion":
return `[${block.name}]`
case "switch_mode":
return `[${block.name} to '${block.params.mode_slug}'${block.params.reason ? ` because: ${block.params.reason}` : ""}]`
case "codebase_search":
return `[${block.name} for '${block.params.query}']`
case "update_todo_list":
return `[${block.name}]`
case "new_task":
return `[${block.name} in ${block.params.mode} mode: '${block.params.message}']`
case "run_slash_command":
return `[${block.name} for '${block.params.command}'${block.params.args ? ` with args: ${block.params.args}` : ""}]`
case "generate_image":
return `[${block.name} for '${block.params.path}']`
default:
return `[${block.name}]`
}
}
}
161 changes: 161 additions & 0 deletions src/core/tools/ToolRegistry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { ToolName, ToolGroup } from "@roo-code/types"
import { RooTool } from "./base/RooTool"
import { WriteToFileTool } from "./implementations/WriteToFileTool"
// Import other tool implementations as they are created
// import { ReadFileTool } from "./implementations/ReadFileTool"
// import { ExecuteCommandTool } from "./implementations/ExecuteCommandTool"
// etc...

/**
* ToolRegistry - Singleton registry for managing all tool instances
* This centralizes tool management and eliminates the need for scattered
* tool maps and switch statements throughout the codebase.
*/
export class ToolRegistry {
private static instance: ToolRegistry
private tools: Map<ToolName, RooTool>

private constructor() {
this.tools = new Map()
this.registerTools()
}

/**
* Get the singleton instance of the ToolRegistry
*/
static getInstance(): ToolRegistry {
if (!ToolRegistry.instance) {
ToolRegistry.instance = new ToolRegistry()
}
return ToolRegistry.instance
}

/**
* Register all available tools
* This is where new tool implementations are added to the registry
*/
private registerTools(): void {
// Register each tool implementation
this.registerTool(new WriteToFileTool())
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be helpful to add a TODO comment here tracking which tools still need migration? Something like:

Suggested change
this.registerTool(new WriteToFileTool())
// Register each tool implementation
this.registerTool(new WriteToFileTool())
// TODO: Migrate remaining tools (21 remaining):
// Priority 1 (Core editing):
// - ReadFileTool
// - ApplyDiffTool
// - ExecuteCommandTool
// Priority 2 (Search/Navigation):
// - SearchFilesTool
// - ListFilesTool
// - ListCodeDefinitionNamesTool
// - CodebaseSearchTool
// Priority 3 (Other):
// ... etc


// Add other tools as they are implemented
// this.registerTool(new ReadFileTool())
// this.registerTool(new ExecuteCommandTool())
// this.registerTool(new ApplyDiffTool())
// this.registerTool(new SearchFilesTool())
// this.registerTool(new ListFilesTool())
// this.registerTool(new ListCodeDefinitionNamesTool())
// this.registerTool(new BrowserActionTool())
// this.registerTool(new UseMcpToolTool())
// this.registerTool(new AccessMcpResourceTool())
// this.registerTool(new AskFollowupQuestionTool())
// this.registerTool(new AttemptCompletionTool())
// this.registerTool(new SwitchModeTool())
// this.registerTool(new NewTaskTool())
// this.registerTool(new InsertContentTool())
// this.registerTool(new SearchAndReplaceTool())
// this.registerTool(new CodebaseSearchTool())
// this.registerTool(new UpdateTodoListTool())
// this.registerTool(new RunSlashCommandTool())
// this.registerTool(new GenerateImageTool())
// this.registerTool(new FetchInstructionsTool())
}

/**
* Register a tool in the registry
*/
private registerTool(tool: RooTool): void {
this.tools.set(tool.name, tool)
}

/**
* Get a tool by name
*/
getTool(name: ToolName): RooTool | undefined {
return this.tools.get(name)
}

/**
* Get all tools
*/
getAllTools(): RooTool[] {
return Array.from(this.tools.values())
}

/**
* Get tools by group
*/
getToolsByGroup(group: ToolGroup): RooTool[] {
return this.getAllTools().filter((tool) => tool.belongsToGroup(group))
}

/**
* Get all tool names
*/
getToolNames(): ToolName[] {
return Array.from(this.tools.keys())
}

/**
* Check if a tool exists
*/
hasTool(name: ToolName): boolean {
return this.tools.has(name)
}

/**
* Get tool description map for prompts
* This replaces the old toolDescriptionMap
*/
getToolDescriptionMap(): Record<string, (args: any) => string | undefined> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we improve type safety here by using ToolArgs instead of any?

Suggested change
getToolDescriptionMap(): Record<string, (args: any) => string | undefined> {
getToolDescriptionMap(): Record<string, (args: ToolArgs) => string | undefined> {
const descriptionMap: Record<string, (args: ToolArgs) => string | undefined> = {}

const descriptionMap: Record<string, (args: any) => string | undefined> = {}

for (const [name, tool] of this.tools) {
descriptionMap[name] = (args) => tool.getDescription(args)
}

return descriptionMap
}

/**
* Get tool display names
* This replaces the old TOOL_DISPLAY_NAMES constant
*/
getToolDisplayNames(): Record<ToolName, string> {
const displayNames: Record<string, string> = {}

// Map tool names to display names
const nameMapping: Record<ToolName, string> = {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This duplicates the TOOL_DISPLAY_NAMES constant from shared/tools.ts. Could we import and reuse the existing constant to avoid duplication and potential sync issues?

execute_command: "run commands",
read_file: "read files",
fetch_instructions: "fetch instructions",
write_to_file: "write files",
apply_diff: "apply changes",
search_files: "search files",
list_files: "list files",
list_code_definition_names: "list definitions",
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",
insert_content: "insert content",
search_and_replace: "search and replace",
codebase_search: "codebase search",
update_todo_list: "update todo list",
run_slash_command: "run slash command",
generate_image: "generate images",
}

for (const name of this.getToolNames()) {
displayNames[name] = nameMapping[name] || name
}

return displayNames as Record<ToolName, string>
}
}

// Export a singleton instance for convenience
export const toolRegistry = ToolRegistry.getInstance()
Loading
Loading