(toolName: string, input: unknown, mode: ModeType)
| 27 | } |
| 28 | |
| 29 | export async function executeLocalTool(toolName: string, input: unknown, mode: ModeType) { |
| 30 | if (mode === Mode.PLAN && !["readFile", "listDirectory", "glob", "grep"].includes(toolName)) { |
| 31 | throw new Error(`Tool ${toolName} is not available in PLAN mode`); |
| 32 | } |
| 33 | |
| 34 | switch (toolName) { |
| 35 | case "readFile": { |
| 36 | const { path } = toolInputSchemas.readFile.parse(input); |
| 37 | const { resolved } = resolveInsideCwd(path); |
| 38 | const content = await readFile(resolved, "utf-8"); |
| 39 | return content.length > MAX_FILE_SIZE |
| 40 | ? { content: content.slice(0, MAX_FILE_SIZE), truncated: true, totalLength: content.length } |
| 41 | : { content }; |
| 42 | } |
| 43 | case "listDirectory": { |
| 44 | const { path } = toolInputSchemas.listDirectory.parse(input); |
| 45 | const { cwd, resolved } = resolveInsideCwd(path); |
| 46 | const entries = await readdir(resolved); |
| 47 | const results: { name: string; type: "file" | "directory" }[] = []; |
| 48 | |
| 49 | for (const entry of entries) { |
| 50 | if (entry.startsWith(".") || entry === "node_modules") continue; |
| 51 | const info = await stat(join(resolved, entry)); |
| 52 | results.push({ name: entry, type: info.isDirectory() ? "directory" : "file" }); |
| 53 | } |
| 54 | |
| 55 | results.sort((a, b) => |
| 56 | a.type !== b.type ? (a.type === "directory" ? -1 : 1) : a.name.localeCompare(b.name), |
| 57 | ); |
| 58 | return { path: relative(cwd, resolved) || ".", entries: results }; |
| 59 | } |
| 60 | case "glob": { |
| 61 | const { pattern, path } = toolInputSchemas.glob.parse(input); |
| 62 | const { cwd, resolved } = resolveInsideCwd(path); |
| 63 | const glob = new Bun.Glob(pattern); |
| 64 | const files: string[] = []; |
| 65 | let truncated = false; |
| 66 | |
| 67 | for await (const match of glob.scan({ cwd: resolved, dot: false, onlyFiles: true })) { |
| 68 | if (match.includes("node_modules")) continue; |
| 69 | if (files.length >= MAX_RESULTS) { |
| 70 | truncated = true; |
| 71 | break; |
| 72 | } |
| 73 | files.push(relative(cwd, resolve(resolved, match))); |
| 74 | } |
| 75 | |
| 76 | files.sort(); |
| 77 | return { files, ...(truncated ? { truncated: true } : {}) }; |
| 78 | } |
| 79 | case "grep": { |
| 80 | const { pattern, path, include } = toolInputSchemas.grep.parse(input); |
| 81 | const { cwd, resolved } = resolveInsideCwd(path); |
| 82 | const args = [ |
| 83 | "-rn", |
| 84 | "--color=never", |
| 85 | "--exclude-dir=node_modules", |
| 86 | "--exclude-dir=.git", |
no test coverage detected