Skip to content

Commit fba8508

Browse files
feat: add search_replace native tool for single-replacement operations (#9918)
Adds a new search_replace tool that performs a single search and replace operation on a file, requiring the old_string to uniquely identify the target text with 3-5 lines of context. Parameters: - file_path: Path to file (relative or absolute) - old_string: Text to find (must be unique in file) - new_string: Replacement text (must differ from old_string)
1 parent efbf427 commit fba8508

File tree

8 files changed

+760
-1
lines changed

8 files changed

+760
-1
lines changed

packages/types/src/tool.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export const toolNames = [
2020
"write_to_file",
2121
"apply_diff",
2222
"search_and_replace",
23+
"search_replace",
2324
"apply_patch",
2425
"search_files",
2526
"list_files",

src/core/assistant-message/NativeToolCallParser.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,20 @@ export class NativeToolCallParser {
499499
}
500500
break
501501

502+
case "search_replace":
503+
if (
504+
partialArgs.file_path !== undefined ||
505+
partialArgs.old_string !== undefined ||
506+
partialArgs.new_string !== undefined
507+
) {
508+
nativeArgs = {
509+
file_path: partialArgs.file_path,
510+
old_string: partialArgs.old_string,
511+
new_string: partialArgs.new_string,
512+
}
513+
}
514+
break
515+
502516
// Add other tools as needed
503517
default:
504518
break
@@ -742,6 +756,20 @@ export class NativeToolCallParser {
742756
}
743757
break
744758

759+
case "search_replace":
760+
if (
761+
args.file_path !== undefined &&
762+
args.old_string !== undefined &&
763+
args.new_string !== undefined
764+
) {
765+
nativeArgs = {
766+
file_path: args.file_path,
767+
old_string: args.old_string,
768+
new_string: args.new_string,
769+
} as NativeArgsFor<TName>
770+
}
771+
break
772+
745773
default:
746774
break
747775
}

src/core/assistant-message/presentAssistantMessage.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { shouldUseSingleFileRead, TOOL_PROTOCOL } from "@roo-code/types"
1818
import { writeToFileTool } from "../tools/WriteToFileTool"
1919
import { applyDiffTool } from "../tools/MultiApplyDiffTool"
2020
import { searchAndReplaceTool } from "../tools/SearchAndReplaceTool"
21+
import { searchReplaceTool } from "../tools/SearchReplaceTool"
2122
import { applyPatchTool } from "../tools/ApplyPatchTool"
2223
import { listCodeDefinitionNamesTool } from "../tools/ListCodeDefinitionNamesTool"
2324
import { searchFilesTool } from "../tools/SearchFilesTool"
@@ -385,6 +386,8 @@ export async function presentAssistantMessage(cline: Task) {
385386
}]`
386387
case "search_and_replace":
387388
return `[${block.name} for '${block.params.path}']`
389+
case "search_replace":
390+
return `[${block.name} for '${block.params.file_path}']`
388391
case "apply_patch":
389392
return `[${block.name}]`
390393
case "list_files":
@@ -835,6 +838,16 @@ export async function presentAssistantMessage(cline: Task) {
835838
toolProtocol,
836839
})
837840
break
841+
case "search_replace":
842+
await checkpointSaveAndMark(cline)
843+
await searchReplaceTool.handle(cline, block as ToolUse<"search_replace">, {
844+
askApproval,
845+
handleError,
846+
pushToolResult,
847+
removeClosingTag,
848+
toolProtocol,
849+
})
850+
break
838851
case "apply_patch":
839852
await checkpointSaveAndMark(cline)
840853
await applyPatchTool.handle(cline, block as ToolUse<"apply_patch">, {

src/core/prompts/tools/native-tools/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import newTask from "./new_task"
1515
import { createReadFileTool } from "./read_file"
1616
import runSlashCommand from "./run_slash_command"
1717
import searchAndReplace from "./search_and_replace"
18+
import searchReplace from "./search_replace"
1819
import searchFiles from "./search_files"
1920
import switchMode from "./switch_mode"
2021
import updateTodoList from "./update_todo_list"
@@ -47,6 +48,7 @@ export function getNativeTools(partialReadsEnabled: boolean = true): OpenAI.Chat
4748
createReadFileTool(partialReadsEnabled),
4849
runSlashCommand,
4950
searchAndReplace,
51+
searchReplace,
5052
searchFiles,
5153
switchMode,
5254
updateTodoList,
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type OpenAI from "openai"
2+
3+
const SEARCH_REPLACE_DESCRIPTION = `Use this tool to propose a search and replace operation on an existing file.
4+
5+
The tool will replace ONE occurrence of old_string with new_string in the specified file.
6+
7+
CRITICAL REQUIREMENTS FOR USING THIS TOOL:
8+
9+
1. UNIQUENESS: The old_string MUST uniquely identify the specific instance you want to change. This means:
10+
- Include AT LEAST 3-5 lines of context BEFORE the change point
11+
- Include AT LEAST 3-5 lines of context AFTER the change point
12+
- Include all whitespace, indentation, and surrounding code exactly as it appears in the file
13+
14+
2. SINGLE INSTANCE: This tool can only change ONE instance at a time. If you need to change multiple instances:
15+
- Make separate calls to this tool for each instance
16+
- Each call must uniquely identify its specific instance using extensive context
17+
18+
3. VERIFICATION: Before using this tool:
19+
- If multiple instances exist, gather enough context to uniquely identify each one
20+
- Plan separate tool calls for each instance`
21+
22+
const search_replace = {
23+
type: "function",
24+
function: {
25+
name: "search_replace",
26+
description: SEARCH_REPLACE_DESCRIPTION,
27+
parameters: {
28+
type: "object",
29+
properties: {
30+
file_path: {
31+
type: "string",
32+
description:
33+
"The path to the file you want to search and replace in. You can use either a relative path in the workspace or an absolute path. If an absolute path is provided, it will be preserved as is.",
34+
},
35+
old_string: {
36+
type: "string",
37+
description:
38+
"The text to replace (must be unique within the file, and must match the file contents exactly, including all whitespace and indentation)",
39+
},
40+
new_string: {
41+
type: "string",
42+
description: "The edited text to replace the old_string (must be different from the old_string)",
43+
},
44+
},
45+
required: ["file_path", "old_string", "new_string"],
46+
additionalProperties: false,
47+
},
48+
},
49+
} satisfies OpenAI.Chat.ChatCompletionTool
50+
51+
export default search_replace

0 commit comments

Comments
 (0)