Update prompt and grep tool definition to limit context size (#18780)

This commit is contained in:
Christian Gunderman
2026-02-11 19:20:51 +00:00
committed by GitHub
parent 2dac98dc8d
commit 2a08456ed0
9 changed files with 414 additions and 1 deletions

134
evals/frugalSearch.eval.ts Normal file
View File

@@ -0,0 +1,134 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, expect } from 'vitest';
import { evalTest } from './test-helper.js';
describe('Frugal Search', () => {
const getGrepParams = (call: any): any => {
let args = call.toolRequest.args;
if (typeof args === 'string') {
try {
args = JSON.parse(args);
} catch (e) {
// Ignore parse errors
}
}
return args;
};
evalTest('USUALLY_PASSES', {
name: 'should use targeted search with limit',
prompt: 'find me a sample usage of path.resolve() in the codebase',
files: {
'package.json': JSON.stringify({
name: 'test-project',
version: '1.0.0',
main: 'dist/index.js',
scripts: {
build: 'tsc',
test: 'vitest',
},
dependencies: {
typescript: '^5.0.0',
'@types/node': '^20.0.0',
vitest: '^1.0.0',
},
}),
'src/index.ts': `
import { App } from './app.ts';
const app = new App();
app.start();
`,
'src/app.ts': `
import * as path from 'path';
import { UserController } from './controllers/user.ts';
export class App {
constructor() {
console.log('App initialized');
}
public start(): void {
const userController = new UserController();
console.log('Static path:', path.resolve(__dirname, '../public'));
}
}
`,
'src/utils.ts': `
import * as path from 'path';
import * as fs from 'fs';
export function resolvePath(p: string): string {
return path.resolve(process.cwd(), p);
}
export function ensureDir(dirPath: string): void {
const absolutePath = path.resolve(dirPath);
if (!fs.existsSync(absolutePath)) {
fs.mkdirSync(absolutePath, { recursive: true });
}
}
`,
'src/config.ts': `
import * as path from 'path';
export const config = {
dbPath: path.resolve(process.cwd(), 'data/db.sqlite'),
logLevel: 'info',
};
`,
'src/controllers/user.ts': `
import * as path from 'path';
export class UserController {
public getUsers(): any[] {
console.log('Loading users from:', path.resolve('data/users.json'));
return [{ id: 1, name: 'Alice' }];
}
}
`,
'tests/app.test.ts': `
import { describe, it, expect } from 'vitest';
import * as path from 'path';
describe('App', () => {
it('should resolve paths', () => {
const p = path.resolve('test');
expect(p).toBeDefined();
});
});
`,
},
assert: async (rig) => {
const toolCalls = rig.readToolLogs();
const grepCalls = toolCalls.filter(
(call) => call.toolRequest.name === 'grep_search',
);
expect(grepCalls.length).toBeGreaterThan(0);
const hasFrugalLimit = grepCalls.some((call) => {
const params = getGrepParams(call);
// Check for explicitly set small limit for "sample" or "example" requests
return (
params.total_max_matches !== undefined &&
params.total_max_matches <= 100
);
});
expect(
hasFrugalLimit,
`Expected agent to use a small total_max_matches for a sample usage request. Params used: ${JSON.stringify(
grepCalls.map(getGrepParams),
null,
2,
)}`,
).toBe(true);
},
});
});

View File

