@travisennis/acai 0.0.4 → 0.0.5
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 +225 -6
- package/dist/api/exa/index.d.ts +177 -0
- package/dist/api/exa/index.d.ts.map +1 -0
- package/dist/api/exa/index.js +439 -0
- package/dist/cli.d.ts +3 -2
- package/dist/cli.d.ts.map +1 -0
- package/dist/commands/application-log-command.d.ts +1 -0
- package/dist/commands/application-log-command.d.ts.map +1 -0
- package/dist/commands/application-log-command.js +5 -3
- package/dist/commands/clear-command.d.ts +1 -0
- package/dist/commands/clear-command.d.ts.map +1 -0
- package/dist/commands/clear-command.js +2 -3
- package/dist/commands/compact-command.d.ts +1 -0
- package/dist/commands/compact-command.d.ts.map +1 -0
- package/dist/commands/compact-command.js +1 -1
- package/dist/commands/copy-command.d.ts +1 -0
- package/dist/commands/copy-command.d.ts.map +1 -0
- package/dist/commands/copy-command.js +3 -2
- package/dist/commands/edit-command.d.ts +1 -0
- package/dist/commands/edit-command.d.ts.map +1 -0
- package/dist/commands/edit-command.js +7 -5
- package/dist/commands/edit-prompt-command.d.ts +2 -1
- package/dist/commands/edit-prompt-command.d.ts.map +1 -0
- package/dist/commands/edit-prompt-command.js +15 -7
- package/dist/commands/exit-command.d.ts +13 -2
- package/dist/commands/exit-command.d.ts.map +1 -0
- package/dist/commands/exit-command.js +14 -2
- package/dist/commands/files-command.d.ts +1 -0
- package/dist/commands/files-command.d.ts.map +1 -0
- package/dist/commands/files-command.js +9 -8
- package/dist/commands/generate-rules-command.d.ts +1 -0
- package/dist/commands/generate-rules-command.d.ts.map +1 -0
- package/dist/commands/generate-rules-command.js +4 -3
- package/dist/commands/health-command.d.ts +3 -1
- package/dist/commands/health-command.d.ts.map +1 -0
- package/dist/commands/health-command.js +42 -5
- package/dist/commands/help-command.d.ts +1 -0
- package/dist/commands/help-command.d.ts.map +1 -0
- package/dist/commands/help-command.js +2 -3
- package/dist/commands/init-command.d.ts +1 -0
- package/dist/commands/init-command.d.ts.map +1 -0
- package/dist/commands/init-command.js +1 -2
- package/dist/commands/last-log-command.d.ts +1 -0
- package/dist/commands/last-log-command.d.ts.map +1 -0
- package/dist/commands/last-log-command.js +12 -17
- package/dist/commands/list-tools-command.d.ts +3 -0
- package/dist/commands/list-tools-command.d.ts.map +1 -0
- package/dist/commands/list-tools-command.js +61 -0
- package/dist/commands/manager.d.ts +7 -2
- package/dist/commands/manager.d.ts.map +1 -0
- package/dist/commands/manager.js +43 -6
- package/dist/commands/model-command.d.ts +1 -0
- package/dist/commands/model-command.d.ts.map +1 -0
- package/dist/commands/model-command.js +5 -5
- package/dist/commands/paste-command.d.ts +1 -0
- package/dist/commands/paste-command.d.ts.map +1 -0
- package/dist/commands/paste-command.js +6 -5
- package/dist/commands/prompt-command.d.ts +2 -1
- package/dist/commands/prompt-command.d.ts.map +1 -0
- package/dist/commands/prompt-command.js +62 -8
- package/dist/commands/reset-command.d.ts +1 -0
- package/dist/commands/reset-command.d.ts.map +1 -0
- package/dist/commands/reset-command.js +1 -1
- package/dist/commands/rules-command.d.ts +1 -0
- package/dist/commands/rules-command.d.ts.map +1 -0
- package/dist/commands/rules-command.js +5 -3
- package/dist/commands/save-command.d.ts +1 -0
- package/dist/commands/save-command.d.ts.map +1 -0
- package/dist/commands/save-command.js +1 -1
- package/dist/commands/shell-command.d.ts +3 -0
- package/dist/commands/shell-command.d.ts.map +1 -0
- package/dist/commands/shell-command.js +60 -0
- package/dist/commands/types.d.ts +9 -6
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/usage-command.d.ts +1 -0
- package/dist/commands/usage-command.d.ts.map +1 -0
- package/dist/commands/usage-command.js +2 -3
- package/dist/config.d.ts +22 -34
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +61 -15
- package/dist/conversation-analyzer.d.ts +2 -1
- package/dist/conversation-analyzer.d.ts.map +1 -0
- package/dist/dedent.d.ts +1 -0
- package/dist/dedent.d.ts.map +1 -0
- package/dist/execution/index.d.ts +112 -0
- package/dist/execution/index.d.ts.map +1 -0
- package/dist/execution/index.js +432 -0
- package/dist/formatting.d.ts +2 -13
- package/dist/formatting.d.ts.map +1 -0
- package/dist/formatting.js +5 -64
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -4
- package/dist/logger.d.ts +1 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/mentions.d.ts +4 -0
- package/dist/mentions.d.ts.map +1 -0
- package/dist/mentions.js +42 -10
- package/dist/messages.d.ts +8 -20
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +33 -53
- package/dist/middleware/audit-message.d.ts +1 -0
- package/dist/middleware/audit-message.d.ts.map +1 -0
- package/dist/middleware/index.d.ts +1 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/rate-limit.d.ts +1 -0
- package/dist/middleware/rate-limit.d.ts.map +1 -0
- package/dist/models/ai-config.d.ts +1 -0
- package/dist/models/ai-config.d.ts.map +1 -0
- package/dist/models/anthropic-provider.d.ts +1 -0
- package/dist/models/anthropic-provider.d.ts.map +1 -0
- package/dist/models/deepseek-provider.d.ts +1 -0
- package/dist/models/deepseek-provider.d.ts.map +1 -0
- package/dist/models/google-provider.d.ts +1 -0
- package/dist/models/google-provider.d.ts.map +1 -0
- package/dist/models/groq-provider.d.ts +20 -0
- package/dist/models/groq-provider.d.ts.map +1 -0
- package/dist/models/groq-provider.js +31 -0
- package/dist/models/manager.d.ts +1 -0
- package/dist/models/manager.d.ts.map +1 -0
- package/dist/models/openai-provider.d.ts +2 -1
- package/dist/models/openai-provider.d.ts.map +1 -0
- package/dist/models/openrouter-provider.d.ts +31 -22
- package/dist/models/openrouter-provider.d.ts.map +1 -0
- package/dist/models/openrouter-provider.js +115 -1
- package/dist/models/providers.d.ts +4 -5
- package/dist/models/providers.d.ts.map +1 -0
- package/dist/models/providers.js +7 -3
- package/dist/models/xai-provider.d.ts +1 -0
- package/dist/models/xai-provider.d.ts.map +1 -0
- package/dist/parsing.d.ts +2 -1
- package/dist/parsing.d.ts.map +1 -0
- package/dist/prompts/manager.d.ts +14 -2
- package/dist/prompts/manager.d.ts.map +1 -0
- package/dist/prompts.d.ts +1 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +15 -11
- package/dist/repl/display-tool-messages.d.ts +4 -0
- package/dist/repl/display-tool-messages.d.ts.map +1 -0
- package/dist/repl/display-tool-messages.js +55 -0
- package/dist/repl/display-tool-use.d.ts +14 -0
- package/dist/repl/display-tool-use.d.ts.map +1 -0
- package/dist/repl/display-tool-use.js +63 -0
- package/dist/repl/get-prompt-header.d.ts +8 -0
- package/dist/repl/get-prompt-header.d.ts.map +1 -0
- package/dist/repl/get-prompt-header.js +38 -0
- package/dist/repl/tool-call-repair.d.ts +4 -0
- package/dist/repl/tool-call-repair.d.ts.map +1 -0
- package/dist/repl/tool-call-repair.js +50 -0
- package/dist/repl-prompt.d.ts +1 -0
- package/dist/repl-prompt.d.ts.map +1 -0
- package/dist/repl.d.ts +8 -4
- package/dist/repl.d.ts.map +1 -0
- package/dist/repl.js +108 -252
- package/dist/terminal/ansi-styles.d.ts +77 -0
- package/dist/terminal/ansi-styles.d.ts.map +1 -0
- package/dist/terminal/ansi-styles.js +215 -0
- package/dist/terminal/checkbox-prompt.d.ts +36 -0
- package/dist/terminal/checkbox-prompt.d.ts.map +1 -0
- package/dist/terminal/checkbox-prompt.js +362 -0
- package/dist/terminal/default-theme.d.ts +6 -0
- package/dist/terminal/default-theme.d.ts.map +1 -0
- package/dist/terminal/default-theme.js +182 -0
- package/dist/terminal/east-asian-width.d.ts +8 -0
- package/dist/terminal/east-asian-width.d.ts.map +1 -0
- package/dist/terminal/east-asian-width.js +409 -0
- package/dist/terminal/editor-prompt.d.ts +10 -0
- package/dist/terminal/editor-prompt.d.ts.map +1 -0
- package/dist/terminal/editor-prompt.js +61 -0
- package/dist/terminal/errors.d.ts +19 -0
- package/dist/terminal/errors.d.ts.map +1 -0
- package/dist/terminal/errors.js +37 -0
- package/dist/terminal/formatting.d.ts +1 -11
- package/dist/terminal/formatting.d.ts.map +1 -0
- package/dist/terminal/formatting.js +4 -20
- package/dist/terminal/highlight/index.d.ts +53 -0
- package/dist/terminal/highlight/index.d.ts.map +1 -0
- package/dist/terminal/highlight/index.js +90 -0
- package/dist/terminal/highlight/theme.d.ts +233 -0
- package/dist/terminal/highlight/theme.d.ts.map +1 -0
- package/dist/terminal/highlight/theme.js +83 -0
- package/dist/terminal/index.d.ts +16 -9
- package/dist/terminal/index.d.ts.map +1 -0
- package/dist/terminal/index.js +42 -126
- package/dist/terminal/input-prompt.d.ts +16 -0
- package/dist/terminal/input-prompt.d.ts.map +1 -0
- package/dist/terminal/input-prompt.js +181 -0
- package/dist/terminal/markdown-utils.d.ts +1 -0
- package/dist/terminal/markdown-utils.d.ts.map +1 -0
- package/dist/terminal/markdown.d.ts +1 -0
- package/dist/terminal/markdown.d.ts.map +1 -0
- package/dist/terminal/markdown.js +17 -12
- package/dist/terminal/search-prompt.d.ts +20 -0
- package/dist/terminal/search-prompt.d.ts.map +1 -0
- package/dist/terminal/search-prompt.js +279 -0
- package/dist/terminal/select-prompt.d.ts +26 -0
- package/dist/terminal/select-prompt.d.ts.map +1 -0
- package/dist/terminal/select-prompt.js +298 -0
- package/dist/terminal/string-width.d.ts +7 -0
- package/dist/terminal/string-width.d.ts.map +1 -0
- package/dist/terminal/string-width.js +61 -0
- package/dist/terminal/strip-ansi.d.ts +2 -0
- package/dist/terminal/strip-ansi.d.ts.map +1 -0
- package/dist/terminal/strip-ansi.js +20 -0
- package/dist/terminal/style.d.ts +191 -0
- package/dist/terminal/style.d.ts.map +1 -0
- package/dist/terminal/style.js +259 -0
- package/dist/terminal/supports-color.d.ts +1 -0
- package/dist/terminal/supports-color.d.ts.map +1 -0
- package/dist/terminal/supports-hyperlinks.d.ts +1 -3
- package/dist/terminal/supports-hyperlinks.d.ts.map +1 -0
- package/dist/terminal/supports-hyperlinks.js +1 -1
- package/dist/terminal/types.d.ts +1 -37
- package/dist/terminal/types.d.ts.map +1 -0
- package/dist/terminal/wrap-ansi.d.ts +8 -0
- package/dist/terminal/wrap-ansi.d.ts.map +1 -0
- package/dist/terminal/wrap-ansi.js +190 -0
- package/dist/{token-utils.d.ts → tokens/counter.d.ts} +1 -0
- package/dist/tokens/counter.d.ts.map +1 -0
- package/dist/{token-utils.js → tokens/counter.js} +1 -1
- package/dist/tokens/manage-output.d.ts +34 -0
- package/dist/tokens/manage-output.d.ts.map +1 -0
- package/dist/tokens/manage-output.js +44 -0
- package/dist/{token-tracker.d.ts → tokens/tracker.d.ts} +1 -0
- package/dist/tokens/tracker.d.ts.map +1 -0
- package/dist/tool-executor.d.ts +28 -0
- package/dist/tool-executor.d.ts.map +1 -0
- package/dist/tool-executor.js +74 -0
- package/dist/tools/agent.d.ts +3 -2
- package/dist/tools/agent.d.ts.map +1 -0
- package/dist/tools/agent.js +7 -4
- package/dist/tools/bash-utils.d.ts +7 -0
- package/dist/tools/bash-utils.d.ts.map +1 -0
- package/dist/tools/bash-utils.js +212 -0
- package/dist/tools/bash.d.ts +9 -7
- package/dist/tools/bash.d.ts.map +1 -0
- package/dist/tools/bash.js +95 -212
- package/dist/tools/code-interpreter.d.ts +1 -1
- package/dist/tools/code-interpreter.d.ts.map +1 -0
- package/dist/tools/code-interpreter.js +31 -96
- package/dist/tools/delete-file.d.ts +5 -3
- package/dist/tools/delete-file.d.ts.map +1 -0
- package/dist/tools/delete-file.js +47 -33
- package/dist/tools/directory-tree.d.ts +10 -1
- package/dist/tools/directory-tree.d.ts.map +1 -0
- package/dist/tools/directory-tree.js +91 -8
- package/dist/tools/dynamic-tool-loader.d.ts +12 -0
- package/dist/tools/dynamic-tool-loader.d.ts.map +1 -0
- package/dist/tools/dynamic-tool-loader.js +280 -0
- package/dist/tools/dynamic-tool-parser.d.ts +20 -0
- package/dist/tools/dynamic-tool-parser.d.ts.map +1 -0
- package/dist/tools/dynamic-tool-parser.js +21 -0
- package/dist/tools/edit-file.d.ts +10 -2
- package/dist/tools/edit-file.d.ts.map +1 -0
- package/dist/tools/edit-file.js +117 -40
- package/dist/tools/file-editing-utils.d.ts +2 -0
- package/dist/tools/file-editing-utils.d.ts.map +1 -0
- package/dist/tools/file-editing-utils.js +135 -0
- package/dist/tools/filesystem-utils.d.ts +6 -21
- package/dist/tools/filesystem-utils.d.ts.map +1 -0
- package/dist/tools/filesystem-utils.js +96 -148
- package/dist/tools/git-utils.d.ts +1 -0
- package/dist/tools/git-utils.d.ts.map +1 -0
- package/dist/tools/grep.d.ts +5 -3
- package/dist/tools/grep.d.ts.map +1 -0
- package/dist/tools/grep.js +67 -27
- package/dist/tools/index.d.ts +10 -16
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +33 -22
- package/dist/tools/move-file.d.ts +1 -0
- package/dist/tools/move-file.d.ts.map +1 -0
- package/dist/tools/move-file.js +12 -5
- package/dist/tools/read-file.d.ts +2 -1
- package/dist/tools/read-file.d.ts.map +1 -0
- package/dist/tools/read-file.js +13 -6
- package/dist/tools/read-multiple-files.d.ts +2 -1
- package/dist/tools/read-multiple-files.d.ts.map +1 -0
- package/dist/tools/read-multiple-files.js +90 -9
- package/dist/tools/save-file.d.ts +5 -3
- package/dist/tools/save-file.d.ts.map +1 -0
- package/dist/tools/save-file.js +64 -36
- package/dist/tools/think.d.ts +1 -0
- package/dist/tools/think.d.ts.map +1 -0
- package/dist/tools/think.js +5 -1
- package/dist/tools/types.d.ts +14 -1
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/web-fetch.d.ts +4 -2
- package/dist/tools/web-fetch.d.ts.map +1 -0
- package/dist/tools/web-fetch.js +2 -2
- package/dist/tools/web-search.d.ts +2 -1
- package/dist/tools/web-search.d.ts.map +1 -0
- package/dist/tools/web-search.js +46 -11
- package/dist/utils/filesystem.d.ts +23 -0
- package/dist/utils/filesystem.d.ts.map +1 -0
- package/dist/utils/filesystem.js +140 -0
- package/dist/utils/filetype-detection.d.ts +3 -0
- package/dist/utils/filetype-detection.d.ts.map +1 -0
- package/dist/utils/filetype-detection.js +112 -0
- package/dist/utils/glob.d.ts +52 -0
- package/dist/utils/glob.d.ts.map +1 -0
- package/dist/utils/glob.js +376 -0
- package/dist/utils/ignore.d.ts +104 -0
- package/dist/utils/ignore.d.ts.map +1 -0
- package/dist/utils/ignore.js +649 -0
- package/dist/utils/process.d.ts +3 -2
- package/dist/utils/process.d.ts.map +1 -0
- package/dist/utils/process.js +17 -2
- package/dist/utils/zod-utils.d.ts +4 -0
- package/dist/utils/zod-utils.d.ts.map +1 -0
- package/dist/utils/zod-utils.js +7 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.d.ts.map +1 -0
- package/package.json +32 -30
- package/dist/tools/command-validation.d.ts +0 -11
- package/dist/tools/command-validation.js +0 -45
- /package/dist/{token-tracker.js → tokens/tracker.js} +0 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { tool } from "ai";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { config } from "../config.js";
|
|
8
|
+
import { logger } from "../logger.js";
|
|
9
|
+
import { parseToolMetadata } from "./dynamic-tool-parser.js";
|
|
10
|
+
function generateZodSchema(parameters) {
|
|
11
|
+
const fields = {};
|
|
12
|
+
for (const param of parameters) {
|
|
13
|
+
let schema;
|
|
14
|
+
switch (param.type) {
|
|
15
|
+
case "string":
|
|
16
|
+
schema = z.string();
|
|
17
|
+
break;
|
|
18
|
+
case "number":
|
|
19
|
+
schema = z.coerce.number();
|
|
20
|
+
break;
|
|
21
|
+
case "boolean":
|
|
22
|
+
schema = z.coerce.boolean();
|
|
23
|
+
break;
|
|
24
|
+
default:
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (!param.required) {
|
|
28
|
+
schema = schema.optional();
|
|
29
|
+
}
|
|
30
|
+
if (param.default !== undefined) {
|
|
31
|
+
schema = schema.default(param.default);
|
|
32
|
+
}
|
|
33
|
+
fields[param.name] = schema.describe(param.description);
|
|
34
|
+
}
|
|
35
|
+
return z.object(fields);
|
|
36
|
+
}
|
|
37
|
+
async function getMetadata(scriptPath) {
|
|
38
|
+
return new Promise((resolve) => {
|
|
39
|
+
let child;
|
|
40
|
+
try {
|
|
41
|
+
// Find the node executable path
|
|
42
|
+
const nodePath = process.execPath;
|
|
43
|
+
child = spawn(nodePath, [scriptPath], {
|
|
44
|
+
env: {
|
|
45
|
+
...process.env,
|
|
46
|
+
// biome-ignore lint/style/useNamingConvention: Environment variables are conventionally uppercase
|
|
47
|
+
TOOL_ACTION: "describe",
|
|
48
|
+
// biome-ignore lint/style/useNamingConvention: Environment variables are conventionally uppercase
|
|
49
|
+
NODE_ENV: "production",
|
|
50
|
+
},
|
|
51
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
logger.error(`Failed to spawn ${scriptPath}: ${e}`);
|
|
56
|
+
resolve(null);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
let stdout = "";
|
|
60
|
+
let stderr = "";
|
|
61
|
+
child.stdout?.on("data", (data) => {
|
|
62
|
+
stdout += data.toString();
|
|
63
|
+
});
|
|
64
|
+
child.stderr?.on("data", (data) => {
|
|
65
|
+
stderr += data.toString();
|
|
66
|
+
});
|
|
67
|
+
child.on("close", (code) => {
|
|
68
|
+
if (code !== 0) {
|
|
69
|
+
logger.error(`Script ${scriptPath} failed to describe: ${stderr}`);
|
|
70
|
+
resolve(null);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const metadata = parseToolMetadata(stdout);
|
|
75
|
+
resolve(metadata);
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
logger.error(`Failed to parse metadata from ${scriptPath}: ${String(e)}`);
|
|
79
|
+
resolve(null);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
child.on("error", (err) => {
|
|
83
|
+
logger.error(`Spawn error for ${scriptPath}: ${err}`);
|
|
84
|
+
resolve(null);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
export function createDynamicTool(scriptPath, metadata, sendData) {
|
|
89
|
+
const inputSchema = generateZodSchema(metadata.parameters);
|
|
90
|
+
const toolName = `dynamic-${metadata.name}`;
|
|
91
|
+
return {
|
|
92
|
+
[toolName]: tool({
|
|
93
|
+
description: metadata.description,
|
|
94
|
+
inputSchema,
|
|
95
|
+
execute: async (params, { toolCallId, abortSignal }) => {
|
|
96
|
+
if (abortSignal?.aborted) {
|
|
97
|
+
throw new Error("Execution aborted");
|
|
98
|
+
}
|
|
99
|
+
sendData?.({
|
|
100
|
+
id: toolCallId,
|
|
101
|
+
event: "tool-init",
|
|
102
|
+
data: `Executing dynamic tool: ${metadata.name}`,
|
|
103
|
+
});
|
|
104
|
+
// Validate params again for safety
|
|
105
|
+
try {
|
|
106
|
+
inputSchema.parse(params);
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
const errMsg = `Invalid parameters for tool ${metadata.name}: ${e.message}`;
|
|
110
|
+
sendData?.({
|
|
111
|
+
id: toolCallId,
|
|
112
|
+
event: "tool-error",
|
|
113
|
+
data: errMsg,
|
|
114
|
+
});
|
|
115
|
+
throw new Error(errMsg);
|
|
116
|
+
}
|
|
117
|
+
const paramsArray = Object.entries(params).map(([name, value]) => ({
|
|
118
|
+
name,
|
|
119
|
+
value,
|
|
120
|
+
}));
|
|
121
|
+
const paramsJson = JSON.stringify(paramsArray);
|
|
122
|
+
return new Promise((resolve) => {
|
|
123
|
+
const child = spawn(process.execPath, [scriptPath], {
|
|
124
|
+
cwd: path.dirname(scriptPath),
|
|
125
|
+
env: {
|
|
126
|
+
...process.env,
|
|
127
|
+
// biome-ignore lint/style/useNamingConvention: Environment variables are conventionally uppercase
|
|
128
|
+
TOOL_ACTION: "execute",
|
|
129
|
+
// biome-ignore lint/style/useNamingConvention: Environment variables are conventionally uppercase
|
|
130
|
+
NODE_ENV: "production",
|
|
131
|
+
},
|
|
132
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
133
|
+
});
|
|
134
|
+
let stdout = "";
|
|
135
|
+
let stderr = "";
|
|
136
|
+
let hasTimedOut = false;
|
|
137
|
+
const timer = setTimeout(() => {
|
|
138
|
+
hasTimedOut = true;
|
|
139
|
+
child.kill();
|
|
140
|
+
sendData?.({
|
|
141
|
+
id: toolCallId,
|
|
142
|
+
event: "tool-update",
|
|
143
|
+
data: { primary: "Execution timed out after 30 seconds" },
|
|
144
|
+
});
|
|
145
|
+
resolve("Execution timed out after 30 seconds");
|
|
146
|
+
}, 30000);
|
|
147
|
+
child.stdin.write(`${paramsJson}\n`);
|
|
148
|
+
child.stdin.end();
|
|
149
|
+
child.stdout.on("data", (data) => {
|
|
150
|
+
stdout += data.toString();
|
|
151
|
+
});
|
|
152
|
+
child.stderr.on("data", (data) => {
|
|
153
|
+
stderr += data.toString();
|
|
154
|
+
});
|
|
155
|
+
child.on("close", (code) => {
|
|
156
|
+
clearTimeout(timer);
|
|
157
|
+
if (hasTimedOut)
|
|
158
|
+
return;
|
|
159
|
+
if (code !== 0) {
|
|
160
|
+
const errorMsg = `Dynamic tool ${metadata.name} failed: ${stderr || `Exited with code ${code}`}`;
|
|
161
|
+
sendData?.({
|
|
162
|
+
id: toolCallId,
|
|
163
|
+
event: "tool-error",
|
|
164
|
+
data: errorMsg,
|
|
165
|
+
});
|
|
166
|
+
resolve(errorMsg);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
let output = stdout.trim();
|
|
170
|
+
const maxOutputBytes = 2000000;
|
|
171
|
+
if (output.length > maxOutputBytes) {
|
|
172
|
+
output = `${output.substring(0, maxOutputBytes)}\n[Output truncated]`;
|
|
173
|
+
}
|
|
174
|
+
// If no stdout, prefer stderr so callers get useful info
|
|
175
|
+
const errText = stderr.trim();
|
|
176
|
+
if (!output && errText) {
|
|
177
|
+
output = errText;
|
|
178
|
+
}
|
|
179
|
+
// Fallback to a non-empty placeholder to satisfy callers
|
|
180
|
+
if (!output) {
|
|
181
|
+
output = `[No output from dynamic tool ${metadata.name}]`;
|
|
182
|
+
}
|
|
183
|
+
// Send tool-update event with last 20 lines of output
|
|
184
|
+
const outputLines = output.split("\n");
|
|
185
|
+
const lastLines = outputLines.slice(-20).join("\n");
|
|
186
|
+
sendData?.({
|
|
187
|
+
id: toolCallId,
|
|
188
|
+
event: "tool-update",
|
|
189
|
+
data: {
|
|
190
|
+
primary: `Last 20 lines of output from ${metadata.name}:`,
|
|
191
|
+
secondary: lastLines.split("\n"),
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
// Attempt to parse as JSON if structured
|
|
195
|
+
if (output &&
|
|
196
|
+
(output.startsWith("{") || output.startsWith("["))) {
|
|
197
|
+
try {
|
|
198
|
+
resolve(JSON.parse(output));
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
resolve(output);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
resolve(output);
|
|
206
|
+
}
|
|
207
|
+
sendData?.({
|
|
208
|
+
id: toolCallId,
|
|
209
|
+
event: "tool-completion",
|
|
210
|
+
data: `Dynamic tool ${metadata.name} completed`,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
if (abortSignal) {
|
|
215
|
+
abortSignal.addEventListener("abort", () => {
|
|
216
|
+
child.kill();
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
},
|
|
221
|
+
}),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
export async function loadDynamicTools({ baseDir, sendData, }) {
|
|
225
|
+
const projectConfig = await config.readProjectConfig();
|
|
226
|
+
const dynamicConfig = projectConfig.tools.dynamicTools;
|
|
227
|
+
if (!dynamicConfig.enabled) {
|
|
228
|
+
logger.info("Dynamic tools disabled in config.");
|
|
229
|
+
return {};
|
|
230
|
+
}
|
|
231
|
+
const projectToolsDir = path.join(baseDir, ".acai", "tools");
|
|
232
|
+
const userToolsDir = path.join(os.homedir(), ".acai", "tools");
|
|
233
|
+
const toolMap = new Map();
|
|
234
|
+
const scanDir = async (dir, isProject = false) => {
|
|
235
|
+
if (!fs.existsSync(dir))
|
|
236
|
+
return;
|
|
237
|
+
try {
|
|
238
|
+
const files = fs
|
|
239
|
+
.readdirSync(dir)
|
|
240
|
+
.filter((f) => f.endsWith(".js") || f.endsWith(".mjs"));
|
|
241
|
+
for (const file of files) {
|
|
242
|
+
const scriptPath = path.join(dir, file);
|
|
243
|
+
try {
|
|
244
|
+
const metadata = await getMetadata(scriptPath);
|
|
245
|
+
if (metadata) {
|
|
246
|
+
toolMap.set(metadata.name, { path: scriptPath, metadata });
|
|
247
|
+
logger.info(`Loaded ${isProject ? "project" : "user"} tool: ${metadata.name}`);
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
logger.warn(`Skipped invalid tool: ${file}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch (e) {
|
|
254
|
+
logger.error(`Error scanning ${file}: ${e}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
catch (e) {
|
|
259
|
+
logger.error(`Error reading dir ${dir}: ${e}`);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
// Scan user first, then project to let project override
|
|
263
|
+
await scanDir(userToolsDir, false);
|
|
264
|
+
await scanDir(projectToolsDir, true);
|
|
265
|
+
// Enforce maxTools, preferring recent (project) entries
|
|
266
|
+
if (toolMap.size > dynamicConfig.maxTools) {
|
|
267
|
+
logger.warn(`Warning: ${toolMap.size} dynamic tools found, limiting to ${dynamicConfig.maxTools}`);
|
|
268
|
+
const entries = Array.from(toolMap.entries());
|
|
269
|
+
const limitedEntries = entries.slice(-dynamicConfig.maxTools);
|
|
270
|
+
toolMap.clear();
|
|
271
|
+
for (const [name, value] of limitedEntries) {
|
|
272
|
+
toolMap.set(name, value);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const tools = {};
|
|
276
|
+
for (const [_, { path, metadata }] of toolMap) {
|
|
277
|
+
Object.assign(tools, createDynamicTool(path, metadata, sendData));
|
|
278
|
+
}
|
|
279
|
+
return tools;
|
|
280
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
declare const toolMetadataSchema: z.ZodObject<{
|
|
3
|
+
name: z.ZodString;
|
|
4
|
+
description: z.ZodString;
|
|
5
|
+
parameters: z.ZodArray<z.ZodObject<{
|
|
6
|
+
name: z.ZodString;
|
|
7
|
+
type: z.ZodEnum<{
|
|
8
|
+
string: "string";
|
|
9
|
+
number: "number";
|
|
10
|
+
boolean: "boolean";
|
|
11
|
+
}>;
|
|
12
|
+
description: z.ZodString;
|
|
13
|
+
required: z.ZodDefault<z.ZodBoolean>;
|
|
14
|
+
default: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
15
|
+
}, z.core.$strip>>;
|
|
16
|
+
}, z.core.$strip>;
|
|
17
|
+
export type ToolMetadata = z.infer<typeof toolMetadataSchema>;
|
|
18
|
+
export declare function parseToolMetadata(output: string): ToolMetadata;
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=dynamic-tool-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dynamic-tool-parser.d.ts","sourceRoot":"","sources":["../../source/tools/dynamic-tool-parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;iBAYtB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAS9D"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const toolMetadataSchema = z.object({
|
|
3
|
+
name: z.string().regex(/^[a-zA-Z_][a-zA-Z0-9_-]*$/),
|
|
4
|
+
description: z.string().min(1),
|
|
5
|
+
parameters: z.array(z.object({
|
|
6
|
+
name: z.string().regex(/^[a-zA-Z_][a-zA-Z0-9_-]*$/),
|
|
7
|
+
type: z.enum(["string", "number", "boolean"]),
|
|
8
|
+
description: z.string().min(1),
|
|
9
|
+
required: z.boolean().default(true),
|
|
10
|
+
default: z.union([z.string(), z.number(), z.boolean()]).optional(),
|
|
11
|
+
})),
|
|
12
|
+
});
|
|
13
|
+
export function parseToolMetadata(output) {
|
|
14
|
+
try {
|
|
15
|
+
const parsed = JSON.parse(output.trim());
|
|
16
|
+
return toolMetadataSchema.parse(parsed);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
throw new Error(`Failed to parse tool metadata: ${error instanceof Error ? error.message : String(error)}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { Terminal } from "../terminal/index.ts";
|
|
2
|
+
import type { ToolExecutor } from "../tool-executor.ts";
|
|
2
3
|
import type { SendData } from "./types.ts";
|
|
3
4
|
export declare const EditFileTool: {
|
|
4
5
|
name: "editFile";
|
|
5
6
|
};
|
|
6
|
-
export declare const createEditFileTool: ({ workingDir, terminal, sendData,
|
|
7
|
+
export declare const createEditFileTool: ({ workingDir, terminal, sendData, toolExecutor, }: {
|
|
7
8
|
workingDir: string;
|
|
8
9
|
terminal?: Terminal;
|
|
9
10
|
sendData?: SendData;
|
|
10
|
-
|
|
11
|
+
toolExecutor?: ToolExecutor;
|
|
11
12
|
}) => Promise<{
|
|
12
13
|
editFile: import("ai").Tool<{
|
|
13
14
|
path: string;
|
|
@@ -17,3 +18,10 @@ export declare const createEditFileTool: ({ workingDir, terminal, sendData, auto
|
|
|
17
18
|
}[];
|
|
18
19
|
}, string>;
|
|
19
20
|
}>;
|
|
21
|
+
interface FileEdit {
|
|
22
|
+
oldText: string;
|
|
23
|
+
newText: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function applyFileEdits(filePath: string, edits: FileEdit[], dryRun?: boolean, abortSignal?: AbortSignal): Promise<string>;
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=edit-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edit-file.d.ts","sourceRoot":"","sources":["../../source/tools/edit-file.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,KAAK,EAAe,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAErE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,eAAO,MAAM,YAAY;;CAExB,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAU,mDAKtC;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;;;;;;;;EA+JA,CAAC;AAyBF,UAAU,QAAQ;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,QAAQ,EAAE,EACjB,MAAM,UAAQ,EACd,WAAW,CAAC,EAAE,WAAW,GACxB,OAAO,CAAC,MAAM,CAAC,CAmEjB"}
|
package/dist/tools/edit-file.js
CHANGED
|
@@ -1,71 +1,85 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { tool } from "ai";
|
|
3
|
-
import
|
|
3
|
+
import { createTwoFilesPatch } from "diff";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import
|
|
5
|
+
import style from "../terminal/style.js";
|
|
6
|
+
import { joinWorkingDir, validatePath } from "./filesystem-utils.js";
|
|
6
7
|
export const EditFileTool = {
|
|
7
8
|
name: "editFile",
|
|
8
9
|
};
|
|
9
|
-
export const createEditFileTool = async ({ workingDir, terminal, sendData,
|
|
10
|
+
export const createEditFileTool = async ({ workingDir, terminal, sendData, toolExecutor, }) => {
|
|
10
11
|
const allowedDirectory = workingDir;
|
|
11
|
-
let autoAcceptEdits = autoAcceptAll;
|
|
12
12
|
return {
|
|
13
13
|
[EditFileTool.name]: tool({
|
|
14
14
|
description: "Make line-based edits to a text file. Each edit replaces exact line sequences " +
|
|
15
|
-
"with new content.
|
|
16
|
-
"
|
|
15
|
+
"with new content. Exact literal matching is used: no whitespace, indentation, escape, or newline normalization is applied when locating matches. " +
|
|
16
|
+
"Provide enough context so the match is unique; otherwise the operation errors. Returns a git-style diff showing the changes made. " +
|
|
17
|
+
"Only works within allowed directories. " +
|
|
18
|
+
"Note: Special characters in oldText must be properly escaped for JSON (e.g., backticks as \\`...\\`). " +
|
|
19
|
+
"Multi-line strings require exact character-by-character matching including whitespace.",
|
|
17
20
|
inputSchema: z.object({
|
|
18
21
|
path: z.string().describe("The path of the file to edit."),
|
|
19
22
|
edits: z.array(z.object({
|
|
20
23
|
oldText: z
|
|
21
24
|
.string()
|
|
22
|
-
.describe("Text to search for - must match exactly and enough context must be provided to uniquely match the target text"
|
|
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."),
|
|
23
28
|
newText: z.string().describe("Text to replace with"),
|
|
24
29
|
})),
|
|
25
30
|
}),
|
|
26
|
-
execute: async ({ path, edits }, { toolCallId }) => {
|
|
31
|
+
execute: async ({ path, edits }, { toolCallId, abortSignal }) => {
|
|
32
|
+
// Check if execution has been aborted
|
|
33
|
+
if (abortSignal?.aborted) {
|
|
34
|
+
throw new Error("File editing aborted");
|
|
35
|
+
}
|
|
27
36
|
sendData?.({
|
|
28
37
|
id: toolCallId,
|
|
29
38
|
event: "tool-init",
|
|
30
|
-
data: `Editing file: ${
|
|
39
|
+
data: `Editing file: ${style.cyan(path)}`,
|
|
31
40
|
});
|
|
32
41
|
try {
|
|
33
|
-
const validPath = await validatePath(joinWorkingDir(path, workingDir), allowedDirectory);
|
|
42
|
+
const validPath = await validatePath(joinWorkingDir(path, workingDir), allowedDirectory, { abortSignal });
|
|
34
43
|
if (terminal) {
|
|
35
|
-
terminal.writeln(`\n${
|
|
36
|
-
terminal.lineBreak();
|
|
37
|
-
const result = await applyFileEdits(validPath, edits, true);
|
|
38
|
-
terminal.writeln(`The agent is proposing the following ${chalk.cyan(edits.length)} edits:`);
|
|
44
|
+
terminal.writeln(`\n${style.blue.bold("●")} Proposing file changes: ${style.cyan(path)}`);
|
|
39
45
|
terminal.lineBreak();
|
|
46
|
+
const result = await applyFileEdits(validPath, edits, true, abortSignal);
|
|
47
|
+
terminal.writeln(`The agent is proposing the following ${style.cyan(edits.length)} edits:`);
|
|
48
|
+
terminal.hr();
|
|
40
49
|
terminal.display(result);
|
|
41
|
-
terminal.
|
|
42
|
-
let
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
else {
|
|
48
|
-
userChoice = await select({
|
|
50
|
+
terminal.hr();
|
|
51
|
+
let userResponse;
|
|
52
|
+
if (toolExecutor) {
|
|
53
|
+
const ctx = {
|
|
54
|
+
toolName: EditFileTool.name,
|
|
55
|
+
toolCallId,
|
|
49
56
|
message: "What would you like to do with these changes?",
|
|
50
|
-
choices:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
choices: {
|
|
58
|
+
accept: "Accept these changes",
|
|
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
|
+
}
|
|
60
72
|
}
|
|
73
|
+
const { result: userChoice, reason } = userResponse ?? {
|
|
74
|
+
result: "accept",
|
|
75
|
+
};
|
|
61
76
|
terminal.lineBreak();
|
|
62
77
|
if (userChoice === "accept-all") {
|
|
63
|
-
|
|
64
|
-
terminal.writeln(chalk.yellow("✓ Auto-accept mode enabled for all future edits"));
|
|
78
|
+
terminal.writeln(style.yellow("✓ Auto-accept mode enabled for all edits"));
|
|
65
79
|
terminal.lineBreak();
|
|
66
80
|
}
|
|
67
81
|
if (userChoice === "accept" || userChoice === "accept-all") {
|
|
68
|
-
const finalEdits = await applyFileEdits(validPath, edits, false);
|
|
82
|
+
const finalEdits = await applyFileEdits(validPath, edits, false, abortSignal);
|
|
69
83
|
// Send completion message indicating success
|
|
70
84
|
sendData?.({
|
|
71
85
|
id: toolCallId,
|
|
@@ -74,17 +88,17 @@ export const createEditFileTool = async ({ workingDir, terminal, sendData, autoA
|
|
|
74
88
|
});
|
|
75
89
|
return finalEdits;
|
|
76
90
|
}
|
|
77
|
-
const reason = await input({ message: "Feedback: " });
|
|
78
91
|
terminal.lineBreak();
|
|
79
92
|
// Send completion message indicating rejection
|
|
93
|
+
const rejectionReason = reason || "No reason provided";
|
|
80
94
|
sendData?.({
|
|
81
95
|
id: toolCallId,
|
|
82
96
|
event: "tool-completion",
|
|
83
|
-
data: `Edits rejected by user. Reason: ${
|
|
97
|
+
data: `Edits rejected by user. Reason: ${rejectionReason}`,
|
|
84
98
|
});
|
|
85
|
-
return `The user rejected these changes. Reason: ${
|
|
99
|
+
return `The user rejected these changes. Reason: ${rejectionReason}`;
|
|
86
100
|
}
|
|
87
|
-
const finalEdits = await applyFileEdits(validPath, edits, false);
|
|
101
|
+
const finalEdits = await applyFileEdits(validPath, edits, false, abortSignal);
|
|
88
102
|
// Send completion message indicating success
|
|
89
103
|
sendData?.({
|
|
90
104
|
id: toolCallId,
|
|
@@ -105,3 +119,66 @@ export const createEditFileTool = async ({ workingDir, terminal, sendData, autoA
|
|
|
105
119
|
}),
|
|
106
120
|
};
|
|
107
121
|
};
|
|
122
|
+
// file editing and diffing utilities
|
|
123
|
+
function normalizeLineEndings(text) {
|
|
124
|
+
return text.replace(/\r\n/g, "\n");
|
|
125
|
+
}
|
|
126
|
+
function createUnifiedDiff(originalContent, newContent, filepath = "file") {
|
|
127
|
+
// Ensure consistent line endings for diff
|
|
128
|
+
const normalizedOriginal = normalizeLineEndings(originalContent);
|
|
129
|
+
const normalizedNew = normalizeLineEndings(newContent);
|
|
130
|
+
return createTwoFilesPatch(filepath, filepath, normalizedOriginal, normalizedNew, "original", "modified");
|
|
131
|
+
}
|
|
132
|
+
export async function applyFileEdits(filePath, edits, dryRun = false, abortSignal) {
|
|
133
|
+
if (abortSignal?.aborted) {
|
|
134
|
+
throw new Error("File edit operation aborted");
|
|
135
|
+
}
|
|
136
|
+
// Read file content literally with signal
|
|
137
|
+
const originalContent = await readFile(filePath, {
|
|
138
|
+
encoding: "utf-8",
|
|
139
|
+
signal: abortSignal,
|
|
140
|
+
});
|
|
141
|
+
if (edits.find((edit) => edit.oldText.length === 0)) {
|
|
142
|
+
throw new Error("Invalid oldText in edit. The value of oldText must be at least one character");
|
|
143
|
+
}
|
|
144
|
+
// Apply edits sequentially using strict literal, unique matches
|
|
145
|
+
let modifiedContent = originalContent;
|
|
146
|
+
for (const edit of edits) {
|
|
147
|
+
if (abortSignal?.aborted) {
|
|
148
|
+
throw new Error("File edit operation aborted during processing");
|
|
149
|
+
}
|
|
150
|
+
const { oldText, newText } = edit; // Use literal oldText and newText
|
|
151
|
+
// Strict literal match: find exactly one occurrence without any normalization
|
|
152
|
+
const firstIndex = modifiedContent.indexOf(oldText);
|
|
153
|
+
if (firstIndex === -1) {
|
|
154
|
+
throw new Error("oldText not found in content");
|
|
155
|
+
}
|
|
156
|
+
const secondIndex = modifiedContent.indexOf(oldText, firstIndex + oldText.length);
|
|
157
|
+
if (secondIndex !== -1) {
|
|
158
|
+
throw new Error("oldText found multiple times and requires more code context to uniquely identify the intended match");
|
|
159
|
+
}
|
|
160
|
+
modifiedContent =
|
|
161
|
+
modifiedContent.slice(0, firstIndex) +
|
|
162
|
+
newText +
|
|
163
|
+
modifiedContent.slice(firstIndex + oldText.length);
|
|
164
|
+
}
|
|
165
|
+
// Create unified diff (createUnifiedDiff normalizes line endings internally for diffing)
|
|
166
|
+
const diff = createUnifiedDiff(originalContent, modifiedContent, filePath);
|
|
167
|
+
// Format diff with appropriate number of backticks
|
|
168
|
+
let numBackticks = 3;
|
|
169
|
+
while (diff.includes("`".repeat(numBackticks))) {
|
|
170
|
+
numBackticks++;
|
|
171
|
+
}
|
|
172
|
+
const formattedDiff = `${"`".repeat(numBackticks)}diff\n${diff}${"`".repeat(numBackticks)}\n\n`;
|
|
173
|
+
if (!dryRun) {
|
|
174
|
+
if (abortSignal?.aborted) {
|
|
175
|
+
throw new Error("File edit operation aborted before writing");
|
|
176
|
+
}
|
|
177
|
+
// Write the modified content with signal
|
|
178
|
+
await writeFile(filePath, modifiedContent, {
|
|
179
|
+
encoding: "utf-8",
|
|
180
|
+
signal: abortSignal,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return formattedDiff;
|
|
184
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-editing-utils.d.ts","sourceRoot":"","sources":["../../source/tools/file-editing-utils.ts"],"names":[],"mappings":"AA6HA,wBAAgB,OAAO,CACrB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,UAAU,UAAQ,GACjB,MAAM,CAmCR"}
|