Skip to content

Commit 4c0add7

Browse files
committed
feat: Add a way to limit the number of results from search_files tool
Currently search_files will produce up to 300 results (an arbitrary number). In cortain cases this is too large number because only first few occurrences are wanted. Unfortunately if too generic search pattern is selected, lots of results will be returned to the LLM and this will reduce remaining context significantly and require more time to process than needed. To address this, an explicit optional max_results parameter has been added to the search_files tool. Its explanation is simple and it does not increase the Roo Code prompt size too much. If the max_results parameter is not supplied, code works exactly as before.
1 parent 72d1a90 commit 4c0add7

File tree

8 files changed

+101
-26
lines changed

8 files changed

+101
-26
lines changed

src/core/Cline.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,14 @@ export class Cline extends EventEmitter<ClineEvents> {
582582
return formatResponse.toolError(formatResponse.missingToolParameterError(paramName))
583583
}
584584

585+
async sayAndCreateInvalidNumberParamError(toolName: ToolUseName, paramName: string) {
586+
await this.say(
587+
"error",
588+
`Roo tried to use ${toolName} with invalid number value for parameter '${paramName}'. Retrying...`,
589+
)
590+
return formatResponse.toolError(formatResponse.invalidNumberParameterError(paramName))
591+
}
592+
585593
// Task lifecycle
586594

