Skip to content

Commit 539e01e

Browse files
connor4312Yevhen MohylevskyyUbuntuyemohyleyemohyle
authored
tools: add multi-replace-string tool (#593)
* add multi edit tool * comment out reminder * comment out reminder * fix multi_replace_string_in_file issue in intents * add multi_replace_string_in_file related instructions * add more instructions * remove next tool prediction parsing * remove next_tool_prediction from toolSchemaNormalizer.ts * add initial user reminder * add hasMultiReplaceString * add hasMultiReplaceString * add references to MultiReplaceStringTool * Delete test_implementation.js * Delete test_dummy_parameter.js * Delete test_required_dummy.js * Delete verify_implementation.js * Delete src/extension/tools/test/common/addNextToolPredictionParameter.spec.ts * Fix formatting of multi-replace string reminder * tidy up and exp * eng: refactor multi and single edit tools --------- Co-authored-by: Yevhen Mohylevskyy <[email protected]> Co-authored-by: Ubuntu <yevhen@ubuntu22-vm.1kpbk2tnedsetibfv2d2keui5d.xx.internal.cloudapp.net> Co-authored-by: yemohyleyemohyle <[email protected]>
1 parent b46c958 commit 539e01e

20 files changed

+835
-382
lines changed

package.json

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,58 @@
740740
]
741741
}
742742
},
743+
{
744+
"name": "copilot_multiReplaceString",
745+
"toolReferenceName": "multiReplaceString",
746+
"displayName": "%copilot.tools.multiReplaceString.name%",
747+
"modelDescription": "This tool allows you to apply multiple replace_string_in_file operations in a single call, which is more efficient than calling replace_string_in_file multiple times. It takes an array of replacement operations and applies them sequentially. Each replacement operation has the same parameters as replace_string_in_file: filePath, oldString, newString, and explanation. This tool is ideal when you need to make multiple edits across different files or multiple edits in the same file. The tool will provide a summary of successful and failed operations.",
748+
"when": "!config.github.copilot.chat.disableReplaceTool",
749+
"inputSchema": {
750+
"type": "object",
751+
"properties": {
752+
"explanation": {
753+
"type": "string",
754+
"description": "A brief explanation of what the multi-replace operation will accomplish."
755+
},
756+
"replacements": {
757+
"type": "array",
758+
"description": "An array of replacement operations to apply sequentially.",
759+
"items": {
760+
"type": "object",
761+
"properties": {
762+
"explanation": {
763+
"type": "string",
764+
"description": "A brief explanation of this specific replacement operation."
765+
},
766+
"filePath": {
767+
"type": "string",
768+
"description": "An absolute path to the file to edit."
769+
},
770+
"oldString": {
771+
"type": "string",
772+
"description": "The exact literal text to replace, preferably unescaped. Include at least 3 lines of context BEFORE and AFTER the target text, matching whitespace and indentation precisely. If this string is not the exact literal text or does not match exactly, this replacement will fail."
773+
},
774+
"newString": {
775+
"type": "string",
776+
"description": "The exact literal text to replace `oldString` with, preferably unescaped. Provide the EXACT text. Ensure the resulting code is correct and idiomatic."
777+
}
778+
},
779+
"required": [
780+
"explanation",
781+
"filePath",
782+
"oldString",
783+
"newString"
784+
]
785+
},
786+
"minItems": 1
787+
}
788+
},
789+
"required": [
790+
"explanation",
791+
"replacements"
792+
]
793+
}
794+
},
743795
{
744796
"name": "copilot_editNotebook",
745797
"toolReferenceName": "editNotebook",

package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@
271271
"copilot.tools.createFile.name": "Create File",
272272
"copilot.tools.insertEdit.name": "Edit File",
273273
"copilot.tools.replaceString.name": "Replace String in File",
274+
"copilot.tools.multiReplaceString.name": "Multi-Replace String in Files",
274275
"copilot.tools.editNotebook.name": "Edit Notebook",
275276
"copilot.tools.runNotebookCell.name": "Run Notebook Cell",
276277
"copilot.tools.getNotebookCellOutput.name": "Get Notebook Cell Output",

src/extension/intents/node/agentIntent.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,20 @@ const getTools = (instaService: IInstantiationService, request: vscode.ChatReque
6161

6262
const allowTools: Record<string, boolean> = {};
6363
allowTools[ToolName.EditFile] = true;
64-
allowTools[ToolName.ReplaceString] = modelSupportsReplaceString(model) || !!(model.family.includes('gemini') && configurationService.getExperimentBasedConfig(ConfigKey.Internal.GeminiReplaceString, experimentationService));
64+
allowTools[ToolName.ReplaceString] = modelSupportsReplaceString(model);
6565
allowTools[ToolName.ApplyPatch] = await modelSupportsApplyPatch(model) && !!toolsService.getTool(ToolName.ApplyPatch);
6666

6767
if (modelCanUseReplaceStringExclusively(model)) {
6868
allowTools[ToolName.ReplaceString] = true;
6969
allowTools[ToolName.EditFile] = false;
7070
}
7171

72+
if (allowTools[ToolName.ReplaceString]) {
73+
if (configurationService.getExperimentBasedConfig(ConfigKey.Internal.MultiReplaceString, experimentationService)) {
74+
allowTools[ToolName.MultiReplaceString] = true;
75+
}
76+
}
77+
7278
allowTools[ToolName.RunTests] = await testService.hasAnyTests();
7379
allowTools[ToolName.CoreRunTask] = !!(configurationService.getConfig(ConfigKey.AgentCanRunTasks) && tasksService.getTasks().length);
7480

src/extension/intents/node/editCodeIntent2.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import type * as vscode from 'vscode';
77
import { ChatLocation } from '../../../platform/chat/common/commonTypes';
88
import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';
9+
import { modelSupportsReplaceString } from '../../../platform/endpoint/common/chatModelCapabilities';
910
import { IEndpointProvider } from '../../../platform/endpoint/common/endpointProvider';
1011
import { IEnvService } from '../../../platform/env/common/envService';
1112
import { ILogService } from '../../../platform/log/common/logService';
@@ -40,14 +41,18 @@ const getTools = (instaService: IInstantiationService, request: vscode.ChatReque
4041
const experimentalService = accessor.get<IExperimentationService>(IExperimentationService);
4142
const model = await endpointProvider.getChatEndpoint(request);
4243
const lookForTools = new Set<string>([ToolName.EditFile]);
44+
const experimentationService = accessor.get<IExperimentationService>(IExperimentationService);
4345

4446

4547
if (configurationService.getExperimentBasedConfig(ConfigKey.EditsCodeNewNotebookAgentEnabled, experimentalService) !== false && requestHasNotebookRefs(request, notebookService, { checkPromptAsWell: true })) {
4648
lookForTools.add(ToolName.CreateNewJupyterNotebook);
4749
}
4850

49-
if (model.family.startsWith('claude')) {
51+
if (modelSupportsReplaceString(model)) {
5052
lookForTools.add(ToolName.ReplaceString);
53+
if (configurationService.getExperimentBasedConfig(ConfigKey.Internal.MultiReplaceString, experimentationService)) {
54+
lookForTools.add(ToolName.MultiReplaceString);
55+
}
5156
}
5257
lookForTools.add(ToolName.EditNotebook);
5358
if (requestHasNotebookRefs(request, notebookService, { checkPromptAsWell: true })) {

src/extension/intents/node/notebookEditorIntent.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { IToolsService } from '../../tools/common/toolsService';
3333
import { EditCodeIntent, EditCodeIntentOptions } from './editCodeIntent';
3434
import { EditCode2IntentInvocation } from './editCodeIntent2';
3535
import { getRequestedToolCallIterationLimit } from './toolCallingLoop';
36+
import { modelSupportsReplaceString } from '../../../platform/endpoint/common/chatModelCapabilities';
3637

3738
const getTools = (instaService: IInstantiationService, request: vscode.ChatRequest): Promise<vscode.LanguageModelToolInformation[]> =>
3839
instaService.invokeFunction(async accessor => {
@@ -43,13 +44,17 @@ const getTools = (instaService: IInstantiationService, request: vscode.ChatReque
4344
const experimentalService = accessor.get<IExperimentationService>(IExperimentationService);
4445
const model = await endpointProvider.getChatEndpoint(request);
4546
const lookForTools = new Set<string>([ToolName.EditFile]);
47+
const experimentationService = accessor.get<IExperimentationService>(IExperimentationService);
4648

4749
if (configurationService.getExperimentBasedConfig(ConfigKey.EditsCodeNewNotebookAgentEnabled, experimentalService) !== false && requestHasNotebookRefs(request, notebookService, { checkPromptAsWell: true })) {
4850
lookForTools.add(ToolName.CreateNewJupyterNotebook);
4951
}
5052

51-
if (model.family.startsWith('claude')) {
53+
if (modelSupportsReplaceString(model)) {
5254
lookForTools.add(ToolName.ReplaceString);
55+
if (configurationService.getExperimentBasedConfig(ConfigKey.Internal.MultiReplaceString, experimentationService)) {
56+
lookForTools.add(ToolName.MultiReplaceString);
57+
}
5358
}
5459

5560
lookForTools.add(ToolName.EditNotebook);

src/extension/prompts/node/agent/agentInstructions.tsx

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,15 @@ export class DefaultAgentPrompt extends PromptElement<DefaultAgentPromptProps> {
129129
{tools[ToolName.ReplaceString] ?
130130
<>
131131
Before you edit an existing file, make sure you either already have it in the provided context, or read it with the {ToolName.ReadFile} tool, so that you can make proper changes.<br />
132-
Use the {ToolName.ReplaceString} tool to edit files, paying attention to context to ensure your replacement is unique. You can use this tool multiple times per file.<br />
133-
Use the {ToolName.EditFile} tool to insert code into a file ONLY if {ToolName.ReplaceString} has failed.<br />
132+
{tools[ToolName.MultiReplaceString]
133+
? <>Use the {ToolName.ReplaceString} tool for single string replacements, paying attention to context to ensure your replacement is unique. Prefer the {ToolName.MultiReplaceString} tool when you need to make multiple string replacements across one or more files in a single operation. This is significantly more efficient than calling {ToolName.ReplaceString} multiple times and should be your first choice for: fixing similar patterns across files, applying consistent formatting changes, bulk refactoring operations, or any scenario where you need to make the same type of change in multiple places.<br /></>
134+
: <>Use the {ToolName.ReplaceString} tool to edit files, paying attention to context to ensure your replacement is unique. You can use this tool multiple times per file.<br /></>}
135+
Use the {ToolName.EditFile} tool to insert code into a file ONLY if {tools[ToolName.MultiReplaceString] ? `${ToolName.MultiReplaceString}/` : ''}{ToolName.ReplaceString} has failed.<br />
134136
When editing files, group your changes by file.<br />
135137
{isGpt5 && <>Make the smallest set of edits needed and avoid reformatting or moving unrelated code. Preserve existing style and conventions, and keep imports, exports, and public APIs stable unless the task requires changes. Prefer completing all edits for a file within a single message when practical.<br /></>}
136138
NEVER show the changes to the user, just call the tool, and the edits will be applied and shown to the user.<br />
137-
NEVER print a codeblock that represents a change to a file, use {ToolName.ReplaceString} or {ToolName.EditFile} instead.<br />
138-
For each file, give a short description of what needs to be changed, then use the {ToolName.ReplaceString} or {ToolName.EditFile} tools. You can use any tool multiple times in a response, and you can keep writing text after using a tool.<br /></>
139+
NEVER print a codeblock that represents a change to a file, use {ToolName.ReplaceString}{tools[ToolName.MultiReplaceString] ? `, ${ToolName.MultiReplaceString},` : ''} or {ToolName.EditFile} instead.<br />
140+
For each file, give a short description of what needs to be changed, then use the {ToolName.ReplaceString}{tools[ToolName.MultiReplaceString] ? `, ${ToolName.MultiReplaceString},` : ''} or {ToolName.EditFile} tools. You can use any tool multiple times in a response, and you can keep writing text after using a tool.<br /></>
139141
: <>
140142
Don't try to edit an existing file without reading it first, so you can make changes properly.<br />
141143
Use the {ToolName.EditFile} tool to edit files. When editing files, group your changes by file.<br />
@@ -655,20 +657,22 @@ export class AlternateGPTPrompt extends PromptElement<DefaultAgentPromptProps> {
655657
{tools[ToolName.ReplaceString] ?
656658
<>
657659
Before you edit an existing file, make sure you either already have it in the provided context, or read it with the {ToolName.ReadFile} tool, so that you can make proper changes.<br />
658-
Use the {ToolName.ReplaceString} tool to edit files, paying attention to context to ensure your replacement is unique. You can use this tool multiple times per file.<br />
659-
Use the {ToolName.EditFile} tool to insert code into a file ONLY if {ToolName.ReplaceString} has failed.<br />
660+
{tools[ToolName.MultiReplaceString]
661+
? <>Use the {ToolName.ReplaceString} tool for single string replacements, paying attention to context to ensure your replacement is unique. Prefer the {ToolName.MultiReplaceString} tool when you need to make multiple string replacements across one or more files in a single operation. This is significantly more efficient than calling {ToolName.ReplaceString} multiple times and should be your first choice for: fixing similar patterns across files, applying consistent formatting changes, bulk refactoring operations, or any scenario where you need to make the same type of change in multiple places.<br /></>
662+
: <>Use the {ToolName.ReplaceString} tool to edit files, paying attention to context to ensure your replacement is unique. You can use this tool multiple times per file.<br /></>}
663+
Use the {ToolName.EditFile} tool to insert code into a file ONLY if {tools[ToolName.MultiReplaceString] ? `${ToolName.MultiReplaceString}/` : ''}{ToolName.ReplaceString} has failed.<br />
660664
When editing files, group your changes by file.<br />
661665
{isGpt5 && <>Make the smallest set of edits needed and avoid reformatting or moving unrelated code. Preserve existing style and conventions, and keep imports, exports, and public APIs stable unless the task requires changes. Prefer completing all edits for a file within a single message when practical.<br /></>}
662666
NEVER show the changes to the user, just call the tool, and the edits will be applied and shown to the user.<br />
663-
NEVER print a codeblock that represents a change to a file, use {ToolName.ReplaceString} or {ToolName.EditFile} instead.<br />
664-
For each file, give a short description of what needs to be changed, then use the {ToolName.ReplaceString} or {ToolName.EditFile} tools. You can use any tool multiple times in a response, and you can keep writing text after using a tool.<br /></> :
667+
NEVER print a codeblock that represents a change to a file, use {ToolName.ReplaceString}{tools[ToolName.MultiReplaceString] ? `, ${ToolName.MultiReplaceString},` : ''} or {ToolName.EditFile} instead.<br />
668+
For each file, give a short description of what needs to be changed, then use the {ToolName.ReplaceString}{tools[ToolName.MultiReplaceString] ? `, ${ToolName.MultiReplaceString},` : ''} or {ToolName.EditFile} tools. You can use any tool multiple times in a response, and you can keep writing text after using a tool.<br /></> :
665669
<>
666670
Don't try to edit an existing file without reading it first, so you can make changes properly.<br />
667-
Use the {ToolName.ReplaceString} tool to edit files. When editing files, group your changes by file.<br />
671+
Use the {ToolName.EditFile} tool to edit files. When editing files, group your changes by file.<br />
668672
{isGpt5 && <>Make the smallest set of edits needed and avoid reformatting or moving unrelated code. Preserve existing style and conventions, and keep imports, exports, and public APIs stable unless the task requires changes. Prefer completing all edits for a file within a single message when practical.<br /></>}
669673
NEVER show the changes to the user, just call the tool, and the edits will be applied and shown to the user.<br />
670-
NEVER print a codeblock that represents a change to a file, use {ToolName.ReplaceString} instead.<br />
671-
For each file, give a short description of what needs to be changed, then use the {ToolName.ReplaceString} tool. You can use any tool multiple times in a response, and you can keep writing text after using a tool.<br />
674+
NEVER print a codeblock that represents a change to a file, use {ToolName.EditFile} instead.<br />
675+
For each file, give a short description of what needs to be changed, then use the {ToolName.EditFile} tool. You can use any tool multiple times in a response, and you can keep writing text after using a tool.<br />
672676
</>}
673677
<GenericEditingTips {...this.props} />
674678
The {ToolName.EditFile} tool is very smart and can understand how to apply your edits to the user's files, you just need to provide minimal hints.<br />
@@ -857,6 +861,7 @@ export class SweBenchAgentPrompt extends PromptElement<DefaultAgentPromptProps>
857861
</Tag>
858862
{!!tools[ToolName.ReplaceString] && <Tag name='ReplaceStringToolInstructions'>
859863
{ToolName.ReplaceString} tool is a tool for editing files. For moving or renaming files, you should generally use the {ToolName.CoreRunInTerminal} with the 'mv' command instead. For larger edits, split it into small edits and call the edit tool multiple times to finish the whole edit carefully.<br />
864+
{tools[ToolName.MultiReplaceString] && <>Use the {ToolName.MultiReplaceString} tool when you need to make multiple string replacements across one or more files in a single operation.<br /></>}
860865
Before using {ToolName.ReplaceString} tool, you must use {ToolName.ReadFile} tool to understand the file's contents and context you want to edit<br />
861866
To make a file edit, provide the following:<br />
862867
1. filePath: The absolute path to the file to modify (must be absolute, not relative)<br />

0 commit comments

Comments
 (0)