diff --git a/src/core/Cline.ts b/src/core/Cline.ts index b83a7157cf..142f6bd3db 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -30,6 +30,7 @@ import { } from "../integrations/misc/extract-text" import { countFileLines } from "../integrations/misc/line-counter" import { fetchInstructionsTool } from "./tools/fetchInstructionsTool" +import { listFilesTool } from "./tools/listFilesTool" import { readFileTool } from "./tools/readFileTool" import { ExitCodeDetails } from "../integrations/terminal/TerminalProcess" import { Terminal } from "../integrations/terminal/Terminal" @@ -1516,7 +1517,7 @@ export class Cline extends EventEmitter { } // If block is partial, remove partial closing tag so its not presented to user - const removeClosingTag = (tag: ToolParamName, text?: string) => { + const removeClosingTag = (tag: ToolParamName, text?: string): string => { if (!block.partial) { return text || "" } @@ -2265,54 +2266,8 @@ export class Cline extends EventEmitter { } case "list_files": { - const relDirPath: string | undefined = block.params.path - const recursiveRaw: string | undefined = block.params.recursive - const recursive = recursiveRaw?.toLowerCase() === "true" - const sharedMessageProps: ClineSayTool = { - tool: !recursive ? "listFilesTopLevel" : "listFilesRecursive", - path: getReadablePath(this.cwd, removeClosingTag("path", relDirPath)), - } - try { - if (block.partial) { - const partialMessage = JSON.stringify({ - ...sharedMessageProps, - content: "", - } satisfies ClineSayTool) - await this.ask("tool", partialMessage, block.partial).catch(() => {}) - break - } else { - if (!relDirPath) { - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("list_files", "path")) - break - } - this.consecutiveMistakeCount = 0 - const absolutePath = path.resolve(this.cwd, relDirPath) - const [files, didHitLimit] = await listFiles(absolutePath, recursive, 200) - const { showRooIgnoredFiles = true } = - (await this.providerRef.deref()?.getState()) ?? {} - const result = formatResponse.formatFilesList( - absolutePath, - files, - didHitLimit, - this.rooIgnoreController, - showRooIgnoredFiles, - ) - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - content: result, - } satisfies ClineSayTool) - const didApprove = await askApproval("tool", completeMessage) - if (!didApprove) { - break - } - pushToolResult(result) - break - } - } catch (error) { - await handleError("listing files", error) - break - } + await listFilesTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + break } case "list_code_definition_names": { const relPath: string | undefined = block.params.path diff --git a/src/core/tools/listFilesTool.ts b/src/core/tools/listFilesTool.ts new file mode 100644 index 0000000000..879cc7b6df --- /dev/null +++ b/src/core/tools/listFilesTool.ts @@ -0,0 +1,78 @@ +import * as path from "path" +import { Cline } from "../Cline" +import { ClineSayTool } from "../../shared/ExtensionMessage" +import { ToolParamName, ToolUse } from "../assistant-message" +import { formatResponse } from "../prompts/responses" +import { listFiles } from "../../services/glob/list-files" +import { getReadablePath } from "../../utils/path" +import { AskApproval, HandleError, PushToolResult } from "./types" +/** + * Implements the list_files tool. + * + * @param cline - The instance of Cline that is executing this tool. + * @param block - The block of assistant message content that specifies the + * parameters for this tool. + * @param askApproval - A function that asks the user for approval to show a + * message. + * @param handleError - A function that handles an error that occurred while + * executing this tool. + * @param pushToolResult - A function that pushes the result of this tool to the + * conversation. + * @param removeClosingTag - A function that removes a closing tag from a string. + */ +export async function listFilesTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: (tag: ToolParamName, text?: string) => string, +) { + const relDirPath: string | undefined = block.params.path + const recursiveRaw: string | undefined = block.params.recursive + const recursive = recursiveRaw?.toLowerCase() === "true" + const sharedMessageProps: ClineSayTool = { + tool: !recursive ? "listFilesTopLevel" : "listFilesRecursive", + path: getReadablePath(cline.cwd, removeClosingTag("path", relDirPath)), + } + try { + if (block.partial) { + const partialMessage = JSON.stringify({ + ...sharedMessageProps, + content: "", + } satisfies ClineSayTool) + await cline.ask("tool", partialMessage, block.partial).catch(() => {}) + return + } else { + if (!relDirPath) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("list_files", "path")) + return + } + cline.consecutiveMistakeCount = 0 + const absolutePath = path.resolve(cline.cwd, relDirPath) + const [files, didHitLimit] = await listFiles(absolutePath, recursive, 200) + const { showRooIgnoredFiles = true } = (await cline.providerRef.deref()?.getState()) ?? {} + const result = formatResponse.formatFilesList( + absolutePath, + files, + didHitLimit, + cline.rooIgnoreController, + showRooIgnoredFiles, + ) + const completeMessage = JSON.stringify({ + ...sharedMessageProps, + content: result, + } satisfies ClineSayTool) + const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { + return + } + pushToolResult(result) + return + } + } catch (error) { + await handleError("listing files", error) + return + } +}