@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.
Files changed (316) hide show
  1. package/README.md +225 -6
  2. package/dist/api/exa/index.d.ts +177 -0
  3. package/dist/api/exa/index.d.ts.map +1 -0
  4. package/dist/api/exa/index.js +439 -0
  5. package/dist/cli.d.ts +3 -2
  6. package/dist/cli.d.ts.map +1 -0
  7. package/dist/commands/application-log-command.d.ts +1 -0
  8. package/dist/commands/application-log-command.d.ts.map +1 -0
  9. package/dist/commands/application-log-command.js +5 -3
  10. package/dist/commands/clear-command.d.ts +1 -0
  11. package/dist/commands/clear-command.d.ts.map +1 -0
  12. package/dist/commands/clear-command.js +2 -3
  13. package/dist/commands/compact-command.d.ts +1 -0
  14. package/dist/commands/compact-command.d.ts.map +1 -0
  15. package/dist/commands/compact-command.js +1 -1
  16. package/dist/commands/copy-command.d.ts +1 -0
  17. package/dist/commands/copy-command.d.ts.map +1 -0
  18. package/dist/commands/copy-command.js +3 -2
  19. package/dist/commands/edit-command.d.ts +1 -0
  20. package/dist/commands/edit-command.d.ts.map +1 -0
  21. package/dist/commands/edit-command.js +7 -5
  22. package/dist/commands/edit-prompt-command.d.ts +2 -1
  23. package/dist/commands/edit-prompt-command.d.ts.map +1 -0
  24. package/dist/commands/edit-prompt-command.js +15 -7
  25. package/dist/commands/exit-command.d.ts +13 -2
  26. package/dist/commands/exit-command.d.ts.map +1 -0
  27. package/dist/commands/exit-command.js +14 -2
  28. package/dist/commands/files-command.d.ts +1 -0
  29. package/dist/commands/files-command.d.ts.map +1 -0
  30. package/dist/commands/files-command.js +9 -8
  31. package/dist/commands/generate-rules-command.d.ts +1 -0
  32. package/dist/commands/generate-rules-command.d.ts.map +1 -0
  33. package/dist/commands/generate-rules-command.js +4 -3
  34. package/dist/commands/health-command.d.ts +3 -1
  35. package/dist/commands/health-command.d.ts.map +1 -0
  36. package/dist/commands/health-command.js +42 -5
  37. package/dist/commands/help-command.d.ts +1 -0
  38. package/dist/commands/help-command.d.ts.map +1 -0
  39. package/dist/commands/help-command.js +2 -3
  40. package/dist/commands/init-command.d.ts +1 -0
  41. package/dist/commands/init-command.d.ts.map +1 -0
  42. package/dist/commands/init-command.js +1 -2
  43. package/dist/commands/last-log-command.d.ts +1 -0
  44. package/dist/commands/last-log-command.d.ts.map +1 -0
  45. package/dist/commands/last-log-command.js +12 -17
  46. package/dist/commands/list-tools-command.d.ts +3 -0
  47. package/dist/commands/list-tools-command.d.ts.map +1 -0
  48. package/dist/commands/list-tools-command.js +61 -0
  49. package/dist/commands/manager.d.ts +7 -2
  50. package/dist/commands/manager.d.ts.map +1 -0
  51. package/dist/commands/manager.js +43 -6
  52. package/dist/commands/model-command.d.ts +1 -0
  53. package/dist/commands/model-command.d.ts.map +1 -0
  54. package/dist/commands/model-command.js +5 -5
  55. package/dist/commands/paste-command.d.ts +1 -0
  56. package/dist/commands/paste-command.d.ts.map +1 -0
  57. package/dist/commands/paste-command.js +6 -5
  58. package/dist/commands/prompt-command.d.ts +2 -1
  59. package/dist/commands/prompt-command.d.ts.map +1 -0
  60. package/dist/commands/prompt-command.js +62 -8
  61. package/dist/commands/reset-command.d.ts +1 -0
  62. package/dist/commands/reset-command.d.ts.map +1 -0
  63. package/dist/commands/reset-command.js +1 -1
  64. package/dist/commands/rules-command.d.ts +1 -0
  65. package/dist/commands/rules-command.d.ts.map +1 -0
  66. package/dist/commands/rules-command.js +5 -3
  67. package/dist/commands/save-command.d.ts +1 -0
  68. package/dist/commands/save-command.d.ts.map +1 -0
  69. package/dist/commands/save-command.js +1 -1
  70. package/dist/commands/shell-command.d.ts +3 -0
  71. package/dist/commands/shell-command.d.ts.map +1 -0
  72. package/dist/commands/shell-command.js +60 -0
  73. package/dist/commands/types.d.ts +9 -6
  74. package/dist/commands/types.d.ts.map +1 -0
  75. package/dist/commands/usage-command.d.ts +1 -0
  76. package/dist/commands/usage-command.d.ts.map +1 -0
  77. package/dist/commands/usage-command.js +2 -3
  78. package/dist/config.d.ts +22 -34
  79. package/dist/config.d.ts.map +1 -0
  80. package/dist/config.js +61 -15
  81. package/dist/conversation-analyzer.d.ts +2 -1
  82. package/dist/conversation-analyzer.d.ts.map +1 -0
  83. package/dist/dedent.d.ts +1 -0
  84. package/dist/dedent.d.ts.map +1 -0
  85. package/dist/execution/index.d.ts +112 -0
  86. package/dist/execution/index.d.ts.map +1 -0
  87. package/dist/execution/index.js +432 -0
  88. package/dist/formatting.d.ts +2 -13
  89. package/dist/formatting.d.ts.map +1 -0
  90. package/dist/formatting.js +5 -64
  91. package/dist/index.d.ts +1 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +14 -4
  94. package/dist/logger.d.ts +1 -0
  95. package/dist/logger.d.ts.map +1 -0
  96. package/dist/mentions.d.ts +4 -0
  97. package/dist/mentions.d.ts.map +1 -0
  98. package/dist/mentions.js +42 -10
  99. package/dist/messages.d.ts +8 -20
  100. package/dist/messages.d.ts.map +1 -0
  101. package/dist/messages.js +33 -53
  102. package/dist/middleware/audit-message.d.ts +1 -0
  103. package/dist/middleware/audit-message.d.ts.map +1 -0
  104. package/dist/middleware/index.d.ts +1 -0
  105. package/dist/middleware/index.d.ts.map +1 -0
  106. package/dist/middleware/rate-limit.d.ts +1 -0
  107. package/dist/middleware/rate-limit.d.ts.map +1 -0
  108. package/dist/models/ai-config.d.ts +1 -0
  109. package/dist/models/ai-config.d.ts.map +1 -0
  110. package/dist/models/anthropic-provider.d.ts +1 -0
  111. package/dist/models/anthropic-provider.d.ts.map +1 -0
  112. package/dist/models/deepseek-provider.d.ts +1 -0
  113. package/dist/models/deepseek-provider.d.ts.map +1 -0
  114. package/dist/models/google-provider.d.ts +1 -0
  115. package/dist/models/google-provider.d.ts.map +1 -0
  116. package/dist/models/groq-provider.d.ts +20 -0
  117. package/dist/models/groq-provider.d.ts.map +1 -0
  118. package/dist/models/groq-provider.js +31 -0
  119. package/dist/models/manager.d.ts +1 -0
  120. package/dist/models/manager.d.ts.map +1 -0
  121. package/dist/models/openai-provider.d.ts +2 -1
  122. package/dist/models/openai-provider.d.ts.map +1 -0
  123. package/dist/models/openrouter-provider.d.ts +31 -22
  124. package/dist/models/openrouter-provider.d.ts.map +1 -0
  125. package/dist/models/openrouter-provider.js +115 -1
  126. package/dist/models/providers.d.ts +4 -5
  127. package/dist/models/providers.d.ts.map +1 -0
  128. package/dist/models/providers.js +7 -3
  129. package/dist/models/xai-provider.d.ts +1 -0
  130. package/dist/models/xai-provider.d.ts.map +1 -0
  131. package/dist/parsing.d.ts +2 -1
  132. package/dist/parsing.d.ts.map +1 -0
  133. package/dist/prompts/manager.d.ts +14 -2
  134. package/dist/prompts/manager.d.ts.map +1 -0
  135. package/dist/prompts.d.ts +1 -0
  136. package/dist/prompts.d.ts.map +1 -0
  137. package/dist/prompts.js +15 -11
  138. package/dist/repl/display-tool-messages.d.ts +4 -0
  139. package/dist/repl/display-tool-messages.d.ts.map +1 -0
  140. package/dist/repl/display-tool-messages.js +55 -0
  141. package/dist/repl/display-tool-use.d.ts +14 -0
  142. package/dist/repl/display-tool-use.d.ts.map +1 -0
  143. package/dist/repl/display-tool-use.js +63 -0
  144. package/dist/repl/get-prompt-header.d.ts +8 -0
  145. package/dist/repl/get-prompt-header.d.ts.map +1 -0
  146. package/dist/repl/get-prompt-header.js +38 -0
  147. package/dist/repl/tool-call-repair.d.ts +4 -0
  148. package/dist/repl/tool-call-repair.d.ts.map +1 -0
  149. package/dist/repl/tool-call-repair.js +50 -0
  150. package/dist/repl-prompt.d.ts +1 -0
  151. package/dist/repl-prompt.d.ts.map +1 -0
  152. package/dist/repl.d.ts +8 -4
  153. package/dist/repl.d.ts.map +1 -0
  154. package/dist/repl.js +108 -252
  155. package/dist/terminal/ansi-styles.d.ts +77 -0
  156. package/dist/terminal/ansi-styles.d.ts.map +1 -0
  157. package/dist/terminal/ansi-styles.js +215 -0
  158. package/dist/terminal/checkbox-prompt.d.ts +36 -0
  159. package/dist/terminal/checkbox-prompt.d.ts.map +1 -0
  160. package/dist/terminal/checkbox-prompt.js +362 -0
  161. package/dist/terminal/default-theme.d.ts +6 -0
  162. package/dist/terminal/default-theme.d.ts.map +1 -0
  163. package/dist/terminal/default-theme.js +182 -0
  164. package/dist/terminal/east-asian-width.d.ts +8 -0
  165. package/dist/terminal/east-asian-width.d.ts.map +1 -0
  166. package/dist/terminal/east-asian-width.js +409 -0
  167. package/dist/terminal/editor-prompt.d.ts +10 -0
  168. package/dist/terminal/editor-prompt.d.ts.map +1 -0
  169. package/dist/terminal/editor-prompt.js +61 -0
  170. package/dist/terminal/errors.d.ts +19 -0
  171. package/dist/terminal/errors.d.ts.map +1 -0
  172. package/dist/terminal/errors.js +37 -0
  173. package/dist/terminal/formatting.d.ts +1 -11
  174. package/dist/terminal/formatting.d.ts.map +1 -0
  175. package/dist/terminal/formatting.js +4 -20
  176. package/dist/terminal/highlight/index.d.ts +53 -0
  177. package/dist/terminal/highlight/index.d.ts.map +1 -0
  178. package/dist/terminal/highlight/index.js +90 -0
  179. package/dist/terminal/highlight/theme.d.ts +233 -0
  180. package/dist/terminal/highlight/theme.d.ts.map +1 -0
  181. package/dist/terminal/highlight/theme.js +83 -0
  182. package/dist/terminal/index.d.ts +16 -9
  183. package/dist/terminal/index.d.ts.map +1 -0
  184. package/dist/terminal/index.js +42 -126
  185. package/dist/terminal/input-prompt.d.ts +16 -0
  186. package/dist/terminal/input-prompt.d.ts.map +1 -0
  187. package/dist/terminal/input-prompt.js +181 -0
  188. package/dist/terminal/markdown-utils.d.ts +1 -0
  189. package/dist/terminal/markdown-utils.d.ts.map +1 -0
  190. package/dist/terminal/markdown.d.ts +1 -0
  191. package/dist/terminal/markdown.d.ts.map +1 -0
  192. package/dist/terminal/markdown.js +17 -12
  193. package/dist/terminal/search-prompt.d.ts +20 -0
  194. package/dist/terminal/search-prompt.d.ts.map +1 -0
  195. package/dist/terminal/search-prompt.js +279 -0
  196. package/dist/terminal/select-prompt.d.ts +26 -0
  197. package/dist/terminal/select-prompt.d.ts.map +1 -0
  198. package/dist/terminal/select-prompt.js +298 -0
  199. package/dist/terminal/string-width.d.ts +7 -0
  200. package/dist/terminal/string-width.d.ts.map +1 -0
  201. package/dist/terminal/string-width.js +61 -0
  202. package/dist/terminal/strip-ansi.d.ts +2 -0
  203. package/dist/terminal/strip-ansi.d.ts.map +1 -0
  204. package/dist/terminal/strip-ansi.js +20 -0
  205. package/dist/terminal/style.d.ts +191 -0
  206. package/dist/terminal/style.d.ts.map +1 -0
  207. package/dist/terminal/style.js +259 -0
  208. package/dist/terminal/supports-color.d.ts +1 -0
  209. package/dist/terminal/supports-color.d.ts.map +1 -0
  210. package/dist/terminal/supports-hyperlinks.d.ts +1 -3
  211. package/dist/terminal/supports-hyperlinks.d.ts.map +1 -0
  212. package/dist/terminal/supports-hyperlinks.js +1 -1
  213. package/dist/terminal/types.d.ts +1 -37
  214. package/dist/terminal/types.d.ts.map +1 -0
  215. package/dist/terminal/wrap-ansi.d.ts +8 -0
  216. package/dist/terminal/wrap-ansi.d.ts.map +1 -0
  217. package/dist/terminal/wrap-ansi.js +190 -0
  218. package/dist/{token-utils.d.ts → tokens/counter.d.ts} +1 -0
  219. package/dist/tokens/counter.d.ts.map +1 -0
  220. package/dist/{token-utils.js → tokens/counter.js} +1 -1
  221. package/dist/tokens/manage-output.d.ts +34 -0
  222. package/dist/tokens/manage-output.d.ts.map +1 -0
  223. package/dist/tokens/manage-output.js +44 -0
  224. package/dist/{token-tracker.d.ts → tokens/tracker.d.ts} +1 -0
  225. package/dist/tokens/tracker.d.ts.map +1 -0
  226. package/dist/tool-executor.d.ts +28 -0
  227. package/dist/tool-executor.d.ts.map +1 -0
  228. package/dist/tool-executor.js +74 -0
  229. package/dist/tools/agent.d.ts +3 -2
  230. package/dist/tools/agent.d.ts.map +1 -0
  231. package/dist/tools/agent.js +7 -4
  232. package/dist/tools/bash-utils.d.ts +7 -0
  233. package/dist/tools/bash-utils.d.ts.map +1 -0
  234. package/dist/tools/bash-utils.js +212 -0
  235. package/dist/tools/bash.d.ts +9 -7
  236. package/dist/tools/bash.d.ts.map +1 -0
  237. package/dist/tools/bash.js +95 -212
  238. package/dist/tools/code-interpreter.d.ts +1 -1
  239. package/dist/tools/code-interpreter.d.ts.map +1 -0
  240. package/dist/tools/code-interpreter.js +31 -96
  241. package/dist/tools/delete-file.d.ts +5 -3
  242. package/dist/tools/delete-file.d.ts.map +1 -0
  243. package/dist/tools/delete-file.js +47 -33
  244. package/dist/tools/directory-tree.d.ts +10 -1
  245. package/dist/tools/directory-tree.d.ts.map +1 -0
  246. package/dist/tools/directory-tree.js +91 -8
  247. package/dist/tools/dynamic-tool-loader.d.ts +12 -0
  248. package/dist/tools/dynamic-tool-loader.d.ts.map +1 -0
  249. package/dist/tools/dynamic-tool-loader.js +280 -0
  250. package/dist/tools/dynamic-tool-parser.d.ts +20 -0
  251. package/dist/tools/dynamic-tool-parser.d.ts.map +1 -0
  252. package/dist/tools/dynamic-tool-parser.js +21 -0
  253. package/dist/tools/edit-file.d.ts +10 -2
  254. package/dist/tools/edit-file.d.ts.map +1 -0
  255. package/dist/tools/edit-file.js +117 -40
  256. package/dist/tools/file-editing-utils.d.ts +2 -0
  257. package/dist/tools/file-editing-utils.d.ts.map +1 -0
  258. package/dist/tools/file-editing-utils.js +135 -0
  259. package/dist/tools/filesystem-utils.d.ts +6 -21
  260. package/dist/tools/filesystem-utils.d.ts.map +1 -0
  261. package/dist/tools/filesystem-utils.js +96 -148
  262. package/dist/tools/git-utils.d.ts +1 -0
  263. package/dist/tools/git-utils.d.ts.map +1 -0
  264. package/dist/tools/grep.d.ts +5 -3
  265. package/dist/tools/grep.d.ts.map +1 -0
  266. package/dist/tools/grep.js +67 -27
  267. package/dist/tools/index.d.ts +10 -16
  268. package/dist/tools/index.d.ts.map +1 -0
  269. package/dist/tools/index.js +33 -22
  270. package/dist/tools/move-file.d.ts +1 -0
  271. package/dist/tools/move-file.d.ts.map +1 -0
  272. package/dist/tools/move-file.js +12 -5
  273. package/dist/tools/read-file.d.ts +2 -1
  274. package/dist/tools/read-file.d.ts.map +1 -0
  275. package/dist/tools/read-file.js +13 -6
  276. package/dist/tools/read-multiple-files.d.ts +2 -1
  277. package/dist/tools/read-multiple-files.d.ts.map +1 -0
  278. package/dist/tools/read-multiple-files.js +90 -9
  279. package/dist/tools/save-file.d.ts +5 -3
  280. package/dist/tools/save-file.d.ts.map +1 -0
  281. package/dist/tools/save-file.js +64 -36
  282. package/dist/tools/think.d.ts +1 -0
  283. package/dist/tools/think.d.ts.map +1 -0
  284. package/dist/tools/think.js +5 -1
  285. package/dist/tools/types.d.ts +14 -1
  286. package/dist/tools/types.d.ts.map +1 -0
  287. package/dist/tools/web-fetch.d.ts +4 -2
  288. package/dist/tools/web-fetch.d.ts.map +1 -0
  289. package/dist/tools/web-fetch.js +2 -2
  290. package/dist/tools/web-search.d.ts +2 -1
  291. package/dist/tools/web-search.d.ts.map +1 -0
  292. package/dist/tools/web-search.js +46 -11
  293. package/dist/utils/filesystem.d.ts +23 -0
  294. package/dist/utils/filesystem.d.ts.map +1 -0
  295. package/dist/utils/filesystem.js +140 -0
  296. package/dist/utils/filetype-detection.d.ts +3 -0
  297. package/dist/utils/filetype-detection.d.ts.map +1 -0
  298. package/dist/utils/filetype-detection.js +112 -0
  299. package/dist/utils/glob.d.ts +52 -0
  300. package/dist/utils/glob.d.ts.map +1 -0
  301. package/dist/utils/glob.js +376 -0
  302. package/dist/utils/ignore.d.ts +104 -0
  303. package/dist/utils/ignore.d.ts.map +1 -0
  304. package/dist/utils/ignore.js +649 -0
  305. package/dist/utils/process.d.ts +3 -2
  306. package/dist/utils/process.d.ts.map +1 -0
  307. package/dist/utils/process.js +17 -2
  308. package/dist/utils/zod-utils.d.ts +4 -0
  309. package/dist/utils/zod-utils.d.ts.map +1 -0
  310. package/dist/utils/zod-utils.js +7 -0
  311. package/dist/version.d.ts +1 -0
  312. package/dist/version.d.ts.map +1 -0
  313. package/package.json +32 -30
  314. package/dist/tools/command-validation.d.ts +0 -11
  315. package/dist/tools/command-validation.js +0 -45
  316. /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, autoAcceptAll, }: {
7
+ export declare const createEditFileTool: ({ workingDir, terminal, sendData, toolExecutor, }: {
7
8
  workingDir: string;
8
9
  terminal?: Terminal;
9
10
  sendData?: SendData;
10
- autoAcceptAll: boolean;
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"}
@@ -1,71 +1,85 @@
1
- import { input, select } from "@inquirer/prompts";
1
+ import { readFile, writeFile } from "node:fs/promises";
2
2
  import { tool } from "ai";
3
- import chalk from "chalk";
3
+ import { createTwoFilesPatch } from "diff";
4
4
  import { z } from "zod";
5
- import { applyFileEdits, joinWorkingDir, validatePath, } from "./filesystem-utils.js";
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, autoAcceptAll, }) => {
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. Returns a git-style diff showing the changes made. " +
16
- "Only works within allowed directories.",
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: ${chalk.cyan(path)}`,
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${chalk.blue.bold("●")} Proposing file changes: ${chalk.cyan(path)}`);
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.lineBreak();
42
- let userChoice;
43
- if (autoAcceptEdits) {
44
- terminal.writeln(chalk.green("✓ Auto-accepting edits (all future edits will be accepted)"));
45
- userChoice = "accept";
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
- { name: "Accept these changes", value: "accept" },
52
- {
53
- name: "Accept all future edits (including these)",
54
- value: "accept-all",
55
- },
56
- { name: "Reject these changes", value: "reject" },
57
- ],
58
- default: "accept",
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
- autoAcceptEdits = true;
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: ${reason}`,
97
+ data: `Edits rejected by user. Reason: ${rejectionReason}`,
84
98
  });
85
- return `The user rejected these changes. Reason: ${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,2 @@
1
+ export declare function replace(content: string, oldString: string, newString: string, replaceAll?: boolean): string;
2
+ //# sourceMappingURL=file-editing-utils.d.ts.map
@@ -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"}