diff --git a/src/core/assistant-message/NativeToolCallParser.ts b/src/core/assistant-message/NativeToolCallParser.ts index e88c26a913f..58d07239628 100644 --- a/src/core/assistant-message/NativeToolCallParser.ts +++ b/src/core/assistant-message/NativeToolCallParser.ts @@ -6,6 +6,7 @@ import { toolParamNames, type NativeToolArgs, } from "../../shared/tools" +import { resolveToolAlias } from "../prompts/tools/filter-tools-for-mode" import { parseJSON } from "partial-json" import type { ApiStreamToolCallStartChunk, @@ -246,12 +247,18 @@ export class NativeToolCallParser { try { const partialArgs = parseJSON(toolCall.argumentsAccumulator) + // Resolve tool alias to canonical name + const resolvedName = resolveToolAlias(toolCall.name) as ToolName + // Preserve original name if it differs from resolved (i.e., it was an alias) + const originalName = toolCall.name !== resolvedName ? toolCall.name : undefined + // Create partial ToolUse with extracted values return this.createPartialToolUse( toolCall.id, - toolCall.name as ToolName, + resolvedName, partialArgs || {}, true, // partial + originalName, ) } catch { // Even partial-json-parser can fail on severely malformed JSON @@ -327,12 +334,14 @@ export class NativeToolCallParser { /** * Create a partial ToolUse from currently parsed arguments. * Used during streaming to show progress. + * @param originalName - The original tool name as called by the model (if different from canonical name) */ private static createPartialToolUse( id: string, name: ToolName, partialArgs: Record, partial: boolean, + originalName?: string, ): ToolUse | null { // Build legacy params for display // NOTE: For streaming partial updates, we MUST populate params even for complex types @@ -505,18 +514,33 @@ export class NativeToolCallParser { } break - // Add other tools as needed + case "search_and_replace": + if (partialArgs.path !== undefined || partialArgs.operations !== undefined) { + nativeArgs = { + path: partialArgs.path, + operations: partialArgs.operations, + } + } + break + default: break } - return { + const result: ToolUse = { type: "tool_use" as const, name, params, partial, nativeArgs, } + + // Preserve original name for API history when an alias was used + if (originalName) { + result.originalName = originalName + } + + return result } /** @@ -535,9 +559,12 @@ export class NativeToolCallParser { return this.parseDynamicMcpTool(toolCall) } - // Validate tool name - if (!toolNames.includes(toolCall.name as ToolName)) { - console.error(`Invalid tool name: ${toolCall.name}`) + // Resolve tool alias to canonical name (e.g., "edit_file" -> "apply_diff", "temp_edit_file" -> "search_and_replace") + const resolvedName = resolveToolAlias(toolCall.name as string) as TName + + // Validate tool name (after alias resolution) + if (!toolNames.includes(resolvedName as ToolName)) { + console.error(`Invalid tool name: ${toolCall.name} (resolved: ${resolvedName})`) console.error(`Valid tool names:`, toolNames) return null } @@ -554,13 +581,13 @@ export class NativeToolCallParser { // Skip complex parameters that have been migrated to nativeArgs. // For read_file, the 'files' parameter is a FileEntry[] array that can't be // meaningfully stringified. The properly typed data is in nativeArgs instead. - if (toolCall.name === "read_file" && key === "files") { + if (resolvedName === "read_file" && key === "files") { continue } // Validate parameter name if (!toolParamNames.includes(key as ToolParamName)) { - console.warn(`Unknown parameter '${key}' for tool '${toolCall.name}'`) + console.warn(`Unknown parameter '${key}' for tool '${resolvedName}'`) console.warn(`Valid param names:`, toolParamNames) continue } @@ -580,7 +607,7 @@ export class NativeToolCallParser { // will fall back to legacy parameter parsing if supported. let nativeArgs: NativeArgsFor | undefined = undefined - switch (toolCall.name) { + switch (resolvedName) { case "read_file": if (args.files && Array.isArray(args.files)) { nativeArgs = { files: this.convertFileEntries(args.files) } as NativeArgsFor @@ -761,12 +788,17 @@ export class NativeToolCallParser { const result: ToolUse = { type: "tool_use" as const, - name: toolCall.name, + name: resolvedName, params, partial: false, // Native tool calls are always complete when yielded nativeArgs, } + // Preserve original name for API history when an alias was used + if (toolCall.name !== resolvedName) { + result.originalName = toolCall.name + } + return result } catch (error) { console.error( diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index 54e01927261..dd1337b6d52 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -695,7 +695,11 @@ export async function presentAssistantMessage(cline: Task) { // potentially causing the stream to appear frozen. if (!block.partial) { const modelInfo = cline.api.getModel() - const includedTools = modelInfo?.info?.includedTools + // Resolve aliases in includedTools before validation + // e.g., "edit_file" should resolve to "apply_diff" + const rawIncludedTools = modelInfo?.info?.includedTools + const { resolveToolAlias } = await import("../prompts/tools/filter-tools-for-mode") + const includedTools = rawIncludedTools?.map((tool) => resolveToolAlias(tool)) try { validateToolUse( diff --git a/src/core/prompts/tools/__tests__/filter-tools-for-mode.spec.ts b/src/core/prompts/tools/__tests__/filter-tools-for-mode.spec.ts index d2ccaa84fb0..d189b999150 100644 --- a/src/core/prompts/tools/__tests__/filter-tools-for-mode.spec.ts +++ b/src/core/prompts/tools/__tests__/filter-tools-for-mode.spec.ts @@ -487,7 +487,7 @@ describe("filterMcpToolsForMode", () => { it("should return original tools when modelInfo is undefined", () => { const tools = new Set(["read_file", "write_to_file", "apply_diff"]) const result = applyModelToolCustomization(tools, codeMode, undefined) - expect(result).toEqual(tools) + expect(result.allowedTools).toEqual(tools) }) it("should exclude tools specified in excludedTools", () => { @@ -498,9 +498,9 @@ describe("filterMcpToolsForMode", () => { excludedTools: ["apply_diff"], } const result = applyModelToolCustomization(tools, codeMode, modelInfo) - expect(result.has("read_file")).toBe(true) - expect(result.has("write_to_file")).toBe(true) - expect(result.has("apply_diff")).toBe(false) + expect(result.allowedTools.has("read_file")).toBe(true) + expect(result.allowedTools.has("write_to_file")).toBe(true) + expect(result.allowedTools.has("apply_diff")).toBe(false) }) it("should exclude multiple tools", () => { @@ -511,10 +511,10 @@ describe("filterMcpToolsForMode", () => { excludedTools: ["apply_diff", "write_to_file"], } const result = applyModelToolCustomization(tools, codeMode, modelInfo) - expect(result.has("read_file")).toBe(true) - expect(result.has("execute_command")).toBe(true) - expect(result.has("write_to_file")).toBe(false) - expect(result.has("apply_diff")).toBe(false) + expect(result.allowedTools.has("read_file")).toBe(true) + expect(result.allowedTools.has("execute_command")).toBe(true) + expect(result.allowedTools.has("write_to_file")).toBe(false) + expect(result.allowedTools.has("apply_diff")).toBe(false) }) it("should include tools only if they belong to allowed groups", () => { @@ -525,9 +525,9 @@ describe("filterMcpToolsForMode", () => { includedTools: ["write_to_file", "apply_diff"], // Both in edit group } const result = applyModelToolCustomization(tools, codeMode, modelInfo) - expect(result.has("read_file")).toBe(true) - expect(result.has("write_to_file")).toBe(true) - expect(result.has("apply_diff")).toBe(true) + expect(result.allowedTools.has("read_file")).toBe(true) + expect(result.allowedTools.has("write_to_file")).toBe(true) + expect(result.allowedTools.has("apply_diff")).toBe(true) }) it("should NOT include tools from groups not allowed by mode", () => { @@ -539,9 +539,9 @@ describe("filterMcpToolsForMode", () => { } // Architect mode doesn't have edit group const result = applyModelToolCustomization(tools, architectMode, modelInfo) - expect(result.has("read_file")).toBe(true) - expect(result.has("write_to_file")).toBe(false) // Not in allowed groups - expect(result.has("apply_diff")).toBe(false) // Not in allowed groups + expect(result.allowedTools.has("read_file")).toBe(true) + expect(result.allowedTools.has("write_to_file")).toBe(false) // Not in allowed groups + expect(result.allowedTools.has("apply_diff")).toBe(false) // Not in allowed groups }) it("should apply both exclude and include operations", () => { @@ -553,10 +553,10 @@ describe("filterMcpToolsForMode", () => { includedTools: ["search_and_replace"], // Another edit tool (customTool) } const result = applyModelToolCustomization(tools, codeMode, modelInfo) - expect(result.has("read_file")).toBe(true) - expect(result.has("write_to_file")).toBe(true) - expect(result.has("apply_diff")).toBe(false) // Excluded - expect(result.has("search_and_replace")).toBe(true) // Included + expect(result.allowedTools.has("read_file")).toBe(true) + expect(result.allowedTools.has("write_to_file")).toBe(true) + expect(result.allowedTools.has("apply_diff")).toBe(false) // Excluded + expect(result.allowedTools.has("search_and_replace")).toBe(true) // Included }) it("should handle empty excludedTools and includedTools arrays", () => { @@ -568,7 +568,7 @@ describe("filterMcpToolsForMode", () => { includedTools: [], } const result = applyModelToolCustomization(tools, codeMode, modelInfo) - expect(result).toEqual(tools) + expect(result.allowedTools).toEqual(tools) }) it("should ignore excluded tools that are not in the original set", () => { @@ -579,9 +579,9 @@ describe("filterMcpToolsForMode", () => { excludedTools: ["apply_diff", "nonexistent_tool"], } const result = applyModelToolCustomization(tools, codeMode, modelInfo) - expect(result.has("read_file")).toBe(true) - expect(result.has("write_to_file")).toBe(true) - expect(result.size).toBe(2) + expect(result.allowedTools.has("read_file")).toBe(true) + expect(result.allowedTools.has("write_to_file")).toBe(true) + expect(result.allowedTools.size).toBe(2) }) it("should NOT include customTools by default", () => { @@ -594,8 +594,8 @@ describe("filterMcpToolsForMode", () => { } const result = applyModelToolCustomization(tools, codeMode, modelInfo) // customTools should not be in the result unless explicitly included - expect(result.has("read_file")).toBe(true) - expect(result.has("write_to_file")).toBe(true) + expect(result.allowedTools.has("read_file")).toBe(true) + expect(result.allowedTools.has("write_to_file")).toBe(true) }) it("should NOT include tools that are not in any TOOL_GROUPS", () => { @@ -606,8 +606,8 @@ describe("filterMcpToolsForMode", () => { includedTools: ["my_custom_tool"], // Not in any tool group } const result = applyModelToolCustomization(tools, codeMode, modelInfo) - expect(result.has("read_file")).toBe(true) - expect(result.has("my_custom_tool")).toBe(false) + expect(result.allowedTools.has("read_file")).toBe(true) + expect(result.allowedTools.has("my_custom_tool")).toBe(false) }) it("should NOT include undefined tools even with allowed groups", () => { @@ -619,8 +619,8 @@ describe("filterMcpToolsForMode", () => { } // Even though architect mode has read group, undefined tools are not added const result = applyModelToolCustomization(tools, architectMode, modelInfo) - expect(result.has("read_file")).toBe(true) - expect(result.has("custom_edit_tool")).toBe(false) + expect(result.allowedTools.has("read_file")).toBe(true) + expect(result.allowedTools.has("custom_edit_tool")).toBe(false) }) describe("with customTools defined in TOOL_GROUPS", () => { @@ -647,9 +647,9 @@ describe("filterMcpToolsForMode", () => { includedTools: ["special_edit_tool"], // customTool from edit group } const result = applyModelToolCustomization(tools, codeMode, modelInfo) - expect(result.has("read_file")).toBe(true) - expect(result.has("write_to_file")).toBe(true) - expect(result.has("special_edit_tool")).toBe(true) // customTool should be included + expect(result.allowedTools.has("read_file")).toBe(true) + expect(result.allowedTools.has("write_to_file")).toBe(true) + expect(result.allowedTools.has("special_edit_tool")).toBe(true) // customTool should be included }) it("should NOT include customTools when not specified in includedTools", () => { @@ -660,9 +660,9 @@ describe("filterMcpToolsForMode", () => { // No includedTools specified } const result = applyModelToolCustomization(tools, codeMode, modelInfo) - expect(result.has("read_file")).toBe(true) - expect(result.has("write_to_file")).toBe(true) - expect(result.has("special_edit_tool")).toBe(false) // customTool should NOT be included by default + expect(result.allowedTools.has("read_file")).toBe(true) + expect(result.allowedTools.has("write_to_file")).toBe(true) + expect(result.allowedTools.has("special_edit_tool")).toBe(false) // customTool should NOT be included by default }) it("should NOT include customTools from groups not allowed by mode", () => { @@ -674,8 +674,8 @@ describe("filterMcpToolsForMode", () => { } // Architect mode doesn't have edit group const result = applyModelToolCustomization(tools, architectMode, modelInfo) - expect(result.has("read_file")).toBe(true) - expect(result.has("special_edit_tool")).toBe(false) // customTool should NOT be included + expect(result.allowedTools.has("read_file")).toBe(true) + expect(result.allowedTools.has("special_edit_tool")).toBe(false) // customTool should NOT be included }) }) }) @@ -822,5 +822,31 @@ describe("filterMcpToolsForMode", () => { expect(toolNames).toContain("search_and_replace") // Included expect(toolNames).not.toContain("apply_diff") // Excluded }) + + it("should rename tools to alias names when model includes aliases", () => { + const codeMode: ModeConfig = { + slug: "code", + name: "Code", + roleDefinition: "Test", + groups: ["read", "edit", "browser", "command", "mcp"] as const, + } + + const modelInfo: ModelInfo = { + contextWindow: 100000, + supportsPromptCache: false, + includedTools: ["edit_file", "write_file"], + } + + const filtered = filterNativeToolsForMode(mockNativeTools, "code", [codeMode], {}, undefined, { + modelInfo, + }) + + const toolNames = filtered.map((t) => ("function" in t ? t.function.name : "")) + + expect(toolNames).toContain("edit_file") + expect(toolNames).toContain("write_file") + expect(toolNames).not.toContain("apply_diff") + expect(toolNames).not.toContain("write_to_file") + }) }) }) diff --git a/src/core/prompts/tools/filter-tools-for-mode.ts b/src/core/prompts/tools/filter-tools-for-mode.ts index eb87c9bbeca..3c1b2e3676d 100644 --- a/src/core/prompts/tools/filter-tools-for-mode.ts +++ b/src/core/prompts/tools/filter-tools-for-mode.ts @@ -1,11 +1,131 @@ import type OpenAI from "openai" import type { ModeConfig, ToolName, ToolGroup, ModelInfo } from "@roo-code/types" import { getModeBySlug, getToolsForMode, isToolAllowedForMode } from "../../../shared/modes" -import { TOOL_GROUPS, ALWAYS_AVAILABLE_TOOLS } from "../../../shared/tools" +import { TOOL_GROUPS, ALWAYS_AVAILABLE_TOOLS, TOOL_ALIASES } from "../../../shared/tools" import { defaultModeSlug } from "../../../shared/modes" import type { CodeIndexManager } from "../../../services/code-index/manager" import type { McpHub } from "../../../services/mcp/McpHub" +/** + * Reverse lookup map - maps alias name to canonical tool name. + * Built once at module load from the central TOOL_ALIASES constant. + */ +const ALIAS_TO_CANONICAL: Map = new Map( + Object.entries(TOOL_ALIASES).map(([alias, canonical]) => [alias, canonical]), +) + +/** + * Canonical to aliases map - maps canonical tool name to array of alias names. + * Built once at module load from the central TOOL_ALIASES constant. + */ +const CANONICAL_TO_ALIASES: Map = new Map() + +// Build the reverse mapping (canonical -> aliases) +for (const [alias, canonical] of Object.entries(TOOL_ALIASES)) { + const existing = CANONICAL_TO_ALIASES.get(canonical) ?? [] + existing.push(alias) + CANONICAL_TO_ALIASES.set(canonical, existing) +} + +/** + * Pre-computed alias groups map - maps any tool name (canonical or alias) to its full group. + * Built once at module load for O(1) lookup. + */ +const ALIAS_GROUPS: Map = new Map() + +// Build alias groups for all tools +for (const [canonical, aliases] of CANONICAL_TO_ALIASES.entries()) { + const group = Object.freeze([canonical, ...aliases]) + // Map canonical to group + ALIAS_GROUPS.set(canonical, group) + // Map each alias to the same group + for (const alias of aliases) { + ALIAS_GROUPS.set(alias, group) + } +} + +/** + * Cache for renamed tool definitions. + * Maps "canonicalName:aliasName" to the pre-built tool definition. + * This avoids creating new objects via spread operators on every assistant message. + */ +const RENAMED_TOOL_CACHE: Map = new Map() + +/** + * Gets or creates a renamed tool definition with the alias name. + * Uses RENAMED_TOOL_CACHE to avoid repeated object allocation. + * + * @param tool - The original tool definition + * @param aliasName - The alias name to use + * @returns Cached or newly created renamed tool definition + */ +function getOrCreateRenamedTool( + tool: OpenAI.Chat.ChatCompletionTool, + aliasName: string, +): OpenAI.Chat.ChatCompletionTool { + if (!("function" in tool) || !tool.function) { + return tool + } + + const cacheKey = `${tool.function.name}:${aliasName}` + let renamedTool = RENAMED_TOOL_CACHE.get(cacheKey) + + if (!renamedTool) { + renamedTool = { + ...tool, + function: { + ...tool.function, + name: aliasName, + }, + } + RENAMED_TOOL_CACHE.set(cacheKey, renamedTool) + } + + return renamedTool +} + +/** + * Resolves a tool name to its canonical name. + * If the tool name is an alias, returns the canonical tool name. + * If it's already a canonical name or unknown, returns as-is. + * + * @param toolName - The tool name to resolve (may be an alias) + * @returns The canonical tool name + */ +export function resolveToolAlias(toolName: string): string { + const canonical = ALIAS_TO_CANONICAL.get(toolName) + return canonical ?? toolName +} + +/** + * Applies tool alias resolution to a set of allowed tools. + * Resolves any aliases to their canonical tool names. + * + * @param allowedTools - Set of tools that may contain aliases + * @returns Set with aliases resolved to canonical names + */ +export function applyToolAliases(allowedTools: Set): Set { + const result = new Set() + + for (const tool of allowedTools) { + // Resolve alias to canonical name + result.add(resolveToolAlias(tool)) + } + + return result +} + +/** + * Gets all tools in an alias group (including the canonical tool). + * Uses pre-computed ALIAS_GROUPS map for O(1) lookup. + * + * @param toolName - Any tool name in the alias group + * @returns Array of all tool names in the alias group, or just the tool if not aliased + */ +export function getToolAliasGroup(toolName: string): readonly string[] { + return ALIAS_GROUPS.get(toolName) ?? [toolName] +} + /** * Apply model-specific tool customization to a set of allowed tools. * @@ -18,21 +138,33 @@ import type { McpHub } from "../../../services/mcp/McpHub" * @param modelInfo - Model configuration with tool customization * @returns Modified set of tools after applying model customization */ +/** + * Result of applying model tool customization. + * Contains the set of allowed tools and any alias renames to apply. + */ +interface ModelToolCustomizationResult { + allowedTools: Set + /** Maps canonical tool name to alias name for tools that should be renamed */ + aliasRenames: Map +} + export function applyModelToolCustomization( allowedTools: Set, modeConfig: ModeConfig, modelInfo?: ModelInfo, -): Set { +): ModelToolCustomizationResult { if (!modelInfo) { - return allowedTools + return { allowedTools, aliasRenames: new Map() } } const result = new Set(allowedTools) + const aliasRenames = new Map() // Apply excluded tools (remove from allowed set) if (modelInfo.excludedTools && modelInfo.excludedTools.length > 0) { modelInfo.excludedTools.forEach((tool) => { - result.delete(tool) + const resolvedTool = resolveToolAlias(tool) + result.delete(resolvedTool) }) } @@ -59,16 +191,21 @@ export function applyModelToolCustomization( ) // Add included tools only if they belong to an allowed group - // This includes both regular tools and customTools + // If the tool was specified as an alias, track the rename modelInfo.includedTools.forEach((tool) => { - const toolGroup = toolToGroup.get(tool) + const resolvedTool = resolveToolAlias(tool) + const toolGroup = toolToGroup.get(resolvedTool) if (toolGroup && allowedGroups.has(toolGroup)) { - result.add(tool) + result.add(resolvedTool) + // If the tool was specified as an alias, rename it in the API + if (tool !== resolvedTool) { + aliasRenames.set(resolvedTool, tool) + } } }) } - return result + return { allowedTools: result, aliasRenames } } /** @@ -123,7 +260,12 @@ export function filterNativeToolsForMode( // Apply model-specific tool customization const modelInfo = settings?.modelInfo as ModelInfo | undefined - allowedToolNames = applyModelToolCustomization(allowedToolNames, modeConfig, modelInfo) + const { allowedTools: customizedTools, aliasRenames } = applyModelToolCustomization( + allowedToolNames, + modeConfig, + modelInfo, + ) + allowedToolNames = customizedTools // Conditionally exclude codebase_search if feature is disabled or not configured if ( @@ -163,14 +305,27 @@ export function filterNativeToolsForMode( allowedToolNames.delete("access_mcp_resource") } - // Filter native tools based on allowed tool names - return nativeTools.filter((tool) => { + // Filter native tools based on allowed tool names and apply alias renames + const filteredTools: OpenAI.Chat.ChatCompletionTool[] = [] + + for (const tool of nativeTools) { // Handle both ChatCompletionTool and ChatCompletionCustomTool if ("function" in tool && tool.function) { - return allowedToolNames.has(tool.function.name) + const toolName = tool.function.name + if (allowedToolNames.has(toolName)) { + // Check if this tool should be renamed to an alias + const aliasName = aliasRenames.get(toolName) + if (aliasName) { + // Use cached renamed tool definition to avoid per-message object allocation + filteredTools.push(getOrCreateRenamedTool(tool, aliasName)) + } else { + filteredTools.push(tool) + } + } } - return false - }) + } + + return filteredTools } /** @@ -232,7 +387,16 @@ export function isToolAllowedInMode( } // Check if the tool is allowed by the mode's groups - return isToolAllowedForMode(toolName, modeSlug, customModes ?? [], undefined, undefined, experiments ?? {}) + // Resolve to canonical name and check that single value + const canonicalTool = resolveToolAlias(toolName) + return isToolAllowedForMode( + canonicalTool as ToolName, + modeSlug, + customModes ?? [], + undefined, + undefined, + experiments ?? {}, + ) } /** diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index f7e4946f53c..6ae97b4004a 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -3106,10 +3106,16 @@ export class Task extends EventEmitter implements TaskLike { // nativeArgs is already in the correct API format for all tools const input = toolUse.nativeArgs || toolUse.params + // Use originalName (alias) if present for API history consistency. + // When tool aliases are used (e.g., "edit_file" -> "search_and_replace"), + // we want the alias name in the conversation history to match what the model + // was told the tool was named, preventing confusion in multi-turn conversations. + const toolNameForHistory = toolUse.originalName ?? toolUse.name + assistantContent.push({ type: "tool_use" as const, id: toolCallId, - name: toolUse.name, + name: toolNameForHistory, input, }) } diff --git a/src/shared/tools.ts b/src/shared/tools.ts index f1f7d3ed80e..de7a65bfb79 100644 --- a/src/shared/tools.ts +++ b/src/shared/tools.ts @@ -120,6 +120,12 @@ export interface ToolUse { 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> partial: boolean @@ -293,6 +299,22 @@ export const ALWAYS_AVAILABLE_TOOLS: ToolName[] = [ "run_slash_command", ] 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 = { + edit_file: "apply_diff", + write_file: "write_to_file", + temp_edit_file: "search_and_replace", +} as const + export type DiffResult = | { success: true; content: string; failParts?: DiffResult[] } | ({