587595
private async startTask(task?: string, images?: string[]): Promise<void> {

src/core/assistant-message/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export const toolParamNames = [
3838
"line_count",
3939
"regex",
4040
"file_pattern",
41+
"max_results",
4142
"recursive",
4243
"action",
4344
"url",
@@ -100,7 +101,7 @@ export interface InsertCodeBlockToolUse extends ToolUse {
100101

101102
export interface SearchFilesToolUse extends ToolUse {
102103
name: "search_files"
103-
params: Partial<Pick<Record<ToolParamName, string>, "path" | "regex" | "file_pattern">>
104+
params: Partial<Pick<Record<ToolParamName, string>, "path" | "regex" | "file_pattern" | "max_results">>
104105
}
105106

106107
export interface ListFilesToolUse extends ToolUse {

src/core/prompts/__tests__/__snapshots__/system.test.ts.snap

Lines changed: 60 additions & 15 deletions
Large diffs are not rendered by default.

src/core/prompts/responses.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ Otherwise, if you have not completed the task and do not need additional informa
3535
missingToolParameterError: (paramName: string) =>
3636
`Missing value for required parameter '${paramName}'. Please retry with complete response.\n\n${toolUseInstructionsReminder}`,
3737

38+
invalidNumberParameterError: (paramName: string) =>
39+
`Incorrect number value for parameter '${paramName}'. Please retry and use a number.`,
40+
3841
invalidMcpToolArgumentError: (serverName: string, toolName: string) =>
3942
`Invalid JSON argument used with ${serverName} for ${toolName}. Please retry with a properly formatted JSON argument.`,
4043

src/core/prompts/tools/search-files.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,20 @@ Parameters:
77
- path: (required) The path of the directory to search in (relative to the current working directory ${args.cwd}). This directory will be recursively searched.
88
- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax.
99
- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*).
10+
- max_results: (optional) The maximum number of results to return. If not provided, a reasonable default is used.
1011
Usage:
1112
<search_files>
1213
<path>Directory path here</path>
1314
<regex>Your regex pattern here</regex>
1415
<file_pattern>file pattern here (optional)</file_pattern>
16+
<max_results>Maximum number of results to return (optional)</max_results>
1517
</search_files>
1618
17-
Example: Requesting to search for all .ts files in the current directory
19+
Example: Requesting to search for all .ts files in the current directory and limit the result count to 100.
1820
<search_files>
1921
<path>.</path>
2022
<regex>.*</regex>
2123
<file_pattern>*.ts</file_pattern>
24+
<max_results>100</max_results>
2225
</search_files>`
2326
}

src/core/tools/searchFilesTool.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ export async function searchFilesTool(
1717
const relDirPath: string | undefined = block.params.path
1818
const regex: string | undefined = block.params.regex
1919
const filePattern: string | undefined = block.params.file_pattern
20+
const maxResults: string | undefined = block.params.max_results
2021
const sharedMessageProps: ClineSayTool = {
2122
tool: "searchFiles",
2223
path: getReadablePath(cline.cwd, removeClosingTag("path", relDirPath)),
2324
regex: removeClosingTag("regex", regex),
2425
filePattern: removeClosingTag("file_pattern", filePattern),
26+
maxResults: removeClosingTag("max_results", maxResults),
2527
}
2628
try {
2729
if (block.partial) {
@@ -42,13 +44,20 @@ export async function searchFilesTool(
4244
pushToolResult(await cline.sayAndCreateMissingParamError("search_files", "regex"))
4345
return
4446
}
47+
const maxResultsNumber = parseInt(maxResults ?? "")
48+
if (maxResults !== undefined && isNaN(maxResultsNumber)) {
49+
cline.consecutiveMistakeCount++
50+
pushToolResult(await cline.sayAndCreateInvalidNumberParamError("search_files", "max_results"))
51+
return
52+
}
4553
cline.consecutiveMistakeCount = 0
4654
const absolutePath = path.resolve(cline.cwd, relDirPath)
4755
const results = await regexSearchFiles(
4856
cline.cwd,
4957
absolutePath,
5058
regex,
5159
filePattern,
60+
maxResultsNumber,
5261
cline.rooIgnoreController,
5362
)
5463
const completeMessage = JSON.stringify({

src/services/ripgrep/index.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ interface SearchLineResult {
6666
column?: number
6767
}
6868
// Constants
69-
const MAX_RESULTS = 300
69+
const DEFAULT_MAX_RESULTS = 300
7070
const MAX_LINE_LENGTH = 500
7171

7272
/**
@@ -95,7 +95,7 @@ export async function getBinPath(vscodeAppRoot: string): Promise<string | undefi
9595
)
9696
}
9797

98-
async function execRipgrep(bin: string, args: string[]): Promise<string> {
98+
async function execRipgrep(bin: string, args: string[], maxResults: number): Promise<string> {
9999
return new Promise((resolve, reject) => {
100100
const rgProcess = childProcess.spawn(bin, args)
101101
// cross-platform alternative to head, which is ripgrep author's recommendation for limiting output.
@@ -106,7 +106,7 @@ async function execRipgrep(bin: string, args: string[]): Promise<string> {
106106

107107
let output = ""
108108
let lineCount = 0
109-
const maxLines = MAX_RESULTS * 5 // limiting ripgrep output with max lines since there's no other way to limit results. it's okay that we're outputting as json, since we're parsing it line by line and ignore anything that's not part of a match. This assumes each result is at most 5 lines.
109+
const maxLines = maxResults * 5 // limiting ripgrep output with max lines since there's no other way to limit results. it's okay that we're outputting as json, since we're parsing it line by line and ignore anything that's not part of a match. This assumes each result is at most 5 lines.
110110

111111
rl.on("line", (line) => {
112112
if (lineCount < maxLines) {
@@ -140,6 +140,7 @@ export async function regexSearchFiles(
140140
directoryPath: string,
141141
regex: string,
142142
filePattern?: string,
143+
maxResults?: number,
143144
rooIgnoreController?: RooIgnoreController,
144145
): Promise<string> {
145146
const vscodeAppRoot = vscode.env.appRoot
@@ -151,9 +152,13 @@ export async function regexSearchFiles(
151152

152153
const args = ["--json", "-e", regex, "--glob", filePattern || "*", "--context", "1", directoryPath]
153154

155+
if (maxResults === undefined) {
156+
maxResults = DEFAULT_MAX_RESULTS
157+
}
158+
154159
let output: string
155160
try {
156-
output = await execRipgrep(rgPath, args)
161+
output = await execRipgrep(rgPath, args, maxResults)
157162
} catch (error) {
158163
console.error("Error executing ripgrep:", error)
159164
return "No results found"
@@ -217,22 +222,22 @@ export async function regexSearchFiles(
217222
? results.filter((result) => rooIgnoreController.validateAccess(result.file))
218223
: results
219224

220-
return formatResults(filteredResults, cwd)
225+
return formatResults(filteredResults, cwd, maxResults)
221226
}
222227

223-
function formatResults(fileResults: SearchFileResult[], cwd: string): string {
228+
function formatResults(fileResults: SearchFileResult[], cwd: string, maxResults: number): string {
224229
const groupedResults: { [key: string]: SearchResult[] } = {}
225230

226231
let totalResults = fileResults.reduce((sum, file) => sum + file.searchResults.length, 0)
227232
let output = ""
228-
if (totalResults >= MAX_RESULTS) {
229-
output += `Showing first ${MAX_RESULTS} of ${MAX_RESULTS}+ results. Use a more specific search if necessary.\n\n`
233+
if (totalResults >= maxResults) {
234+
output += `Showing first ${maxResults} of ${maxResults}+ results. Use a more specific search if necessary.\n\n`
230235
} else {
231236
output += `Found ${totalResults === 1 ? "1 result" : `${totalResults.toLocaleString()} results`}.\n\n`
232237
}
233238

234239
// Group results by file name
235-
fileResults.slice(0, MAX_RESULTS).forEach((file) => {
240+
fileResults.slice(0, maxResults).forEach((file) => {
236241
const relativeFilePath = path.relative(cwd, file.file)
237242
if (!groupedResults[relativeFilePath]) {
238243
groupedResults[relativeFilePath] = []

src/shared/ExtensionMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ export interface ClineSayTool {
226226
content?: string
227227
regex?: string
228228
filePattern?: string
229+
maxResults?: string
229230
mode?: string
230231
reason?: string
231232
isOutsideWorkspace?: boolean

0 commit comments

Comments
 (0)