Skip to content
Merged
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

Large diffs are not rendered by default.

176 changes: 176 additions & 0 deletions packages/core/src/tools/definitions/coreTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export const LS_TOOL_NAME = 'list_directory';
export const READ_FILE_TOOL_NAME = 'read_file';
export const SHELL_TOOL_NAME = 'run_shell_command';
export const WRITE_FILE_TOOL_NAME = 'write_file';
export const EDIT_TOOL_NAME = 'replace';
export const WEB_SEARCH_TOOL_NAME = 'google_web_search';

// ============================================================================
// READ_FILE TOOL
Expand Down Expand Up @@ -126,6 +128,180 @@ export const GREP_DEFINITION: ToolDefinition = {
},
};

// ============================================================================
// RIP_GREP TOOL
// ============================================================================

export const RIP_GREP_DEFINITION: ToolDefinition = {
base: {
name: GREP_TOOL_NAME,
description:
'Searches for a regular expression pattern within file contents.',
parametersJsonSchema: {
type: 'object',
properties: {
pattern: {
description: `The pattern to search for. By default, treated as a Rust-flavored regular expression. Use '\\b' for precise symbol matching (e.g., '\\bMatchMe\\b').`,
type: 'string',
},
dir_path: {
description:
"Directory or file to search. Directories are searched recursively. Relative paths are resolved against current working directory. Defaults to current working directory ('.') if omitted.",
type: 'string',
},
include: {
description:
Comment on lines +151 to +153

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The grep_search tool (specifically the RipGrepTool implementation) includes a no_ignore parameter that, when set to true, causes the tool to bypass all file ignore rules (such as .gitignore and .geminiignore). This allows the LLM to search and read potentially sensitive files that are intended to be hidden, such as .env files containing secrets, private keys, or configuration files. Since grep_search is a "read" tool (Kind.Search), it may not require user confirmation in many configurations, allowing an LLM to exfiltrate sensitive data without the user's knowledge if it is tricked via prompt injection.

Consider removing the no_ignore parameter from the tool's public schema to prevent the LLM from bypassing ignore rules. If the functionality is required for users, ensure that its use by the LLM always triggers a high-visibility warning or requires explicit user confirmation.

"Glob pattern to filter files (e.g., '*.ts', 'src/**'). Recommended for large repositories to reduce noise. Defaults to all files if omitted.",
type: 'string',
},
exclude_pattern: {
description:
'Optional: A regular expression pattern to exclude from the search results. If a line matches both the pattern and the exclude_pattern, it will be omitted.',
type: 'string',
},
names_only: {
description:
'Optional: If true, only the file paths of the matches will be returned, without the line content or line numbers. This is useful for gathering a list of files.',
type: 'boolean',
},
case_sensitive: {
description:
'If true, search is case-sensitive. Defaults to false (ignore case) if omitted.',
type: 'boolean',
},
fixed_strings: {
description:
'If true, treats the `pattern` as a literal string instead of a regular expression. Defaults to false (basic regex) if omitted.',
type: 'boolean',
},
context: {
description:
'Show this many lines of context around each match (equivalent to grep -C). Defaults to 0 if omitted.',
type: 'integer',
},
after: {
description:
'Show this many lines after each match (equivalent to grep -A). Defaults to 0 if omitted.',
type: 'integer',
minimum: 0,
},
before: {
description:
'Show this many lines before each match (equivalent to grep -B). Defaults to 0 if omitted.',
type: 'integer',
minimum: 0,
},
no_ignore: {
description:
'If true, searches all files including those usually ignored (like in .gitignore, build/, dist/, etc). Defaults to false if omitted.',
type: 'boolean',
},
max_matches_per_file: {
description:
'Optional: Maximum number of matches to return per file. Use this to prevent being overwhelmed by repetitive matches in large files.',
type: 'integer',
minimum: 1,
},
total_max_matches: {
description:
'Optional: Maximum number of total matches to return. Use this to limit the overall size of the response. Defaults to 100 if omitted.',
type: 'integer',
minimum: 1,
},
},
required: ['pattern'],
},
},
};

// ============================================================================
// WEB_SEARCH TOOL
// ============================================================================

export const WEB_SEARCH_DEFINITION: ToolDefinition = {
base: {
name: WEB_SEARCH_TOOL_NAME,
description:
'Performs a web search using Google Search (via the Gemini API) and returns the results. This tool is useful for finding information on the internet based on a query.',
parametersJsonSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'The search query to find information on the web.',
},
},
required: ['query'],
},
},
};

// ============================================================================
// EDIT TOOL
// ============================================================================

