Skip to content

Commit b25d4af

Browse files
authored
Merge pull request #7267 from continuedev/dallin/search_and_replace_makeover
feat: multi edit and single search and replace tools
2 parents c3c432d + fc93a2e commit b25d4af

37 files changed

+3286
-163
lines changed

core/config/load.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ import {
5858
import { localPathToUri } from "../util/pathToUri";
5959

6060
import CustomContextProviderClass from "../context/providers/CustomContextProvider";
61-
import { getToolsForIde } from "../tools";
61+
import { getBaseToolDefinitions } from "../tools";
6262
import { resolveRelativePathInDir } from "../util/ideUtils";
6363
import { getWorkspaceRcConfigs } from "./json/loadRcConfigs";
6464
import { loadConfigContextProviders } from "./loadContextProviders";
@@ -498,7 +498,7 @@ async function intermediateToFinalConfig({
498498
const continueConfig: ContinueConfig = {
499499
...config,
500500
contextProviders,
501-
tools: await getToolsForIde(ide),
501+
tools: getBaseToolDefinitions(),
502502
mcpServerStatuses: [],
503503
slashCommands: [],
504504
modelsByRole: {

core/config/profile/doLoadConfig.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,8 @@ export default async function doLoadConfig(options: {
282282
enableExperimentalTools:
283283
newConfig.experimental?.enableExperimentalTools ?? false,
284284
isSignedIn,
285+
isRemote: await ide.isWorkspaceRemote(),
286+
modelName: newConfig.selectedModelByRole.chat?.model,
285287
}),
286288
);
287289

core/config/yaml/loadYaml.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { modifyAnyConfigWithSharedConfig } from "../sharedConfig";
2525
import { convertPromptBlockToSlashCommand } from "../../commands/slash/promptBlockSlashCommand";
2626
import { slashCommandFromPromptFile } from "../../commands/slash/promptFileSlashCommand";
2727
import { getControlPlaneEnvSync } from "../../control-plane/env";
28-
import { getToolsForIde } from "../../tools";
28+
import { getBaseToolDefinitions } from "../../tools";
2929
import { getCleanUriPath } from "../../util/uri";
3030
import { loadConfigContextProviders } from "../loadContextProviders";
3131
import { getAllDotContinueDefinitionFiles } from "../loadLocalAssistants";
@@ -168,7 +168,7 @@ async function configYamlToContinueConfig(options: {
168168

169169
const continueConfig: ContinueConfig = {
170170
slashCommands: [],
171-
tools: await getToolsForIde(ide),
171+
tools: getBaseToolDefinitions(),
172172
mcpServerStatuses: [],
173173
contextProviders: [],
174174
modelsByRole: {

core/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,6 +1112,8 @@ export interface ConfigDependentToolParams {
11121112
rules: RuleWithSource[];
11131113
enableExperimentalTools: boolean;
11141114
isSignedIn: boolean;
1115+
isRemote: boolean;
1116+
modelName: string | undefined;
11151117
}
11161118

11171119
export type GetTool = (params: ConfigDependentToolParams) => Tool;

core/llm/toolSupport.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,7 @@ export function isRecommendedAgentModel(modelName: string): boolean {
375375
[/deepseek/, /r1|reasoner/],
376376
[/gemini/, /2\.5/, /pro/],
377377
[/gpt/, /4/],
378+
[/gpt-5/],
378379
[/claude/, /sonnet/, /3\.5|3\.7|3-5|3-7|-4/],
379380
[/claude/, /opus/, /-4/],
380381
];

core/tools/builtIn.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ export enum BuiltInToolNames {
22
ReadFile = "read_file",
33
EditExistingFile = "edit_existing_file",
44
SearchAndReplaceInFile = "search_and_replace_in_file",
5+
SingleFindAndReplace = "single_find_and_replace",
6+
MultiEdit = "multi_edit",
57
ReadCurrentlyOpenFile = "read_currently_open_file",
68
CreateNewFile = "create_new_file",
79
RunTerminalCommand = "run_terminal_command",
@@ -25,4 +27,6 @@ export const BUILT_IN_GROUP_NAME = "Built-In";
2527
export const CLIENT_TOOLS_IMPLS = [
2628
BuiltInToolNames.EditExistingFile,
2729
BuiltInToolNames.SearchAndReplaceInFile,
30+
BuiltInToolNames.SingleFindAndReplace,
31+
BuiltInToolNames.MultiEdit,
2832
];

core/tools/definitions/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ export { fetchUrlContentTool } from "./fetchUrlContent";
66
export { globSearchTool } from "./globSearch";
77
export { grepSearchTool } from "./grepSearch";
88
export { lsTool } from "./ls";
9+
export { multiEditTool } from "./multiEdit";
910
export { readCurrentlyOpenFileTool } from "./readCurrentlyOpenFile";
1011
export { readFileTool } from "./readFile";
1112
export { requestRuleTool } from "./requestRule";
1213
export { runTerminalCommandTool } from "./runTerminalCommand";
1314
export { searchAndReplaceInFileTool } from "./searchAndReplaceInFile";
1415
export { searchWebTool } from "./searchWeb";
16+
export { singleFindAndReplaceTool } from "./singleFindAndReplace";
1517
export { viewDiffTool } from "./viewDiff";
1618
export { viewRepoMapTool } from "./viewRepoMap";
1719
export { viewSubdirectoryTool } from "./viewSubdirectory";

core/tools/definitions/multiEdit.ts

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { Tool } from "../..";
2+
import { BUILT_IN_GROUP_NAME, BuiltInToolNames } from "../builtIn";
3+
4+
export interface EditOperation {
5+
old_string: string;
6+
new_string: string;
7+
replace_all?: boolean;
8+
}
9+
10+
export interface MultiEditArgs {
11+
filepath: string;
12+
edits: EditOperation[];
13+
}
14+
15+
export const multiEditTool: Tool = {
16+
type: "function",
17+
displayTitle: "Multi Edit",
18+
wouldLikeTo: "edit {{{ filepath }}}",
19+
isCurrently: "editing {{{ filepath }}}",
20+
hasAlready: "edited {{{ filepath }}}",
21+
group: BUILT_IN_GROUP_NAME,
22+
readonly: false,
23+
isInstant: false,
24+
function: {
25+
name: BuiltInToolNames.MultiEdit,
26+
description: `This is a tool for making multiple edits to a single file in one operation. It
27+
is built on top of the single find and replace tool and allows you to perform multiple
28+
find-and-replace operations efficiently. Prefer this tool over the single find and replace tool
29+
when you need to make multiple edits to the same file.
30+
31+
Before using this tool:
32+
33+
1. Use the read_file tool to understand the file's contents and context
34+
2. Verify the directory path is correct
35+
36+
To make multiple file edits, provide the following:
37+
38+
1. filepath: The path to the file to modify (relative to the root of the workspace)
39+
2. edits: An array of edit operations to perform, where each edit contains:
40+
- old_string: The text to replace (must match the file contents exactly, including all whitespace and indentation)
41+
- new_string: The edited text to replace the old_string
42+
- replace_all: Replace all occurrences of old_string. This parameter is optional and defaults to false.
43+
44+
IMPORTANT:
45+
- All edits are applied in sequence, in the order they are provided
46+
- Each edit operates on the result of the previous edit
47+
- All edits must be valid for the operation to succeed - if any edit fails,
48+
none will be applied
49+
- This tool is ideal when you need to make several changes to different parts
50+
of the same file
51+
52+
CRITICAL REQUIREMENTS:
53+
54+
1. All edits follow the same requirements as the single find and replace tool
55+
2. The edits are atomic - either all succeed or none are applied
56+
3. Plan your edits carefully to avoid conflicts between sequential operations
57+
58+
WARNING:
59+
60+
- The tool will fail if edits.old_string doesn't match the file contents
61+
exactly (including whitespace)
62+
- The tool will fail if edits.old_string and edits.new_string are the same
63+
- Since edits are applied in sequence, ensure that earlier edits don't affect
64+
the text that later edits are trying to find
65+
66+
When making edits:
67+
68+
- Ensure all edits result in idiomatic, correct code
69+
- Do not leave the code in a broken state
70+
- Only use emojis if the user explicitly requests it. Avoid adding emojis to
71+
files unless asked.
72+
- Use replace_all for replacing and renaming strings across the file. This
73+
parameter is useful if you want to rename a variable for instance.
74+
75+
If you want to create a new file, use:
76+
- A new file path
77+
- First edit: empty old_string and the new file's contents as new_string
78+
- Subsequent edits are not allowed - there is no need since you are creating`,
79+
parameters: {
80+
type: "object",
81+
required: ["filepath", "edits"],
82+
properties: {
83+
filepath: {
84+
type: "string",
85+
description:
86+
"The path to the file to modify, relative to the root of the workspace",
87+
},
88+
edits: {
89+
type: "array",
90+
description:
91+
"Array of edit operations to perform sequentially on the file",
92+
items: {
93+
type: "object",
94+
required: ["old_string", "new_string"],
95+
properties: {
96+
old_string: {
97+
type: "string",
98+
description: "The text to replace",
99+
},
100+
new_string: {
101+
type: "string",
102+
description: "The text to replace it with",
103+
},
104+
replace_all: {
105+
type: "boolean",
106+
description:
107+
"Replace all occurrences of old_string (default false)",
108+
},
109+
},
110+
},
111+
},
112+
},
113+
},
114+
},
115+
systemMessageDescription: {
116+
prefix: `To make multiple edits to a single file, use the ${BuiltInToolNames.MultiEdit} tool with a filepath (relative to the root of the workspace) and an array of edit operations.
117+
118+
For example, you could respond with:`,
119+
exampleArgs: [
120+
["filepath", "path/to/file.ts"],
121+
[
122+
"edits",
123+
`[
124+
{ "old_string": "const oldVar = 'value'", "new_string": "const newVar = 'updated'" },
125+
{ "old_string": "oldFunction()", "new_string": "newFunction()", "replace_all": true }
126+
]`,
127+
],
128+
],
129+
},
130+
defaultToolPolicy: "allowedWithPermission",
131+
};
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { Tool } from "../..";
2+
import { BUILT_IN_GROUP_NAME, BuiltInToolNames } from "../builtIn";
3+
4+
export interface SingleFindAndReplaceArgs {
5+
filepath: string;
6+
old_string: string;
7+
new_string: string;
8+
replace_all?: boolean;
9+
}
10+
11+
export const singleFindAndReplaceTool: Tool = {
12+
type: "function",
13+
displayTitle: "Find and Replace",
14+
wouldLikeTo: "find and replace in {{{ filepath }}}",
15+
isCurrently: "finding and replacing in {{{ filepath }}}",
16+
hasAlready: "found and replaced in {{{ filepath }}}",
17+
group: BUILT_IN_GROUP_NAME,
18+
readonly: false,
19+
isInstant: false,
20+
function: {
21+
name: BuiltInToolNames.SingleFindAndReplace,
22+
description: `Performs exact string replacements in files.
23+
24+
Usage:
25+
26+
- You must use your \`read_file\` tool at least once in the conversation before
27+
editing. This tool will error if you attempt an edit without reading the file.
28+
29+
- When editing text from read_file tool output, ensure you preserve the exact
30+
indentation (tabs/spaces) as it appears AFTER the line number prefix. The line
31+
number prefix format is: spaces + line number + tab. Everything after that tab
32+
is the actual file content to match. Never include any part of the line number
33+
prefix in the old_string or new_string.
34+
35+
- ALWAYS prefer editing existing files in the codebase. NEVER write new files
36+
unless explicitly required.
37+
38+
- Only use emojis if the user explicitly requests it. Avoid adding emojis to
39+
files unless asked.
40+
41+
- The edit will FAIL if \`old_string\` is not unique in the file. Either provide
42+
a larger string with more surrounding context to make it unique or use
43+
\`replace_all\` to change every instance of \`old_string\`.
44+
45+
- Use \`replace_all\` for replacing and renaming strings across the file. This
46+
parameter is useful if you want to rename a variable for instance.`,
47+
parameters: {
48+
type: "object",
49+
required: ["filepath", "old_string", "new_string"],
50+
properties: {
51+
filepath: {
52+
type: "string",
53+
description:
54+
"The path to the file to modify, relative to the root of the workspace",
55+
},
56+
old_string: {
57+
type: "string",
58+
description: "The text to replace",
59+
},
60+
new_string: {
61+
type: "string",
62+
description:
63+
"The text to replace it with (must be different from old_string)",
64+
},
65+
replace_all: {
66+
type: "boolean",
67+
description: "Replace all occurrences of old_string (default false)",
68+
},
69+
},
70+
},
71+
},
72+
systemMessageDescription: {
73+
prefix: `To perform exact string replacements in files, use the ${BuiltInToolNames.SingleFindAndReplace} tool with a filepath (relative to the root of the workspace) and the strings to find and replace.
74+
75+
For example, you could respond with:`,
76+
exampleArgs: [
77+
["filepath", "path/to/file.ts"],
78+
["old_string", "const oldVariable = 'value'"],
79+
["new_string", "const newVariable = 'updated'"],
80+
["replace_all", "false"],
81+
],
82+
},
83+
defaultToolPolicy: "allowedWithPermission",
84+
};

core/tools/definitions/toolDefinitions.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ describe("Tool Definitions", () => {
88
rules: [],
99
enableExperimentalTools: false,
1010
isSignedIn: false,
11+
isRemote: false,
12+
modelName: "a model",
1113
};
1214

1315
// Helper function to get the actual tool object

0 commit comments

Comments
 (0)