@@ -519,6 +519,9 @@ exports[`Core System Prompt (prompts.ts) > should append userMemory with separat
- **Credential Protection:** Never log, print, or commit secrets, API keys, or sensitive credentials. Rigorously protect \`.env\` files, \`.git\`, and system configuration folders.
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always minimize wasted context window by scoping and limiting all of your grep_search searches. e.g.: pass total_max_matches, include, and max_matches_per_file.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
@@ -646,6 +649,9 @@ exports[`Core System Prompt (prompts.ts) > should handle CodebaseInvestigator wi
- **Credential Protection:** Never log, print, or commit secrets, API keys, or sensitive credentials. Rigorously protect \`.env\` files, \`.git\`, and system configuration folders.
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always minimize wasted context window by scoping and limiting all of your grep_search searches. e.g.: pass total_max_matches, include, and max_matches_per_file.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
@@ -738,6 +744,9 @@ exports[`Core System Prompt (prompts.ts) > should handle CodebaseInvestigator wi
- **Credential Protection:** Never log, print, or commit secrets, API keys, or sensitive credentials. Rigorously protect \`.env\` files, \`.git\`, and system configuration folders.
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always minimize wasted context window by scoping and limiting all of your grep_search searches. e.g.: pass total_max_matches, include, and max_matches_per_file.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
@@ -1299,6 +1308,9 @@ exports[`Core System Prompt (prompts.ts) > should include available_skills with
- **Credential Protection:** Never log, print, or commit secrets, API keys, or sensitive credentials. Rigorously protect \`.env\` files, \`.git\`, and system configuration folders.
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always minimize wasted context window by scoping and limiting all of your grep_search searches. e.g.: pass total_max_matches, include, and max_matches_per_file.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
@@ -1422,6 +1434,9 @@ exports[`Core System Prompt (prompts.ts) > should include correct sandbox instru
- **Credential Protection:** Never log, print, or commit secrets, API keys, or sensitive credentials. Rigorously protect \`.env\` files, \`.git\`, and system configuration folders.
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always minimize wasted context window by scoping and limiting all of your grep_search searches. e.g.: pass total_max_matches, include, and max_matches_per_file.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
@@ -1536,6 +1551,9 @@ exports[`Core System Prompt (prompts.ts) > should include correct sandbox instru
- **Credential Protection:** Never log, print, or commit secrets, API keys, or sensitive credentials. Rigorously protect \`.env\` files, \`.git\`, and system configuration folders.
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always minimize wasted context window by scoping and limiting all of your grep_search searches. e.g.: pass total_max_matches, include, and max_matches_per_file.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
@@ -1650,6 +1668,9 @@ exports[`Core System Prompt (prompts.ts) > should include correct sandbox instru
- **Credential Protection:** Never log, print, or commit secrets, API keys, or sensitive credentials. Rigorously protect \`.env\` files, \`.git\`, and system configuration folders.
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always minimize wasted context window by scoping and limiting all of your grep_search searches. e.g.: pass total_max_matches, include, and max_matches_per_file.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
@@ -1760,6 +1781,9 @@ exports[`Core System Prompt (prompts.ts) > should include planning phase suggest
- **Credential Protection:** Never log, print, or commit secrets, API keys, or sensitive credentials. Rigorously protect \`.env\` files, \`.git\`, and system configuration folders.
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always minimize wasted context window by scoping and limiting all of your grep_search searches. e.g.: pass total_max_matches, include, and max_matches_per_file.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
@@ -1870,6 +1894,9 @@ exports[`Core System Prompt (prompts.ts) > should include sub-agents in XML for
- **Credential Protection:** Never log, print, or commit secrets, API keys, or sensitive credentials. Rigorously protect \`.env\` files, \`.git\`, and system configuration folders.
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always minimize wasted context window by scoping and limiting all of your grep_search searches. e.g.: pass total_max_matches, include, and max_matches_per_file.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
@@ -2219,6 +2246,9 @@ exports[`Core System Prompt (prompts.ts) > should return the base prompt when us
- **Credential Protection:** Never log, print, or commit secrets, API keys, or sensitive credentials. Rigorously protect \`.env\` files, \`.git\`, and system configuration folders.
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always minimize wasted context window by scoping and limiting all of your grep_search searches. e.g.: pass total_max_matches, include, and max_matches_per_file.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
@@ -2329,6 +2359,9 @@ exports[`Core System Prompt (prompts.ts) > should return the base prompt when us
- **Credential Protection:** Never log, print, or commit secrets, API keys, or sensitive credentials. Rigorously protect \`.env\` files, \`.git\`, and system configuration folders.
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always minimize wasted context window by scoping and limiting all of your grep_search searches. e.g.: pass total_max_matches, include, and max_matches_per_file.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
@@ -2550,6 +2583,9 @@ exports[`Core System Prompt (prompts.ts) > should use chatty system prompt for p
- **Credential Protection:** Never log, print, or commit secrets, API keys, or sensitive credentials. Rigorously protect \`.env\` files, \`.git\`, and system configuration folders.
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always minimize wasted context window by scoping and limiting all of your grep_search searches. e.g.: pass total_max_matches, include, and max_matches_per_file.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
@@ -2660,6 +2696,9 @@ exports[`Core System Prompt (prompts.ts) > should use chatty system prompt for p
- **Credential Protection:** Never log, print, or commit secrets, API keys, or sensitive credentials. Rigorously protect \`.env\` files, \`.git\`, and system configuration folders.
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always minimize wasted context window by scoping and limiting all of your grep_search searches. e.g.: pass total_max_matches, include, and max_matches_per_file.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.

View File

@@ -164,6 +164,9 @@ export function renderCoreMandates(options?: CoreMandatesOptions): string {
- **Credential Protection:** Never log, print, or commit secrets, API keys, or sensitive credentials. Rigorously protect \`.env\` files, \`.git\`, and system configuration folders.
- **Source Control:** Do not stage or commit changes unless specifically requested by the user.
## Context Efficiency:
- Always minimize wasted context window by scoping and limiting all of your ${GREP_TOOL_NAME} searches. e.g.: pass total_max_matches, include, and max_matches_per_file.
## Engineering Standards
- **Contextual Precedence:** Instructions found in ${formattedFilenames} files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.

View File

@@ -45,6 +45,10 @@ exports[`coreTools snapshots for specific models > Model: gemini-2.5-pro > snaps
"description": "Optional: The absolute path to the directory to search within. If omitted, searches the current working directory.",
"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",
},
"include": {
"description": "Optional: A glob pattern to filter which files are searched (e.g., '*.js', '*.{ts,tsx}', 'src/**'). If omitted, searches all files (respecting potential global ignores).",
"type": "string",
@@ -54,6 +58,10 @@ exports[`coreTools snapshots for specific models > Model: gemini-2.5-pro > snaps
"minimum": 1,
"type": "integer",
},
"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",
},
"pattern": {
"description": "The regular expression (regex) pattern to search for within file contents (e.g., 'function\\s+myFunction', 'import\\s+\\{.*\\}\\s+from\\s+.*').",
"type": "string",
@@ -254,6 +262,10 @@ exports[`coreTools snapshots for specific models > Model: gemini-3-pro-preview >
"description": "Optional: The absolute path to the directory to search within. If omitted, searches the current working directory.",
"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",
},
"include": {
"description": "Optional: A glob pattern to filter which files are searched (e.g., '*.js', '*.{ts,tsx}', 'src/**'). If omitted, searches all files (respecting potential global ignores).",
"type": "string",
@@ -263,6 +275,10 @@ exports[`coreTools snapshots for specific models > Model: gemini-3-pro-preview >
"minimum": 1,
"type": "integer",
},
"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",
},
"pattern": {
"description": "The regular expression (regex) pattern to search for within file contents (e.g., 'function\\s+myFunction', 'import\\s+\\{.*\\}\\s+from\\s+.*').",
"type": "string",

View File

@@ -98,6 +98,16 @@ export const GREP_DEFINITION: ToolDefinition = {
description: `Optional: A glob pattern to filter which files are searched (e.g., '*.js', '*.{ts,tsx}', 'src/**'). If omitted, searches all files (respecting potential global ignores).`,
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',
},
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.',

View File

@@ -498,6 +498,41 @@ describe('GrepTool', () => {
expect(result.llmContent).toContain('File: sub/fileC.txt');
expect(result.llmContent).toContain('L1: another world in sub dir');
});
it('should return only file paths when names_only is true', async () => {
const params: GrepToolParams = {
pattern: 'world',
names_only: true,
};
const invocation = grepTool.build(params);
const result = await invocation.execute(abortSignal);
expect(result.llmContent).toContain('Found 2 files with matches');
expect(result.llmContent).toContain('fileA.txt');
expect(result.llmContent).toContain('sub/fileC.txt');
expect(result.llmContent).not.toContain('L1:');
expect(result.llmContent).not.toContain('hello world');
});
it('should filter out matches based on exclude_pattern', async () => {
await fs.writeFile(
path.join(tempRootDir, 'copyright.txt'),
'Copyright 2025 Google LLC\nCopyright 2026 Google LLC',
);
const params: GrepToolParams = {
pattern: 'Copyright .* Google LLC',
exclude_pattern: '2026',
dir_path: '.',
};
const invocation = grepTool.build(params);
const result = await invocation.execute(abortSignal);
expect(result.llmContent).toContain('Found 1 match');
expect(result.llmContent).toContain('copyright.txt');
expect(result.llmContent).toContain('Copyright 2025 Google LLC');
expect(result.llmContent).not.toContain('Copyright 2026 Google LLC');
});
});
describe('getDescription', () => {

View File

@@ -49,6 +49,16 @@ export interface GrepToolParams {
*/
include?: string;
/**
* Optional: A regular expression pattern to exclude from the search results.
*/
exclude_pattern?: string;
/**
* Optional: If true, only the file paths of the matches will be returned.
*/
names_only?: boolean;
/**
* Optional: Maximum number of matches to return per file. Use this to prevent being overwhelmed by repetitive matches in large files.
*/
@@ -225,6 +235,7 @@ class GrepToolInvocation extends BaseToolInvocation<
pattern: this.params.pattern,
path: searchDir,
include: this.params.include,
exclude_pattern: this.params.exclude_pattern,
maxMatches: remainingLimit,
max_matches_per_file: this.params.max_matches_per_file,
signal: timeoutController.signal,
@@ -280,6 +291,16 @@ class GrepToolInvocation extends BaseToolInvocation<
const matchCount = allMatches.length;
const matchTerm = matchCount === 1 ? 'match' : 'matches';
if (this.params.names_only) {
const filePaths = Object.keys(matchesByFile).sort();
let llmContent = `Found ${filePaths.length} files with matches for pattern "${this.params.pattern}" ${searchLocationDescription}${this.params.include ? ` (filter: "${this.params.include}")` : ''}${wasTruncated ? ` (results limited to ${totalMaxMatches} matches for performance)` : ''}:\n`;
llmContent += filePaths.join('\n');
return {
llmContent: llmContent.trim(),
returnDisplay: `Found ${filePaths.length} files${wasTruncated ? ' (limited)' : ''}`,
};
}
let llmContent = `Found ${matchCount} ${matchTerm} for pattern "${this.params.pattern}" ${searchLocationDescription}${this.params.include ? ` (filter: "${this.params.include}")` : ''}`;
if (wasTruncated) {
@@ -354,6 +375,7 @@ class GrepToolInvocation extends BaseToolInvocation<
pattern: string;
path: string; // Expects absolute path
include?: string;
exclude_pattern?: string;
maxMatches: number;
max_matches_per_file?: number;
signal: AbortSignal;
@@ -362,12 +384,18 @@ class GrepToolInvocation extends BaseToolInvocation<
pattern,
path: absolutePath,
include,
exclude_pattern,
maxMatches,
max_matches_per_file,
} = options;
let strategyUsed = 'none';
try {
let excludeRegex: RegExp | null = null;
if (exclude_pattern) {
excludeRegex = new RegExp(exclude_pattern, 'i');
}
// --- Strategy 1: git grep ---
const isGit = isGitRepository(absolutePath);
const gitAvailable = isGit && (await this.isCommandAvailable('git'));
@@ -400,6 +428,9 @@ class GrepToolInvocation extends BaseToolInvocation<
for await (const line of generator) {
const match = this.parseGrepLine(line, absolutePath);
if (match) {
if (excludeRegex && excludeRegex.test(match.line)) {
continue;
}
results.push(match);
if (results.length >= maxMatches) {
break;
@@ -467,6 +498,9 @@ class GrepToolInvocation extends BaseToolInvocation<
for await (const line of generator) {
const match = this.parseGrepLine(line, absolutePath);
if (match) {
if (excludeRegex && excludeRegex.test(match.line)) {
continue;
}
results.push(match);
if (results.length >= maxMatches) {
break;
@@ -528,6 +562,9 @@ class GrepToolInvocation extends BaseToolInvocation<
for (let index = 0; index < lines.length; index++) {
const line = lines[index];
if (regex.test(line)) {
if (excludeRegex && excludeRegex.test(line)) {
continue;
}
allMatches.push({
filePath:
path.relative(absolutePath, fileAbsolutePath) ||
@@ -637,6 +674,14 @@ export class GrepTool extends BaseDeclarativeTool<GrepToolParams, ToolResult> {
return `Invalid regular expression pattern provided: ${params.pattern}. Error: ${getErrorMessage(error)}`;
}
if (params.exclude_pattern) {
try {
new RegExp(params.exclude_pattern);
} catch (error) {
return `Invalid exclude regular expression pattern provided: ${params.exclude_pattern}. Error: ${getErrorMessage(error)}`;
}
}
if (
params.max_matches_per_file !== undefined &&
params.max_matches_per_file < 1

View File

@@ -1930,6 +1930,85 @@ describe('RipGrepTool', () => {
expect(result.llmContent).not.toContain('L3: match 3');
expect(result.returnDisplay).toBe('Found 2 matches (limited)');
});
it('should return only file paths when names_only is true', async () => {
mockSpawn.mockImplementationOnce(
createMockSpawn({
outputData:
JSON.stringify({
type: 'match',
data: {
path: { text: 'fileA.txt' },
line_number: 1,
lines: { text: 'hello world\n' },
},
}) +
'\n' +
JSON.stringify({
type: 'match',
data: {
path: { text: 'fileB.txt' },
line_number: 5,
lines: { text: 'hello again\n' },
},
}) +
'\n',
exitCode: 0,
}),
);
const params: RipGrepToolParams = {
pattern: 'hello',
names_only: true,
};
const invocation = grepTool.build(params);
const result = await invocation.execute(abortSignal);
expect(result.llmContent).toContain('Found 2 files with matches');
expect(result.llmContent).toContain('fileA.txt');
expect(result.llmContent).toContain('fileB.txt');
expect(result.llmContent).not.toContain('L1:');
expect(result.llmContent).not.toContain('hello world');
});
it('should filter out matches based on exclude_pattern', async () => {
mockSpawn.mockImplementationOnce(
createMockSpawn({
outputData:
JSON.stringify({
type: 'match',
data: {
path: { text: 'fileA.txt' },
line_number: 1,
lines: { text: 'Copyright 2025 Google LLC\n' },
},
}) +
'\n' +
JSON.stringify({
type: 'match',
data: {
path: { text: 'fileB.txt' },
line_number: 1,
lines: { text: 'Copyright 2026 Google LLC\n' },
},
}) +
'\n',
exitCode: 0,
}),
);
const params: RipGrepToolParams = {
pattern: 'Copyright .* Google LLC',
exclude_pattern: '2026',
};
const invocation = grepTool.build(params);
const result = await invocation.execute(abortSignal);
expect(result.llmContent).toContain('Found 1 match');
expect(result.llmContent).toContain('fileA.txt');
expect(result.llmContent).not.toContain('fileB.txt');
expect(result.llmContent).toContain('Copyright 2025 Google LLC');
});
});
});

View File

@@ -102,6 +102,16 @@ export interface RipGrepToolParams {
*/
include?: string;
/**
* Optional: A regular expression pattern to exclude from the search results.
*/
exclude_pattern?: string;
/**
* Optional: If true, only the file paths of the matches will be returned.
*/
names_only?: boolean;
/**
* If true, searches case-sensitively. Defaults to false.
*/
@@ -244,6 +254,7 @@ class GrepToolInvocation extends BaseToolInvocation<
pattern: this.params.pattern,
path: searchDirAbs,
include: this.params.include,
exclude_pattern: this.params.exclude_pattern,
case_sensitive: this.params.case_sensitive,
fixed_strings: this.params.fixed_strings,
context: this.params.context,
@@ -299,6 +310,16 @@ class GrepToolInvocation extends BaseToolInvocation<
const wasTruncated = matchCount >= totalMaxMatches;
if (this.params.names_only) {
const filePaths = Object.keys(matchesByFile).sort();
let llmContent = `Found ${filePaths.length} files with matches for pattern "${this.params.pattern}" ${searchLocationDescription}${this.params.include ? ` (filter: "${this.params.include}")` : ''}${wasTruncated ? ` (results limited to ${totalMaxMatches} matches for performance)` : ''}:\n`;
llmContent += filePaths.join('\n');
return {
llmContent: llmContent.trim(),
returnDisplay: `Found ${filePaths.length} files${wasTruncated ? ' (limited)' : ''}`,
};
}
let llmContent = `Found ${matchCount} ${matchTerm} for pattern "${this.params.pattern}" ${searchLocationDescription}${this.params.include ? ` (filter: "${this.params.include}")` : ''}${wasTruncated ? ` (results limited to ${totalMaxMatches} matches for performance)` : ''}:\n---\n`;
for (const filePath in matchesByFile) {
@@ -330,6 +351,7 @@ class GrepToolInvocation extends BaseToolInvocation<
pattern: string;
path: string;
include?: string;
exclude_pattern?: string;
case_sensitive?: boolean;
fixed_strings?: boolean;
context?: number;
@@ -344,6 +366,7 @@ class GrepToolInvocation extends BaseToolInvocation<
pattern,
path: absolutePath,
include,
exclude_pattern,
case_sensitive,
fixed_strings,
context,
@@ -423,9 +446,18 @@ class GrepToolInvocation extends BaseToolInvocation<
});
let matchesFound = 0;
let excludeRegex: RegExp | null = null;
if (exclude_pattern) {
excludeRegex = new RegExp(exclude_pattern, case_sensitive ? '' : 'i');
}
for await (const line of generator) {
const match = this.parseRipgrepJsonLine(line, absolutePath);
if (match) {
if (excludeRegex && excludeRegex.test(match.line)) {
continue;
}
results.push(match);
if (!match.isContext) {
matchesFound++;
@@ -527,7 +559,7 @@ export class RipGrepTool extends BaseDeclarativeTool<
super(
RipGrepTool.Name,
'SearchText',
'Searches for a regular expression pattern within file contents. Max 100 matches.',
'Searches for a regular expression pattern within file contents.',
Kind.Search,
{
properties: {
@@ -546,6 +578,16 @@ export class RipGrepTool extends BaseDeclarativeTool<
"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.',
@@ -565,11 +607,13 @@ export class RipGrepTool extends BaseDeclarativeTool<
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:
@@ -618,6 +662,14 @@ export class RipGrepTool extends BaseDeclarativeTool<
}
}
if (params.exclude_pattern) {
try {
new RegExp(params.exclude_pattern);
} catch (error) {
return `Invalid exclude regular expression pattern provided: ${params.exclude_pattern}. Error: ${getErrorMessage(error)}`;
}
}
if (
params.max_matches_per_file !== undefined &&
params.max_matches_per_file < 1