@travisennis/acai 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/agent/index.d.ts +119 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +406 -0
- package/dist/agent/manual-loop.d.ts +41 -0
- package/dist/agent/manual-loop.d.ts.map +1 -0
- package/dist/agent/manual-loop.js +278 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +27 -33
- package/dist/commands/add-directory-command.d.ts +3 -0
- package/dist/commands/add-directory-command.d.ts.map +1 -0
- package/dist/commands/add-directory-command.js +85 -0
- package/dist/commands/application-log-command.d.ts.map +1 -1
- package/dist/commands/application-log-command.js +34 -0
- package/dist/commands/clear-command.d.ts.map +1 -1
- package/dist/commands/clear-command.js +8 -0
- package/dist/commands/compact-command.d.ts.map +1 -1
- package/dist/commands/compact-command.js +15 -2
- package/dist/commands/context-command.d.ts +3 -0
- package/dist/commands/context-command.d.ts.map +1 -0
- package/dist/commands/context-command.js +183 -0
- package/dist/commands/copy-command.d.ts.map +1 -1
- package/dist/commands/copy-command.js +28 -0
- package/dist/commands/edit-command.d.ts.map +1 -1
- package/dist/commands/edit-command.js +33 -0
- package/dist/commands/edit-prompt-command.d.ts.map +1 -1
- package/dist/commands/edit-prompt-command.js +28 -0
- package/dist/commands/exit-command.d.ts.map +1 -1
- package/dist/commands/exit-command.js +20 -0
- package/dist/commands/files-command.d.ts.map +1 -1
- package/dist/commands/files-command.js +57 -0
- package/dist/commands/generate-rules-command.d.ts.map +1 -1
- package/dist/commands/generate-rules-command.js +311 -1
- package/dist/commands/handoff-command.d.ts +3 -0
- package/dist/commands/handoff-command.d.ts.map +1 -0
- package/dist/commands/handoff-command.js +202 -0
- package/dist/commands/health-command.d.ts.map +1 -1
- package/dist/commands/health-command.js +119 -2
- package/dist/commands/help-command.d.ts.map +1 -1
- package/dist/commands/help-command.js +28 -0
- package/dist/commands/history-command.d.ts +3 -0
- package/dist/commands/history-command.d.ts.map +1 -0
- package/dist/commands/history-command.js +534 -0
- package/dist/commands/init-command.d.ts +1 -1
- package/dist/commands/init-command.d.ts.map +1 -1
- package/dist/commands/init-command.js +55 -18
- package/dist/commands/last-log-command.d.ts.map +1 -1
- package/dist/commands/last-log-command.js +27 -0
- package/dist/commands/list-directories-command.d.ts +3 -0
- package/dist/commands/list-directories-command.d.ts.map +1 -0
- package/dist/commands/list-directories-command.js +48 -0
- package/dist/commands/list-tools-command.d.ts.map +1 -1
- package/dist/commands/list-tools-command.js +66 -3
- package/dist/commands/manager.d.ts +15 -3
- package/dist/commands/manager.d.ts.map +1 -1
- package/dist/commands/manager.js +86 -26
- package/dist/commands/model-command.d.ts +22 -0
- package/dist/commands/model-command.d.ts.map +1 -1
- package/dist/commands/model-command.js +256 -0
- package/dist/commands/paste-command.d.ts.map +1 -1
- package/dist/commands/paste-command.js +92 -0
- package/dist/commands/pickup-command.d.ts +3 -0
- package/dist/commands/pickup-command.d.ts.map +1 -0
- package/dist/commands/pickup-command.js +161 -0
- package/dist/commands/prompt-command.d.ts +1 -1
- package/dist/commands/prompt-command.d.ts.map +1 -1
- package/dist/commands/prompt-command.js +117 -2
- package/dist/commands/remove-directory-command.d.ts +3 -0
- package/dist/commands/remove-directory-command.d.ts.map +1 -0
- package/dist/commands/remove-directory-command.js +87 -0
- package/dist/commands/reset-command.d.ts +1 -1
- package/dist/commands/reset-command.d.ts.map +1 -1
- package/dist/commands/reset-command.js +13 -2
- package/dist/commands/rules-command.d.ts.map +1 -1
- package/dist/commands/rules-command.js +65 -0
- package/dist/commands/save-command.d.ts.map +1 -1
- package/dist/commands/save-command.js +12 -0
- package/dist/commands/shell-command.d.ts.map +1 -1
- package/dist/commands/shell-command.js +68 -0
- package/dist/commands/types.d.ts +9 -4
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/usage-command.d.ts.map +1 -1
- package/dist/commands/usage-command.js +22 -0
- package/dist/config.d.ts +6 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +23 -29
- package/dist/formatting.d.ts +108 -0
- package/dist/formatting.d.ts.map +1 -1
- package/dist/formatting.js +147 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +140 -38
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +47 -18
- package/dist/mentions.d.ts +2 -1
- package/dist/mentions.d.ts.map +1 -1
- package/dist/mentions.js +16 -1
- package/dist/messages.d.ts +8 -0
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +56 -19
- package/dist/middleware/cache.d.ts +3 -0
- package/dist/middleware/cache.d.ts.map +1 -0
- package/dist/middleware/cache.js +53 -0
- package/dist/middleware/index.d.ts +1 -0
- package/dist/middleware/index.d.ts.map +1 -1
- package/dist/middleware/index.js +1 -0
- package/dist/models/ai-config.d.ts +4 -2
- package/dist/models/ai-config.d.ts.map +1 -1
- package/dist/models/ai-config.js +12 -2
- package/dist/models/anthropic-provider.d.ts.map +1 -1
- package/dist/models/anthropic-provider.js +3 -60
- package/dist/models/manager.d.ts +2 -1
- package/dist/models/manager.d.ts.map +1 -1
- package/dist/models/manager.js +26 -2
- package/dist/models/openrouter-provider.d.ts +7 -14
- package/dist/models/openrouter-provider.d.ts.map +1 -1
- package/dist/models/openrouter-provider.js +114 -169
- package/dist/models/providers.d.ts +1 -1
- package/dist/models/providers.d.ts.map +1 -1
- package/dist/prompts.d.ts +1 -0
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +53 -4
- package/dist/repl/display-tool-messages.d.ts +1 -1
- package/dist/repl/display-tool-messages.d.ts.map +1 -1
- package/dist/repl/display-tool-messages.js +47 -44
- package/dist/repl/get-prompt-header.d.ts.map +1 -1
- package/dist/repl/get-prompt-header.js +1 -30
- package/dist/repl/project-status-line.d.ts +2 -0
- package/dist/repl/project-status-line.d.ts.map +1 -0
- package/dist/repl/project-status-line.js +31 -0
- package/dist/repl/prompt.d.ts +21 -0
- package/dist/repl/prompt.d.ts.map +1 -0
- package/dist/{repl-prompt.js → repl/prompt.js} +119 -22
- package/dist/repl/tool-call-repair.d.ts.map +1 -1
- package/dist/repl/tool-call-repair.js +8 -4
- package/dist/repl-new.d.ts +53 -0
- package/dist/repl-new.d.ts.map +1 -0
- package/dist/repl-new.js +374 -0
- package/dist/repl.d.ts +3 -5
- package/dist/repl.d.ts.map +1 -1
- package/dist/repl.js +74 -166
- package/dist/terminal/checkbox-prompt.d.ts.map +1 -1
- package/dist/terminal/checkbox-prompt.js +10 -4
- package/dist/terminal/index.d.ts +7 -0
- package/dist/terminal/index.d.ts.map +1 -1
- package/dist/terminal/index.js +94 -0
- package/dist/terminal/input-prompt.d.ts +2 -1
- package/dist/terminal/input-prompt.d.ts.map +1 -1
- package/dist/terminal/markdown.js +3 -0
- package/dist/terminal/search-prompt.d.ts.map +1 -1
- package/dist/terminal/search-prompt.js +11 -10
- package/dist/terminal/select-prompt.d.ts +2 -2
- package/dist/terminal/select-prompt.d.ts.map +1 -1
- package/dist/terminal/select-prompt.js +47 -39
- package/dist/tokens/threshold.d.ts +35 -0
- package/dist/tokens/threshold.d.ts.map +1 -0
- package/dist/tokens/threshold.js +85 -0
- package/dist/tools/advanced-edit-file.d.ts +69 -0
- package/dist/tools/advanced-edit-file.d.ts.map +1 -0
- package/dist/tools/advanced-edit-file.js +281 -0
- package/dist/tools/agent.d.ts +16 -5
- package/dist/tools/agent.d.ts.map +1 -1
- package/dist/tools/agent.js +71 -58
- package/dist/tools/bash-utils.d.ts +1 -1
- package/dist/tools/bash-utils.d.ts.map +1 -1
- package/dist/tools/bash-utils.js +14 -6
- package/dist/tools/bash.d.ts +21 -12
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +88 -135
- package/dist/tools/code-interpreter.d.ts +21 -9
- package/dist/tools/code-interpreter.d.ts.map +1 -1
- package/dist/tools/code-interpreter.js +138 -137
- package/dist/tools/delete-file.d.ts +17 -10
- package/dist/tools/delete-file.d.ts.map +1 -1
- package/dist/tools/delete-file.js +51 -95
- package/dist/tools/directory-tree.d.ts +17 -6
- package/dist/tools/directory-tree.d.ts.map +1 -1
- package/dist/tools/directory-tree.js +47 -49
- package/dist/tools/dynamic-tool-loader.d.ts +18 -8
- package/dist/tools/dynamic-tool-loader.d.ts.map +1 -1
- package/dist/tools/dynamic-tool-loader.js +121 -129
- package/dist/tools/dynamic-tool-parser.d.ts +1 -0
- package/dist/tools/dynamic-tool-parser.d.ts.map +1 -1
- package/dist/tools/dynamic-tool-parser.js +1 -0
- package/dist/tools/edit-file.d.ts +35 -15
- package/dist/tools/edit-file.d.ts.map +1 -1
- package/dist/tools/edit-file.js +112 -112
- package/dist/tools/filesystem-utils.d.ts +2 -1
- package/dist/tools/filesystem-utils.d.ts.map +1 -1
- package/dist/tools/filesystem-utils.js +31 -17
- package/dist/tools/glob.d.ts +36 -0
- package/dist/tools/glob.d.ts.map +1 -0
- package/dist/tools/glob.js +143 -0
- package/dist/tools/grep.d.ts +73 -12
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +413 -168
- package/dist/tools/index.d.ts +204 -124
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +242 -135
- package/dist/tools/llm-edit-fixer.d.ts +25 -0
- package/dist/tools/llm-edit-fixer.d.ts.map +1 -0
- package/dist/tools/llm-edit-fixer.js +150 -0
- package/dist/tools/move-file.d.ts +19 -7
- package/dist/tools/move-file.d.ts.map +1 -1
- package/dist/tools/move-file.js +40 -33
- package/dist/tools/read-file.d.ts +47 -9
- package/dist/tools/read-file.d.ts.map +1 -1
- package/dist/tools/read-file.js +74 -69
- package/dist/tools/read-multiple-files.d.ts +17 -6
- package/dist/tools/read-multiple-files.d.ts.map +1 -1
- package/dist/tools/read-multiple-files.js +76 -73
- package/dist/tools/save-file.d.ts +45 -12
- package/dist/tools/save-file.d.ts.map +1 -1
- package/dist/tools/save-file.js +58 -101
- package/dist/tools/think.d.ts +15 -7
- package/dist/tools/think.d.ts.map +1 -1
- package/dist/tools/think.js +30 -22
- package/dist/tools/types.d.ts +4 -10
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/types.js +9 -0
- package/dist/tools/utils.d.ts +14 -0
- package/dist/tools/utils.d.ts.map +1 -0
- package/dist/tools/utils.js +16 -0
- package/dist/tools/web-fetch.d.ts +11 -4
- package/dist/tools/web-fetch.d.ts.map +1 -1
- package/dist/tools/web-fetch.js +39 -38
- package/dist/tools/web-search.d.ts +15 -6
- package/dist/tools/web-search.d.ts.map +1 -1
- package/dist/tools/web-search.js +50 -32
- package/dist/tui/autocomplete.d.ts +44 -0
- package/dist/tui/autocomplete.d.ts.map +1 -0
- package/dist/tui/autocomplete.js +466 -0
- package/dist/tui/components/assistant-message.d.ts +18 -0
- package/dist/tui/components/assistant-message.d.ts.map +1 -0
- package/dist/tui/components/assistant-message.js +29 -0
- package/dist/tui/components/editor.d.ts +51 -0
- package/dist/tui/components/editor.d.ts.map +1 -0
- package/dist/tui/components/editor.js +758 -0
- package/dist/tui/components/footer.d.ts +24 -0
- package/dist/tui/components/footer.d.ts.map +1 -0
- package/dist/tui/components/footer.js +197 -0
- package/dist/tui/components/input.d.ts +14 -0
- package/dist/tui/components/input.d.ts.map +1 -0
- package/dist/tui/components/input.js +122 -0
- package/dist/tui/components/loader.d.ts +19 -0
- package/dist/tui/components/loader.d.ts.map +1 -0
- package/dist/tui/components/loader.js +45 -0
- package/dist/tui/components/markdown.d.ts +103 -0
- package/dist/tui/components/markdown.d.ts.map +1 -0
- package/dist/tui/components/markdown.js +533 -0
- package/dist/tui/components/modal.d.ts +40 -0
- package/dist/tui/components/modal.d.ts.map +1 -0
- package/dist/tui/components/modal.js +292 -0
- package/dist/tui/components/prompt-status.d.ts +16 -0
- package/dist/tui/components/prompt-status.d.ts.map +1 -0
- package/dist/tui/components/prompt-status.js +21 -0
- package/dist/tui/components/select-list.d.ts +22 -0
- package/dist/tui/components/select-list.d.ts.map +1 -0
- package/dist/tui/components/select-list.js +143 -0
- package/dist/tui/components/spacer.d.ts +16 -0
- package/dist/tui/components/spacer.d.ts.map +1 -0
- package/dist/tui/components/spacer.js +27 -0
- package/dist/tui/components/text.d.ts +26 -0
- package/dist/tui/components/text.d.ts.map +1 -0
- package/dist/tui/components/text.js +143 -0
- package/dist/tui/components/thinking-block.d.ts +14 -0
- package/dist/tui/components/thinking-block.d.ts.map +1 -0
- package/dist/tui/components/thinking-block.js +30 -0
- package/dist/tui/components/tool-execution.d.ts +17 -0
- package/dist/tui/components/tool-execution.d.ts.map +1 -0
- package/dist/tui/components/tool-execution.js +153 -0
- package/dist/tui/components/user-message.d.ts +9 -0
- package/dist/tui/components/user-message.d.ts.map +1 -0
- package/dist/tui/components/user-message.js +21 -0
- package/dist/tui/components/welcome.d.ts +6 -0
- package/dist/tui/components/welcome.d.ts.map +1 -0
- package/dist/tui/components/welcome.js +30 -0
- package/dist/tui/index.d.ts +14 -0
- package/dist/tui/index.d.ts.map +1 -0
- package/dist/tui/index.js +18 -0
- package/dist/tui/terminal.d.ts +37 -0
- package/dist/tui/terminal.d.ts.map +1 -0
- package/dist/tui/terminal.js +104 -0
- package/dist/tui/tui.d.ts +67 -0
- package/dist/tui/tui.d.ts.map +1 -0
- package/dist/tui/tui.js +184 -0
- package/dist/tui/utils.d.ts +19 -0
- package/dist/tui/utils.d.ts.map +1 -0
- package/dist/tui/utils.js +31 -0
- package/dist/utils/generators.d.ts +3 -0
- package/dist/utils/generators.d.ts.map +1 -0
- package/dist/utils/generators.js +25 -0
- package/dist/utils/iterables.d.ts +2 -0
- package/dist/utils/iterables.d.ts.map +1 -0
- package/dist/utils/iterables.js +6 -0
- package/package.json +16 -16
- package/dist/conversation-analyzer.d.ts +0 -11
- package/dist/conversation-analyzer.d.ts.map +0 -1
- package/dist/conversation-analyzer.js +0 -88
- package/dist/repl-prompt.d.ts +0 -15
- package/dist/repl-prompt.d.ts.map +0 -1
- package/dist/tokens/manage-output.d.ts +0 -34
- package/dist/tokens/manage-output.d.ts.map +0 -1
- package/dist/tokens/manage-output.js +0 -44
- package/dist/tool-executor.d.ts +0 -28
- package/dist/tool-executor.d.ts.map +0 -1
- package/dist/tool-executor.js +0 -74
- package/dist/tools/file-editing-utils.d.ts +0 -2
- package/dist/tools/file-editing-utils.d.ts.map +0 -1
- package/dist/tools/file-editing-utils.js +0 -135
package/dist/tools/edit-file.js
CHANGED
|
@@ -1,122 +1,71 @@
|
|
|
1
1
|
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
-
import { tool } from "ai";
|
|
3
2
|
import { createTwoFilesPatch } from "diff";
|
|
4
3
|
import { z } from "zod";
|
|
4
|
+
import { logger } from "../logger.js";
|
|
5
5
|
import style from "../terminal/style.js";
|
|
6
6
|
import { joinWorkingDir, validatePath } from "./filesystem-utils.js";
|
|
7
|
+
import { fixLlmEditWithInstruction } from "./llm-edit-fixer.js";
|
|
7
8
|
export const EditFileTool = {
|
|
8
9
|
name: "editFile",
|
|
9
10
|
};
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
const inputSchema = z.object({
|
|
12
|
+
path: z.string().describe("The path of the file to edit."),
|
|
13
|
+
edits: z.array(z.object({
|
|
14
|
+
oldText: z
|
|
15
|
+
.string()
|
|
16
|
+
.describe("Text to search for - must match exactly and enough context must be provided to uniquely match the target text. " +
|
|
17
|
+
"Special characters require JSON escaping: backticks (\\`...\\`), quotes, backslashes. " +
|
|
18
|
+
"For multi-line content, include exact newlines and indentation."),
|
|
19
|
+
newText: z.string().describe("Text to replace with"),
|
|
20
|
+
})),
|
|
21
|
+
});
|
|
22
|
+
export const createEditFileTool = async ({ workingDir, allowedDirs, modelManager, tokenTracker, enableLlmFix = true, }) => {
|
|
23
|
+
const allowedDirectory = allowedDirs ?? [workingDir];
|
|
12
24
|
return {
|
|
13
|
-
|
|
25
|
+
toolDef: {
|
|
14
26
|
description: "Make line-based edits to a text file. Each edit replaces exact line sequences " +
|
|
15
27
|
"with new content. Exact literal matching is used: no whitespace, indentation, escape, or newline normalization is applied when locating matches. " +
|
|
16
28
|
"Provide enough context so the match is unique; otherwise the operation errors. Returns a git-style diff showing the changes made. " +
|
|
17
29
|
"Only works within allowed directories. " +
|
|
18
30
|
"Note: Special characters in oldText must be properly escaped for JSON (e.g., backticks as \\`...\\`). " +
|
|
19
31
|
"Multi-line strings require exact character-by-character matching including whitespace.",
|
|
20
|
-
inputSchema
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
.string()
|
|
25
|
-
.describe("Text to search for - must match exactly and enough context must be provided to uniquely match the target text. " +
|
|
26
|
-
"Special characters require JSON escaping: backticks (\\`...\\`), quotes, backslashes. " +
|
|
27
|
-
"For multi-line content, include exact newlines and indentation."),
|
|
28
|
-
newText: z.string().describe("Text to replace with"),
|
|
29
|
-
})),
|
|
30
|
-
}),
|
|
31
|
-
execute: async ({ path, edits }, { toolCallId, abortSignal }) => {
|
|
32
|
-
// Check if execution has been aborted
|
|
32
|
+
inputSchema,
|
|
33
|
+
},
|
|
34
|
+
async *execute({ path, edits }, { toolCallId, abortSignal }) {
|
|
35
|
+
try {
|
|
33
36
|
if (abortSignal?.aborted) {
|
|
34
37
|
throw new Error("File editing aborted");
|
|
35
38
|
}
|
|
36
|
-
|
|
39
|
+
yield {
|
|
40
|
+
name: EditFileTool.name,
|
|
37
41
|
id: toolCallId,
|
|
38
42
|
event: "tool-init",
|
|
39
|
-
data:
|
|
43
|
+
data: `${style.cyan(path)}`,
|
|
44
|
+
};
|
|
45
|
+
const validPath = await validatePath(joinWorkingDir(path, workingDir), allowedDirectory, { abortSignal });
|
|
46
|
+
const result = await applyFileEdits(validPath, edits, false, abortSignal, {
|
|
47
|
+
modelManager,
|
|
48
|
+
tokenTracker,
|
|
49
|
+
enableLlmFix,
|
|
40
50
|
});
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
acceptAll: "Accept all future edits (including these)",
|
|
60
|
-
reject: "Reject these changes",
|
|
61
|
-
},
|
|
62
|
-
};
|
|
63
|
-
try {
|
|
64
|
-
userResponse = await toolExecutor.ask(ctx, { abortSignal });
|
|
65
|
-
}
|
|
66
|
-
catch (e) {
|
|
67
|
-
if (e.name === "AbortError") {
|
|
68
|
-
throw new Error("File editing aborted during user input");
|
|
69
|
-
}
|
|
70
|
-
throw e;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
const { result: userChoice, reason } = userResponse ?? {
|
|
74
|
-
result: "accept",
|
|
75
|
-
};
|
|
76
|
-
terminal.lineBreak();
|
|
77
|
-
if (userChoice === "accept-all") {
|
|
78
|
-
terminal.writeln(style.yellow("✓ Auto-accept mode enabled for all edits"));
|
|
79
|
-
terminal.lineBreak();
|
|
80
|
-
}
|
|
81
|
-
if (userChoice === "accept" || userChoice === "accept-all") {
|
|
82
|
-
const finalEdits = await applyFileEdits(validPath, edits, false, abortSignal);
|
|
83
|
-
// Send completion message indicating success
|
|
84
|
-
sendData?.({
|
|
85
|
-
id: toolCallId,
|
|
86
|
-
event: "tool-completion",
|
|
87
|
-
data: "Edits accepted and applied successfully.",
|
|
88
|
-
});
|
|
89
|
-
return finalEdits;
|
|
90
|
-
}
|
|
91
|
-
terminal.lineBreak();
|
|
92
|
-
// Send completion message indicating rejection
|
|
93
|
-
const rejectionReason = reason || "No reason provided";
|
|
94
|
-
sendData?.({
|
|
95
|
-
id: toolCallId,
|
|
96
|
-
event: "tool-completion",
|
|
97
|
-
data: `Edits rejected by user. Reason: ${rejectionReason}`,
|
|
98
|
-
});
|
|
99
|
-
return `The user rejected these changes. Reason: ${rejectionReason}`;
|
|
100
|
-
}
|
|
101
|
-
const finalEdits = await applyFileEdits(validPath, edits, false, abortSignal);
|
|
102
|
-
// Send completion message indicating success
|
|
103
|
-
sendData?.({
|
|
104
|
-
id: toolCallId,
|
|
105
|
-
event: "tool-completion",
|
|
106
|
-
data: "Edits accepted and applied successfully.",
|
|
107
|
-
});
|
|
108
|
-
return finalEdits;
|
|
109
|
-
}
|
|
110
|
-
catch (error) {
|
|
111
|
-
sendData?.({
|
|
112
|
-
event: "tool-error",
|
|
113
|
-
id: toolCallId,
|
|
114
|
-
data: `Failed to edit file: ${error.message}`,
|
|
115
|
-
});
|
|
116
|
-
return `Failed to edit file: ${error.message}`;
|
|
117
|
-
}
|
|
118
|
-
},
|
|
119
|
-
}),
|
|
51
|
+
yield {
|
|
52
|
+
name: EditFileTool.name,
|
|
53
|
+
id: toolCallId,
|
|
54
|
+
event: "tool-completion",
|
|
55
|
+
data: `Applied ${edits.length} edits`,
|
|
56
|
+
};
|
|
57
|
+
yield result;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
yield {
|
|
61
|
+
name: EditFileTool.name,
|
|
62
|
+
event: "tool-error",
|
|
63
|
+
id: toolCallId,
|
|
64
|
+
data: error.message,
|
|
65
|
+
};
|
|
66
|
+
yield `Failed to edit file: ${error.message}`;
|
|
67
|
+
}
|
|
68
|
+
},
|
|
120
69
|
};
|
|
121
70
|
};
|
|
122
71
|
// file editing and diffing utilities
|
|
@@ -129,7 +78,7 @@ function createUnifiedDiff(originalContent, newContent, filepath = "file") {
|
|
|
129
78
|
const normalizedNew = normalizeLineEndings(newContent);
|
|
130
79
|
return createTwoFilesPatch(filepath, filepath, normalizedOriginal, normalizedNew, "original", "modified");
|
|
131
80
|
}
|
|
132
|
-
export async function applyFileEdits(filePath, edits, dryRun = false, abortSignal) {
|
|
81
|
+
export async function applyFileEdits(filePath, edits, dryRun = false, abortSignal, options) {
|
|
133
82
|
if (abortSignal?.aborted) {
|
|
134
83
|
throw new Error("File edit operation aborted");
|
|
135
84
|
}
|
|
@@ -141,26 +90,19 @@ export async function applyFileEdits(filePath, edits, dryRun = false, abortSigna
|
|
|
141
90
|
if (edits.find((edit) => edit.oldText.length === 0)) {
|
|
142
91
|
throw new Error("Invalid oldText in edit. The value of oldText must be at least one character");
|
|
143
92
|
}
|
|
144
|
-
// Apply edits sequentially using
|
|
93
|
+
// Apply edits sequentially using literal matches (allow multiple matches)
|
|
145
94
|
let modifiedContent = originalContent;
|
|
146
95
|
for (const edit of edits) {
|
|
147
96
|
if (abortSignal?.aborted) {
|
|
148
97
|
throw new Error("File edit operation aborted during processing");
|
|
149
98
|
}
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (firstIndex === -1) {
|
|
154
|
-
throw new Error("oldText not found in content");
|
|
99
|
+
const result = await applyEditWithLlmFix(edit, modifiedContent, abortSignal, options);
|
|
100
|
+
if (result.success) {
|
|
101
|
+
modifiedContent = result.content;
|
|
155
102
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
throw new Error("oldText found multiple times and requires more code context to uniquely identify the intended match");
|
|
103
|
+
else {
|
|
104
|
+
throw new Error("oldText not found in content");
|
|
159
105
|
}
|
|
160
|
-
modifiedContent =
|
|
161
|
-
modifiedContent.slice(0, firstIndex) +
|
|
162
|
-
newText +
|
|
163
|
-
modifiedContent.slice(firstIndex + oldText.length);
|
|
164
106
|
}
|
|
165
107
|
// Create unified diff (createUnifiedDiff normalizes line endings internally for diffing)
|
|
166
108
|
const diff = createUnifiedDiff(originalContent, modifiedContent, filePath);
|
|
@@ -182,3 +124,61 @@ export async function applyFileEdits(filePath, edits, dryRun = false, abortSigna
|
|
|
182
124
|
}
|
|
183
125
|
return formattedDiff;
|
|
184
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Applies a single edit with LLM fix fallback
|
|
129
|
+
*/
|
|
130
|
+
async function applyEditWithLlmFix(edit, content, abortSignal, options) {
|
|
131
|
+
const { oldText, newText } = edit;
|
|
132
|
+
// Try the original edit first
|
|
133
|
+
const originalResult = applyLiteralEdit(content, oldText, newText);
|
|
134
|
+
if (originalResult.matchCount > 0) {
|
|
135
|
+
return { success: true, content: originalResult.content };
|
|
136
|
+
}
|
|
137
|
+
// If LLM fix is enabled and dependencies are available, try to fix the edit
|
|
138
|
+
if (options?.enableLlmFix !== false &&
|
|
139
|
+
options?.modelManager &&
|
|
140
|
+
options?.tokenTracker) {
|
|
141
|
+
try {
|
|
142
|
+
const fixedEdit = await fixLlmEditWithInstruction(options.instruction, oldText, newText, "oldText not found in content", content, options.modelManager ?? undefined, abortSignal);
|
|
143
|
+
if (fixedEdit && !fixedEdit.noChangesRequired) {
|
|
144
|
+
// Retry the edit with the corrected search string
|
|
145
|
+
const correctedResult = applyLiteralEdit(content, fixedEdit.search, fixedEdit.replace);
|
|
146
|
+
if (correctedResult.matchCount > 0) {
|
|
147
|
+
return { success: true, content: correctedResult.content };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else if (fixedEdit?.noChangesRequired) {
|
|
151
|
+
// No changes required, skip this edit
|
|
152
|
+
return { success: true, content };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch (llmError) {
|
|
156
|
+
// If LLM fix fails, fall back to original error
|
|
157
|
+
logger.warn(llmError, "LLM edit fix failed:");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return { success: false, content };
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Applies a literal search and replace operation
|
|
164
|
+
*/
|
|
165
|
+
function applyLiteralEdit(content, search, replace) {
|
|
166
|
+
let modifiedContent = content;
|
|
167
|
+
let matchCount = 0;
|
|
168
|
+
let currentIndex = 0;
|
|
169
|
+
while (currentIndex < modifiedContent.length) {
|
|
170
|
+
const matchIndex = modifiedContent.indexOf(search, currentIndex);
|
|
171
|
+
if (matchIndex === -1) {
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
matchCount++;
|
|
175
|
+
// Apply the replacement
|
|
176
|
+
modifiedContent =
|
|
177
|
+
modifiedContent.slice(0, matchIndex) +
|
|
178
|
+
replace +
|
|
179
|
+
modifiedContent.slice(matchIndex + search.length);
|
|
180
|
+
// Move current index past the replacement
|
|
181
|
+
currentIndex = matchIndex + replace.length;
|
|
182
|
+
}
|
|
183
|
+
return { matchCount, content: modifiedContent };
|
|
184
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare function joinWorkingDir(userPath: string, workingDir: string): string;
|
|
2
2
|
export declare function isPathWithinBaseDir(requestedPath: string, baseDir: string): boolean;
|
|
3
|
-
export declare function
|
|
3
|
+
export declare function isPathWithinAllowedDirs(requestedPath: string, allowedDirs: string[]): boolean;
|
|
4
|
+
export declare function validatePath(requestedPath: string, allowedDirectory: string | string[], options?: {
|
|
4
5
|
requireExistence?: boolean;
|
|
5
6
|
abortSignal?: AbortSignal;
|
|
6
7
|
}): Promise<string>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filesystem-utils.d.ts","sourceRoot":"","sources":["../../source/tools/filesystem-utils.ts"],"names":[],"mappings":"AAWA,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAK3E;AAUD,wBAAgB,mBAAmB,CACjC,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,GACd,OAAO,CAsBT;AAGD,wBAAsB,YAAY,CAChC,aAAa,EAAE,MAAM,EACrB,gBAAgB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"filesystem-utils.d.ts","sourceRoot":"","sources":["../../source/tools/filesystem-utils.ts"],"names":[],"mappings":"AAWA,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAK3E;AAUD,wBAAgB,mBAAmB,CACjC,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,GACd,OAAO,CAsBT;AAGD,wBAAgB,uBAAuB,CACrC,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,EAAE,GACpB,OAAO,CAIT;AAGD,wBAAsB,YAAY,CAChC,aAAa,EAAE,MAAM,EACrB,gBAAgB,EAAE,MAAM,GAAG,MAAM,EAAE,EACnC,OAAO,GAAE;IAAE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,WAAW,CAAA;CAAO,GACtE,OAAO,CAAC,MAAM,CAAC,CAwHjB"}
|
|
@@ -42,6 +42,10 @@ export function isPathWithinBaseDir(requestedPath, baseDir) {
|
|
|
42
42
|
const rel = path.relative(baseReal, target);
|
|
43
43
|
return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
|
|
44
44
|
}
|
|
45
|
+
// Check if path is within any of the allowed directories
|
|
46
|
+
export function isPathWithinAllowedDirs(requestedPath, allowedDirs) {
|
|
47
|
+
return allowedDirs.some((allowedDir) => isPathWithinBaseDir(requestedPath, allowedDir));
|
|
48
|
+
}
|
|
45
49
|
// Security utilities
|
|
46
50
|
export async function validatePath(requestedPath, allowedDirectory, options = {}) {
|
|
47
51
|
const { requireExistence = true, abortSignal } = options;
|
|
@@ -53,27 +57,37 @@ export async function validatePath(requestedPath, allowedDirectory, options = {}
|
|
|
53
57
|
? path.resolve(expandedPath)
|
|
54
58
|
: path.resolve(process.cwd(), expandedPath);
|
|
55
59
|
const normalizedRequested = normalizePath(absolute);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
// Handle both single directory and array of directories
|
|
61
|
+
const allowedDirectories = Array.isArray(allowedDirectory)
|
|
62
|
+
? allowedDirectory
|
|
63
|
+
: [allowedDirectory];
|
|
64
|
+
// Resolve and normalize all allowed directories
|
|
65
|
+
const normalizedAllowedDirs = await Promise.all(allowedDirectories.map(async (dir) => {
|
|
66
|
+
let normalizedDir = normalizePath(path.resolve(dir));
|
|
67
|
+
// Try to resolve real path for allowedDirectory when it exists to handle symlinked roots
|
|
68
|
+
try {
|
|
69
|
+
const stats = await fs.stat(normalizedDir);
|
|
70
|
+
if (stats.isDirectory()) {
|
|
71
|
+
const allowedReal = await fs.realpath(normalizedDir);
|
|
72
|
+
normalizedDir = normalizePath(allowedReal);
|
|
73
|
+
}
|
|
63
74
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
75
|
+
catch (_err) {
|
|
76
|
+
// If allowedDirectory doesn't exist, keep normalizedDir as-is
|
|
77
|
+
}
|
|
78
|
+
return normalizedDir;
|
|
79
|
+
}));
|
|
80
|
+
// Helper to check if a path is within any allowed directory using path.relative
|
|
69
81
|
const isWithinAllowed = (targetPath) => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
82
|
+
return normalizedAllowedDirs.some((normalizedAllowed) => {
|
|
83
|
+
const rel = path.relative(normalizedAllowed, targetPath);
|
|
84
|
+
// Allow the allowed directory itself (rel === "") and any descendant paths
|
|
85
|
+
return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
|
|
86
|
+
});
|
|
73
87
|
};
|
|
74
|
-
// Check intended path is within allowed directory
|
|
88
|
+
// Check intended path is within any allowed directory
|
|
75
89
|
if (!isWithinAllowed(normalizedRequested)) {
|
|
76
|
-
throw new Error(`Access denied - path outside allowed directories: ${absolute} not in ${
|
|
90
|
+
throw new Error(`Access denied - path outside allowed directories: ${absolute} not in any of ${allowedDirectories.join(", ")}`);
|
|
77
91
|
}
|
|
78
92
|
let validatedPath;
|
|
79
93
|
// Try to resolve real path for existing targets to handle symlinks safely
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ToolCallOptions } from "ai";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { TokenCounter } from "../tokens/counter.ts";
|
|
4
|
+
import type { ToolResult } from "./types.ts";
|
|
5
|
+
export declare const GlobTool: {
|
|
6
|
+
name: "globFiles";
|
|
7
|
+
};
|
|
8
|
+
export declare const inputSchema: z.ZodObject<{
|
|
9
|
+
patterns: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
|
|
10
|
+
path: z.ZodString;
|
|
11
|
+
gitignore: z.ZodNullable<z.ZodCoercedBoolean<unknown>>;
|
|
12
|
+
recursive: z.ZodNullable<z.ZodCoercedBoolean<unknown>>;
|
|
13
|
+
expandDirectories: z.ZodNullable<z.ZodCoercedBoolean<unknown>>;
|
|
14
|
+
ignoreFiles: z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
15
|
+
cwd: z.ZodNullable<z.ZodCoercedString<unknown>>;
|
|
16
|
+
}, z.core.$strip>;
|
|
17
|
+
type GlobInputSchema = z.infer<typeof inputSchema>;
|
|
18
|
+
export declare const createGlobTool: (options: {
|
|
19
|
+
tokenCounter: TokenCounter;
|
|
20
|
+
}) => {
|
|
21
|
+
toolDef: {
|
|
22
|
+
description: string;
|
|
23
|
+
inputSchema: z.ZodObject<{
|
|
24
|
+
patterns: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
|
|
25
|
+
path: z.ZodString;
|
|
26
|
+
gitignore: z.ZodNullable<z.ZodCoercedBoolean<unknown>>;
|
|
27
|
+
recursive: z.ZodNullable<z.ZodCoercedBoolean<unknown>>;
|
|
28
|
+
expandDirectories: z.ZodNullable<z.ZodCoercedBoolean<unknown>>;
|
|
29
|
+
ignoreFiles: z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
30
|
+
cwd: z.ZodNullable<z.ZodCoercedString<unknown>>;
|
|
31
|
+
}, z.core.$strip>;
|
|
32
|
+
};
|
|
33
|
+
execute({ patterns, path, gitignore, recursive, expandDirectories, ignoreFiles, cwd, }: GlobInputSchema, { toolCallId, abortSignal }: ToolCallOptions): AsyncGenerator<ToolResult>;
|
|
34
|
+
};
|
|
35
|
+
export {};
|
|
36
|
+
//# sourceMappingURL=glob.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glob.d.ts","sourceRoot":"","sources":["../../source/tools/glob.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,IAAI,CAAC;AAC1C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,eAAO,MAAM,QAAQ;;CAEpB,CAAC;AAEF,eAAO,MAAM,WAAW;;;;;;;;iBA2BtB,CAAC;AAEH,KAAK,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEnD,eAAO,MAAM,cAAc,GAAI,SAAS;IAAE,YAAY,EAAE,YAAY,CAAA;CAAE;;;;;;;;;;;;;4FAkB7D,eAAe,+BACW,eAAe,GAC3C,cAAc,CAAC,UAAU,CAAC;CA2HhC,CAAC"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as nodePath from "node:path";
|
|
3
|
+
import { inspect } from "node:util";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import style from "../terminal/style.js";
|
|
6
|
+
import { manageTokenLimit } from "../tokens/threshold.js";
|
|
7
|
+
import { glob } from "../utils/glob.js";
|
|
8
|
+
export const GlobTool = {
|
|
9
|
+
name: "globFiles",
|
|
10
|
+
};
|
|
11
|
+
export const inputSchema = z.object({
|
|
12
|
+
patterns: z
|
|
13
|
+
.union([z.string(), z.array(z.string())])
|
|
14
|
+
.describe("Glob patterns to search for (e.g., '*.ts', '**/*.test.ts', 'src/**/*.js')"),
|
|
15
|
+
path: z.string().describe("Base directory to search in"),
|
|
16
|
+
gitignore: z.coerce
|
|
17
|
+
.boolean()
|
|
18
|
+
.nullable()
|
|
19
|
+
.describe("Respect ignore patterns in .gitignore files. (default: true)"),
|
|
20
|
+
recursive: z.coerce
|
|
21
|
+
.boolean()
|
|
22
|
+
.nullable()
|
|
23
|
+
.describe("Search recursively. (default: true)"),
|
|
24
|
+
expandDirectories: z.coerce
|
|
25
|
+
.boolean()
|
|
26
|
+
.nullable()
|
|
27
|
+
.describe("Automatically expand directories to files. (default: true)"),
|
|
28
|
+
ignoreFiles: z
|
|
29
|
+
.union([z.string(), z.array(z.string())])
|
|
30
|
+
.nullable()
|
|
31
|
+
.describe("Glob patterns to look for ignore files. (default: undefined)"),
|
|
32
|
+
cwd: z.coerce
|
|
33
|
+
.string()
|
|
34
|
+
.nullable()
|
|
35
|
+
.describe("Current working directory override. (default: process.cwd())"),
|
|
36
|
+
});
|
|
37
|
+
export const createGlobTool = (options) => {
|
|
38
|
+
const { tokenCounter } = options;
|
|
39
|
+
return {
|
|
40
|
+
toolDef: {
|
|
41
|
+
description: "Search for files using glob patterns (e.g., `*.ts`, `**/*.test.ts`, `src/**/*.js`). Uses the fast-glob library with support for gitignore, recursive searching, and directory expansion.",
|
|
42
|
+
inputSchema,
|
|
43
|
+
},
|
|
44
|
+
async *execute({ patterns, path, gitignore, recursive, expandDirectories, ignoreFiles, cwd, }, { toolCallId, abortSignal }) {
|
|
45
|
+
// Check if execution has been aborted
|
|
46
|
+
if (abortSignal?.aborted) {
|
|
47
|
+
throw new Error("Glob search aborted");
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const patternArray = Array.isArray(patterns) ? patterns : [patterns];
|
|
51
|
+
yield {
|
|
52
|
+
name: GlobTool.name,
|
|
53
|
+
event: "tool-init",
|
|
54
|
+
id: toolCallId,
|
|
55
|
+
data: `${style.cyan(inspect(patternArray))} in ${style.cyan(path)}`,
|
|
56
|
+
};
|
|
57
|
+
// Build glob options
|
|
58
|
+
const globOptions = {
|
|
59
|
+
cwd: cwd || process.cwd(),
|
|
60
|
+
};
|
|
61
|
+
if (gitignore !== null) {
|
|
62
|
+
globOptions["gitignore"] = gitignore;
|
|
63
|
+
}
|
|
64
|
+
if (recursive !== null) {
|
|
65
|
+
globOptions["recursive"] = recursive;
|
|
66
|
+
}
|
|
67
|
+
if (expandDirectories !== null) {
|
|
68
|
+
globOptions["expandDirectories"] = expandDirectories;
|
|
69
|
+
}
|
|
70
|
+
if (ignoreFiles !== null) {
|
|
71
|
+
globOptions["ignoreFiles"] = ignoreFiles;
|
|
72
|
+
}
|
|
73
|
+
// Execute glob search
|
|
74
|
+
const matchingFiles = await glob(patternArray, {
|
|
75
|
+
...globOptions,
|
|
76
|
+
cwd: path,
|
|
77
|
+
});
|
|
78
|
+
// Get file stats and sort by recency then alphabetically
|
|
79
|
+
const filesWithStats = await Promise.all(matchingFiles.map(async (filePath) => {
|
|
80
|
+
const fullPath = nodePath.join(path, filePath);
|
|
81
|
+
try {
|
|
82
|
+
const stats = await fs.promises.stat(fullPath);
|
|
83
|
+
return {
|
|
84
|
+
path: filePath,
|
|
85
|
+
mtime: stats.mtime,
|
|
86
|
+
isRecent: Date.now() - stats.mtime.getTime() < 7 * 24 * 60 * 60 * 1000, // 7 days
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// If stat fails, treat as old file
|
|
91
|
+
return {
|
|
92
|
+
path: filePath,
|
|
93
|
+
mtime: new Date(0),
|
|
94
|
+
isRecent: false,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}));
|
|
98
|
+
// Sort files: recent files first (newest to oldest), then older files alphabetically
|
|
99
|
+
const sortedFiles = filesWithStats
|
|
100
|
+
.sort((a, b) => {
|
|
101
|
+
// Recent files come first
|
|
102
|
+
if (a.isRecent && !b.isRecent)
|
|
103
|
+
return -1;
|
|
104
|
+
if (!a.isRecent && b.isRecent)
|
|
105
|
+
return 1;
|
|
106
|
+
// Both recent: sort by modification time (newest first)
|
|
107
|
+
if (a.isRecent && b.isRecent) {
|
|
108
|
+
return b.mtime.getTime() - a.mtime.getTime();
|
|
109
|
+
}
|
|
110
|
+
// Both old: sort alphabetically by path
|
|
111
|
+
return a.path.localeCompare(b.path);
|
|
112
|
+
})
|
|
113
|
+
.map((file) => file.path);
|
|
114
|
+
// Format results
|
|
115
|
+
const resultContent = sortedFiles.length > 0
|
|
116
|
+
? sortedFiles.join("\n")
|
|
117
|
+
: "No files found matching the specified patterns.";
|
|
118
|
+
const result = await manageTokenLimit(resultContent, tokenCounter, "Glob", "Use more specific glob patterns or recursive=false to reduce matches");
|
|
119
|
+
let completionMessage = `Found ${style.cyan(sortedFiles.length)} files.`;
|
|
120
|
+
if (result.truncated) {
|
|
121
|
+
completionMessage += ` ${result.content}`;
|
|
122
|
+
}
|
|
123
|
+
completionMessage += ` (${result.tokenCount} tokens)`;
|
|
124
|
+
yield {
|
|
125
|
+
name: GlobTool.name,
|
|
126
|
+
event: "tool-completion",
|
|
127
|
+
id: toolCallId,
|
|
128
|
+
data: completionMessage,
|
|
129
|
+
};
|
|
130
|
+
yield result.content;
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
yield {
|
|
134
|
+
name: GlobTool.name,
|
|
135
|
+
event: "tool-error",
|
|
136
|
+
id: toolCallId,
|
|
137
|
+
data: error.message,
|
|
138
|
+
};
|
|
139
|
+
yield error.message;
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
};
|
package/dist/tools/grep.d.ts
CHANGED
|
@@ -1,22 +1,40 @@
|
|
|
1
|
+
import type { ToolCallOptions } from "ai";
|
|
2
|
+
import { z } from "zod";
|
|
1
3
|
import type { TokenCounter } from "../tokens/counter.ts";
|
|
2
|
-
import type {
|
|
4
|
+
import type { ToolResult } from "./types.ts";
|
|
3
5
|
export declare const GrepTool: {
|
|
4
6
|
name: "grepFiles";
|
|
5
7
|
};
|
|
8
|
+
declare const inputSchema: z.ZodObject<{
|
|
9
|
+
pattern: z.ZodString;
|
|
10
|
+
path: z.ZodString;
|
|
11
|
+
recursive: z.ZodNullable<z.ZodCoercedBoolean<unknown>>;
|
|
12
|
+
ignoreCase: z.ZodNullable<z.ZodCoercedBoolean<unknown>>;
|
|
13
|
+
filePattern: z.ZodNullable<z.ZodCoercedString<unknown>>;
|
|
14
|
+
contextLines: z.ZodNullable<z.ZodCoercedNumber<unknown>>;
|
|
15
|
+
searchIgnored: z.ZodNullable<z.ZodCoercedBoolean<unknown>>;
|
|
16
|
+
literal: z.ZodNullable<z.ZodCoercedBoolean<unknown>>;
|
|
17
|
+
maxResults: z.ZodNullable<z.ZodCoercedNumber<unknown>>;
|
|
18
|
+
}, z.core.$strip>;
|
|
19
|
+
type GrepInputSchema = z.infer<typeof inputSchema>;
|
|
6
20
|
export declare const createGrepTool: (options: {
|
|
7
|
-
sendData?: SendData | undefined;
|
|
8
21
|
tokenCounter: TokenCounter;
|
|
9
22
|
}) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
toolDef: {
|
|
24
|
+
description: string;
|
|
25
|
+
inputSchema: z.ZodObject<{
|
|
26
|
+
pattern: z.ZodString;
|
|
27
|
+
path: z.ZodString;
|
|
28
|
+
recursive: z.ZodNullable<z.ZodCoercedBoolean<unknown>>;
|
|
29
|
+
ignoreCase: z.ZodNullable<z.ZodCoercedBoolean<unknown>>;
|
|
30
|
+
filePattern: z.ZodNullable<z.ZodCoercedString<unknown>>;
|
|
31
|
+
contextLines: z.ZodNullable<z.ZodCoercedNumber<unknown>>;
|
|
32
|
+
searchIgnored: z.ZodNullable<z.ZodCoercedBoolean<unknown>>;
|
|
33
|
+
literal: z.ZodNullable<z.ZodCoercedBoolean<unknown>>;
|
|
34
|
+
maxResults: z.ZodNullable<z.ZodCoercedNumber<unknown>>;
|
|
35
|
+
}, z.core.$strip>;
|
|
36
|
+
};
|
|
37
|
+
execute({ pattern, path, recursive, ignoreCase, filePattern, contextLines, searchIgnored, literal, maxResults, }: GrepInputSchema, { toolCallId, abortSignal }: ToolCallOptions): AsyncGenerator<ToolResult>;
|
|
20
38
|
};
|
|
21
39
|
interface GrepOptions {
|
|
22
40
|
recursive?: boolean | null;
|
|
@@ -25,7 +43,9 @@ interface GrepOptions {
|
|
|
25
43
|
contextLines?: number | null;
|
|
26
44
|
searchIgnored?: boolean | null;
|
|
27
45
|
literal?: boolean | null;
|
|
46
|
+
maxResults?: number | null;
|
|
28
47
|
}
|
|
48
|
+
export declare function likelyUnbalancedRegex(pattern: string): boolean;
|
|
29
49
|
/**
|
|
30
50
|
* Search files for patterns using ripgrep
|
|
31
51
|
*
|
|
@@ -35,5 +55,46 @@ interface GrepOptions {
|
|
|
35
55
|
* @returns The result of the grep command
|
|
36
56
|
*/
|
|
37
57
|
export declare function buildGrepCommand(pattern: string, path: string, options?: GrepOptions): string;
|
|
58
|
+
export interface ParsedMatch {
|
|
59
|
+
file?: string;
|
|
60
|
+
line: number;
|
|
61
|
+
content: string;
|
|
62
|
+
isMatch: boolean;
|
|
63
|
+
isContext?: boolean;
|
|
64
|
+
}
|
|
65
|
+
interface GrepResult {
|
|
66
|
+
rawOutput: string;
|
|
67
|
+
parsedMatches: ParsedMatch[];
|
|
68
|
+
matchCount: number;
|
|
69
|
+
displayedCount?: number;
|
|
70
|
+
contextCount: number;
|
|
71
|
+
hasMatches: boolean;
|
|
72
|
+
isTruncated?: boolean;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Parse ripgrep output and extract structured match information
|
|
76
|
+
*/
|
|
77
|
+
export declare function parseRipgrepOutput(content: string): ParsedMatch[];
|
|
78
|
+
/**
|
|
79
|
+
* Count actual matches (excluding context lines)
|
|
80
|
+
*/
|
|
81
|
+
export declare function countActualMatches(parsed: ParsedMatch[]): number;
|
|
82
|
+
/**
|
|
83
|
+
* Count context lines
|
|
84
|
+
*/
|
|
85
|
+
export declare function countContextLines(parsed: ParsedMatch[]): number;
|
|
86
|
+
/**
|
|
87
|
+
* Truncate matches to a maximum number of results
|
|
88
|
+
*/
|
|
89
|
+
export declare function truncateMatches(matches: ParsedMatch[], maxResults: number | null | undefined): {
|
|
90
|
+
truncated: ParsedMatch[];
|
|
91
|
+
isTruncated: boolean;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Extract matches from content (backwards compatibility wrapper)
|
|
95
|
+
*/
|
|
96
|
+
export declare function extractMatches(content: string): string[];
|
|
97
|
+
export declare function grepFiles(pattern: string, path: string, options?: GrepOptions): string;
|
|
98
|
+
export declare function grepFilesStructured(pattern: string, path: string, options?: GrepOptions): GrepResult;
|
|
38
99
|
export {};
|
|
39
100
|
//# sourceMappingURL=grep.d.ts.map
|