Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 2 additions & 2 deletions core/config/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ import {
} from "../util/paths";
import { localPathToUri } from "../util/pathToUri";

import { getToolsForIde } from "../tools";
import { getBaseToolDefinitions } from "../tools";
import { resolveRelativePathInDir } from "../util/ideUtils";
import { getWorkspaceRcConfigs } from "./json/loadRcConfigs";
import { modifyAnyConfigWithSharedConfig } from "./sharedConfig";
Expand Down Expand Up @@ -526,7 +526,7 @@ async function intermediateToFinalConfig({
const continueConfig: ContinueConfig = {
...config,
contextProviders,
tools: await getToolsForIde(ide),
tools: getBaseToolDefinitions(),
mcpServerStatuses: [],
slashCommands: [],
modelsByRole: {
Expand Down
2 changes: 2 additions & 0 deletions core/config/profile/doLoadConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,8 @@ export default async function doLoadConfig(options: {
enableExperimentalTools:
newConfig.experimental?.enableExperimentalTools ?? false,
isSignedIn,
isRemote: await ide.isWorkspaceRemote(),
modelName: newConfig.selectedModelByRole.chat?.model,
}),
);

Expand Down
4 changes: 2 additions & 2 deletions core/config/yaml/loadYaml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { modifyAnyConfigWithSharedConfig } from "../sharedConfig";
import { convertPromptBlockToSlashCommand } from "../../commands/slash/promptBlockSlashCommand";
import { slashCommandFromPromptFile } from "../../commands/slash/promptFileSlashCommand";
import { getControlPlaneEnvSync } from "../../control-plane/env";
import { getToolsForIde } from "../../tools";
import { getBaseToolDefinitions } from "../../tools";
import { getCleanUriPath } from "../../util/uri";
import { getAllDotContinueDefinitionFiles } from "../loadLocalAssistants";
import { unrollLocalYamlBlocks } from "./loadLocalYamlBlocks";
Expand Down Expand Up @@ -177,7 +177,7 @@ async function configYamlToContinueConfig(options: {

const continueConfig: ContinueConfig = {
slashCommands: [],
tools: await getToolsForIde(ide),
tools: getBaseToolDefinitions(),
mcpServerStatuses: [],
contextProviders: [],
modelsByRole: {
Expand Down
2 changes: 2 additions & 0 deletions core/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1112,6 +1112,8 @@ export interface ConfigDependentToolParams {
rules: RuleWithSource[];
enableExperimentalTools: boolean;
isSignedIn: boolean;
isRemote: boolean;
modelName: string | undefined;
}

export type GetTool = (params: ConfigDependentToolParams) => Tool;
Expand Down
4 changes: 4 additions & 0 deletions core/tools/builtIn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ export enum BuiltInToolNames {
ReadFile = "read_file",
EditExistingFile = "edit_existing_file",
SearchAndReplaceInFile = "search_and_replace_in_file",
SingleFindAndReplace = "single_find_and_replace",
MultiEdit = "multi_edit",
ReadCurrentlyOpenFile = "read_currently_open_file",
CreateNewFile = "create_new_file",
RunTerminalCommand = "run_terminal_command",
Expand All @@ -25,4 +27,6 @@ export const BUILT_IN_GROUP_NAME = "Built-In";
export const CLIENT_TOOLS_IMPLS = [
BuiltInToolNames.EditExistingFile,
BuiltInToolNames.SearchAndReplaceInFile,
BuiltInToolNames.SingleFindAndReplace,
BuiltInToolNames.MultiEdit,
];
2 changes: 2 additions & 0 deletions core/tools/definitions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ export { fetchUrlContentTool } from "./fetchUrlContent";
export { globSearchTool } from "./globSearch";
export { grepSearchTool } from "./grepSearch";
export { lsTool } from "./ls";
export { multiEditTool } from "./multiEdit";
export { readCurrentlyOpenFileTool } from "./readCurrentlyOpenFile";
export { readFileTool } from "./readFile";
export { requestRuleTool } from "./requestRule";
export { runTerminalCommandTool } from "./runTerminalCommand";
export { searchAndReplaceInFileTool } from "./searchAndReplaceInFile";
export { searchWebTool } from "./searchWeb";
export { singleFindAndReplaceTool } from "./singleFindAndReplace";
export { viewDiffTool } from "./viewDiff";
export { viewRepoMapTool } from "./viewRepoMap";
export { viewSubdirectoryTool } from "./viewSubdirectory";
131 changes: 131 additions & 0 deletions core/tools/definitions/multiEdit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { Tool } from "../..";
import { BUILT_IN_GROUP_NAME, BuiltInToolNames } from "../builtIn";

export interface EditOperation {
old_string: string;
new_string: string;
replace_all?: boolean;
}

export interface MultiEditArgs {
filepath: string;
edits: EditOperation[];
}

export const multiEditTool: Tool = {
type: "function",
displayTitle: "Multi Edit",
wouldLikeTo: "edit {{{ filepath }}}",
isCurrently: "editing {{{ filepath }}}",
hasAlready: "edited {{{ filepath }}}",
group: BUILT_IN_GROUP_NAME,
readonly: false,
isInstant: false,
function: {
name: BuiltInToolNames.MultiEdit,
description: `This is a tool for making multiple edits to a single file in one operation. It
is built on top of the single find and replace tool and allows you to perform multiple
find-and-replace operations efficiently. Prefer this tool over the single find and replace tool
when you need to make multiple edits to the same file.
Before using this tool:
1. Use the read_file tool to understand the file's contents and context
2. Verify the directory path is correct
To make multiple file edits, provide the following:
1. filepath: The path to the file to modify (relative to the root of the workspace)
2. edits: An array of edit operations to perform, where each edit contains:
- old_string: The text to replace (must match the file contents exactly, including all whitespace and indentation)
- new_string: The edited text to replace the old_string
- replace_all: Replace all occurrences of old_string. This parameter is optional and defaults to false.
IMPORTANT:
- All edits are applied in sequence, in the order they are provided
- Each edit operates on the result of the previous edit
- All edits must be valid for the operation to succeed - if any edit fails,
none will be applied
- This tool is ideal when you need to make several changes to different parts
of the same file
CRITICAL REQUIREMENTS:
1. All edits follow the same requirements as the single find and replace tool
2. The edits are atomic - either all succeed or none are applied
3. Plan your edits carefully to avoid conflicts between sequential operations
WARNING:
- The tool will fail if edits.old_string doesn't match the file contents
exactly (including whitespace)
- The tool will fail if edits.old_string and edits.new_string are the same
- Since edits are applied in sequence, ensure that earlier edits don't affect
the text that later edits are trying to find
When making edits:
- Ensure all edits result in idiomatic, correct code
- Do not leave the code in a broken state
- Only use emojis if the user explicitly requests it. Avoid adding emojis to
files unless asked.
- Use replace_all for replacing and renaming strings across the file. This
parameter is useful if you want to rename a variable for instance.
If you want to create a new file, use:
- A new file path
- First edit: empty old_string and the new file's contents as new_string
- Subsequent edits: normal edit operations on the created content`,
parameters: {
type: "object",
required: ["filepath", "edits"],
properties: {
filepath: {
type: "string",
description:
"The path to the file to modify, relative to the root of the workspace",
},
edits: {
type: "array",
description:
"Array of edit operations to perform sequentially on the file",
items: {
type: "object",
required: ["old_string", "new_string"],
properties: {
old_string: {
type: "string",
description: "The text to replace",
},
new_string: {
type: "string",
description: "The text to replace it with",
},
replace_all: {
type: "boolean",
description:
"Replace all occurrences of old_string (default false)",
},
},
},
},
},
},
},
systemMessageDescription: {
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.
For example, you could respond with:`,
exampleArgs: [
["filepath", "path/to/file.ts"],
[
"edits",
`[
{ "old_string": "const oldVar = 'value'", "new_string": "const newVar = 'updated'" },
{ "old_string": "oldFunction()", "new_string": "newFunction()", "replace_all": true }
]`,
],
],
},
defaultToolPolicy: "allowedWithPermission",
};
84 changes: 84 additions & 0 deletions core/tools/definitions/singleFindAndReplace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Tool } from "../..";
import { BUILT_IN_GROUP_NAME, BuiltInToolNames } from "../builtIn";

export interface SingleFindAndReplaceArgs {
filepath: string;
old_string: string;
new_string: string;
replace_all?: boolean;
}

export const singleFindAndReplaceTool: Tool = {
type: "function",
displayTitle: "Find and Replace",
wouldLikeTo: "find and replace in {{{ filepath }}}",
isCurrently: "finding and replacing in {{{ filepath }}}",
hasAlready: "found and replaced in {{{ filepath }}}",
group: BUILT_IN_GROUP_NAME,
readonly: false,
isInstant: false,
function: {
name: BuiltInToolNames.SingleFindAndReplace,
description: `Performs exact string replacements in files.
Usage:
- You must use your \`read_file\` tool at least once in the conversation before
editing. This tool will error if you attempt an edit without reading the file.
- When editing text from read_file tool output, ensure you preserve the exact
indentation (tabs/spaces) as it appears AFTER the line number prefix. The line
number prefix format is: spaces + line number + tab. Everything after that tab
is the actual file content to match. Never include any part of the line number
prefix in the old_string or new_string.
- ALWAYS prefer editing existing files in the codebase. NEVER write new files
unless explicitly required.
- Only use emojis if the user explicitly requests it. Avoid adding emojis to
files unless asked.
- The edit will FAIL if \`old_string\` is not unique in the file. Either provide
a larger string with more surrounding context to make it unique or use
\`replace_all\` to change every instance of \`old_string\`.
- Use \`replace_all\` for replacing and renaming strings across the file. This
parameter is useful if you want to rename a variable for instance.`,
parameters: {
type: "object",
required: ["filepath", "old_string", "new_string"],
properties: {
filepath: {
type: "string",
description:
"The path to the file to modify, relative to the root of the workspace",
},
old_string: {
type: "string",
description: "The text to replace",
},
new_string: {
type: "string",
description:
"The text to replace it with (must be different from old_string)",
},
replace_all: {
type: "boolean",
description: "Replace all occurrences of old_string (default false)",
},
},
},
},
systemMessageDescription: {
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.
For example, you could respond with:`,
exampleArgs: [
["filepath", "path/to/file.ts"],
["old_string", "const oldVariable = 'value'"],
["new_string", "const newVariable = 'updated'"],
["replace_all", "false"],
],
},
defaultToolPolicy: "allowedWithPermission",
};
62 changes: 36 additions & 26 deletions core/tools/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import { ConfigDependentToolParams, IDE, Tool } from "..";
import { ConfigDependentToolParams, Tool } from "..";
import * as toolDefinitions from "./definitions";

// I'm writing these as functions because we've messed up 3 TIMES by pushing to const, causing duplicate tool definitions on subsequent config loads.

// missing support for remote os calls: https://github.com/microsoft/vscode/issues/252269
const getLocalOnlyToolDefinitions = () => [toolDefinitions.grepSearchTool];

const getBaseToolDefinitions = () => [
export const getBaseToolDefinitions = () => [
toolDefinitions.readFileTool,
toolDefinitions.createNewFileTool,
toolDefinitions.runTerminalCommandTool,
Expand All @@ -20,24 +16,38 @@ const getBaseToolDefinitions = () => [

export const getConfigDependentToolDefinitions = (
params: ConfigDependentToolParams,
): Tool[] => [
toolDefinitions.requestRuleTool(params),
// Search and replace is now generally available
toolDefinitions.searchAndReplaceInFileTool,
// Keep edit file tool available for models that need it
toolDefinitions.editFileTool,
// Web search is only available for signed-in users
...(params.isSignedIn ? [toolDefinitions.searchWebTool] : []),
...(params.enableExperimentalTools
? [
toolDefinitions.viewRepoMapTool,
toolDefinitions.viewSubdirectoryTool,
toolDefinitions.codebaseTool,
]
: []),
];
): Tool[] => {
const { modelName, isSignedIn, enableExperimentalTools, isRemote } = params;
const tools: Tool[] = [];

tools.push(toolDefinitions.requestRuleTool(params));

if (isSignedIn) {
// Web search is only available for signed-in users
tools.push(toolDefinitions.searchWebTool);
}

if (enableExperimentalTools) {
tools.push(
toolDefinitions.viewRepoMapTool,
toolDefinitions.viewSubdirectoryTool,
toolDefinitions.codebaseTool,
);
}

// OLD SEARCH AND REPLACE IS CURRENTLY NOT USED
// toolDefinitions.searchAndReplaceInFileTool
if (modelName?.includes("claude") || modelName?.includes("gpt-5")) {
tools.push(toolDefinitions.multiEditTool);
} else {
tools.push(toolDefinitions.singleFindAndReplaceTool);
tools.push(toolDefinitions.editFileTool);
}

// missing support for remote os calls: https://github.com/microsoft/vscode/issues/252269
if (!isRemote) {
tools.push(toolDefinitions.grepSearchTool);
}

export const getToolsForIde = async (ide: IDE): Promise<Tool[]> =>
(await ide.isWorkspaceRemote())
? getBaseToolDefinitions()
: [...getBaseToolDefinitions(), ...getLocalOnlyToolDefinitions()];
return tools;
};
Loading
Loading