export const EDIT_DEFINITION: ToolDefinition = {
base: {
name: EDIT_TOOL_NAME,
description: `Replaces text within a file. By default, replaces a single occurrence, but can replace multiple occurrences when \`expected_replacements\` is specified. This tool requires providing significant context around the change to ensure precise targeting. Always use the ${READ_FILE_TOOL_NAME} tool to examine the file's current content before attempting a text replacement.

The user has the ability to modify the \`new_string\` content. If modified, this will be stated in the response.

Expectation for required parameters:
1. \`old_string\` MUST be the exact literal text to replace (including all whitespace, indentation, newlines, and surrounding code etc.).
2. \`new_string\` MUST be the exact literal text to replace \`old_string\` with (also including all whitespace, indentation, newlines, and surrounding code etc.). Ensure the resulting code is correct and idiomatic and that \`old_string\` and \`new_string\` are different.
3. \`instruction\` is the detailed instruction of what needs to be changed. It is important to Make it specific and detailed so developers or large language models can understand what needs to be changed and perform the changes on their own if necessary.
4. NEVER escape \`old_string\` or \`new_string\`, that would break the exact literal text requirement.
**Important:** If ANY of the above are not satisfied, the tool will fail. CRITICAL for \`old_string\`: Must uniquely identify the single instance to change. Include at least 3 lines of context BEFORE and AFTER the target text, matching whitespace and indentation precisely. If this string matches multiple locations, or does not match exactly, the tool will fail.
5. Prefer to break down complex and long changes into multiple smaller atomic calls to this tool. Always check the content of the file after changes or not finding a string to match.
**Multiple replacements:** Set \`expected_replacements\` to the number of occurrences you want to replace. The tool will replace ALL occurrences that match \`old_string\` exactly. Ensure the number of replacements matches your expectation.`,
parametersJsonSchema: {
type: 'object',
properties: {
file_path: {
description: 'The path to the file to modify.',
type: 'string',
},
instruction: {
description: `A clear, semantic instruction for the code change, acting as a high-quality prompt for an expert LLM assistant. It must be self-contained and explain the goal of the change.

A good instruction should concisely answer:
1. WHY is the change needed? (e.g., "To fix a bug where users can be null...")
2. WHERE should the change happen? (e.g., "...in the 'renderUserProfile' function...")
3. WHAT is the high-level change? (e.g., "...add a null check for the 'user' object...")
4. WHAT is the desired outcome? (e.g., "...so that it displays a loading spinner instead of crashing.")

**GOOD Example:** "In the 'calculateTotal' function, correct the sales tax calculation by updating the 'taxRate' constant from 0.05 to 0.075 to reflect the new regional tax laws."

**BAD Examples:**
- "Change the text." (Too vague)
- "Fix the bug." (Doesn't explain the bug or the fix)
- "Replace the line with this new line." (Brittle, just repeats the other parameters)
`,
type: 'string',
},
old_string: {
description:
'The exact literal text to replace, preferably unescaped. For single replacements (default), 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 (i.e. you escaped it) or does not match exactly, the tool will fail.',
type: 'string',
},
new_string: {
description:
'The exact literal text to replace `old_string` with, preferably unescaped. Provide the EXACT text. Ensure the resulting code is correct and idiomatic.',
type: 'string',
},
expected_replacements: {
type: 'number',
description:
'Number of replacements expected. Defaults to 1 if not specified. Use when you want to replace multiple occurrences.',
minimum: 1,
},
},
required: ['file_path', 'instruction', 'old_string', 'new_string'],
},
},
};

// ============================================================================
// GLOB TOOL
// ============================================================================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@ import {
READ_FILE_DEFINITION,
WRITE_FILE_DEFINITION,
GREP_DEFINITION,
RIP_GREP_DEFINITION,
GLOB_DEFINITION,
LS_DEFINITION,
getShellDefinition,
EDIT_DEFINITION,
WEB_SEARCH_DEFINITION,
} from './coreTools.js';

