Skip to content
Draft
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
Empty file added IMPLEMENTATION_SUMMARY.md
Empty file.
98 changes: 97 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,49 @@
]
}
},
{
"name": "copilot_multiReadFile",
"toolReferenceName": "multiReadFile",
"displayName": "%copilot.tools.multiReadFile.name%",
"modelDescription": "This tool allows you to read multiple files in a single call using specific line ranges, which is more efficient than calling read_file multiple times. Use read_file for single file reads; use multiReadFile for multiple file reads (2+ files). It takes an array of file read operations with filePath, startLine, and endLine parameters. This tool is ideal when you need to: read multiple files for analysis, comparison, or understanding project structure; read specific sections from multiple files; or any scenario requiring multiple read operations with known line ranges. The tool will provide a summary of successful and failed operations.",
"inputSchema": {
"type": "object",
"properties": {
"explanation": {
"type": "string",
"description": "A brief explanation of what the multi-read operation will accomplish."
},
"files": {
"type": "array",
"description": "An array of file read operations to process sequentially. Each operation specifies a file path and line range.",
"items": {
"type": "object",
"description": "Read a specific line range from a file",
"properties": {
"filePath": {
"type": "string",
"description": "The absolute path of the file to read."
},
"startLine": {
"type": "number",
"description": "The line number to start reading from, 1-based."
},
"endLine": {
"type": "number",
"description": "The inclusive line number to end reading at, 1-based."
}
},
"required": ["filePath", "startLine", "endLine"]
},
"minItems": 1
}
},
"required": [
"explanation",
"files"
]
}
},
{
"name": "copilot_listDirectory",
"toolReferenceName": "listDirectory",
Expand Down Expand Up @@ -762,6 +805,58 @@
]
}
},
{
"name": "copilot_multiReplaceString",
"toolReferenceName": "multiReplaceString",
"displayName": "%copilot.tools.multiReplaceString.name%",
"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.",
"when": "!config.github.copilot.chat.disableReplaceTool",
"inputSchema": {
"type": "object",
"properties": {
"explanation": {
"type": "string",
"description": "A brief explanation of what the multi-replace operation will accomplish."
},
"replacements": {
"type": "array",
"description": "An array of replacement operations to apply sequentially.",
"items": {
"type": "object",
"properties": {
"explanation": {
"type": "string",
"description": "A brief explanation of this specific replacement operation."
},
"filePath": {
"type": "string",
"description": "An absolute path to the file to edit."
},
"oldString": {
"type": "string",
"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."
},
"newString": {
"type": "string",
"description": "The exact literal text to replace `oldString` with, preferably unescaped. Provide the EXACT text. Ensure the resulting code is correct and idiomatic."
}
},
"required": [
"explanation",
"filePath",
"oldString",
"newString"
]
},
"minItems": 1
}
},
"required": [
"explanation",
"replacements"
]
}
},
{
"name": "copilot_editNotebook",
"toolReferenceName": "editNotebook",
Expand Down Expand Up @@ -1052,7 +1147,8 @@
"textSearch",
"listDirectory",
"readNotebookCellOutput",
"readFile"
"readFile",
"multiReadFile"
]
},
{
Expand Down
2 changes: 2 additions & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,8 @@
"copilot.tools.createFile.name": "Create File",
"copilot.tools.insertEdit.name": "Edit File",
"copilot.tools.replaceString.name": "Replace String in File",
"copilot.tools.multiReplaceString.name": "Multi-Replace String in Files",
"copilot.tools.multiReadFile.name": "Multi-Read Files (Line Ranges)",
"copilot.tools.editNotebook.name": "Edit Notebook",
"copilot.tools.runNotebookCell.name": "Run Notebook Cell",
"copilot.tools.getNotebookCellOutput.name": "Get Notebook Cell Output",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,17 +133,13 @@ export class LanguageModelAccess extends Disposable implements IExtensionContrib
}

const baseCount = await PromptRenderer.create(this._instantiationService, endpoint, LanguageModelAccessPrompt, { noSafety: false, messages: [] }).countTokens();
let multiplierString = endpoint.multiplier !== undefined ? `${endpoint.multiplier}x` : undefined;
if (endpoint.model === AutoChatEndpoint.id) {
multiplierString = 'Variable';
}

