Skip to content
Closed
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
8 changes: 8 additions & 0 deletions src/core/Cline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,14 @@ export class Cline extends EventEmitter<ClineEvents> {
return formatResponse.toolError(formatResponse.missingToolParameterError(paramName))
}

async sayAndCreateInvalidNumberParamError(toolName: ToolUseName, paramName: string) {
await this.say(
"error",
`Roo tried to use ${toolName} with invalid number value for parameter '${paramName}'. Retrying...`,
)
return formatResponse.toolError(formatResponse.invalidNumberParameterError(paramName))
}

// Task lifecycle

private async startTask(task?: string, images?: string[]): Promise<void> {
Expand Down
3 changes: 2 additions & 1 deletion src/core/assistant-message/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const toolParamNames = [
"line_count",
"regex",
"file_pattern",
"max_results",
"recursive",
"action",
"url",
Expand Down Expand Up @@ -100,7 +101,7 @@ export interface InsertCodeBlockToolUse extends ToolUse {

export interface SearchFilesToolUse extends ToolUse {
name: "search_files"
params: Partial<Pick<Record<ToolParamName, string>, "path" | "regex" | "file_pattern">>
params: Partial<Pick<Record<ToolParamName, string>, "path" | "regex" | "file_pattern" | "max_results">>
}

export interface ListFilesToolUse extends ToolUse {
Expand Down
75 changes: 60 additions & 15 deletions src/core/prompts/__tests__/__snapshots__/system.test.ts.snap

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions src/core/prompts/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ Otherwise, if you have not completed the task and do not need additional informa
missingToolParameterError: (paramName: string) =>
`Missing value for required parameter '${paramName}'. Please retry with complete response.\n\n${toolUseInstructionsReminder}`,

invalidNumberParameterError: (paramName: string) =>
`Incorrect number value for parameter '${paramName}'. Please retry and use a number.`,

invalidMcpToolArgumentError: (serverName: string, toolName: string) =>
`Invalid JSON argument used with ${serverName} for ${toolName}. Please retry with a properly formatted JSON argument.`,

Expand Down
5 changes: 4 additions & 1 deletion src/core/prompts/tools/search-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,20 @@ Parameters:
- path: (required) The path of the directory to search in (relative to the current working directory ${args.cwd}). This directory will be recursively searched.
- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax.
- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*).
- max_results: (optional) The maximum number of results to return. If not provided, a reasonable default is used.
Usage:
<search_files>
<path>Directory path here</path>
<regex>Your regex pattern here</regex>
<file_pattern>file pattern here (optional)</file_pattern>
<max_results>Maximum number of results to return (optional)</max_results>
</search_files>

Example: Requesting to search for all .ts files in the current directory
Example: Requesting to search for all .ts files in the current directory and limit the result count to 100.
<search_files>
<path>.</path>
<regex>.*</regex>
<file_pattern>*.ts</file_pattern>
<max_results>100</max_results>
</search_files>`
}
9 changes: 9 additions & 0 deletions src/core/tools/searchFilesTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ export async function searchFilesTool(
const relDirPath: string | undefined = block.params.path
const regex: string | undefined = block.params.regex
const filePattern: string | undefined = block.params.file_pattern
const maxResults: string | undefined = block.params.max_results
const sharedMessageProps: ClineSayTool = {
tool: "searchFiles",
path: getReadablePath(cline.cwd, removeClosingTag("path", relDirPath)),
regex: removeClosingTag("regex", regex),
filePattern: removeClosingTag("file_pattern", filePattern),
maxResults: removeClosingTag("max_results", maxResults),
}
try {
if (block.partial) {
Expand All @@ -42,13 +44,20 @@ export async function searchFilesTool(
pushToolResult(await cline.sayAndCreateMissingParamError("search_files", "regex"))
return
}
const maxResultsNumber = parseInt(maxResults ?? "")
if (maxResults !== undefined && isNaN(maxResultsNumber)) {
cline.consecutiveMistakeCount++
pushToolResult(await cline.sayAndCreateInvalidNumberParamError("search_files", "max_results"))
return
}
cline.consecutiveMistakeCount = 0
const absolutePath = path.resolve(cline.cwd, relDirPath)
const results = await regexSearchFiles(
cline.cwd,
absolutePath,
regex,
filePattern,
maxResultsNumber,
cline.rooIgnoreController,
)
const completeMessage = JSON.stringify({
Expand Down
23 changes: 14 additions & 9 deletions src/services/ripgrep/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ interface SearchLineResult {
column?: number
}
// Constants
const MAX_RESULTS = 300
const DEFAULT_MAX_RESULTS = 300
const MAX_LINE_LENGTH = 500

/**
Expand Down Expand Up @@ -95,7 +95,7 @@ export async function getBinPath(vscodeAppRoot: string): Promise<string | undefi
)
}

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

let output = ""
let lineCount = 0
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.
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.

rl.on("line", (line) => {
if (lineCount < maxLines) {
Expand Down Expand Up @@ -140,6 +140,7 @@ export async function regexSearchFiles(
directoryPath: string,
regex: string,
filePattern?: string,
maxResults?: number,
rooIgnoreController?: RooIgnoreController,
): Promise<string> {
const vscodeAppRoot = vscode.env.appRoot
Expand All @@ -151,9 +152,13 @@ export async function regexSearchFiles(

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

if (maxResults === undefined) {
maxResults = DEFAULT_MAX_RESULTS
}

let output: string
try {
output = await execRipgrep(rgPath, args)
output = await execRipgrep(rgPath, args, maxResults)
} catch (error) {
console.error("Error executing ripgrep:", error)
return "No results found"
Expand Down Expand Up @@ -217,22 +222,22 @@ export async function regexSearchFiles(
? results.filter((result) => rooIgnoreController.validateAccess(result.file))
: results

return formatResults(filteredResults, cwd)
return formatResults(filteredResults, cwd, maxResults)
}

function formatResults(fileResults: SearchFileResult[], cwd: string): string {
function formatResults(fileResults: SearchFileResult[], cwd: string, maxResults: number): string {
const groupedResults: { [key: string]: SearchResult[] } = {}

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

// Group results by file name
fileResults.slice(0, MAX_RESULTS).forEach((file) => {
fileResults.slice(0, maxResults).forEach((file) => {
const relativeFilePath = path.relative(cwd, file.file)
if (!groupedResults[relativeFilePath]) {
groupedResults[relativeFilePath] = []
Expand Down
1 change: 1 addition & 0 deletions src/shared/ExtensionMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ export interface ClineSayTool {
content?: string
regex?: string
filePattern?: string
maxResults?: string
mode?: string
reason?: string
isOutsideWorkspace?: boolean
Expand Down