describe('coreTools snapshots for specific models', () => {
Expand Down Expand Up @@ -52,12 +55,15 @@ describe('coreTools snapshots for specific models', () => {
{ name: 'read_file', definition: READ_FILE_DEFINITION },
{ name: 'write_file', definition: WRITE_FILE_DEFINITION },
{ name: 'grep_search', definition: GREP_DEFINITION },
{ name: 'grep_search_ripgrep', definition: RIP_GREP_DEFINITION },

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this change the name of the tool as seen by the model? I think we want that to stay 'grep'.

{ name: 'glob', definition: GLOB_DEFINITION },
{ name: 'list_directory', definition: LS_DEFINITION },
{
name: 'run_shell_command',
definition: getShellDefinition(true, true),
},
{ name: 'replace', definition: EDIT_DEFINITION },
{ name: 'google_web_search', definition: WEB_SEARCH_DEFINITION },
];

for (const modelId of modelIds) {
Expand Down
64 changes: 8 additions & 56 deletions packages/core/src/tools/edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ import { logEditCorrectionEvent } from '../telemetry/loggers.js';
import { correctPath } from '../utils/pathCorrector.js';
import { EDIT_TOOL_NAME, READ_FILE_TOOL_NAME } from './tool-names.js';
import { debugLogger } from '../utils/debugLogger.js';
import { EDIT_DEFINITION } from './definitions/coreTools.js';
import { resolveToolDeclaration } from './definitions/resolver.js';
interface ReplacementContext {
params: EditToolParams;
currentContent: string;
Expand Down Expand Up @@ -906,63 +908,9 @@ export class EditTool
super(
EditTool.Name,
'Edit',
`Replaces text within a file. By default, replaces a single occurrence, but can replace multiple occurrences when \`expected_replacements\` is specified. This tool requires providing significant context around the change to ensure precise targeting. Always use the ${READ_FILE_TOOL_NAME} tool to examine the file's current content before attempting a text replacement.

The user has the ability to modify the \`new_string\` content. If modified, this will be stated in the response.

Expectation for required parameters:
1. \`old_string\` MUST be the exact literal text to replace (including all whitespace, indentation, newlines, and surrounding code etc.).
2. \`new_string\` MUST be the exact literal text to replace \`old_string\` with (also including all whitespace, indentation, newlines, and surrounding code etc.). Ensure the resulting code is correct and idiomatic and that \`old_string\` and \`new_string\` are different.
3. \`instruction\` is the detailed instruction of what needs to be changed. It is important to Make it specific and detailed so developers or large language models can understand what needs to be changed and perform the changes on their own if necessary.
4. NEVER escape \`old_string\` or \`new_string\`, that would break the exact literal text requirement.
**Important:** If ANY of the above are not satisfied, the tool will fail. CRITICAL for \`old_string\`: Must uniquely identify the single instance to change. Include at least 3 lines of context BEFORE and AFTER the target text, matching whitespace and indentation precisely. If this string matches multiple locations, or does not match exactly, the tool will fail.
5. Prefer to break down complex and long changes into multiple smaller atomic calls to this tool. Always check the content of the file after changes or not finding a string to match.
**Multiple replacements:** Set \`expected_replacements\` to the number of occurrences you want to replace. The tool will replace ALL occurrences that match \`old_string\` exactly. Ensure the number of replacements matches your expectation.`,
EDIT_DEFINITION.base.description!,
Kind.Edit,
{
properties: {
file_path: {
description: 'The path to the file to modify.',
type: 'string',
},
instruction: {
description: `A clear, semantic instruction for the code change, acting as a high-quality prompt for an expert LLM assistant. It must be self-contained and explain the goal of the change.

A good instruction should concisely answer:
1. WHY is the change needed? (e.g., "To fix a bug where users can be null...")
2. WHERE should the change happen? (e.g., "...in the 'renderUserProfile' function...")
3. WHAT is the high-level change? (e.g., "...add a null check for the 'user' object...")
4. WHAT is the desired outcome? (e.g., "...so that it displays a loading spinner instead of crashing.")

**GOOD Example:** "In the 'calculateTotal' function, correct the sales tax calculation by updating the 'taxRate' constant from 0.05 to 0.075 to reflect the new regional tax laws."

**BAD Examples:**
- "Change the text." (Too vague)
- "Fix the bug." (Doesn't explain the bug or the fix)
- "Replace the line with this new line." (Brittle, just repeats the other parameters)
`,
type: 'string',
},
old_string: {
description:
'The exact literal text to replace, preferably unescaped. For single replacements (default), 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 (i.e. you escaped it) or does not match exactly, the tool will fail.',
type: 'string',
},
new_string: {
description:
'The exact literal text to replace `old_string` with, preferably unescaped. Provide the EXACT text. Ensure the resulting code is correct and idiomatic.',
type: 'string',
},
expected_replacements: {
type: 'number',
description:
'Number of replacements expected. Defaults to 1 if not specified. Use when you want to replace multiple occurrences.',
minimum: 1,
},
},
required: ['file_path', 'instruction', 'old_string', 'new_string'],
type: 'object',
},
EDIT_DEFINITION.base.parametersJsonSchema,
messageBus,
true, // isOutputMarkdown
false, // canUpdateOutput
Expand Down Expand Up @@ -1008,6 +956,10 @@ A good instruction should concisely answer:
);
}

override getSchema(modelId?: string) {
return resolveToolDeclaration(EDIT_DEFINITION, modelId);
}

getModifyContext(_: AbortSignal): ModifyContext<EditToolParams> {
return {
getFilePath: (params: EditToolParams) => params.file_path,
Expand Down
Loading
Loading