const model: vscode.LanguageModelChatInformation = {
id: endpoint.model,
name: endpoint.name,
family: endpoint.family,
description: modelDescription,
cost: multiplierString,
cost: endpoint.multiplier !== undefined && endpoint.multiplier !== 0 ? `${endpoint.multiplier}x` : endpoint.multiplier === 0 ? localize('languageModel.costIncluded', 'Included') : undefined,
category: modelCategory,
version: endpoint.version,
maxInputTokens: endpoint.modelMaxPromptTokens - baseCount - BaseTokensPerCompletion,
Expand Down
3 changes: 3 additions & 0 deletions src/extension/intents/node/agentIntent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,13 @@ const getTools = (instaService: IInstantiationService, request: vscode.ChatReque
const allowTools: Record<string, boolean> = {};
allowTools[ToolName.EditFile] = true;
allowTools[ToolName.ReplaceString] = modelSupportsReplaceString(model) || !!(model.family.includes('gemini') && configurationService.getExperimentBasedConfig(ConfigKey.Internal.GeminiReplaceString, experimentationService));
allowTools[ToolName.MultiReplaceString] = modelSupportsReplaceString(model) || !!(model.family.includes('gemini') && configurationService.getExperimentBasedConfig(ConfigKey.Internal.GeminiReplaceString, experimentationService));
allowTools[ToolName.MultiReadFile] = true; // Multi-read is always available as it's a read-only operation
allowTools[ToolName.ApplyPatch] = modelSupportsApplyPatch(model) && !!toolsService.getTool(ToolName.ApplyPatch);

if (modelCanUseReplaceStringExclusively(model)) {
allowTools[ToolName.ReplaceString] = true;
allowTools[ToolName.MultiReplaceString] = true;
allowTools[ToolName.EditFile] = false;
}

Expand Down
1 change: 1 addition & 0 deletions src/extension/intents/node/editCodeIntent2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const getTools = (instaService: IInstantiationService, request: vscode.ChatReque

if (model.family.startsWith('claude')) {
lookForTools.add(ToolName.ReplaceString);
lookForTools.add(ToolName.MultiReplaceString);
}
lookForTools.add(ToolName.EditNotebook);
if (requestHasNotebookRefs(request, notebookService, { checkPromptAsWell: true })) {
Expand Down
1 change: 1 addition & 0 deletions src/extension/intents/node/notebookEditorIntent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const getTools = (instaService: IInstantiationService, request: vscode.ChatReque

if (model.family.startsWith('claude')) {
lookForTools.add(ToolName.ReplaceString);
lookForTools.add(ToolName.MultiReplaceString);
}

lookForTools.add(ToolName.EditNotebook);
Expand Down
20 changes: 13 additions & 7 deletions src/extension/prompts/node/agent/agentInstructions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export class DefaultAgentPrompt extends PromptElement<DefaultAgentPromptProps> {
async render(state: void, sizing: PromptSizing) {
const hasTerminalTool = !!this.props.availableTools?.find(tool => tool.name === ToolName.CoreRunInTerminal);
const hasReplaceStringTool = !!this.props.availableTools?.find(tool => tool.name === ToolName.ReplaceString);
const hasMultiReplaceStringTool = !!this.props.availableTools?.find(tool => tool.name === ToolName.MultiReplaceString);
const hasMultiReadFileTool = !!this.props.availableTools?.find(tool => tool.name === ToolName.MultiReadFile);
const hasInsertEditTool = !!this.props.availableTools?.find(tool => tool.name === ToolName.EditFile);
const hasApplyPatchTool = !!this.props.availableTools?.find(tool => tool.name === ToolName.ApplyPatch);
const hasReadFileTool = !!this.props.availableTools?.find(tool => tool.name === ToolName.ReadFile);
Expand All @@ -37,8 +39,9 @@ export class DefaultAgentPrompt extends PromptElement<DefaultAgentPromptProps> {

return <InstructionMessage>
<Tag name='instructions'>
You are a highly sophisticated automated coding agent with expert-level knowledge across many different programming languages and frameworks.<br />
You are a highly sophisticated and efficient automated coding agent with expert-level knowledge across many different programming languages and frameworks.<br />
The user will ask a question, or ask you to perform a task, and it may require lots of research to answer correctly. There is a selection of tools that let you perform actions or retrieve helpful context to answer the user's question.<br />
Do not sacrifice correctness for efficiency. However, time and action efficiency is very important as you are working for a user who is actively waiting for the final solution. If you need to do multiple independent operations, do them simultaneously instead of sequentially. Avoid repeating tasks you already performed or any redundant tasks, especially repetitive information retrieving tasks. You can safely assume no one else is touching the system you are working on.<br />
{getKeepGoingReminder(this.props.modelFamily)}
You will be given some context and attachments along with the user prompt. You can use them if they are relevant to the task, and ignore them if not.{hasReadFileTool && <> Some attachments may be summarized. You can use the {ToolName.ReadFile} tool to read more context, but only do this if the attached file is incomplete.</>}<br />
If you can infer the project type (languages, frameworks, and libraries) from the user's query or the context that you have, make sure to keep them in mind when making changes.<br />
Expand All @@ -59,6 +62,7 @@ export class DefaultAgentPrompt extends PromptElement<DefaultAgentPromptProps> {
NEVER say the name of a tool to a user. For example, instead of saying that you'll use the {ToolName.CoreRunInTerminal} tool, say "I'll run the command in a terminal".<br />
If you think running multiple tools can answer the user's question, prefer calling them in parallel whenever possible{hasCodebaseTool && <>, but do not call {ToolName.Codebase} in parallel.</>}<br />
{hasReadFileTool && <>When using the {ToolName.ReadFile} tool, prefer reading a large section over calling the {ToolName.ReadFile} tool many times in sequence. You can also think of all the pieces you may be interested in and read them in parallel. Read large enough context to ensure you get what you need.<br /></>}
{hasMultiReadFileTool && <>Prefer the {ToolName.ReadFile} tool for single file reads and the {ToolName.MultiReadFile} tool when you need to read multiple files (2+ files) with specific line ranges in a single operation. The {ToolName.MultiReadFile} tool requires filePath, startLine, and endLine for each file read operation. This is significantly more efficient than calling {ToolName.ReadFile} multiple times and should be your first choice for: analyzing multiple configuration files, comparing implementations across files, understanding project structure, reading specific sections from multiple files, or any scenario where you need multiple read operations with known line ranges.<br /></>}
{hasCodebaseTool && <>If {ToolName.Codebase} returns the full contents of the text files in the workspace, you have all the workspace context.<br /></>}
{hasFindTextTool && <>You can use the {ToolName.FindTextInFiles} to get an overview of a file by searching for a string within that one file, instead of using {ToolName.ReadFile} many times.<br /></>}
{hasCodebaseTool && <>If you don't know exactly the string or filename pattern you're looking for, use {ToolName.Codebase} to do a semantic search across the workspace.<br /></>}
Expand All @@ -75,18 +79,20 @@ export class DefaultAgentPrompt extends PromptElement<DefaultAgentPromptProps> {
{hasReplaceStringTool ?
<>
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 />
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 />
{hasMultiReplaceStringTool && <>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 /></>}
Use the {ToolName.ReplaceString} tool for single string replacements, paying attention to context to ensure your replacement is unique.<br />
Use the {ToolName.EditFile} tool to insert code into a file ONLY if {ToolName.ReplaceString} has failed.<br />
When editing files, group your changes by file.<br />
When editing files, group your changes by file and consider whether {hasMultiReplaceStringTool && <>{ToolName.MultiReplaceString} or </>}{ToolName.ReplaceString} would be more efficient.<br />
NEVER show the changes to the user, just call the tool, and the edits will be applied and shown to the user.<br />
NEVER print a codeblock that represents a change to a file, use {ToolName.ReplaceString} or {ToolName.EditFile} instead.<br />
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 /></> :
NEVER print a codeblock that represents a change to a file, use {ToolName.ReplaceString}, {ToolName.MultiReplaceString}, or {ToolName.EditFile} instead.<br />
For each file, give a short description of what needs to be changed, then use the {ToolName.ReplaceString}, {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 /></> :
<>
Don't try to edit an existing file without reading it first, so you can make changes properly.<br />
{hasMultiReplaceStringTool && <>Prefer the {ToolName.MultiReplaceString} tool when you need to make multiple string replacements across one or more files in a single operation. This should be your first choice for bulk edits.<br /></>}
Use the {ToolName.ReplaceString} tool to edit files. When editing files, group your changes by file.<br />
NEVER show the changes to the user, just call the tool, and the edits will be applied and shown to the user.<br />
NEVER print a codeblock that represents a change to a file, use {ToolName.ReplaceString} instead.<br />
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 />
NEVER print a codeblock that represents a change to a file, use {ToolName.ReplaceString} or {ToolName.MultiReplaceString} instead.<br />
For each file, give a short description of what needs to be changed, then use the {ToolName.ReplaceString} or {ToolName.MultiReplaceString} tool. You can use any tool multiple times in a response, and you can keep writing text after using a tool.<br />
</>}
<GenericEditingTips {...this.props} />
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 />
Expand Down
3 changes: 3 additions & 0 deletions src/extension/prompts/node/agent/agentPrompt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,9 @@ export class AgentUserMessage extends PromptElement<AgentUserMessageProps> {
{getEditingReminder(hasEditFileTool, hasReplaceStringTool, modelNeedsStrongReplaceStringHint(this.props.endpoint))}
<NotebookReminderInstructions chatVariables={this.props.chatVariables} query={this.props.request} />
</Tag>
<Tag name='reminderParallelization'>
For maximum efficiency, whenever you perform multiple independent operations, invoke corresponding tools simultaneously rather than sequentially. Avoid repeating tasks you already performed or any redundant tasks. This would greatly improve efficiency and user waiting time.<br />
</Tag>
{query && <Tag name='userRequest' priority={900} flexGrow={7}>{query + attachmentHint}</Tag>}
{this.props.enableCacheBreakpoints && <cacheBreakpoint type={CacheType} />}
</UserMessage>
Expand Down
28 changes: 28 additions & 0 deletions src/extension/prompts/node/panel/toolCalling.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,33 @@ class ToolResultElement extends PromptElement<ToolResultElementProps, void> {
}
}
this.sendToolCallTelemetry(outcome, validation);
} // Check if next_tool_prediction contains "replace_string_in_file" and prepare reminder text
let reminderText = '';
if (toolResult) {
try {
const toolArgs = JSON.parse(this.props.toolCall.arguments);
this.logService.logger.info(`Tool call arguments parsed: ${JSON.stringify(toolArgs)}`);

if (toolArgs.next_tool_prediction && Array.isArray(toolArgs.next_tool_prediction)) {
this.logService.logger.info(`Found next_tool_prediction: ${JSON.stringify(toolArgs.next_tool_prediction)}`);
const hasSomeTool = toolArgs.next_tool_prediction.some((tool: string) =>
typeof tool === 'string' && tool.includes('replace_string_in_file')
);

if (hasSomeTool) {
this.logService.logger.info('Found "replace_string_in_file" in next_tool_prediction, adding reminder');
//reminderText = '\n\n<reminder>\nIf you need to make multiple edits using replace_string_in_file tool, consider making them in parallel whenever possible.\n</reminder>';
reminderText = '';
} else {
this.logService.logger.info('No "replace_string_in_file" found in next_tool_prediction');
}
} else {
this.logService.logger.info('No next_tool_prediction found or not an array');
}
} catch (error) {
// If parsing fails, continue with original result
this.logService.logger.warn('Failed to parse tool arguments for next_tool_prediction check');
}
}

const toolResultElement = this.props.enableCacheBreakpoints ?
Expand All @@ -222,6 +249,7 @@ class ToolResultElement extends PromptElement<ToolResultElementProps, void> {
<meta value={new ToolResultMetadata(this.props.toolCall.id!, toolResult, isCancelled)} />
{...extraMetadata.map(m => <meta value={m} />)}
{toolResultElement}
{reminderText && reminderText}
{this.props.isLast && this.props.enableCacheBreakpoints && <cacheBreakpoint type={CacheType} />}
</ToolMessage>
);
Expand Down
Loading