Frugal search

This commit is contained in:
Christian Gunderman
2026-02-02 22:23:58 -08:00
parent 1b274b081d
commit 28afd9bbff
4 changed files with 110 additions and 1 deletions
+39 -1
View File
@@ -46,6 +46,11 @@ export interface GrepToolParams {
* File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}")
*/
include?: string;
/**
* Optional: Maximum number of matches to return per file.
*/
max_matches_per_file?: number;
}
/**
@@ -210,6 +215,7 @@ class GrepToolInvocation extends BaseToolInvocation<
path: searchDir,
include: this.params.include,
maxMatches: remainingLimit,
maxMatchesPerFile: this.params.max_matches_per_file,
signal: timeoutController.signal,
});
@@ -338,9 +344,16 @@ class GrepToolInvocation extends BaseToolInvocation<
path: string; // Expects absolute path
include?: string;
maxMatches: number;
maxMatchesPerFile?: number;
signal: AbortSignal;
}): Promise<GrepMatch[]> {
const { pattern, path: absolutePath, include, maxMatches } = options;
const {
pattern,
path: absolutePath,
include,
maxMatches,
maxMatchesPerFile,
} = options;
let strategyUsed = 'none';
try {
@@ -370,9 +383,19 @@ class GrepToolInvocation extends BaseToolInvocation<
});
const results: GrepMatch[] = [];
const matchesPerFile = new Map<string, number>();
for await (const line of generator) {
const match = this.parseGrepLine(line, absolutePath);
if (match) {
if (maxMatchesPerFile) {
const count = matchesPerFile.get(match.filePath) || 0;
if (count >= maxMatchesPerFile) {
continue;
}
matchesPerFile.set(match.filePath, count + 1);
}
results.push(match);
if (results.length >= maxMatches) {
break;
@@ -423,6 +446,11 @@ class GrepToolInvocation extends BaseToolInvocation<
if (include) {
grepArgs.push(`--include=${include}`);
}
if (maxMatchesPerFile) {
grepArgs.push(`-m`, maxMatchesPerFile.toString());
}
grepArgs.push(pattern);
grepArgs.push('.');
@@ -494,6 +522,7 @@ class GrepToolInvocation extends BaseToolInvocation<
try {
const content = await fsPromises.readFile(fileAbsolutePath, 'utf8');
const lines = content.split(/\r?\n/);
let fileMatchCount = 0;
for (let index = 0; index < lines.length; index++) {
const line = lines[index];
if (regex.test(line)) {
@@ -504,7 +533,10 @@ class GrepToolInvocation extends BaseToolInvocation<
lineNumber: index + 1,
line,
});
fileMatchCount++;
if (allMatches.length >= maxMatches) break;
if (maxMatchesPerFile && fileMatchCount >= maxMatchesPerFile)
break;
}
}
} catch (readError: unknown) {
@@ -593,6 +625,12 @@ export class GrepTool extends BaseDeclarativeTool<GrepToolParams, ToolResult> {
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',
},
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,
},
},
required: ['pattern'],
type: 'object',