amai 0.0.21 → 0.0.22
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 +10 -15
- package/dist/cli.cjs +265 -2337
- package/dist/cli.js +243 -2298
- package/dist/lib/code-server.cjs +1 -1
- package/dist/lib/code-server.js +1 -1
- package/dist/lib/daemon-entry.cjs +229 -2302
- package/dist/lib/daemon-entry.js +210 -2266
- package/dist/server.cjs +201 -2274
- package/dist/server.js +182 -2238
- package/package.json +10 -6
package/dist/server.js
CHANGED
|
@@ -1,1856 +1,23 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
import WebSocket2 from 'ws';
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
]);
|
|
22
|
-
function isMutatingTool(toolName) {
|
|
23
|
-
return MUTATING_TOOLS.has(toolName);
|
|
24
|
-
}
|
|
25
|
-
function isPathWithinProject(filePath, projectCwd) {
|
|
26
|
-
try {
|
|
27
|
-
const resolvedCwd = safeRealpath(projectCwd);
|
|
28
|
-
const resolved = path10.resolve(resolvedCwd, filePath);
|
|
29
|
-
const resolvedTarget = safeRealpath(resolved);
|
|
30
|
-
const rel = path10.relative(resolvedCwd, resolvedTarget);
|
|
31
|
-
if (rel.startsWith("..") || path10.isAbsolute(rel)) {
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
return true;
|
|
35
|
-
} catch {
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
function safeRealpath(p) {
|
|
40
|
-
try {
|
|
41
|
-
return fs8.realpathSync(p);
|
|
42
|
-
} catch {
|
|
43
|
-
const parent = path10.dirname(p);
|
|
44
|
-
try {
|
|
45
|
-
const realParent = fs8.realpathSync(parent);
|
|
46
|
-
return path10.join(realParent, path10.basename(p));
|
|
47
|
-
} catch {
|
|
48
|
-
return path10.resolve(p);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
function validatePath(filePath, projectCwd) {
|
|
53
|
-
if (!projectCwd) {
|
|
54
|
-
return {
|
|
55
|
-
valid: false,
|
|
56
|
-
error: "ACCESS_DENIED: No project context provided"
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
try {
|
|
60
|
-
if (!isPathWithinProject(filePath, projectCwd)) {
|
|
61
|
-
return {
|
|
62
|
-
valid: false,
|
|
63
|
-
error: `ACCESS_DENIED: Path "${filePath}" is outside project directory "${projectCwd}"`
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
const resolvedCwd = safeRealpath(projectCwd);
|
|
67
|
-
const resolvedPath = path10.resolve(resolvedCwd, filePath);
|
|
68
|
-
return {
|
|
69
|
-
valid: true,
|
|
70
|
-
resolvedPath
|
|
71
|
-
};
|
|
72
|
-
} catch (error) {
|
|
73
|
-
return {
|
|
74
|
-
valid: false,
|
|
75
|
-
error: `ACCESS_DENIED: Invalid path "${filePath}"`
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
function resolveProjectPath(filePath, projectCwd) {
|
|
80
|
-
return path10.resolve(projectCwd, filePath);
|
|
81
|
-
}
|
|
82
|
-
function requireProjectCwd(toolName, projectCwd) {
|
|
83
|
-
if (!projectCwd && isMutatingTool(toolName)) {
|
|
84
|
-
return {
|
|
85
|
-
allowed: false,
|
|
86
|
-
error: `ACCESS_DENIED: Tool "${toolName}" requires a project context (projectCwd) but none was provided`
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
return { allowed: true };
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// src/tools/read-file.ts
|
|
93
|
-
var MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
94
|
-
var MAX_LINES_RETURNED = 2e3;
|
|
95
|
-
var MAX_LINE_LENGTH = 2e3;
|
|
96
|
-
var MAX_LINE_SUFFIX = `... (line truncated to ${MAX_LINE_LENGTH} chars)`;
|
|
97
|
-
var MAX_BYTES = 50 * 1024;
|
|
98
|
-
var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
99
|
-
".zip",
|
|
100
|
-
".tar",
|
|
101
|
-
".gz",
|
|
102
|
-
".exe",
|
|
103
|
-
".dll",
|
|
104
|
-
".so",
|
|
105
|
-
".class",
|
|
106
|
-
".jar",
|
|
107
|
-
".war",
|
|
108
|
-
".7z",
|
|
109
|
-
".doc",
|
|
110
|
-
".docx",
|
|
111
|
-
".xls",
|
|
112
|
-
".xlsx",
|
|
113
|
-
".ppt",
|
|
114
|
-
".pptx",
|
|
115
|
-
".odt",
|
|
116
|
-
".ods",
|
|
117
|
-
".odp",
|
|
118
|
-
".bin",
|
|
119
|
-
".dat",
|
|
120
|
-
".obj",
|
|
121
|
-
".o",
|
|
122
|
-
".a",
|
|
123
|
-
".lib",
|
|
124
|
-
".wasm",
|
|
125
|
-
".pyc",
|
|
126
|
-
".pyo",
|
|
127
|
-
".ico",
|
|
128
|
-
".bmp",
|
|
129
|
-
".ttf",
|
|
130
|
-
".woff",
|
|
131
|
-
".woff2",
|
|
132
|
-
".eot",
|
|
133
|
-
".mp3",
|
|
134
|
-
".mp4",
|
|
135
|
-
".avi",
|
|
136
|
-
".mov",
|
|
137
|
-
".flv"
|
|
138
|
-
]);
|
|
139
|
-
async function isBinaryFile(filepath, fileSize) {
|
|
140
|
-
const ext = path10.extname(filepath).toLowerCase();
|
|
141
|
-
if (BINARY_EXTENSIONS.has(ext)) return true;
|
|
142
|
-
if (fileSize === 0) return false;
|
|
143
|
-
try {
|
|
144
|
-
const fh = await fsp.open(filepath, "r");
|
|
145
|
-
try {
|
|
146
|
-
const sampleSize = Math.min(4096, fileSize);
|
|
147
|
-
const bytes = Buffer.alloc(sampleSize);
|
|
148
|
-
const result = await fh.read(bytes, 0, sampleSize, 0);
|
|
149
|
-
if (result.bytesRead === 0) return false;
|
|
150
|
-
let nonPrintableCount = 0;
|
|
151
|
-
for (let i = 0; i < result.bytesRead; i++) {
|
|
152
|
-
if (bytes[i] === 0) return true;
|
|
153
|
-
if (bytes[i] < 9 || bytes[i] > 13 && bytes[i] < 32) {
|
|
154
|
-
nonPrintableCount++;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
return nonPrintableCount / result.bytesRead > 0.3;
|
|
158
|
-
} finally {
|
|
159
|
-
await fh.close();
|
|
160
|
-
}
|
|
161
|
-
} catch {
|
|
162
|
-
return false;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
async function findSimilarFiles(filepath) {
|
|
166
|
-
const dir = path10.dirname(filepath);
|
|
167
|
-
const base = path10.basename(filepath).toLowerCase();
|
|
168
|
-
try {
|
|
169
|
-
const entries = await fsp.readdir(dir);
|
|
170
|
-
return entries.filter(
|
|
171
|
-
(entry) => entry.toLowerCase().includes(base) || base.includes(entry.toLowerCase())
|
|
172
|
-
).map((entry) => path10.join(dir, entry)).slice(0, 3);
|
|
173
|
-
} catch {
|
|
174
|
-
return [];
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
z.object({
|
|
178
|
-
relative_file_path: z.string().describe("The path to the file or directory to read."),
|
|
179
|
-
should_read_entire_file: z.boolean().describe("Whether to read the entire file.").optional().default(true),
|
|
180
|
-
start_line_one_indexed: z.number().optional().describe("The one-indexed line number to start reading from (inclusive). Alias: offset."),
|
|
181
|
-
end_line_one_indexed: z.number().optional().describe("The one-indexed line number to end reading at (inclusive). Alias: offset + limit.")
|
|
182
|
-
});
|
|
183
|
-
async function readFileContent(absolute_file_path, relative_file_path, should_read_entire_file, start_line_one_indexed, end_line_one_indexed) {
|
|
184
|
-
let stat2;
|
|
185
|
-
try {
|
|
186
|
-
stat2 = fs8.statSync(absolute_file_path);
|
|
187
|
-
} catch {
|
|
188
|
-
const suggestions = await findSimilarFiles(absolute_file_path);
|
|
189
|
-
let message = `File not found: ${relative_file_path}`;
|
|
190
|
-
if (suggestions.length > 0) {
|
|
191
|
-
message += `
|
|
192
|
-
|
|
193
|
-
Did you mean one of these?
|
|
194
|
-
${suggestions.join("\n")}`;
|
|
195
|
-
}
|
|
196
|
-
return {
|
|
197
|
-
success: false,
|
|
198
|
-
message,
|
|
199
|
-
error: "FILE_NOT_FOUND"
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
if (stat2.isDirectory()) {
|
|
203
|
-
try {
|
|
204
|
-
const dirents = await fsp.readdir(absolute_file_path, { withFileTypes: true });
|
|
205
|
-
const entries = await Promise.all(
|
|
206
|
-
dirents.map(async (dirent) => {
|
|
207
|
-
if (dirent.isDirectory()) return dirent.name + "/";
|
|
208
|
-
if (dirent.isSymbolicLink()) {
|
|
209
|
-
const target = await fsp.stat(path10.join(absolute_file_path, dirent.name)).catch(() => void 0);
|
|
210
|
-
if (target?.isDirectory()) return dirent.name + "/";
|
|
211
|
-
}
|
|
212
|
-
return dirent.name;
|
|
213
|
-
})
|
|
214
|
-
);
|
|
215
|
-
entries.sort((a, b) => a.localeCompare(b));
|
|
216
|
-
const truncated = entries.length > MAX_LINES_RETURNED;
|
|
217
|
-
const sliced = entries.slice(0, MAX_LINES_RETURNED);
|
|
218
|
-
const output = [
|
|
219
|
-
`<path>${absolute_file_path}</path>`,
|
|
220
|
-
`<type>directory</type>`,
|
|
221
|
-
`<entries>`,
|
|
222
|
-
sliced.join("\n"),
|
|
223
|
-
truncated ? `
|
|
224
|
-
(Showing ${sliced.length} of ${entries.length} entries)` : `
|
|
225
|
-
(${entries.length} entries)`,
|
|
226
|
-
`</entries>`
|
|
227
|
-
].join("\n");
|
|
228
|
-
return {
|
|
229
|
-
success: true,
|
|
230
|
-
message: `Listed directory: ${relative_file_path} (${entries.length} entries)`,
|
|
231
|
-
content: output,
|
|
232
|
-
totalLines: entries.length,
|
|
233
|
-
truncated
|
|
234
|
-
};
|
|
235
|
-
} catch (err) {
|
|
236
|
-
return {
|
|
237
|
-
success: false,
|
|
238
|
-
message: `Failed to list directory: ${relative_file_path}`,
|
|
239
|
-
error: "READ_ERROR"
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
try {
|
|
244
|
-
if (stat2.size > MAX_FILE_SIZE) {
|
|
245
|
-
return {
|
|
246
|
-
success: false,
|
|
247
|
-
message: `File too large (${Math.round(stat2.size / 1024 / 1024)}MB). Maximum is ${MAX_FILE_SIZE / 1024 / 1024}MB. Use line ranges to read portions.`,
|
|
248
|
-
error: "FILE_TOO_LARGE"
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
const binary = await isBinaryFile(absolute_file_path, stat2.size);
|
|
252
|
-
if (binary) {
|
|
253
|
-
return {
|
|
254
|
-
success: false,
|
|
255
|
-
message: `Cannot read binary file: ${relative_file_path}`,
|
|
256
|
-
error: "BINARY_FILE"
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
const fileContent = await Bun.file(absolute_file_path).text();
|
|
260
|
-
const lines = fileContent.split(/\r?\n/);
|
|
261
|
-
const totalLines = lines.length;
|
|
262
|
-
const start = should_read_entire_file ? 0 : (start_line_one_indexed ?? 1) - 1;
|
|
263
|
-
const end = should_read_entire_file ? Math.min(totalLines, MAX_LINES_RETURNED) : Math.min(end_line_one_indexed ?? totalLines, totalLines);
|
|
264
|
-
if (start >= totalLines && !(totalLines === 0 && start === 0)) {
|
|
265
|
-
return {
|
|
266
|
-
success: false,
|
|
267
|
-
message: `Offset ${start + 1} is out of range for this file (${totalLines} lines)`,
|
|
268
|
-
error: "INVALID_LINE_RANGE"
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
const outputLines = [];
|
|
272
|
-
let bytes = 0;
|
|
273
|
-
let truncatedByBytes = false;
|
|
274
|
-
let actualEnd = start;
|
|
275
|
-
for (let i = start; i < end; i++) {
|
|
276
|
-
let line = lines[i];
|
|
277
|
-
if (line.length > MAX_LINE_LENGTH) {
|
|
278
|
-
line = line.substring(0, MAX_LINE_LENGTH) + MAX_LINE_SUFFIX;
|
|
279
|
-
}
|
|
280
|
-
const numberedLine = `${i + 1}: ${line}`;
|
|
281
|
-
const lineBytes = Buffer.byteLength(numberedLine, "utf-8") + (outputLines.length > 0 ? 1 : 0);
|
|
282
|
-
if (bytes + lineBytes > MAX_BYTES && outputLines.length > 0) {
|
|
283
|
-
truncatedByBytes = true;
|
|
284
|
-
break;
|
|
285
|
-
}
|
|
286
|
-
outputLines.push(numberedLine);
|
|
287
|
-
bytes += lineBytes;
|
|
288
|
-
actualEnd = i + 1;
|
|
289
|
-
}
|
|
290
|
-
const hasMoreLines = actualEnd < totalLines;
|
|
291
|
-
const truncated = truncatedByBytes || hasMoreLines || should_read_entire_file && totalLines > MAX_LINES_RETURNED;
|
|
292
|
-
let output = `<path>${absolute_file_path}</path>
|
|
293
|
-
<type>file</type>
|
|
294
|
-
<content>
|
|
295
|
-
`;
|
|
296
|
-
output += outputLines.join("\n");
|
|
297
|
-
if (truncatedByBytes) {
|
|
298
|
-
output += `
|
|
299
|
-
|
|
300
|
-
(Output capped at ${MAX_BYTES / 1024} KB. Showing lines ${start + 1}-${actualEnd}. Use start_line_one_indexed=${actualEnd + 1} to continue.)`;
|
|
301
|
-
} else if (hasMoreLines && !should_read_entire_file) {
|
|
302
|
-
output += `
|
|
303
|
-
|
|
304
|
-
(Showing lines ${start + 1}-${actualEnd} of ${totalLines}. Use start_line_one_indexed=${actualEnd + 1} to continue.)`;
|
|
305
|
-
} else {
|
|
306
|
-
output += `
|
|
307
|
-
|
|
308
|
-
(End of file - total ${totalLines} lines)`;
|
|
309
|
-
}
|
|
310
|
-
output += "\n</content>";
|
|
311
|
-
return {
|
|
312
|
-
success: true,
|
|
313
|
-
message: truncated ? `Read lines ${start + 1}-${actualEnd} of ${totalLines} from: ${relative_file_path} (truncated)` : `Successfully read file: ${relative_file_path} (${totalLines} lines)`,
|
|
314
|
-
content: output,
|
|
315
|
-
totalLines,
|
|
316
|
-
truncated
|
|
317
|
-
};
|
|
318
|
-
} catch (error) {
|
|
319
|
-
return {
|
|
320
|
-
success: false,
|
|
321
|
-
message: `Failed to read file: ${relative_file_path}`,
|
|
322
|
-
error: "READ_ERROR"
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
var read_file = async function(input, projectCwd) {
|
|
327
|
-
const { relative_file_path, should_read_entire_file = true, start_line_one_indexed, end_line_one_indexed } = input;
|
|
328
|
-
try {
|
|
329
|
-
if (!relative_file_path) {
|
|
330
|
-
return {
|
|
331
|
-
success: false,
|
|
332
|
-
message: "Missing required parameter: relative_file_path",
|
|
333
|
-
error: "MISSING_TARGET_FILE"
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
if (!should_read_entire_file) {
|
|
337
|
-
if (start_line_one_indexed === void 0 || end_line_one_indexed === void 0) {
|
|
338
|
-
return {
|
|
339
|
-
success: false,
|
|
340
|
-
message: "start_line_one_indexed and end_line_one_indexed are required when should_read_entire_file is false",
|
|
341
|
-
error: "MISSING_LINE_RANGE"
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
|
-
if (!Number.isInteger(start_line_one_indexed) || start_line_one_indexed < 1) {
|
|
345
|
-
return {
|
|
346
|
-
success: false,
|
|
347
|
-
message: "start_line_one_indexed must be a positive integer (1-indexed)",
|
|
348
|
-
error: "INVALID_START_LINE"
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
if (!Number.isInteger(end_line_one_indexed) || end_line_one_indexed < 1) {
|
|
352
|
-
return {
|
|
353
|
-
success: false,
|
|
354
|
-
message: "end_line_one_indexed must be a positive integer (1-indexed)",
|
|
355
|
-
error: "INVALID_END_LINE"
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
if (end_line_one_indexed < start_line_one_indexed) {
|
|
359
|
-
return {
|
|
360
|
-
success: false,
|
|
361
|
-
message: "end_line_one_indexed must be greater than or equal to start_line_one_indexed",
|
|
362
|
-
error: "INVALID_LINE_RANGE"
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
let absolute_file_path;
|
|
367
|
-
if (projectCwd) {
|
|
368
|
-
const validation = validatePath(relative_file_path, projectCwd);
|
|
369
|
-
if (!validation.valid) {
|
|
370
|
-
return {
|
|
371
|
-
success: false,
|
|
372
|
-
message: validation.error || "Path validation failed",
|
|
373
|
-
error: "ACCESS_DENIED"
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
absolute_file_path = validation.resolvedPath;
|
|
377
|
-
} else {
|
|
378
|
-
absolute_file_path = path10.resolve(relative_file_path);
|
|
379
|
-
}
|
|
380
|
-
return await readFileContent(
|
|
381
|
-
absolute_file_path,
|
|
382
|
-
relative_file_path,
|
|
383
|
-
should_read_entire_file,
|
|
384
|
-
start_line_one_indexed,
|
|
385
|
-
end_line_one_indexed
|
|
386
|
-
);
|
|
387
|
-
} catch {
|
|
388
|
-
return {
|
|
389
|
-
success: false,
|
|
390
|
-
message: `Failed to read file: ${relative_file_path}`,
|
|
391
|
-
error: "READ_ERROR"
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
};
|
|
395
|
-
|
|
396
|
-
// ../../node_modules/.bun/diff@8.0.2/node_modules/diff/libesm/diff/base.js
|
|
397
|
-
var Diff = class {
|
|
398
|
-
diff(oldStr, newStr, options = {}) {
|
|
399
|
-
let callback;
|
|
400
|
-
if (typeof options === "function") {
|
|
401
|
-
callback = options;
|
|
402
|
-
options = {};
|
|
403
|
-
} else if ("callback" in options) {
|
|
404
|
-
callback = options.callback;
|
|
405
|
-
}
|
|
406
|
-
const oldString = this.castInput(oldStr, options);
|
|
407
|
-
const newString = this.castInput(newStr, options);
|
|
408
|
-
const oldTokens = this.removeEmpty(this.tokenize(oldString, options));
|
|
409
|
-
const newTokens = this.removeEmpty(this.tokenize(newString, options));
|
|
410
|
-
return this.diffWithOptionsObj(oldTokens, newTokens, options, callback);
|
|
411
|
-
}
|
|
412
|
-
diffWithOptionsObj(oldTokens, newTokens, options, callback) {
|
|
413
|
-
var _a;
|
|
414
|
-
const done = (value) => {
|
|
415
|
-
value = this.postProcess(value, options);
|
|
416
|
-
if (callback) {
|
|
417
|
-
setTimeout(function() {
|
|
418
|
-
callback(value);
|
|
419
|
-
}, 0);
|
|
420
|
-
return void 0;
|
|
421
|
-
} else {
|
|
422
|
-
return value;
|
|
423
|
-
}
|
|
424
|
-
};
|
|
425
|
-
const newLen = newTokens.length, oldLen = oldTokens.length;
|
|
426
|
-
let editLength = 1;
|
|
427
|
-
let maxEditLength = newLen + oldLen;
|
|
428
|
-
if (options.maxEditLength != null) {
|
|
429
|
-
maxEditLength = Math.min(maxEditLength, options.maxEditLength);
|
|
430
|
-
}
|
|
431
|
-
const maxExecutionTime = (_a = options.timeout) !== null && _a !== void 0 ? _a : Infinity;
|
|
432
|
-
const abortAfterTimestamp = Date.now() + maxExecutionTime;
|
|
433
|
-
const bestPath = [{ oldPos: -1, lastComponent: void 0 }];
|
|
434
|
-
let newPos = this.extractCommon(bestPath[0], newTokens, oldTokens, 0, options);
|
|
435
|
-
if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
|
|
436
|
-
return done(this.buildValues(bestPath[0].lastComponent, newTokens, oldTokens));
|
|
437
|
-
}
|
|
438
|
-
let minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity;
|
|
439
|
-
const execEditLength = () => {
|
|
440
|
-
for (let diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) {
|
|
441
|
-
let basePath;
|
|
442
|
-
const removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1];
|
|
443
|
-
if (removePath) {
|
|
444
|
-
bestPath[diagonalPath - 1] = void 0;
|
|
445
|
-
}
|
|
446
|
-
let canAdd = false;
|
|
447
|
-
if (addPath) {
|
|
448
|
-
const addPathNewPos = addPath.oldPos - diagonalPath;
|
|
449
|
-
canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen;
|
|
450
|
-
}
|
|
451
|
-
const canRemove = removePath && removePath.oldPos + 1 < oldLen;
|
|
452
|
-
if (!canAdd && !canRemove) {
|
|
453
|
-
bestPath[diagonalPath] = void 0;
|
|
454
|
-
continue;
|
|
455
|
-
}
|
|
456
|
-
if (!canRemove || canAdd && removePath.oldPos < addPath.oldPos) {
|
|
457
|
-
basePath = this.addToPath(addPath, true, false, 0, options);
|
|
458
|
-
} else {
|
|
459
|
-
basePath = this.addToPath(removePath, false, true, 1, options);
|
|
460
|
-
}
|
|
461
|
-
newPos = this.extractCommon(basePath, newTokens, oldTokens, diagonalPath, options);
|
|
462
|
-
if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
|
|
463
|
-
return done(this.buildValues(basePath.lastComponent, newTokens, oldTokens)) || true;
|
|
464
|
-
} else {
|
|
465
|
-
bestPath[diagonalPath] = basePath;
|
|
466
|
-
if (basePath.oldPos + 1 >= oldLen) {
|
|
467
|
-
maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1);
|
|
468
|
-
}
|
|
469
|
-
if (newPos + 1 >= newLen) {
|
|
470
|
-
minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1);
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
editLength++;
|
|
475
|
-
};
|
|
476
|
-
if (callback) {
|
|
477
|
-
(function exec2() {
|
|
478
|
-
setTimeout(function() {
|
|
479
|
-
if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) {
|
|
480
|
-
return callback(void 0);
|
|
481
|
-
}
|
|
482
|
-
if (!execEditLength()) {
|
|
483
|
-
exec2();
|
|
484
|
-
}
|
|
485
|
-
}, 0);
|
|
486
|
-
})();
|
|
487
|
-
} else {
|
|
488
|
-
while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) {
|
|
489
|
-
const ret = execEditLength();
|
|
490
|
-
if (ret) {
|
|
491
|
-
return ret;
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
addToPath(path14, added, removed, oldPosInc, options) {
|
|
497
|
-
const last = path14.lastComponent;
|
|
498
|
-
if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
|
|
499
|
-
return {
|
|
500
|
-
oldPos: path14.oldPos + oldPosInc,
|
|
501
|
-
lastComponent: { count: last.count + 1, added, removed, previousComponent: last.previousComponent }
|
|
502
|
-
};
|
|
503
|
-
} else {
|
|
504
|
-
return {
|
|
505
|
-
oldPos: path14.oldPos + oldPosInc,
|
|
506
|
-
lastComponent: { count: 1, added, removed, previousComponent: last }
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
extractCommon(basePath, newTokens, oldTokens, diagonalPath, options) {
|
|
511
|
-
const newLen = newTokens.length, oldLen = oldTokens.length;
|
|
512
|
-
let oldPos = basePath.oldPos, newPos = oldPos - diagonalPath, commonCount = 0;
|
|
513
|
-
while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldTokens[oldPos + 1], newTokens[newPos + 1], options)) {
|
|
514
|
-
newPos++;
|
|
515
|
-
oldPos++;
|
|
516
|
-
commonCount++;
|
|
517
|
-
if (options.oneChangePerToken) {
|
|
518
|
-
basePath.lastComponent = { count: 1, previousComponent: basePath.lastComponent, added: false, removed: false };
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
if (commonCount && !options.oneChangePerToken) {
|
|
522
|
-
basePath.lastComponent = { count: commonCount, previousComponent: basePath.lastComponent, added: false, removed: false };
|
|
523
|
-
}
|
|
524
|
-
basePath.oldPos = oldPos;
|
|
525
|
-
return newPos;
|
|
526
|
-
}
|
|
527
|
-
equals(left, right, options) {
|
|
528
|
-
if (options.comparator) {
|
|
529
|
-
return options.comparator(left, right);
|
|
530
|
-
} else {
|
|
531
|
-
return left === right || !!options.ignoreCase && left.toLowerCase() === right.toLowerCase();
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
removeEmpty(array) {
|
|
535
|
-
const ret = [];
|
|
536
|
-
for (let i = 0; i < array.length; i++) {
|
|
537
|
-
if (array[i]) {
|
|
538
|
-
ret.push(array[i]);
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
return ret;
|
|
542
|
-
}
|
|
543
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
544
|
-
castInput(value, options) {
|
|
545
|
-
return value;
|
|
546
|
-
}
|
|
547
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
548
|
-
tokenize(value, options) {
|
|
549
|
-
return Array.from(value);
|
|
550
|
-
}
|
|
551
|
-
join(chars) {
|
|
552
|
-
return chars.join("");
|
|
553
|
-
}
|
|
554
|
-
postProcess(changeObjects, options) {
|
|
555
|
-
return changeObjects;
|
|
556
|
-
}
|
|
557
|
-
get useLongestToken() {
|
|
558
|
-
return false;
|
|
559
|
-
}
|
|
560
|
-
buildValues(lastComponent, newTokens, oldTokens) {
|
|
561
|
-
const components = [];
|
|
562
|
-
let nextComponent;
|
|
563
|
-
while (lastComponent) {
|
|
564
|
-
components.push(lastComponent);
|
|
565
|
-
nextComponent = lastComponent.previousComponent;
|
|
566
|
-
delete lastComponent.previousComponent;
|
|
567
|
-
lastComponent = nextComponent;
|
|
568
|
-
}
|
|
569
|
-
components.reverse();
|
|
570
|
-
const componentLen = components.length;
|
|
571
|
-
let componentPos = 0, newPos = 0, oldPos = 0;
|
|
572
|
-
for (; componentPos < componentLen; componentPos++) {
|
|
573
|
-
const component = components[componentPos];
|
|
574
|
-
if (!component.removed) {
|
|
575
|
-
if (!component.added && this.useLongestToken) {
|
|
576
|
-
let value = newTokens.slice(newPos, newPos + component.count);
|
|
577
|
-
value = value.map(function(value2, i) {
|
|
578
|
-
const oldValue = oldTokens[oldPos + i];
|
|
579
|
-
return oldValue.length > value2.length ? oldValue : value2;
|
|
580
|
-
});
|
|
581
|
-
component.value = this.join(value);
|
|
582
|
-
} else {
|
|
583
|
-
component.value = this.join(newTokens.slice(newPos, newPos + component.count));
|
|
584
|
-
}
|
|
585
|
-
newPos += component.count;
|
|
586
|
-
if (!component.added) {
|
|
587
|
-
oldPos += component.count;
|
|
588
|
-
}
|
|
589
|
-
} else {
|
|
590
|
-
component.value = this.join(oldTokens.slice(oldPos, oldPos + component.count));
|
|
591
|
-
oldPos += component.count;
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
return components;
|
|
595
|
-
}
|
|
596
|
-
};
|
|
597
|
-
|
|
598
|
-
// ../../node_modules/.bun/diff@8.0.2/node_modules/diff/libesm/diff/line.js
|
|
599
|
-
var LineDiff = class extends Diff {
|
|
600
|
-
constructor() {
|
|
601
|
-
super(...arguments);
|
|
602
|
-
this.tokenize = tokenize;
|
|
603
|
-
}
|
|
604
|
-
equals(left, right, options) {
|
|
605
|
-
if (options.ignoreWhitespace) {
|
|
606
|
-
if (!options.newlineIsToken || !left.includes("\n")) {
|
|
607
|
-
left = left.trim();
|
|
608
|
-
}
|
|
609
|
-
if (!options.newlineIsToken || !right.includes("\n")) {
|
|
610
|
-
right = right.trim();
|
|
611
|
-
}
|
|
612
|
-
} else if (options.ignoreNewlineAtEof && !options.newlineIsToken) {
|
|
613
|
-
if (left.endsWith("\n")) {
|
|
614
|
-
left = left.slice(0, -1);
|
|
615
|
-
}
|
|
616
|
-
if (right.endsWith("\n")) {
|
|
617
|
-
right = right.slice(0, -1);
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
return super.equals(left, right, options);
|
|
621
|
-
}
|
|
622
|
-
};
|
|
623
|
-
var lineDiff = new LineDiff();
|
|
624
|
-
function diffLines(oldStr, newStr, options) {
|
|
625
|
-
return lineDiff.diff(oldStr, newStr, options);
|
|
626
|
-
}
|
|
627
|
-
function tokenize(value, options) {
|
|
628
|
-
if (options.stripTrailingCr) {
|
|
629
|
-
value = value.replace(/\r\n/g, "\n");
|
|
630
|
-
}
|
|
631
|
-
const retLines = [], linesAndNewlines = value.split(/(\n|\r\n)/);
|
|
632
|
-
if (!linesAndNewlines[linesAndNewlines.length - 1]) {
|
|
633
|
-
linesAndNewlines.pop();
|
|
634
|
-
}
|
|
635
|
-
for (let i = 0; i < linesAndNewlines.length; i++) {
|
|
636
|
-
const line = linesAndNewlines[i];
|
|
637
|
-
if (i % 2 && !options.newlineIsToken) {
|
|
638
|
-
retLines[retLines.length - 1] += line;
|
|
639
|
-
} else {
|
|
640
|
-
retLines.push(line);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
return retLines;
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
// src/lib/diff.ts
|
|
647
|
-
function calculateDiffStats(oldContent, newContent) {
|
|
648
|
-
const changes = diffLines(oldContent, newContent);
|
|
649
|
-
let linesAdded = 0;
|
|
650
|
-
let linesRemoved = 0;
|
|
651
|
-
for (const change of changes) {
|
|
652
|
-
if (change.added) {
|
|
653
|
-
linesAdded += change.count || 0;
|
|
654
|
-
} else if (change.removed) {
|
|
655
|
-
linesRemoved += change.count || 0;
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
return { linesAdded, linesRemoved };
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
// src/tools/stringReplace.ts
|
|
662
|
-
z.object({
|
|
663
|
-
file_path: z.string().describe("The path to the file you want to search and replace in. You can use either a relative path in the workspace or an absolute path. If an absolute path is provided, it will be preserved as is"),
|
|
664
|
-
new_string: z.string().describe("The edited text to replace the old_string (must be different from the old_string)"),
|
|
665
|
-
old_string: z.string().describe("The text to replace (must be unique within the file, and must match the file contents exactly, including all whitespace and indentation)"),
|
|
666
|
-
replaceAll: z.boolean().optional().describe("Replace all occurrences of old_string (default false)")
|
|
667
|
-
});
|
|
668
|
-
function levenshtein(a, b) {
|
|
669
|
-
if (a === "" || b === "") return Math.max(a.length, b.length);
|
|
670
|
-
const matrix = Array.from(
|
|
671
|
-
{ length: a.length + 1 },
|
|
672
|
-
(_, i) => Array.from({ length: b.length + 1 }, (_2, j) => i === 0 ? j : j === 0 ? i : 0)
|
|
673
|
-
);
|
|
674
|
-
for (let i = 1; i <= a.length; i++) {
|
|
675
|
-
for (let j = 1; j <= b.length; j++) {
|
|
676
|
-
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
677
|
-
matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
return matrix[a.length][b.length];
|
|
681
|
-
}
|
|
682
|
-
var SINGLE_CANDIDATE_SIMILARITY_THRESHOLD = 0;
|
|
683
|
-
var MULTIPLE_CANDIDATES_SIMILARITY_THRESHOLD = 0.3;
|
|
684
|
-
var SimpleReplacer = function* (_content, find) {
|
|
685
|
-
yield find;
|
|
686
|
-
};
|
|
687
|
-
var LineTrimmedReplacer = function* (content, find) {
|
|
688
|
-
const originalLines = content.split("\n");
|
|
689
|
-
const searchLines = find.split("\n");
|
|
690
|
-
if (searchLines[searchLines.length - 1] === "") searchLines.pop();
|
|
691
|
-
for (let i = 0; i <= originalLines.length - searchLines.length; i++) {
|
|
692
|
-
let matches = true;
|
|
693
|
-
for (let j = 0; j < searchLines.length; j++) {
|
|
694
|
-
if (originalLines[i + j].trim() !== searchLines[j].trim()) {
|
|
695
|
-
matches = false;
|
|
696
|
-
break;
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
if (matches) {
|
|
700
|
-
let matchStartIndex = 0;
|
|
701
|
-
for (let k = 0; k < i; k++) matchStartIndex += originalLines[k].length + 1;
|
|
702
|
-
let matchEndIndex = matchStartIndex;
|
|
703
|
-
for (let k = 0; k < searchLines.length; k++) {
|
|
704
|
-
matchEndIndex += originalLines[i + k].length;
|
|
705
|
-
if (k < searchLines.length - 1) matchEndIndex += 1;
|
|
706
|
-
}
|
|
707
|
-
yield content.substring(matchStartIndex, matchEndIndex);
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
};
|
|
711
|
-
var BlockAnchorReplacer = function* (content, find) {
|
|
712
|
-
const originalLines = content.split("\n");
|
|
713
|
-
const searchLines = find.split("\n");
|
|
714
|
-
if (searchLines.length < 3) return;
|
|
715
|
-
if (searchLines[searchLines.length - 1] === "") searchLines.pop();
|
|
716
|
-
const firstLineSearch = searchLines[0].trim();
|
|
717
|
-
const lastLineSearch = searchLines[searchLines.length - 1].trim();
|
|
718
|
-
const searchBlockSize = searchLines.length;
|
|
719
|
-
const candidates = [];
|
|
720
|
-
for (let i = 0; i < originalLines.length; i++) {
|
|
721
|
-
if (originalLines[i].trim() !== firstLineSearch) continue;
|
|
722
|
-
for (let j = i + 2; j < originalLines.length; j++) {
|
|
723
|
-
if (originalLines[j].trim() === lastLineSearch) {
|
|
724
|
-
candidates.push({ startLine: i, endLine: j });
|
|
725
|
-
break;
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
if (candidates.length === 0) return;
|
|
730
|
-
const computeSimilarity = (startLine, endLine) => {
|
|
731
|
-
const actualBlockSize = endLine - startLine + 1;
|
|
732
|
-
const linesToCheck = Math.min(searchBlockSize - 2, actualBlockSize - 2);
|
|
733
|
-
if (linesToCheck <= 0) return 1;
|
|
734
|
-
let similarity = 0;
|
|
735
|
-
for (let j = 1; j < searchBlockSize - 1 && j < actualBlockSize - 1; j++) {
|
|
736
|
-
const originalLine = originalLines[startLine + j].trim();
|
|
737
|
-
const searchLine = searchLines[j].trim();
|
|
738
|
-
const maxLen = Math.max(originalLine.length, searchLine.length);
|
|
739
|
-
if (maxLen === 0) continue;
|
|
740
|
-
const distance = levenshtein(originalLine, searchLine);
|
|
741
|
-
similarity += (1 - distance / maxLen) / linesToCheck;
|
|
742
|
-
}
|
|
743
|
-
return similarity;
|
|
744
|
-
};
|
|
745
|
-
const extractBlock = (startLine, endLine) => {
|
|
746
|
-
let matchStartIndex = 0;
|
|
747
|
-
for (let k = 0; k < startLine; k++) matchStartIndex += originalLines[k].length + 1;
|
|
748
|
-
let matchEndIndex = matchStartIndex;
|
|
749
|
-
for (let k = startLine; k <= endLine; k++) {
|
|
750
|
-
matchEndIndex += originalLines[k].length;
|
|
751
|
-
if (k < endLine) matchEndIndex += 1;
|
|
752
|
-
}
|
|
753
|
-
return content.substring(matchStartIndex, matchEndIndex);
|
|
754
|
-
};
|
|
755
|
-
if (candidates.length === 1) {
|
|
756
|
-
const { startLine, endLine } = candidates[0];
|
|
757
|
-
if (computeSimilarity(startLine, endLine) >= SINGLE_CANDIDATE_SIMILARITY_THRESHOLD) {
|
|
758
|
-
yield extractBlock(startLine, endLine);
|
|
759
|
-
}
|
|
760
|
-
return;
|
|
761
|
-
}
|
|
762
|
-
let bestMatch = null;
|
|
763
|
-
let maxSimilarity = -1;
|
|
764
|
-
for (const candidate of candidates) {
|
|
765
|
-
const similarity = computeSimilarity(candidate.startLine, candidate.endLine);
|
|
766
|
-
if (similarity > maxSimilarity) {
|
|
767
|
-
maxSimilarity = similarity;
|
|
768
|
-
bestMatch = candidate;
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
if (maxSimilarity >= MULTIPLE_CANDIDATES_SIMILARITY_THRESHOLD && bestMatch) {
|
|
772
|
-
yield extractBlock(bestMatch.startLine, bestMatch.endLine);
|
|
773
|
-
}
|
|
774
|
-
};
|
|
775
|
-
var WhitespaceNormalizedReplacer = function* (content, find) {
|
|
776
|
-
const normalizeWhitespace = (text) => text.replace(/\s+/g, " ").trim();
|
|
777
|
-
const normalizedFind = normalizeWhitespace(find);
|
|
778
|
-
const lines = content.split("\n");
|
|
779
|
-
for (let i = 0; i < lines.length; i++) {
|
|
780
|
-
const line = lines[i];
|
|
781
|
-
if (normalizeWhitespace(line) === normalizedFind) {
|
|
782
|
-
yield line;
|
|
783
|
-
} else {
|
|
784
|
-
const normalizedLine = normalizeWhitespace(line);
|
|
785
|
-
if (normalizedLine.includes(normalizedFind)) {
|
|
786
|
-
const words = find.trim().split(/\s+/);
|
|
787
|
-
if (words.length > 0) {
|
|
788
|
-
const pattern = words.map((word) => word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("\\s+");
|
|
789
|
-
try {
|
|
790
|
-
const regex = new RegExp(pattern);
|
|
791
|
-
const match = line.match(regex);
|
|
792
|
-
if (match) yield match[0];
|
|
793
|
-
} catch {
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
const findLines = find.split("\n");
|
|
800
|
-
if (findLines.length > 1) {
|
|
801
|
-
for (let i = 0; i <= lines.length - findLines.length; i++) {
|
|
802
|
-
const block = lines.slice(i, i + findLines.length);
|
|
803
|
-
if (normalizeWhitespace(block.join("\n")) === normalizedFind) {
|
|
804
|
-
yield block.join("\n");
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
};
|
|
809
|
-
var IndentationFlexibleReplacer = function* (content, find) {
|
|
810
|
-
const removeIndentation = (text) => {
|
|
811
|
-
const lines = text.split("\n");
|
|
812
|
-
const nonEmptyLines = lines.filter((line) => line.trim().length > 0);
|
|
813
|
-
if (nonEmptyLines.length === 0) return text;
|
|
814
|
-
const minIndent = Math.min(
|
|
815
|
-
...nonEmptyLines.map((line) => {
|
|
816
|
-
const match = line.match(/^(\s*)/);
|
|
817
|
-
return match ? match[1].length : 0;
|
|
818
|
-
})
|
|
819
|
-
);
|
|
820
|
-
return lines.map((line) => line.trim().length === 0 ? line : line.slice(minIndent)).join("\n");
|
|
821
|
-
};
|
|
822
|
-
const normalizedFind = removeIndentation(find);
|
|
823
|
-
const contentLines = content.split("\n");
|
|
824
|
-
const findLines = find.split("\n");
|
|
825
|
-
for (let i = 0; i <= contentLines.length - findLines.length; i++) {
|
|
826
|
-
const block = contentLines.slice(i, i + findLines.length).join("\n");
|
|
827
|
-
if (removeIndentation(block) === normalizedFind) {
|
|
828
|
-
yield block;
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
};
|
|
832
|
-
var EscapeNormalizedReplacer = function* (content, find) {
|
|
833
|
-
const unescapeString = (str) => {
|
|
834
|
-
return str.replace(/\\(n|t|r|'|"|`|\\|\n|\$)/g, (match, capturedChar) => {
|
|
835
|
-
switch (capturedChar) {
|
|
836
|
-
case "n":
|
|
837
|
-
return "\n";
|
|
838
|
-
case "t":
|
|
839
|
-
return " ";
|
|
840
|
-
case "r":
|
|
841
|
-
return "\r";
|
|
842
|
-
case "'":
|
|
843
|
-
return "'";
|
|
844
|
-
case '"':
|
|
845
|
-
return '"';
|
|
846
|
-
case "`":
|
|
847
|
-
return "`";
|
|
848
|
-
case "\\":
|
|
849
|
-
return "\\";
|
|
850
|
-
case "\n":
|
|
851
|
-
return "\n";
|
|
852
|
-
case "$":
|
|
853
|
-
return "$";
|
|
854
|
-
default:
|
|
855
|
-
return match;
|
|
856
|
-
}
|
|
857
|
-
});
|
|
858
|
-
};
|
|
859
|
-
const unescapedFind = unescapeString(find);
|
|
860
|
-
if (content.includes(unescapedFind)) {
|
|
861
|
-
yield unescapedFind;
|
|
862
|
-
}
|
|
863
|
-
const lines = content.split("\n");
|
|
864
|
-
const findLines = unescapedFind.split("\n");
|
|
865
|
-
for (let i = 0; i <= lines.length - findLines.length; i++) {
|
|
866
|
-
const block = lines.slice(i, i + findLines.length).join("\n");
|
|
867
|
-
if (unescapeString(block) === unescapedFind) {
|
|
868
|
-
yield block;
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
};
|
|
872
|
-
var TrimmedBoundaryReplacer = function* (content, find) {
|
|
873
|
-
const trimmedFind = find.trim();
|
|
874
|
-
if (trimmedFind === find) return;
|
|
875
|
-
if (content.includes(trimmedFind)) {
|
|
876
|
-
yield trimmedFind;
|
|
877
|
-
}
|
|
878
|
-
const lines = content.split("\n");
|
|
879
|
-
const findLines = find.split("\n");
|
|
880
|
-
for (let i = 0; i <= lines.length - findLines.length; i++) {
|
|
881
|
-
const block = lines.slice(i, i + findLines.length).join("\n");
|
|
882
|
-
if (block.trim() === trimmedFind) {
|
|
883
|
-
yield block;
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
};
|
|
887
|
-
var ContextAwareReplacer = function* (content, find) {
|
|
888
|
-
const findLines = find.split("\n");
|
|
889
|
-
if (findLines.length < 3) return;
|
|
890
|
-
if (findLines[findLines.length - 1] === "") findLines.pop();
|
|
891
|
-
const contentLines = content.split("\n");
|
|
892
|
-
const firstLine = findLines[0].trim();
|
|
893
|
-
const lastLine = findLines[findLines.length - 1].trim();
|
|
894
|
-
for (let i = 0; i < contentLines.length; i++) {
|
|
895
|
-
if (contentLines[i].trim() !== firstLine) continue;
|
|
896
|
-
for (let j = i + 2; j < contentLines.length; j++) {
|
|
897
|
-
if (contentLines[j].trim() === lastLine) {
|
|
898
|
-
const blockLines = contentLines.slice(i, j + 1);
|
|
899
|
-
if (blockLines.length === findLines.length) {
|
|
900
|
-
let matchingLines = 0;
|
|
901
|
-
let totalNonEmptyLines = 0;
|
|
902
|
-
for (let k = 1; k < blockLines.length - 1; k++) {
|
|
903
|
-
const blockLine = blockLines[k].trim();
|
|
904
|
-
const findLine = findLines[k].trim();
|
|
905
|
-
if (blockLine.length > 0 || findLine.length > 0) {
|
|
906
|
-
totalNonEmptyLines++;
|
|
907
|
-
if (blockLine === findLine) matchingLines++;
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
if (totalNonEmptyLines === 0 || matchingLines / totalNonEmptyLines >= 0.5) {
|
|
911
|
-
yield blockLines.join("\n");
|
|
912
|
-
break;
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
break;
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
};
|
|
920
|
-
var MultiOccurrenceReplacer = function* (content, find) {
|
|
921
|
-
let startIndex = 0;
|
|
922
|
-
while (true) {
|
|
923
|
-
const index = content.indexOf(find, startIndex);
|
|
924
|
-
if (index === -1) break;
|
|
925
|
-
yield find;
|
|
926
|
-
startIndex = index + find.length;
|
|
927
|
-
}
|
|
928
|
-
};
|
|
929
|
-
var REPLACERS = [
|
|
930
|
-
SimpleReplacer,
|
|
931
|
-
LineTrimmedReplacer,
|
|
932
|
-
BlockAnchorReplacer,
|
|
933
|
-
WhitespaceNormalizedReplacer,
|
|
934
|
-
IndentationFlexibleReplacer,
|
|
935
|
-
EscapeNormalizedReplacer,
|
|
936
|
-
TrimmedBoundaryReplacer,
|
|
937
|
-
ContextAwareReplacer,
|
|
938
|
-
MultiOccurrenceReplacer
|
|
939
|
-
];
|
|
940
|
-
function smartReplace(content, oldString, newString, replaceAll = false) {
|
|
941
|
-
if (oldString === newString) {
|
|
942
|
-
throw new Error("No changes to apply: oldString and newString are identical.");
|
|
943
|
-
}
|
|
944
|
-
let notFound = true;
|
|
945
|
-
for (const replacer of REPLACERS) {
|
|
946
|
-
for (const search of replacer(content, oldString)) {
|
|
947
|
-
const index = content.indexOf(search);
|
|
948
|
-
if (index === -1) continue;
|
|
949
|
-
notFound = false;
|
|
950
|
-
if (replaceAll) {
|
|
951
|
-
return content.replaceAll(search, newString);
|
|
952
|
-
}
|
|
953
|
-
const lastIndex = content.lastIndexOf(search);
|
|
954
|
-
if (index !== lastIndex) continue;
|
|
955
|
-
return content.substring(0, index) + newString + content.substring(index + search.length);
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
if (notFound) {
|
|
959
|
-
throw new Error(
|
|
960
|
-
"oldString not found in content. It must match the file contents exactly, including whitespace, indentation, and line endings."
|
|
961
|
-
);
|
|
962
|
-
}
|
|
963
|
-
throw new Error(
|
|
964
|
-
"Found multiple matches for oldString. Provide more surrounding lines in oldString to identify the correct match."
|
|
965
|
-
);
|
|
966
|
-
}
|
|
967
|
-
var apply_patch = async function(input, projectCwd) {
|
|
968
|
-
const { file_path, new_string, old_string, replaceAll: shouldReplaceAll = false } = input;
|
|
969
|
-
try {
|
|
970
|
-
if (!file_path) {
|
|
971
|
-
return {
|
|
972
|
-
success: false,
|
|
973
|
-
message: "Missing required parameter: file_path",
|
|
974
|
-
error: "MISSING_FILE_PATH"
|
|
975
|
-
};
|
|
976
|
-
}
|
|
977
|
-
if (old_string === void 0 || old_string === null) {
|
|
978
|
-
return {
|
|
979
|
-
success: false,
|
|
980
|
-
message: "Missing required parameter: old_string",
|
|
981
|
-
error: "MISSING_OLD_STRING"
|
|
982
|
-
};
|
|
983
|
-
}
|
|
984
|
-
if (new_string === void 0 || new_string === null) {
|
|
985
|
-
return {
|
|
986
|
-
success: false,
|
|
987
|
-
message: "Missing required parameter: new_string",
|
|
988
|
-
error: "MISSING_NEW_STRING"
|
|
989
|
-
};
|
|
990
|
-
}
|
|
991
|
-
if (old_string === new_string) {
|
|
992
|
-
return {
|
|
993
|
-
success: false,
|
|
994
|
-
message: "old_string and new_string must be different",
|
|
995
|
-
error: "STRINGS_IDENTICAL"
|
|
996
|
-
};
|
|
997
|
-
}
|
|
998
|
-
if (projectCwd) {
|
|
999
|
-
const validation = validatePath(file_path, projectCwd);
|
|
1000
|
-
if (!validation.valid) {
|
|
1001
|
-
return {
|
|
1002
|
-
success: false,
|
|
1003
|
-
message: validation.error || "Path validation failed",
|
|
1004
|
-
error: "ACCESS_DENIED"
|
|
1005
|
-
};
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
const basePath = projectCwd || process.cwd();
|
|
1009
|
-
const absolute_file_path = resolveProjectPath(file_path, basePath);
|
|
1010
|
-
const file = Bun.file(absolute_file_path);
|
|
1011
|
-
const exists = await file.exists();
|
|
1012
|
-
if (!exists) {
|
|
1013
|
-
if (old_string === "") {
|
|
1014
|
-
const { mkdir: mkdir2 } = await import('fs/promises');
|
|
1015
|
-
const path14 = await import('path');
|
|
1016
|
-
await mkdir2(path14.dirname(absolute_file_path), { recursive: true });
|
|
1017
|
-
await Bun.write(absolute_file_path, new_string);
|
|
1018
|
-
const diffStats = calculateDiffStats("", new_string);
|
|
1019
|
-
return {
|
|
1020
|
-
success: true,
|
|
1021
|
-
isNewFile: true,
|
|
1022
|
-
old_string: "",
|
|
1023
|
-
new_string,
|
|
1024
|
-
linesAdded: diffStats.linesAdded,
|
|
1025
|
-
linesRemoved: diffStats.linesRemoved,
|
|
1026
|
-
message: `Created new file: ${file_path}`
|
|
1027
|
-
};
|
|
1028
|
-
}
|
|
1029
|
-
return {
|
|
1030
|
-
success: false,
|
|
1031
|
-
message: `File not found: ${file_path}`,
|
|
1032
|
-
error: "FILE_NOT_FOUND"
|
|
1033
|
-
};
|
|
1034
|
-
}
|
|
1035
|
-
let fileContent;
|
|
1036
|
-
try {
|
|
1037
|
-
fileContent = await file.text();
|
|
1038
|
-
} catch (error) {
|
|
1039
|
-
return {
|
|
1040
|
-
success: false,
|
|
1041
|
-
message: `Failed to read file: ${file_path}`,
|
|
1042
|
-
error: "READ_ERROR"
|
|
1043
|
-
};
|
|
1044
|
-
}
|
|
1045
|
-
let newContent;
|
|
1046
|
-
try {
|
|
1047
|
-
newContent = smartReplace(fileContent, old_string, new_string, shouldReplaceAll);
|
|
1048
|
-
} catch (err) {
|
|
1049
|
-
if (err.message.includes("not found")) {
|
|
1050
|
-
return {
|
|
1051
|
-
success: false,
|
|
1052
|
-
message: `old_string not found in file: ${file_path}. Ensure it matches the file contents exactly, including whitespace and indentation.`,
|
|
1053
|
-
error: "STRING_NOT_FOUND"
|
|
1054
|
-
};
|
|
1055
|
-
}
|
|
1056
|
-
if (err.message.includes("multiple matches")) {
|
|
1057
|
-
const occurrences = fileContent.split(old_string).length - 1;
|
|
1058
|
-
return {
|
|
1059
|
-
success: false,
|
|
1060
|
-
message: `old_string appears ${occurrences > 1 ? occurrences + " times" : "multiple times (via fuzzy match)"} in the file. Provide more surrounding context to make it unique, or set replaceAll to true.`,
|
|
1061
|
-
error: "STRING_NOT_UNIQUE"
|
|
1062
|
-
};
|
|
1063
|
-
}
|
|
1064
|
-
return {
|
|
1065
|
-
success: false,
|
|
1066
|
-
message: err.message,
|
|
1067
|
-
error: "REPLACE_ERROR"
|
|
1068
|
-
};
|
|
1069
|
-
}
|
|
1070
|
-
try {
|
|
1071
|
-
await Bun.write(absolute_file_path, newContent);
|
|
1072
|
-
const diffStats = calculateDiffStats(fileContent, newContent);
|
|
1073
|
-
return {
|
|
1074
|
-
success: true,
|
|
1075
|
-
old_string,
|
|
1076
|
-
new_string,
|
|
1077
|
-
linesAdded: diffStats.linesAdded,
|
|
1078
|
-
linesRemoved: diffStats.linesRemoved,
|
|
1079
|
-
message: `Successfully replaced string in file: ${file_path}`
|
|
1080
|
-
};
|
|
1081
|
-
} catch (error) {
|
|
1082
|
-
return {
|
|
1083
|
-
success: false,
|
|
1084
|
-
message: `Failed to write to file: ${file_path}`,
|
|
1085
|
-
error: "WRITE_ERROR"
|
|
1086
|
-
};
|
|
1087
|
-
}
|
|
1088
|
-
} catch (error) {
|
|
1089
|
-
return {
|
|
1090
|
-
success: false,
|
|
1091
|
-
message: `Unexpected error: ${error.message}`,
|
|
1092
|
-
error: "UNEXPECTED_ERROR"
|
|
1093
|
-
};
|
|
1094
|
-
}
|
|
1095
|
-
};
|
|
1096
|
-
var DEFAULT_SERVER_URL = "wss://bridge.ama.shujan.xyz";
|
|
1097
|
-
var AMA_DIR = path10.join(os3.homedir(), ".amai");
|
|
1098
|
-
path10.join(AMA_DIR, "code");
|
|
1099
|
-
path10.join(AMA_DIR, "storage");
|
|
1100
|
-
z.object({
|
|
1101
|
-
target_file: z.string().describe("The relative path to the file to modify. The tool will create any directories in the path that don't exist"),
|
|
1102
|
-
content: z.string().describe("The full content to write to the file"),
|
|
1103
|
-
providedNewFile: z.boolean().describe("Whether this is a new file (true) or an edit to an existing file (false). Auto-detected if omitted.").optional()
|
|
1104
|
-
});
|
|
1105
|
-
var editFiles = async function(input, projectCwd) {
|
|
1106
|
-
const { target_file, content, providedNewFile } = input;
|
|
1107
|
-
if (!target_file) {
|
|
1108
|
-
return {
|
|
1109
|
-
success: false,
|
|
1110
|
-
error: "Missing required parameter: target_file",
|
|
1111
|
-
message: "target_file is required"
|
|
1112
|
-
};
|
|
1113
|
-
}
|
|
1114
|
-
try {
|
|
1115
|
-
if (projectCwd) {
|
|
1116
|
-
const validation = validatePath(target_file, projectCwd);
|
|
1117
|
-
if (!validation.valid) {
|
|
1118
|
-
return {
|
|
1119
|
-
success: false,
|
|
1120
|
-
error: validation.error || "Path validation failed",
|
|
1121
|
-
message: `Failed to edit file: ${target_file}`
|
|
1122
|
-
};
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
const basePath = projectCwd || process.cwd();
|
|
1126
|
-
const filePath = resolveProjectPath(target_file, basePath);
|
|
1127
|
-
const dirPath = path10.dirname(filePath);
|
|
1128
|
-
await mkdir(dirPath, { recursive: true });
|
|
1129
|
-
let isNewFile = providedNewFile;
|
|
1130
|
-
let existingContent = "";
|
|
1131
|
-
const file = Bun.file(filePath);
|
|
1132
|
-
if (isNewFile === void 0) {
|
|
1133
|
-
const exists = await file.exists();
|
|
1134
|
-
if (exists) {
|
|
1135
|
-
existingContent = await file.text();
|
|
1136
|
-
isNewFile = false;
|
|
1137
|
-
} else {
|
|
1138
|
-
isNewFile = true;
|
|
1139
|
-
}
|
|
1140
|
-
} else if (!isNewFile) {
|
|
1141
|
-
const exists = await file.exists();
|
|
1142
|
-
if (exists) {
|
|
1143
|
-
existingContent = await file.text();
|
|
1144
|
-
} else {
|
|
1145
|
-
isNewFile = true;
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
if (!isNewFile && existingContent === content) {
|
|
1149
|
-
return {
|
|
1150
|
-
success: true,
|
|
1151
|
-
isNewFile: false,
|
|
1152
|
-
message: `No changes needed: ${target_file} (content identical)`,
|
|
1153
|
-
linesAdded: 0,
|
|
1154
|
-
linesRemoved: 0
|
|
1155
|
-
};
|
|
1156
|
-
}
|
|
1157
|
-
await Bun.write(filePath, content);
|
|
1158
|
-
const diffStats = calculateDiffStats(existingContent, content);
|
|
1159
|
-
if (isNewFile) {
|
|
1160
|
-
return {
|
|
1161
|
-
success: true,
|
|
1162
|
-
isNewFile: true,
|
|
1163
|
-
old_string: "",
|
|
1164
|
-
new_string: content,
|
|
1165
|
-
message: `Created new file: ${target_file} (+${diffStats.linesAdded} lines)`,
|
|
1166
|
-
linesAdded: diffStats.linesAdded,
|
|
1167
|
-
linesRemoved: diffStats.linesRemoved
|
|
1168
|
-
};
|
|
1169
|
-
} else {
|
|
1170
|
-
return {
|
|
1171
|
-
success: true,
|
|
1172
|
-
isNewFile: false,
|
|
1173
|
-
old_string: existingContent,
|
|
1174
|
-
new_string: content,
|
|
1175
|
-
message: `Modified file: ${target_file} (+${diffStats.linesAdded} -${diffStats.linesRemoved} lines)`,
|
|
1176
|
-
linesAdded: diffStats.linesAdded,
|
|
1177
|
-
linesRemoved: diffStats.linesRemoved
|
|
1178
|
-
};
|
|
1179
|
-
}
|
|
1180
|
-
} catch (error) {
|
|
1181
|
-
return {
|
|
1182
|
-
success: false,
|
|
1183
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1184
|
-
message: `Failed to edit file: ${target_file}`
|
|
1185
|
-
};
|
|
1186
|
-
}
|
|
1187
|
-
};
|
|
1188
|
-
z.object({
|
|
1189
|
-
path: z.string().describe("Relative file path to delete")
|
|
1190
|
-
});
|
|
1191
|
-
var deleteFile = async function(input, projectCwd) {
|
|
1192
|
-
const { path: realPath } = input;
|
|
1193
|
-
if (!realPath) {
|
|
1194
|
-
return {
|
|
1195
|
-
success: false,
|
|
1196
|
-
message: "Missing required parameter: path",
|
|
1197
|
-
error: "MISSING_PATH"
|
|
1198
|
-
};
|
|
1199
|
-
}
|
|
1200
|
-
if (projectCwd) {
|
|
1201
|
-
const validation = validatePath(realPath, projectCwd);
|
|
1202
|
-
if (!validation.valid) {
|
|
1203
|
-
return {
|
|
1204
|
-
success: false,
|
|
1205
|
-
message: validation.error || "Path validation failed",
|
|
1206
|
-
error: "ACCESS_DENIED"
|
|
1207
|
-
};
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
try {
|
|
1211
|
-
const basePath = projectCwd || process.cwd();
|
|
1212
|
-
const absolute_file_path = resolveProjectPath(realPath, basePath);
|
|
1213
|
-
if (!absolute_file_path) {
|
|
1214
|
-
return {
|
|
1215
|
-
success: false,
|
|
1216
|
-
message: "Invalid file path",
|
|
1217
|
-
error: "INVALID_FILE_PATH"
|
|
1218
|
-
};
|
|
1219
|
-
}
|
|
1220
|
-
const file = Bun.file(absolute_file_path);
|
|
1221
|
-
const exists = await file.exists();
|
|
1222
|
-
if (!exists) {
|
|
1223
|
-
return {
|
|
1224
|
-
success: false,
|
|
1225
|
-
message: `File not found: ${realPath}`,
|
|
1226
|
-
error: "FILE_NOT_FOUND"
|
|
1227
|
-
};
|
|
1228
|
-
}
|
|
1229
|
-
let originalContent;
|
|
1230
|
-
try {
|
|
1231
|
-
originalContent = await file.text();
|
|
1232
|
-
} catch {
|
|
1233
|
-
return {
|
|
1234
|
-
success: false,
|
|
1235
|
-
message: `Failed to read file before deletion: ${realPath}`,
|
|
1236
|
-
error: "READ_ERROR"
|
|
1237
|
-
};
|
|
1238
|
-
}
|
|
1239
|
-
try {
|
|
1240
|
-
await unlink(absolute_file_path);
|
|
1241
|
-
} catch {
|
|
1242
|
-
return {
|
|
1243
|
-
success: false,
|
|
1244
|
-
message: `Failed to delete file: ${realPath}`,
|
|
1245
|
-
error: "DELETE_ERROR"
|
|
1246
|
-
};
|
|
1247
|
-
}
|
|
1248
|
-
return {
|
|
1249
|
-
success: true,
|
|
1250
|
-
message: `Successfully deleted file: ${realPath}`,
|
|
1251
|
-
content: originalContent
|
|
1252
|
-
};
|
|
1253
|
-
} catch (error) {
|
|
1254
|
-
return {
|
|
1255
|
-
success: false,
|
|
1256
|
-
message: `Failed to delete file: ${realPath}`,
|
|
1257
|
-
error: "DELETE_ERROR"
|
|
1258
|
-
};
|
|
1259
|
-
}
|
|
1260
|
-
};
|
|
1261
|
-
var GREP_LIMITS = {
|
|
1262
|
-
DEFAULT_MAX_MATCHES: 200,
|
|
1263
|
-
MAX_LINE_LENGTH: 2e3,
|
|
1264
|
-
// aligned with OpenCode's 2000-char truncation
|
|
1265
|
-
MAX_TOTAL_OUTPUT_SIZE: 1 * 1024 * 1024,
|
|
1266
|
-
EXECUTION_TIMEOUT_MS: 15e3,
|
|
1267
|
-
TRUNCATION_MESSAGE: "\n[Results truncated due to size limits. Use more specific patterns or file filters to narrow your search.]"
|
|
1268
|
-
};
|
|
1269
|
-
z.object({
|
|
1270
|
-
query: z.string().describe("The regex pattern to search for"),
|
|
1271
|
-
options: z.object({
|
|
1272
|
-
includePattern: z.string().optional().describe('Glob pattern for files to include (e.g., "*.ts", "*.{ts,tsx}")'),
|
|
1273
|
-
excludePattern: z.string().optional().describe("Glob pattern for files to exclude"),
|
|
1274
|
-
caseSensitive: z.boolean().optional().describe("Whether the search should be case sensitive"),
|
|
1275
|
-
path: z.string().optional().describe("Subdirectory to search in"),
|
|
1276
|
-
sortByMtime: z.boolean().optional().describe("Sort results by file modification time (default: true)")
|
|
1277
|
-
}).optional()
|
|
1278
|
-
});
|
|
1279
|
-
var _cachedRgPath = null;
|
|
1280
|
-
async function getRipgrepPath() {
|
|
1281
|
-
if (_cachedRgPath) return _cachedRgPath;
|
|
1282
|
-
const paths = [
|
|
1283
|
-
"/opt/homebrew/bin/rg",
|
|
1284
|
-
"/usr/local/bin/rg",
|
|
1285
|
-
"/usr/bin/rg"
|
|
1286
|
-
];
|
|
1287
|
-
for (const rgPath of paths) {
|
|
1288
|
-
if (fs8.existsSync(rgPath)) {
|
|
1289
|
-
_cachedRgPath = rgPath;
|
|
1290
|
-
return rgPath;
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
_cachedRgPath = "rg";
|
|
1294
|
-
return "rg";
|
|
1295
|
-
}
|
|
1296
|
-
getRipgrepPath();
|
|
1297
|
-
async function getMtimesBatched(files) {
|
|
1298
|
-
const mtimeMap = /* @__PURE__ */ new Map();
|
|
1299
|
-
const BATCH_SIZE = 50;
|
|
1300
|
-
for (let i = 0; i < files.length; i += BATCH_SIZE) {
|
|
1301
|
-
const batch = files.slice(i, i + BATCH_SIZE);
|
|
1302
|
-
const results = await Promise.all(
|
|
1303
|
-
batch.map(async (filePath) => {
|
|
1304
|
-
const mtime = await Bun.file(filePath).stat().then((stats) => stats.mtime.getTime()).catch(() => 0);
|
|
1305
|
-
return { path: filePath, mtime };
|
|
1306
|
-
})
|
|
1307
|
-
);
|
|
1308
|
-
results.forEach(({ path: path14, mtime }) => mtimeMap.set(path14, mtime));
|
|
1309
|
-
}
|
|
1310
|
-
return mtimeMap;
|
|
1311
|
-
}
|
|
1312
|
-
var grepTool = async function(input, projectCwd) {
|
|
1313
|
-
const { query, options } = input;
|
|
1314
|
-
if (!query || query.trim() === "") {
|
|
1315
|
-
return {
|
|
1316
|
-
success: false,
|
|
1317
|
-
message: "Missing required parameter: query",
|
|
1318
|
-
error: "MISSING_QUERY"
|
|
1319
|
-
};
|
|
1320
|
-
}
|
|
1321
|
-
try {
|
|
1322
|
-
const { includePattern, excludePattern, caseSensitive, path: subPath, sortByMtime = true } = options || {};
|
|
1323
|
-
let searchDir = projectCwd || process.cwd();
|
|
1324
|
-
if (subPath) {
|
|
1325
|
-
searchDir = path10.isAbsolute(subPath) ? subPath : path10.resolve(searchDir, subPath);
|
|
1326
|
-
if (projectCwd) {
|
|
1327
|
-
const validation = validatePath(subPath, projectCwd);
|
|
1328
|
-
if (!validation.valid) {
|
|
1329
|
-
return {
|
|
1330
|
-
success: false,
|
|
1331
|
-
message: validation.error || "Path validation failed",
|
|
1332
|
-
error: "ACCESS_DENIED"
|
|
1333
|
-
};
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
if (!fs8.existsSync(searchDir)) {
|
|
1338
|
-
return {
|
|
1339
|
-
success: false,
|
|
1340
|
-
message: `Directory not found: ${searchDir}`,
|
|
1341
|
-
error: "DIR_NOT_FOUND"
|
|
1342
|
-
};
|
|
1343
|
-
}
|
|
1344
|
-
const rgPath = await getRipgrepPath();
|
|
1345
|
-
const args = [
|
|
1346
|
-
"-nH",
|
|
1347
|
-
// line numbers + filename (compact form, matching OpenCode)
|
|
1348
|
-
"--hidden",
|
|
1349
|
-
// search hidden files (aligned with OpenCode)
|
|
1350
|
-
"--no-messages",
|
|
1351
|
-
// suppress error messages for unreadable files
|
|
1352
|
-
"--color=never",
|
|
1353
|
-
"--max-count=100",
|
|
1354
|
-
"--max-columns=2000"
|
|
1355
|
-
];
|
|
1356
|
-
if (!caseSensitive) {
|
|
1357
|
-
args.push("-i");
|
|
1358
|
-
}
|
|
1359
|
-
if (includePattern) {
|
|
1360
|
-
args.push("--glob", includePattern);
|
|
1361
|
-
}
|
|
1362
|
-
if (excludePattern) {
|
|
1363
|
-
args.push("--glob", `!${excludePattern}`);
|
|
1364
|
-
}
|
|
1365
|
-
args.push("--glob", "!node_modules/**");
|
|
1366
|
-
args.push("--glob", "!.git/**");
|
|
1367
|
-
args.push("--glob", "!dist/**");
|
|
1368
|
-
args.push("--glob", "!build/**");
|
|
1369
|
-
args.push("--glob", "!*.min.js");
|
|
1370
|
-
args.push("--glob", "!*.min.css");
|
|
1371
|
-
args.push("--glob", "!package-lock.json");
|
|
1372
|
-
args.push("--glob", "!yarn.lock");
|
|
1373
|
-
args.push("--glob", "!bun.lockb");
|
|
1374
|
-
args.push("--glob", "!pnpm-lock.yaml");
|
|
1375
|
-
args.push("--regexp", query);
|
|
1376
|
-
args.push(searchDir);
|
|
1377
|
-
const proc = Bun.spawn([rgPath, ...args], {
|
|
1378
|
-
stdout: "pipe",
|
|
1379
|
-
stderr: "pipe"
|
|
1380
|
-
});
|
|
1381
|
-
let timedOut = false;
|
|
1382
|
-
const timeoutId = setTimeout(() => {
|
|
1383
|
-
timedOut = true;
|
|
1384
|
-
proc.kill();
|
|
1385
|
-
}, GREP_LIMITS.EXECUTION_TIMEOUT_MS);
|
|
1386
|
-
const stdout = await new Response(proc.stdout).text();
|
|
1387
|
-
const stderr = await new Response(proc.stderr).text();
|
|
1388
|
-
const exitCode = await proc.exited;
|
|
1389
|
-
clearTimeout(timeoutId);
|
|
1390
|
-
if (timedOut) {
|
|
1391
|
-
return {
|
|
1392
|
-
success: false,
|
|
1393
|
-
message: `Search timed out after ${GREP_LIMITS.EXECUTION_TIMEOUT_MS}ms. Use more specific patterns.`,
|
|
1394
|
-
error: "GREP_TIMEOUT"
|
|
1395
|
-
};
|
|
1396
|
-
}
|
|
1397
|
-
if (exitCode === 1) {
|
|
1398
|
-
return {
|
|
1399
|
-
success: true,
|
|
1400
|
-
matches: [],
|
|
1401
|
-
detailedMatches: [],
|
|
1402
|
-
query,
|
|
1403
|
-
matchCount: 0,
|
|
1404
|
-
message: `No matches found for pattern: ${query}`
|
|
1405
|
-
};
|
|
1406
|
-
}
|
|
1407
|
-
if (exitCode !== 0 && exitCode !== 2) {
|
|
1408
|
-
return {
|
|
1409
|
-
success: false,
|
|
1410
|
-
message: `Ripgrep error: ${stderr || "Unknown error"}`,
|
|
1411
|
-
error: "GREP_EXEC_ERROR"
|
|
1412
|
-
};
|
|
1413
|
-
}
|
|
1414
|
-
const lines = stdout.trim().split("\n").filter((line) => line.length > 0);
|
|
1415
|
-
const rawMatches = [];
|
|
1416
|
-
const uniqueFiles = /* @__PURE__ */ new Set();
|
|
1417
|
-
for (const line of lines) {
|
|
1418
|
-
const firstColon = line.indexOf(":");
|
|
1419
|
-
const secondColon = line.indexOf(":", firstColon + 1);
|
|
1420
|
-
if (firstColon > 0 && secondColon > firstColon) {
|
|
1421
|
-
const file = line.substring(0, firstColon);
|
|
1422
|
-
const lineNumber = parseInt(line.substring(firstColon + 1, secondColon), 10);
|
|
1423
|
-
let content = line.substring(secondColon + 1);
|
|
1424
|
-
if (content.length > GREP_LIMITS.MAX_LINE_LENGTH) {
|
|
1425
|
-
content = content.substring(0, GREP_LIMITS.MAX_LINE_LENGTH) + "...";
|
|
1426
|
-
}
|
|
1427
|
-
rawMatches.push({
|
|
1428
|
-
file,
|
|
1429
|
-
lineNumber,
|
|
1430
|
-
content: content.trim(),
|
|
1431
|
-
mtime: 0
|
|
1432
|
-
});
|
|
1433
|
-
uniqueFiles.add(file);
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
|
-
if (sortByMtime && uniqueFiles.size > 0) {
|
|
1437
|
-
const mtimeMap = await getMtimesBatched(Array.from(uniqueFiles));
|
|
1438
|
-
for (const match of rawMatches) {
|
|
1439
|
-
match.mtime = mtimeMap.get(match.file) || 0;
|
|
1440
|
-
}
|
|
1441
|
-
rawMatches.sort((a, b) => {
|
|
1442
|
-
if (b.mtime !== a.mtime) return b.mtime - a.mtime;
|
|
1443
|
-
return a.file.localeCompare(b.file);
|
|
1444
|
-
});
|
|
1445
|
-
}
|
|
1446
|
-
const truncated = rawMatches.length > GREP_LIMITS.DEFAULT_MAX_MATCHES;
|
|
1447
|
-
const finalMatches = truncated ? rawMatches.slice(0, GREP_LIMITS.DEFAULT_MAX_MATCHES) : rawMatches;
|
|
1448
|
-
const detailedMatches = finalMatches.map((m) => ({
|
|
1449
|
-
file: m.file,
|
|
1450
|
-
lineNumber: m.lineNumber,
|
|
1451
|
-
content: m.content
|
|
1452
|
-
}));
|
|
1453
|
-
const matches = finalMatches.map(
|
|
1454
|
-
(m) => `${m.file}:${m.lineNumber}:${m.content}`
|
|
1455
|
-
);
|
|
1456
|
-
const groupedOutput = [`Found ${finalMatches.length} matches`];
|
|
1457
|
-
let currentFile = "";
|
|
1458
|
-
for (const match of finalMatches) {
|
|
1459
|
-
if (currentFile !== match.file) {
|
|
1460
|
-
if (currentFile !== "") {
|
|
1461
|
-
groupedOutput.push("");
|
|
1462
|
-
}
|
|
1463
|
-
currentFile = match.file;
|
|
1464
|
-
groupedOutput.push(`${match.file}:`);
|
|
1465
|
-
}
|
|
1466
|
-
groupedOutput.push(` Line ${match.lineNumber}: ${match.content}`);
|
|
1467
|
-
}
|
|
1468
|
-
if (truncated) {
|
|
1469
|
-
groupedOutput.push("");
|
|
1470
|
-
groupedOutput.push(GREP_LIMITS.TRUNCATION_MESSAGE);
|
|
1471
|
-
}
|
|
1472
|
-
return {
|
|
1473
|
-
success: true,
|
|
1474
|
-
matches,
|
|
1475
|
-
detailedMatches,
|
|
1476
|
-
query,
|
|
1477
|
-
matchCount: finalMatches.length,
|
|
1478
|
-
truncated,
|
|
1479
|
-
message: `Found ${finalMatches.length} matches for pattern: ${query}`,
|
|
1480
|
-
content: groupedOutput.join("\n")
|
|
1481
|
-
};
|
|
1482
|
-
} catch (error) {
|
|
1483
|
-
console.error("[grep] error:", error);
|
|
1484
|
-
return {
|
|
1485
|
-
success: false,
|
|
1486
|
-
message: error?.message || String(error),
|
|
1487
|
-
error: "GREP_EXEC_ERROR"
|
|
1488
|
-
};
|
|
1489
|
-
}
|
|
1490
|
-
};
|
|
1491
|
-
z.object({
|
|
1492
|
-
pattern: z.string().describe('Glob pattern to match files (e.g., "**/*.js", "src/**/*.ts", "*.json"). Supports standard glob syntax with *, **, and ? wildcards'),
|
|
1493
|
-
path: z.string().optional().describe("Optional directory path to limit the search scope. If not provided, searches from the project root")
|
|
1494
|
-
});
|
|
1495
|
-
var RESULT_LIMIT = 100;
|
|
1496
|
-
var MTIME_BATCH_SIZE = 50;
|
|
1497
|
-
async function getMtimesBatched2(files) {
|
|
1498
|
-
const results = [];
|
|
1499
|
-
for (let i = 0; i < files.length; i += MTIME_BATCH_SIZE) {
|
|
1500
|
-
const batch = files.slice(i, i + MTIME_BATCH_SIZE);
|
|
1501
|
-
const batchResults = await Promise.all(
|
|
1502
|
-
batch.map(async (filePath) => {
|
|
1503
|
-
const mtime = await Bun.file(filePath).stat().then((stats) => stats.mtime.getTime()).catch(() => 0);
|
|
1504
|
-
return { path: filePath, mtime };
|
|
1505
|
-
})
|
|
1506
|
-
);
|
|
1507
|
-
results.push(...batchResults);
|
|
1508
|
-
}
|
|
1509
|
-
return results;
|
|
1510
|
-
}
|
|
1511
|
-
var globTool = async function(input, projectCwd) {
|
|
1512
|
-
const { pattern, path: inputPath } = input;
|
|
1513
|
-
if (!pattern) {
|
|
1514
|
-
return {
|
|
1515
|
-
success: false,
|
|
1516
|
-
message: "Missing required parameter: pattern",
|
|
1517
|
-
error: "MISSING_PATTERN"
|
|
1518
|
-
};
|
|
1519
|
-
}
|
|
1520
|
-
try {
|
|
1521
|
-
const basePath = projectCwd || process.cwd();
|
|
1522
|
-
const searchPath = inputPath ? resolveProjectPath(inputPath, basePath) : basePath;
|
|
1523
|
-
if (!fs8.existsSync(searchPath)) {
|
|
1524
|
-
return {
|
|
1525
|
-
success: false,
|
|
1526
|
-
message: `Directory not found: ${searchPath}`,
|
|
1527
|
-
error: "DIR_NOT_FOUND"
|
|
1528
|
-
};
|
|
1529
|
-
}
|
|
1530
|
-
if (projectCwd && inputPath) {
|
|
1531
|
-
const validation = validatePath(inputPath, projectCwd);
|
|
1532
|
-
if (!validation.valid) {
|
|
1533
|
-
return {
|
|
1534
|
-
success: false,
|
|
1535
|
-
message: validation.error || "Path validation failed",
|
|
1536
|
-
error: "ACCESS_DENIED"
|
|
1537
|
-
};
|
|
1538
|
-
}
|
|
1539
|
-
}
|
|
1540
|
-
const glob = new Bun.Glob(pattern);
|
|
1541
|
-
const files = [];
|
|
1542
|
-
let truncated = false;
|
|
1543
|
-
for await (const match of glob.scan({
|
|
1544
|
-
cwd: searchPath,
|
|
1545
|
-
absolute: true,
|
|
1546
|
-
onlyFiles: true,
|
|
1547
|
-
followSymlinks: false
|
|
1548
|
-
})) {
|
|
1549
|
-
if (match.includes("/node_modules/") || match.includes("/.git/")) {
|
|
1550
|
-
continue;
|
|
1551
|
-
}
|
|
1552
|
-
if (files.length >= RESULT_LIMIT) {
|
|
1553
|
-
truncated = true;
|
|
1554
|
-
break;
|
|
1555
|
-
}
|
|
1556
|
-
files.push(match);
|
|
1557
|
-
}
|
|
1558
|
-
let sortedFiles;
|
|
1559
|
-
if (files.length > 0) {
|
|
1560
|
-
const filesWithMtime = await getMtimesBatched2(files);
|
|
1561
|
-
filesWithMtime.sort((a, b) => b.mtime - a.mtime);
|
|
1562
|
-
sortedFiles = filesWithMtime.map((f) => f.path);
|
|
1563
|
-
} else {
|
|
1564
|
-
sortedFiles = files;
|
|
1565
|
-
}
|
|
1566
|
-
const output = [];
|
|
1567
|
-
if (sortedFiles.length === 0) {
|
|
1568
|
-
output.push("No files found");
|
|
1569
|
-
} else {
|
|
1570
|
-
output.push(...sortedFiles);
|
|
1571
|
-
if (truncated) {
|
|
1572
|
-
output.push("");
|
|
1573
|
-
output.push("(Results are truncated. Consider using a more specific path or pattern.)");
|
|
1574
|
-
}
|
|
1575
|
-
}
|
|
1576
|
-
const searchLocation = inputPath ? ` in "${inputPath}"` : " in current directory";
|
|
1577
|
-
const message = `Found ${sortedFiles.length} matches for pattern "${pattern}"${searchLocation}`;
|
|
1578
|
-
return {
|
|
1579
|
-
success: true,
|
|
1580
|
-
message,
|
|
1581
|
-
metadata: {
|
|
1582
|
-
count: sortedFiles.length,
|
|
1583
|
-
truncated
|
|
1584
|
-
},
|
|
1585
|
-
content: output.join("\n")
|
|
1586
|
-
};
|
|
1587
|
-
} catch (error) {
|
|
1588
|
-
console.error("[glob] error:", error);
|
|
1589
|
-
return {
|
|
1590
|
-
success: false,
|
|
1591
|
-
message: `Failed to find files matching pattern: ${pattern}`,
|
|
1592
|
-
error: "GLOB_ERROR"
|
|
1593
|
-
};
|
|
1594
|
-
}
|
|
1595
|
-
};
|
|
1596
|
-
var DEFAULT_IGNORE_PATTERNS = [
|
|
1597
|
-
"node_modules",
|
|
1598
|
-
"__pycache__",
|
|
1599
|
-
".git",
|
|
1600
|
-
"dist",
|
|
1601
|
-
"build",
|
|
1602
|
-
"target",
|
|
1603
|
-
"vendor",
|
|
1604
|
-
"bin",
|
|
1605
|
-
"obj",
|
|
1606
|
-
".idea",
|
|
1607
|
-
".vscode",
|
|
1608
|
-
".zig-cache",
|
|
1609
|
-
"zig-out",
|
|
1610
|
-
".coverage",
|
|
1611
|
-
"coverage",
|
|
1612
|
-
"tmp",
|
|
1613
|
-
"temp",
|
|
1614
|
-
".cache",
|
|
1615
|
-
"cache",
|
|
1616
|
-
"logs",
|
|
1617
|
-
".venv",
|
|
1618
|
-
"venv",
|
|
1619
|
-
"env",
|
|
1620
|
-
".next",
|
|
1621
|
-
".turbo",
|
|
1622
|
-
".vercel",
|
|
1623
|
-
".output"
|
|
1624
|
-
];
|
|
1625
|
-
var RESULT_LIMIT2 = 500;
|
|
1626
|
-
var MTIME_BATCH_SIZE2 = 50;
|
|
1627
|
-
z.object({
|
|
1628
|
-
path: z.string().optional().describe("Path to the directory to list"),
|
|
1629
|
-
recursive: z.boolean().optional().describe("Whether to list files recursively (default: true)"),
|
|
1630
|
-
maxDepth: z.number().optional().describe("Maximum recursion depth (default: 3)"),
|
|
1631
|
-
pattern: z.string().optional().describe("File extension (e.g., '.ts') or glob-like pattern"),
|
|
1632
|
-
showHidden: z.boolean().optional().describe("Whether to show hidden files (default: false)"),
|
|
1633
|
-
includeMetadata: z.boolean().optional().describe("Whether to fetch file metadata like mtime (default: false -- faster without I/O)"),
|
|
1634
|
-
ignore: z.array(z.string()).optional().describe("Additional glob patterns to ignore (added to default ignore list)")
|
|
1635
|
-
});
|
|
1636
|
-
function shouldIgnore(name, showHidden, ignoreSet) {
|
|
1637
|
-
if (!showHidden && name.startsWith(".") && name !== ".") {
|
|
1638
|
-
return true;
|
|
1639
|
-
}
|
|
1640
|
-
return ignoreSet.has(name);
|
|
1641
|
-
}
|
|
1642
|
-
function matchPattern(name, pattern) {
|
|
1643
|
-
if (!pattern) return true;
|
|
1644
|
-
if (pattern.startsWith(".") && !pattern.includes("*")) {
|
|
1645
|
-
return name.endsWith(pattern);
|
|
1646
|
-
}
|
|
1647
|
-
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
1648
|
-
const regex = new RegExp(`^${escaped}$`, "i");
|
|
1649
|
-
return regex.test(name);
|
|
1650
|
-
}
|
|
1651
|
-
async function getMtimesBatched3(entries) {
|
|
1652
|
-
for (let i = 0; i < entries.length; i += MTIME_BATCH_SIZE2) {
|
|
1653
|
-
const batch = entries.slice(i, i + MTIME_BATCH_SIZE2);
|
|
1654
|
-
await Promise.all(
|
|
1655
|
-
batch.map(async (entry) => {
|
|
1656
|
-
entry.mtime = await Bun.file(entry.absolutePath).stat().then((stats) => stats.mtime.getTime()).catch(() => 0);
|
|
1657
|
-
})
|
|
1658
|
-
);
|
|
1659
|
-
}
|
|
1660
|
-
}
|
|
1661
|
-
function buildTreeOutput(entries, basePath) {
|
|
1662
|
-
const tree = /* @__PURE__ */ new Map();
|
|
1663
|
-
for (const entry of entries) {
|
|
1664
|
-
const dir = path10.dirname(entry.relativePath);
|
|
1665
|
-
const dirKey = dir === "." ? "" : dir;
|
|
1666
|
-
if (!tree.has(dirKey)) {
|
|
1667
|
-
tree.set(dirKey, []);
|
|
1668
|
-
}
|
|
1669
|
-
tree.get(dirKey).push(entry);
|
|
1670
|
-
}
|
|
1671
|
-
for (const [, items] of tree) {
|
|
1672
|
-
items.sort((a, b) => {
|
|
1673
|
-
if (a.type !== b.type) {
|
|
1674
|
-
return a.type === "directory" ? -1 : 1;
|
|
1675
|
-
}
|
|
1676
|
-
return a.name.localeCompare(b.name);
|
|
1677
|
-
});
|
|
1678
|
-
}
|
|
1679
|
-
const lines = [`${basePath}/`];
|
|
1680
|
-
function renderLevel(dirPath, indent) {
|
|
1681
|
-
const items = tree.get(dirPath) || [];
|
|
1682
|
-
for (let i = 0; i < items.length; i++) {
|
|
1683
|
-
const item = items[i];
|
|
1684
|
-
const isLast = i === items.length - 1;
|
|
1685
|
-
const prefix = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
1686
|
-
const childIndent = indent + (isLast ? " " : "\u2502 ");
|
|
1687
|
-
if (item.type === "directory") {
|
|
1688
|
-
lines.push(`${indent}${prefix}${item.name}/`);
|
|
1689
|
-
const childPath = dirPath ? `${dirPath}/${item.name}` : item.name;
|
|
1690
|
-
renderLevel(childPath, childIndent);
|
|
1691
|
-
} else {
|
|
1692
|
-
lines.push(`${indent}${prefix}${item.name}`);
|
|
1693
|
-
}
|
|
1694
|
-
}
|
|
1695
|
-
}
|
|
1696
|
-
renderLevel("", "");
|
|
1697
|
-
return lines.join("\n");
|
|
1698
|
-
}
|
|
1699
|
-
var list = async function(input, projectCwd) {
|
|
1700
|
-
const {
|
|
1701
|
-
path: relativePath,
|
|
1702
|
-
recursive = true,
|
|
1703
|
-
maxDepth = 3,
|
|
1704
|
-
pattern,
|
|
1705
|
-
showHidden = false,
|
|
1706
|
-
includeMetadata = false,
|
|
1707
|
-
ignore: extraIgnore
|
|
1708
|
-
} = input;
|
|
1709
|
-
if (maxDepth !== void 0 && (!Number.isInteger(maxDepth) || maxDepth < 0)) {
|
|
1710
|
-
return {
|
|
1711
|
-
success: false,
|
|
1712
|
-
message: "maxDepth must be a non-negative integer",
|
|
1713
|
-
error: "INVALID_MAX_DEPTH"
|
|
1714
|
-
};
|
|
1715
|
-
}
|
|
1716
|
-
try {
|
|
1717
|
-
const basePath = projectCwd || process.cwd();
|
|
1718
|
-
const absolutePath = relativePath ? resolveProjectPath(relativePath, basePath) : basePath;
|
|
1719
|
-
if (projectCwd && relativePath) {
|
|
1720
|
-
const validation = validatePath(relativePath, projectCwd);
|
|
1721
|
-
if (!validation.valid) {
|
|
1722
|
-
return {
|
|
1723
|
-
success: false,
|
|
1724
|
-
message: validation.error || "Path validation failed",
|
|
1725
|
-
error: "ACCESS_DENIED"
|
|
1726
|
-
};
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1729
|
-
if (!fs8.existsSync(absolutePath)) {
|
|
1730
|
-
return {
|
|
1731
|
-
success: false,
|
|
1732
|
-
message: `Directory not found: ${absolutePath}`,
|
|
1733
|
-
error: "DIR_NOT_FOUND"
|
|
1734
|
-
};
|
|
1735
|
-
}
|
|
1736
|
-
const stats = fs8.statSync(absolutePath);
|
|
1737
|
-
if (!stats.isDirectory()) {
|
|
1738
|
-
return {
|
|
1739
|
-
success: false,
|
|
1740
|
-
message: `Path is not a directory: ${absolutePath}`,
|
|
1741
|
-
error: "NOT_A_DIRECTORY"
|
|
1742
|
-
};
|
|
1743
|
-
}
|
|
1744
|
-
const ignoreSet = new Set(DEFAULT_IGNORE_PATTERNS);
|
|
1745
|
-
if (extraIgnore && extraIgnore.length > 0) {
|
|
1746
|
-
for (const pat of extraIgnore) {
|
|
1747
|
-
ignoreSet.add(pat);
|
|
1748
|
-
}
|
|
1749
|
-
}
|
|
1750
|
-
const collected = [];
|
|
1751
|
-
let truncated = false;
|
|
1752
|
-
const walk = async (currentDir, depth) => {
|
|
1753
|
-
if (collected.length >= RESULT_LIMIT2) {
|
|
1754
|
-
truncated = true;
|
|
1755
|
-
return;
|
|
1756
|
-
}
|
|
1757
|
-
let entries;
|
|
1758
|
-
try {
|
|
1759
|
-
entries = fs8.readdirSync(currentDir, { withFileTypes: true });
|
|
1760
|
-
} catch {
|
|
1761
|
-
return;
|
|
1762
|
-
}
|
|
1763
|
-
entries.sort((a, b) => {
|
|
1764
|
-
if (a.isDirectory() !== b.isDirectory()) {
|
|
1765
|
-
return a.isDirectory() ? -1 : 1;
|
|
1766
|
-
}
|
|
1767
|
-
return a.name.localeCompare(b.name);
|
|
1768
|
-
});
|
|
1769
|
-
for (const entry of entries) {
|
|
1770
|
-
if (collected.length >= RESULT_LIMIT2) {
|
|
1771
|
-
truncated = true;
|
|
1772
|
-
break;
|
|
1773
|
-
}
|
|
1774
|
-
if (shouldIgnore(entry.name, showHidden, ignoreSet)) {
|
|
1775
|
-
continue;
|
|
1776
|
-
}
|
|
1777
|
-
const entryAbsolutePath = path10.join(currentDir, entry.name);
|
|
1778
|
-
const entryRelativePath = path10.relative(absolutePath, entryAbsolutePath);
|
|
1779
|
-
if (entry.isDirectory()) {
|
|
1780
|
-
collected.push({
|
|
1781
|
-
name: entry.name,
|
|
1782
|
-
absolutePath: entryAbsolutePath,
|
|
1783
|
-
relativePath: entryRelativePath,
|
|
1784
|
-
type: "directory",
|
|
1785
|
-
mtime: 0,
|
|
1786
|
-
depth
|
|
1787
|
-
});
|
|
1788
|
-
if (recursive && depth < maxDepth) {
|
|
1789
|
-
await walk(entryAbsolutePath, depth + 1);
|
|
1790
|
-
}
|
|
1791
|
-
} else if (entry.isFile()) {
|
|
1792
|
-
if (matchPattern(entry.name, pattern)) {
|
|
1793
|
-
collected.push({
|
|
1794
|
-
name: entry.name,
|
|
1795
|
-
absolutePath: entryAbsolutePath,
|
|
1796
|
-
relativePath: entryRelativePath,
|
|
1797
|
-
type: "file",
|
|
1798
|
-
mtime: 0,
|
|
1799
|
-
depth
|
|
1800
|
-
});
|
|
1801
|
-
}
|
|
1802
|
-
}
|
|
1803
|
-
}
|
|
1804
|
-
};
|
|
1805
|
-
await walk(absolutePath, 0);
|
|
1806
|
-
if (includeMetadata) {
|
|
1807
|
-
await getMtimesBatched3(collected);
|
|
1808
|
-
}
|
|
1809
|
-
const totalFiles = collected.filter((item) => item.type === "file").length;
|
|
1810
|
-
const totalDirectories = collected.filter((item) => item.type === "directory").length;
|
|
1811
|
-
const treeOutput = buildTreeOutput(collected, relativePath || path10.basename(absolutePath));
|
|
1812
|
-
let message = `Listed ${collected.length} items`;
|
|
1813
|
-
if (relativePath) {
|
|
1814
|
-
message += ` in "${relativePath}"`;
|
|
1815
|
-
}
|
|
1816
|
-
message += ` (${totalFiles} files, ${totalDirectories} directories)`;
|
|
1817
|
-
if (recursive) {
|
|
1818
|
-
message += ` [depth: ${maxDepth}]`;
|
|
1819
|
-
}
|
|
1820
|
-
if (pattern) {
|
|
1821
|
-
message += ` [filter: ${pattern}]`;
|
|
1822
|
-
}
|
|
1823
|
-
if (truncated) {
|
|
1824
|
-
message += ` [TRUNCATED at ${RESULT_LIMIT2} items]`;
|
|
1825
|
-
}
|
|
1826
|
-
const files = collected.map((item) => ({
|
|
1827
|
-
name: item.name,
|
|
1828
|
-
path: item.relativePath,
|
|
1829
|
-
type: item.type
|
|
1830
|
-
}));
|
|
1831
|
-
return {
|
|
1832
|
-
success: true,
|
|
1833
|
-
message,
|
|
1834
|
-
metadata: {
|
|
1835
|
-
totalFiles,
|
|
1836
|
-
totalDirectories,
|
|
1837
|
-
totalItems: collected.length,
|
|
1838
|
-
truncated,
|
|
1839
|
-
maxDepth,
|
|
1840
|
-
recursive
|
|
1841
|
-
},
|
|
1842
|
-
files,
|
|
1843
|
-
content: treeOutput
|
|
1844
|
-
};
|
|
1845
|
-
} catch (error) {
|
|
1846
|
-
console.error("[list] error:", error);
|
|
1847
|
-
return {
|
|
1848
|
-
success: false,
|
|
1849
|
-
message: `Failed to list directory: ${error}`,
|
|
1850
|
-
error: "LIST_ERROR"
|
|
1851
|
-
};
|
|
1852
|
-
}
|
|
1853
|
-
};
|
|
3
|
+
import path6 from 'path';
|
|
4
|
+
import os3 from 'os';
|
|
5
|
+
import pc3 from 'picocolors';
|
|
6
|
+
import { Hono } from 'hono';
|
|
7
|
+
import { serve } from '@hono/node-server';
|
|
8
|
+
import { cors } from 'hono/cors';
|
|
9
|
+
import fs4, { readdirSync } from 'fs';
|
|
10
|
+
import { exec, spawn } from 'child_process';
|
|
11
|
+
import { promisify } from 'util';
|
|
12
|
+
import fs5 from 'fs/promises';
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
import { createServer } from 'http';
|
|
15
|
+
import { batchTool, bashTool, apply_patch, read_file, list, globTool, grepTool, deleteFile, editFiles } from '@ama/agent';
|
|
16
|
+
|
|
17
|
+
var DEFAULT_SERVER_URL = "wss://bridge.ama.shujan.xyz";
|
|
18
|
+
var AMA_DIR = path6.join(os3.homedir(), ".amai");
|
|
19
|
+
path6.join(AMA_DIR, "code");
|
|
20
|
+
path6.join(AMA_DIR, "storage");
|
|
1854
21
|
var startHttpServer = () => {
|
|
1855
22
|
const app = new Hono();
|
|
1856
23
|
app.use(cors());
|
|
@@ -1866,22 +33,22 @@ var startHttpServer = () => {
|
|
|
1866
33
|
}
|
|
1867
34
|
});
|
|
1868
35
|
};
|
|
1869
|
-
var CREDENTIALS_DIR =
|
|
1870
|
-
var CREDENTIALS_PATH =
|
|
36
|
+
var CREDENTIALS_DIR = path6.join(os3.homedir(), ".amai");
|
|
37
|
+
var CREDENTIALS_PATH = path6.join(CREDENTIALS_DIR, "credentials.json");
|
|
1871
38
|
function getTokens() {
|
|
1872
|
-
if (!
|
|
39
|
+
if (!fs4.existsSync(CREDENTIALS_PATH)) {
|
|
1873
40
|
return null;
|
|
1874
41
|
}
|
|
1875
|
-
const raw =
|
|
42
|
+
const raw = fs4.readFileSync(CREDENTIALS_PATH, "utf8");
|
|
1876
43
|
const data = JSON.parse(raw);
|
|
1877
44
|
return data;
|
|
1878
45
|
}
|
|
1879
46
|
var getUserId = () => {
|
|
1880
47
|
try {
|
|
1881
|
-
if (!
|
|
48
|
+
if (!fs4.existsSync(CREDENTIALS_PATH)) {
|
|
1882
49
|
return;
|
|
1883
50
|
}
|
|
1884
|
-
const raw =
|
|
51
|
+
const raw = fs4.readFileSync(CREDENTIALS_PATH, "utf8");
|
|
1885
52
|
const data = JSON.parse(raw);
|
|
1886
53
|
const fromUserObject = data.user?.id;
|
|
1887
54
|
const fromTopLevel = data.sub ?? data.user_id;
|
|
@@ -1906,168 +73,54 @@ var getUserId = () => {
|
|
|
1906
73
|
throw new Error("Error while getting userId");
|
|
1907
74
|
}
|
|
1908
75
|
};
|
|
1909
|
-
var
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
/\bmkfs\b/,
|
|
1918
|
-
/:\(\)\{.*\|.*&\}\s*;?\s*:/,
|
|
1919
|
-
/\bchmod\s+.*-R.*\s+\/\s*$/,
|
|
1920
|
-
/\bchown\s+.*-R.*\s+\/\s*$/,
|
|
1921
|
-
/\b(curl|wget)\s+.*\|\s*(ba)?sh/,
|
|
1922
|
-
/\bmv\s+(\/|\*)\s/,
|
|
1923
|
-
/\bcat\s+\/dev\/(u?random|zero)\s*>\s*\/dev\//,
|
|
1924
|
-
/\bformat\s+[A-Z]:/i,
|
|
1925
|
-
/\bdiskpart\b/i,
|
|
1926
|
-
/\bcipher\s+\/w:/i
|
|
1927
|
-
];
|
|
1928
|
-
var DANGEROUS_FLAGS = [
|
|
1929
|
-
/--no-preserve-root/,
|
|
1930
|
-
/\bgit\s+push\s+.*--force\b/,
|
|
1931
|
-
/\bgit\s+push\s+-f\b/
|
|
1932
|
-
];
|
|
1933
|
-
var MAX_OUTPUT_SIZE = 1 * 1024 * 1024;
|
|
1934
|
-
var DEFAULT_TIMEOUT = 12e4;
|
|
1935
|
-
function evaluateCommandSafety(command) {
|
|
1936
|
-
const trimmed = command.trim();
|
|
1937
|
-
for (const pattern of BLOCKED_PATTERNS) {
|
|
1938
|
-
if (pattern.test(trimmed)) {
|
|
1939
|
-
return { safe: false, reason: `Blocked by safety policy: matches destructive pattern` };
|
|
1940
|
-
}
|
|
1941
|
-
}
|
|
1942
|
-
for (const flag of DANGEROUS_FLAGS) {
|
|
1943
|
-
if (flag.test(trimmed)) {
|
|
1944
|
-
return { safe: false, reason: `Blocked by safety policy: dangerous flag detected` };
|
|
1945
|
-
}
|
|
1946
|
-
}
|
|
1947
|
-
return { safe: true };
|
|
76
|
+
var MUTATING_TOOLS = /* @__PURE__ */ new Set([
|
|
77
|
+
"editFile",
|
|
78
|
+
"deleteFile",
|
|
79
|
+
"stringReplace",
|
|
80
|
+
"bash"
|
|
81
|
+
]);
|
|
82
|
+
function isMutatingTool(toolName) {
|
|
83
|
+
return MUTATING_TOOLS.has(toolName);
|
|
1948
84
|
}
|
|
1949
|
-
|
|
1950
|
-
command: z.string().describe("The terminal command to execute"),
|
|
1951
|
-
is_background: z.boolean().optional().default(false).describe("Whether the command should be run in the background"),
|
|
1952
|
-
timeout: z.number().optional().describe("Optional timeout in milliseconds. If not specified, commands will time out after 120000ms (2 minutes)."),
|
|
1953
|
-
workdir: z.string().optional().describe("The working directory to run the command in. Defaults to the project directory. Use this instead of 'cd' commands.")
|
|
1954
|
-
}).merge(ExplanationSchema);
|
|
1955
|
-
var runSecureTerminalCommand = async (command, timeout, cwd) => {
|
|
85
|
+
function isPathWithinProject(filePath, projectCwd) {
|
|
1956
86
|
try {
|
|
1957
|
-
const
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
error: "BLOCKED_COMMAND"
|
|
1964
|
-
};
|
|
1965
|
-
}
|
|
1966
|
-
const proc = Bun.spawn(["sh", "-c", command], {
|
|
1967
|
-
cwd: cwd || process.cwd(),
|
|
1968
|
-
stdout: "pipe",
|
|
1969
|
-
stderr: "pipe"
|
|
1970
|
-
});
|
|
1971
|
-
let timedOut = false;
|
|
1972
|
-
let timeoutId = null;
|
|
1973
|
-
if (timeout > 0) {
|
|
1974
|
-
timeoutId = setTimeout(() => {
|
|
1975
|
-
timedOut = true;
|
|
1976
|
-
proc.kill();
|
|
1977
|
-
}, timeout);
|
|
1978
|
-
}
|
|
1979
|
-
const [stdout, stderr, exitCode] = await Promise.all([
|
|
1980
|
-
new Response(proc.stdout).text(),
|
|
1981
|
-
new Response(proc.stderr).text(),
|
|
1982
|
-
proc.exited
|
|
1983
|
-
]);
|
|
1984
|
-
if (timeoutId) {
|
|
1985
|
-
clearTimeout(timeoutId);
|
|
1986
|
-
}
|
|
1987
|
-
if (timedOut) {
|
|
1988
|
-
return {
|
|
1989
|
-
success: false,
|
|
1990
|
-
message: `Command timed out after ${timeout}ms`,
|
|
1991
|
-
error: "TIMEOUT",
|
|
1992
|
-
stdout: stdout.slice(0, MAX_OUTPUT_SIZE),
|
|
1993
|
-
stderr: stderr.slice(0, MAX_OUTPUT_SIZE)
|
|
1994
|
-
};
|
|
87
|
+
const resolvedCwd = safeRealpath(projectCwd);
|
|
88
|
+
const resolved = path6.resolve(resolvedCwd, filePath);
|
|
89
|
+
const resolvedTarget = safeRealpath(resolved);
|
|
90
|
+
const rel = path6.relative(resolvedCwd, resolvedTarget);
|
|
91
|
+
if (rel.startsWith("..") || path6.isAbsolute(rel)) {
|
|
92
|
+
return false;
|
|
1995
93
|
}
|
|
1996
|
-
return
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
exitCode
|
|
2000
|
-
};
|
|
2001
|
-
} catch (error) {
|
|
2002
|
-
console.error("Error while executing the securedShell command", error);
|
|
2003
|
-
return {
|
|
2004
|
-
success: false,
|
|
2005
|
-
message: "Error while executing the securedShell command",
|
|
2006
|
-
error: error.message
|
|
2007
|
-
};
|
|
94
|
+
return true;
|
|
95
|
+
} catch {
|
|
96
|
+
return false;
|
|
2008
97
|
}
|
|
2009
|
-
}
|
|
2010
|
-
|
|
98
|
+
}
|
|
99
|
+
function safeRealpath(p) {
|
|
2011
100
|
try {
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
}
|
|
2021
|
-
if (input.timeout !== void 0 && input.timeout < 0) {
|
|
2022
|
-
return {
|
|
2023
|
-
success: false,
|
|
2024
|
-
message: `Invalid timeout value: ${input.timeout}. Timeout must be a positive number.`,
|
|
2025
|
-
error: "INVALID_TIMEOUT"
|
|
2026
|
-
};
|
|
2027
|
-
}
|
|
2028
|
-
const cwd = input.workdir || projectCwd || process.cwd();
|
|
2029
|
-
const timeout = input.timeout ?? DEFAULT_TIMEOUT;
|
|
2030
|
-
if (input?.is_background) {
|
|
2031
|
-
const proc = Bun.spawn(["sh", "-c", input.command], {
|
|
2032
|
-
cwd,
|
|
2033
|
-
stdout: "ignore",
|
|
2034
|
-
stderr: "ignore"
|
|
2035
|
-
});
|
|
2036
|
-
proc.unref();
|
|
2037
|
-
console.log(`[LOCAL] Background command started: ${input.command}`);
|
|
2038
|
-
return {
|
|
2039
|
-
success: true,
|
|
2040
|
-
message: `Background command started: ${input.command}`,
|
|
2041
|
-
isBackground: true
|
|
2042
|
-
};
|
|
2043
|
-
} else {
|
|
2044
|
-
const result = await runSecureTerminalCommand(
|
|
2045
|
-
input.command,
|
|
2046
|
-
timeout,
|
|
2047
|
-
cwd
|
|
2048
|
-
);
|
|
2049
|
-
if (result?.error && !result?.exitCode) {
|
|
2050
|
-
return result;
|
|
2051
|
-
}
|
|
2052
|
-
const success = result?.exitCode === 0;
|
|
2053
|
-
return {
|
|
2054
|
-
success,
|
|
2055
|
-
stdout: result?.stdout?.trim(),
|
|
2056
|
-
stderr: result?.stderr?.trim(),
|
|
2057
|
-
exitCode: result?.exitCode,
|
|
2058
|
-
message: success ? `Command executed successfully: ${input.command}` : `Command failed with exit code ${result?.exitCode}: ${input.command}`
|
|
2059
|
-
};
|
|
101
|
+
return fs4.realpathSync(p);
|
|
102
|
+
} catch {
|
|
103
|
+
const parent = path6.dirname(p);
|
|
104
|
+
try {
|
|
105
|
+
const realParent = fs4.realpathSync(parent);
|
|
106
|
+
return path6.join(realParent, path6.basename(p));
|
|
107
|
+
} catch {
|
|
108
|
+
return path6.resolve(p);
|
|
2060
109
|
}
|
|
2061
|
-
}
|
|
2062
|
-
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function requireProjectCwd(toolName, projectCwd) {
|
|
113
|
+
if (!projectCwd && isMutatingTool(toolName)) {
|
|
2063
114
|
return {
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
error: error.message
|
|
115
|
+
allowed: false,
|
|
116
|
+
error: `ACCESS_DENIED: Tool "${toolName}" requires a project context (projectCwd) but none was provided`
|
|
2067
117
|
};
|
|
2068
118
|
}
|
|
2069
|
-
};
|
|
2070
|
-
|
|
119
|
+
return { allowed: true };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/lib/project-registry.ts
|
|
123
|
+
var REGISTRY_FILE = path6.join(AMA_DIR, "projects.json");
|
|
2071
124
|
var ProjectRegistry = class {
|
|
2072
125
|
projects = /* @__PURE__ */ new Map();
|
|
2073
126
|
constructor() {
|
|
@@ -2075,14 +128,14 @@ var ProjectRegistry = class {
|
|
|
2075
128
|
}
|
|
2076
129
|
load() {
|
|
2077
130
|
try {
|
|
2078
|
-
if (
|
|
2079
|
-
const data =
|
|
131
|
+
if (fs4.existsSync(REGISTRY_FILE)) {
|
|
132
|
+
const data = fs4.readFileSync(REGISTRY_FILE, "utf8");
|
|
2080
133
|
const parsed = JSON.parse(data);
|
|
2081
134
|
if (!Array.isArray(parsed)) {
|
|
2082
135
|
console.error("Invalid project registry format: expected array, got", typeof parsed);
|
|
2083
136
|
const backupFile = REGISTRY_FILE + ".backup." + Date.now();
|
|
2084
|
-
|
|
2085
|
-
|
|
137
|
+
fs4.copyFileSync(REGISTRY_FILE, backupFile);
|
|
138
|
+
fs4.unlinkSync(REGISTRY_FILE);
|
|
2086
139
|
return;
|
|
2087
140
|
}
|
|
2088
141
|
const projects = parsed;
|
|
@@ -2095,11 +148,11 @@ var ProjectRegistry = class {
|
|
|
2095
148
|
}
|
|
2096
149
|
} catch (error) {
|
|
2097
150
|
console.error("Failed to load project registry:", error);
|
|
2098
|
-
if (
|
|
151
|
+
if (fs4.existsSync(REGISTRY_FILE)) {
|
|
2099
152
|
try {
|
|
2100
153
|
const backupFile = REGISTRY_FILE + ".backup." + Date.now();
|
|
2101
|
-
|
|
2102
|
-
|
|
154
|
+
fs4.copyFileSync(REGISTRY_FILE, backupFile);
|
|
155
|
+
fs4.unlinkSync(REGISTRY_FILE);
|
|
2103
156
|
console.log("Corrupted registry file backed up and removed. Starting fresh.");
|
|
2104
157
|
} catch (backupError) {
|
|
2105
158
|
}
|
|
@@ -2108,21 +161,21 @@ var ProjectRegistry = class {
|
|
|
2108
161
|
}
|
|
2109
162
|
save() {
|
|
2110
163
|
try {
|
|
2111
|
-
if (!
|
|
2112
|
-
|
|
164
|
+
if (!fs4.existsSync(AMA_DIR)) {
|
|
165
|
+
fs4.mkdirSync(AMA_DIR, { recursive: true });
|
|
2113
166
|
}
|
|
2114
167
|
const projects = Array.from(this.projects.values());
|
|
2115
|
-
|
|
168
|
+
fs4.writeFileSync(REGISTRY_FILE, JSON.stringify(projects, null, 2), "utf8");
|
|
2116
169
|
} catch (error) {
|
|
2117
170
|
console.error("Failed to save project registry:", error);
|
|
2118
171
|
}
|
|
2119
172
|
}
|
|
2120
173
|
register(projectId, cwd, name) {
|
|
2121
|
-
const normalizedCwd =
|
|
174
|
+
const normalizedCwd = path6.normalize(path6.resolve(cwd));
|
|
2122
175
|
this.projects.set(projectId, {
|
|
2123
176
|
id: projectId,
|
|
2124
177
|
cwd: normalizedCwd,
|
|
2125
|
-
name: name ||
|
|
178
|
+
name: name || path6.basename(normalizedCwd),
|
|
2126
179
|
active: true
|
|
2127
180
|
});
|
|
2128
181
|
this.save();
|
|
@@ -2155,33 +208,33 @@ var getContext = (dir, base = dir, allFiles = []) => {
|
|
|
2155
208
|
const filePath = readdirSync(dir, { withFileTypes: true });
|
|
2156
209
|
for (const file of filePath) {
|
|
2157
210
|
if (ignoreFiles.includes(file.name)) continue;
|
|
2158
|
-
const fullPath =
|
|
211
|
+
const fullPath = path6.join(dir, file.name);
|
|
2159
212
|
if (file.isDirectory()) {
|
|
2160
213
|
getContext(fullPath, base, allFiles);
|
|
2161
214
|
} else {
|
|
2162
|
-
allFiles.push(
|
|
215
|
+
allFiles.push(path6.relative(base, fullPath));
|
|
2163
216
|
}
|
|
2164
217
|
}
|
|
2165
218
|
return allFiles;
|
|
2166
219
|
};
|
|
2167
220
|
var HOME = os3.homedir();
|
|
2168
221
|
var IDE_PROJECTS_PATHS = {
|
|
2169
|
-
vscode:
|
|
2170
|
-
cursor:
|
|
2171
|
-
claude:
|
|
222
|
+
vscode: path6.join(HOME, ".vscode", "projects"),
|
|
223
|
+
cursor: path6.join(HOME, ".cursor", "projects"),
|
|
224
|
+
claude: path6.join(HOME, ".claude", "projects")
|
|
2172
225
|
};
|
|
2173
226
|
function getWorkspaceStoragePath(ide) {
|
|
2174
227
|
const platform = os3.platform();
|
|
2175
228
|
const appName = ide === "cursor" ? "Cursor" : "Code";
|
|
2176
229
|
const appNameLower = appName.toLowerCase();
|
|
2177
230
|
if (platform === "darwin") {
|
|
2178
|
-
return
|
|
231
|
+
return path6.join(HOME, "Library", "Application Support", appName, "User", "workspaceStorage");
|
|
2179
232
|
} else if (platform === "win32") {
|
|
2180
|
-
return
|
|
233
|
+
return path6.join(process.env.APPDATA || "", appName, "User", "workspaceStorage");
|
|
2181
234
|
} else {
|
|
2182
|
-
const capitalizedPath =
|
|
2183
|
-
const lowercasePath =
|
|
2184
|
-
if (
|
|
235
|
+
const capitalizedPath = path6.join(HOME, ".config", appName, "User", "workspaceStorage");
|
|
236
|
+
const lowercasePath = path6.join(HOME, ".config", appNameLower, "User", "workspaceStorage");
|
|
237
|
+
if (fs4.existsSync(capitalizedPath)) {
|
|
2185
238
|
return capitalizedPath;
|
|
2186
239
|
}
|
|
2187
240
|
return lowercasePath;
|
|
@@ -2190,16 +243,16 @@ function getWorkspaceStoragePath(ide) {
|
|
|
2190
243
|
function scanWorkspaceStorage(ide) {
|
|
2191
244
|
const projects = [];
|
|
2192
245
|
const storagePath = getWorkspaceStoragePath(ide);
|
|
2193
|
-
if (!
|
|
246
|
+
if (!fs4.existsSync(storagePath)) {
|
|
2194
247
|
return projects;
|
|
2195
248
|
}
|
|
2196
249
|
try {
|
|
2197
|
-
const workspaces =
|
|
250
|
+
const workspaces = fs4.readdirSync(storagePath);
|
|
2198
251
|
for (const workspace of workspaces) {
|
|
2199
|
-
const workspaceJsonPath =
|
|
2200
|
-
if (
|
|
252
|
+
const workspaceJsonPath = path6.join(storagePath, workspace, "workspace.json");
|
|
253
|
+
if (fs4.existsSync(workspaceJsonPath)) {
|
|
2201
254
|
try {
|
|
2202
|
-
const content =
|
|
255
|
+
const content = fs4.readFileSync(workspaceJsonPath, "utf-8");
|
|
2203
256
|
const data = JSON.parse(content);
|
|
2204
257
|
if (data.folder && typeof data.folder === "string") {
|
|
2205
258
|
let projectPath = data.folder;
|
|
@@ -2207,9 +260,9 @@ function scanWorkspaceStorage(ide) {
|
|
|
2207
260
|
projectPath = projectPath.replace("file://", "");
|
|
2208
261
|
projectPath = decodeURIComponent(projectPath);
|
|
2209
262
|
}
|
|
2210
|
-
if (
|
|
263
|
+
if (fs4.existsSync(projectPath) && fs4.statSync(projectPath).isDirectory()) {
|
|
2211
264
|
projects.push({
|
|
2212
|
-
name:
|
|
265
|
+
name: path6.basename(projectPath),
|
|
2213
266
|
path: projectPath,
|
|
2214
267
|
type: ide
|
|
2215
268
|
});
|
|
@@ -2231,11 +284,11 @@ var scanIdeProjects = async () => {
|
|
|
2231
284
|
const seenPaths = /* @__PURE__ */ new Set();
|
|
2232
285
|
const addProject = (projectPath, ide) => {
|
|
2233
286
|
try {
|
|
2234
|
-
const resolvedPath =
|
|
2235
|
-
if (
|
|
287
|
+
const resolvedPath = fs4.realpathSync(projectPath);
|
|
288
|
+
if (fs4.existsSync(resolvedPath) && fs4.statSync(resolvedPath).isDirectory() && !seenPaths.has(resolvedPath)) {
|
|
2236
289
|
const isIdeProjectsDir = Object.values(IDE_PROJECTS_PATHS).some((ideDir) => {
|
|
2237
290
|
try {
|
|
2238
|
-
return
|
|
291
|
+
return fs4.realpathSync(ideDir) === resolvedPath;
|
|
2239
292
|
} catch {
|
|
2240
293
|
return false;
|
|
2241
294
|
}
|
|
@@ -2243,7 +296,7 @@ var scanIdeProjects = async () => {
|
|
|
2243
296
|
if (!isIdeProjectsDir) {
|
|
2244
297
|
seenPaths.add(resolvedPath);
|
|
2245
298
|
allProjects.push({
|
|
2246
|
-
name:
|
|
299
|
+
name: path6.basename(resolvedPath),
|
|
2247
300
|
path: resolvedPath,
|
|
2248
301
|
type: ide
|
|
2249
302
|
});
|
|
@@ -2262,30 +315,30 @@ var scanIdeProjects = async () => {
|
|
|
2262
315
|
}
|
|
2263
316
|
for (const [ide, dirPath] of Object.entries(IDE_PROJECTS_PATHS)) {
|
|
2264
317
|
if (ide === "cursor" || ide === "vscode") continue;
|
|
2265
|
-
if (
|
|
2266
|
-
const projects =
|
|
318
|
+
if (fs4.existsSync(dirPath)) {
|
|
319
|
+
const projects = fs4.readdirSync(dirPath);
|
|
2267
320
|
projects.forEach((project) => {
|
|
2268
|
-
const projectPath =
|
|
321
|
+
const projectPath = path6.join(dirPath, project);
|
|
2269
322
|
try {
|
|
2270
|
-
const stats =
|
|
323
|
+
const stats = fs4.lstatSync(projectPath);
|
|
2271
324
|
let actualPath = null;
|
|
2272
325
|
if (stats.isSymbolicLink()) {
|
|
2273
|
-
actualPath =
|
|
326
|
+
actualPath = fs4.realpathSync(projectPath);
|
|
2274
327
|
} else if (stats.isFile()) {
|
|
2275
328
|
try {
|
|
2276
|
-
let content =
|
|
329
|
+
let content = fs4.readFileSync(projectPath, "utf-8").trim();
|
|
2277
330
|
if (content.startsWith("~/") || content === "~") {
|
|
2278
331
|
content = content.replace(/^~/, HOME);
|
|
2279
332
|
}
|
|
2280
|
-
const resolvedContent =
|
|
2281
|
-
if (
|
|
2282
|
-
actualPath =
|
|
333
|
+
const resolvedContent = path6.isAbsolute(content) ? content : path6.resolve(path6.dirname(projectPath), content);
|
|
334
|
+
if (fs4.existsSync(resolvedContent) && fs4.statSync(resolvedContent).isDirectory()) {
|
|
335
|
+
actualPath = fs4.realpathSync(resolvedContent);
|
|
2283
336
|
}
|
|
2284
337
|
} catch {
|
|
2285
338
|
return;
|
|
2286
339
|
}
|
|
2287
340
|
} else if (stats.isDirectory()) {
|
|
2288
|
-
actualPath =
|
|
341
|
+
actualPath = fs4.realpathSync(projectPath);
|
|
2289
342
|
}
|
|
2290
343
|
if (actualPath) {
|
|
2291
344
|
addProject(actualPath, ide);
|
|
@@ -2304,7 +357,7 @@ var scanIdeProjects = async () => {
|
|
|
2304
357
|
var Global;
|
|
2305
358
|
((Global2) => {
|
|
2306
359
|
((Path2) => {
|
|
2307
|
-
Path2.data =
|
|
360
|
+
Path2.data = path6.join(AMA_DIR, "data");
|
|
2308
361
|
})(Global2.Path || (Global2.Path = {}));
|
|
2309
362
|
})(Global || (Global = {}));
|
|
2310
363
|
|
|
@@ -2343,8 +396,8 @@ var Snapshot;
|
|
|
2343
396
|
const worktree = project.cwd;
|
|
2344
397
|
const git = gitdir(projectId);
|
|
2345
398
|
try {
|
|
2346
|
-
await
|
|
2347
|
-
const gitExists = await
|
|
399
|
+
await fs5.mkdir(git, { recursive: true });
|
|
400
|
+
const gitExists = await fs5.access(path6.join(git, "HEAD")).then(() => true).catch(() => false);
|
|
2348
401
|
if (!gitExists) {
|
|
2349
402
|
await runGit(`git init`, {
|
|
2350
403
|
env: { GIT_DIR: git, GIT_WORK_TREE: worktree }
|
|
@@ -2385,7 +438,7 @@ var Snapshot;
|
|
|
2385
438
|
const files = result.stdout;
|
|
2386
439
|
return {
|
|
2387
440
|
hash,
|
|
2388
|
-
files: files.trim().split("\n").map((x) => x.trim()).filter(Boolean).map((x) =>
|
|
441
|
+
files: files.trim().split("\n").map((x) => x.trim()).filter(Boolean).map((x) => path6.join(worktree, x))
|
|
2389
442
|
};
|
|
2390
443
|
}
|
|
2391
444
|
Snapshot2.patch = patch;
|
|
@@ -2427,9 +480,9 @@ var Snapshot;
|
|
|
2427
480
|
if (diffResult.exitCode === 0 && diffResult.stdout.trim()) {
|
|
2428
481
|
const newFiles = diffResult.stdout.trim().split("\n").filter(Boolean);
|
|
2429
482
|
for (const file of newFiles) {
|
|
2430
|
-
const fullPath =
|
|
483
|
+
const fullPath = path6.join(worktree, file);
|
|
2431
484
|
try {
|
|
2432
|
-
await
|
|
485
|
+
await fs5.unlink(fullPath);
|
|
2433
486
|
log.info("deleted newly created file", { file: fullPath });
|
|
2434
487
|
} catch {
|
|
2435
488
|
}
|
|
@@ -2457,7 +510,7 @@ var Snapshot;
|
|
|
2457
510
|
{ cwd: worktree }
|
|
2458
511
|
);
|
|
2459
512
|
if (result.exitCode !== 0) {
|
|
2460
|
-
const relativePath =
|
|
513
|
+
const relativePath = path6.relative(worktree, file);
|
|
2461
514
|
const checkTree = await runGit(
|
|
2462
515
|
`git --git-dir "${git}" --work-tree "${worktree}" ls-tree ${item.hash} -- "${relativePath}"`,
|
|
2463
516
|
{ cwd: worktree }
|
|
@@ -2466,7 +519,7 @@ var Snapshot;
|
|
|
2466
519
|
log.info("file existed in snapshot but checkout failed, keeping", { file });
|
|
2467
520
|
} else {
|
|
2468
521
|
log.info("file did not exist in snapshot, deleting", { file });
|
|
2469
|
-
await
|
|
522
|
+
await fs5.unlink(file).catch(() => {
|
|
2470
523
|
});
|
|
2471
524
|
}
|
|
2472
525
|
}
|
|
@@ -2520,10 +573,10 @@ var Snapshot;
|
|
|
2520
573
|
const lines = numstatResult.stdout.trim().split("\n").filter(Boolean);
|
|
2521
574
|
for (const line of lines) {
|
|
2522
575
|
const [additions, deletions, file] = line.split(" ");
|
|
2523
|
-
const
|
|
576
|
+
const isBinaryFile = additions === "-" && deletions === "-";
|
|
2524
577
|
let before = "";
|
|
2525
578
|
let after = "";
|
|
2526
|
-
if (!
|
|
579
|
+
if (!isBinaryFile) {
|
|
2527
580
|
const beforeResult = await runGit(
|
|
2528
581
|
`git -c core.autocrlf=false --git-dir "${git}" --work-tree "${worktree}" show ${from}:${file}`,
|
|
2529
582
|
{ cwd: worktree }
|
|
@@ -2547,13 +600,13 @@ var Snapshot;
|
|
|
2547
600
|
}
|
|
2548
601
|
Snapshot2.diffFull = diffFull;
|
|
2549
602
|
function gitdir(projectId) {
|
|
2550
|
-
return
|
|
603
|
+
return path6.join(Global.Path.data, "snapshot", projectId);
|
|
2551
604
|
}
|
|
2552
605
|
})(Snapshot || (Snapshot = {}));
|
|
2553
606
|
var CLIENT_ID2 = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
2554
607
|
var ISSUER = "https://auth.openai.com";
|
|
2555
608
|
var OAUTH_PORT = 1455;
|
|
2556
|
-
var CREDENTIALS_PATH2 =
|
|
609
|
+
var CREDENTIALS_PATH2 = path6.join(AMA_DIR, "codex-credentials.json");
|
|
2557
610
|
var CALLBACK_PATH = "/auth/callback";
|
|
2558
611
|
var OAUTH_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
2559
612
|
var REFRESH_BUFFER_MS = 60 * 1e3;
|
|
@@ -2648,19 +701,19 @@ var HTML_ERROR = (error) => `<!doctype html>
|
|
|
2648
701
|
</body>
|
|
2649
702
|
</html>`;
|
|
2650
703
|
function ensureCredentialsDir() {
|
|
2651
|
-
if (!
|
|
2652
|
-
|
|
704
|
+
if (!fs4.existsSync(AMA_DIR)) {
|
|
705
|
+
fs4.mkdirSync(AMA_DIR, { recursive: true });
|
|
2653
706
|
}
|
|
2654
707
|
}
|
|
2655
708
|
function saveCredentials(credentials) {
|
|
2656
709
|
ensureCredentialsDir();
|
|
2657
|
-
|
|
710
|
+
fs4.writeFileSync(CREDENTIALS_PATH2, JSON.stringify(credentials, null, 2), "utf8");
|
|
2658
711
|
}
|
|
2659
712
|
function readCredentials() {
|
|
2660
|
-
if (!
|
|
713
|
+
if (!fs4.existsSync(CREDENTIALS_PATH2)) {
|
|
2661
714
|
return null;
|
|
2662
715
|
}
|
|
2663
|
-
const raw =
|
|
716
|
+
const raw = fs4.readFileSync(CREDENTIALS_PATH2, "utf8");
|
|
2664
717
|
const parsed = JSON.parse(raw);
|
|
2665
718
|
if (typeof parsed.accessToken !== "string" || typeof parsed.refreshToken !== "string" || typeof parsed.accountId !== "string" || typeof parsed.expiresAt !== "number") {
|
|
2666
719
|
return null;
|
|
@@ -2768,56 +821,66 @@ async function startOAuthServer() {
|
|
|
2768
821
|
if (oauthServer) {
|
|
2769
822
|
return { redirectUri: `http://localhost:${OAUTH_PORT}${CALLBACK_PATH}` };
|
|
2770
823
|
}
|
|
2771
|
-
oauthServer =
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
if (!code) {
|
|
2791
|
-
const message = "Missing authorization code";
|
|
2792
|
-
pendingOAuth?.reject(new Error(message));
|
|
2793
|
-
pendingOAuth = void 0;
|
|
2794
|
-
return new Response(HTML_ERROR(message), {
|
|
2795
|
-
status: 400,
|
|
2796
|
-
headers: { "Content-Type": "text/html" }
|
|
2797
|
-
});
|
|
2798
|
-
}
|
|
2799
|
-
if (!pendingOAuth || state !== pendingOAuth.state) {
|
|
2800
|
-
const message = "Invalid state - potential CSRF attack";
|
|
2801
|
-
pendingOAuth?.reject(new Error(message));
|
|
2802
|
-
pendingOAuth = void 0;
|
|
2803
|
-
return new Response(HTML_ERROR(message), {
|
|
2804
|
-
status: 400,
|
|
2805
|
-
headers: { "Content-Type": "text/html" }
|
|
2806
|
-
});
|
|
2807
|
-
}
|
|
2808
|
-
const current = pendingOAuth;
|
|
824
|
+
oauthServer = createServer((req, res) => {
|
|
825
|
+
const requestUrl = new URL(req.url || "/", `http://localhost:${OAUTH_PORT}`);
|
|
826
|
+
if (requestUrl.pathname !== CALLBACK_PATH) {
|
|
827
|
+
res.statusCode = 404;
|
|
828
|
+
res.end("Not found");
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
const code = requestUrl.searchParams.get("code");
|
|
832
|
+
const state = requestUrl.searchParams.get("state");
|
|
833
|
+
const error = requestUrl.searchParams.get("error");
|
|
834
|
+
const errorDescription = requestUrl.searchParams.get("error_description");
|
|
835
|
+
const sendHtml = (html, statusCode = 200) => {
|
|
836
|
+
res.statusCode = statusCode;
|
|
837
|
+
res.setHeader("Content-Type", "text/html");
|
|
838
|
+
res.end(html);
|
|
839
|
+
};
|
|
840
|
+
if (error) {
|
|
841
|
+
const message = errorDescription || error;
|
|
842
|
+
pendingOAuth?.reject(new Error(message));
|
|
2809
843
|
pendingOAuth = void 0;
|
|
2810
|
-
|
|
2811
|
-
return
|
|
2812
|
-
|
|
2813
|
-
|
|
844
|
+
sendHtml(HTML_ERROR(message));
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
if (!code) {
|
|
848
|
+
const message = "Missing authorization code";
|
|
849
|
+
pendingOAuth?.reject(new Error(message));
|
|
850
|
+
pendingOAuth = void 0;
|
|
851
|
+
sendHtml(HTML_ERROR(message), 400);
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
if (!pendingOAuth || state !== pendingOAuth.state) {
|
|
855
|
+
const message = "Invalid state - potential CSRF attack";
|
|
856
|
+
pendingOAuth?.reject(new Error(message));
|
|
857
|
+
pendingOAuth = void 0;
|
|
858
|
+
sendHtml(HTML_ERROR(message), 400);
|
|
859
|
+
return;
|
|
2814
860
|
}
|
|
861
|
+
const current = pendingOAuth;
|
|
862
|
+
pendingOAuth = void 0;
|
|
863
|
+
exchangeCodeForTokens(code, `http://localhost:${OAUTH_PORT}${CALLBACK_PATH}`, current.pkce).then((tokens) => current.resolve(tokens)).catch((err) => current.reject(err));
|
|
864
|
+
sendHtml(HTML_SUCCESS);
|
|
2815
865
|
});
|
|
866
|
+
try {
|
|
867
|
+
await new Promise((resolve, reject) => {
|
|
868
|
+
oauthServer?.once("error", reject);
|
|
869
|
+
oauthServer?.listen(OAUTH_PORT, "127.0.0.1", () => {
|
|
870
|
+
oauthServer?.off("error", reject);
|
|
871
|
+
resolve();
|
|
872
|
+
});
|
|
873
|
+
});
|
|
874
|
+
} catch (error) {
|
|
875
|
+
oauthServer?.close();
|
|
876
|
+
oauthServer = void 0;
|
|
877
|
+
throw error;
|
|
878
|
+
}
|
|
2816
879
|
return { redirectUri: `http://localhost:${OAUTH_PORT}${CALLBACK_PATH}` };
|
|
2817
880
|
}
|
|
2818
881
|
function stopOAuthServer() {
|
|
2819
882
|
if (oauthServer) {
|
|
2820
|
-
oauthServer.
|
|
883
|
+
oauthServer.close();
|
|
2821
884
|
oauthServer = void 0;
|
|
2822
885
|
}
|
|
2823
886
|
}
|
|
@@ -2936,8 +999,8 @@ async function getCodexStatus() {
|
|
|
2936
999
|
async function codexLogout() {
|
|
2937
1000
|
pendingOAuth = void 0;
|
|
2938
1001
|
stopOAuthServer();
|
|
2939
|
-
if (
|
|
2940
|
-
|
|
1002
|
+
if (fs4.existsSync(CREDENTIALS_PATH2)) {
|
|
1003
|
+
fs4.unlinkSync(CREDENTIALS_PATH2);
|
|
2941
1004
|
}
|
|
2942
1005
|
}
|
|
2943
1006
|
|
|
@@ -3220,125 +1283,6 @@ var connectToUserStreams = async (serverUrl) => {
|
|
|
3220
1283
|
return ws;
|
|
3221
1284
|
};
|
|
3222
1285
|
var toolCallSchema = z.object({
|
|
3223
|
-
tool: z.string().describe("The name of the tool to execute"),
|
|
3224
|
-
parameters: z.record(z.string(), z.unknown()).describe("Parameters for the tool")
|
|
3225
|
-
});
|
|
3226
|
-
z.object({
|
|
3227
|
-
tool_calls: z.array(toolCallSchema).min(1, "Provide at least one tool call").max(25, "Maximum of 25 tools allowed in batch").describe("Array of tool calls to execute in parallel")
|
|
3228
|
-
});
|
|
3229
|
-
var DISALLOWED_TOOLS = /* @__PURE__ */ new Set(["batch"]);
|
|
3230
|
-
var MAX_CONCURRENCY = 5;
|
|
3231
|
-
var PER_CALL_TIMEOUT = 3e4;
|
|
3232
|
-
var MAX_BATCH_SIZE = 25;
|
|
3233
|
-
var batchableToolExecutors = {
|
|
3234
|
-
deleteFile,
|
|
3235
|
-
grep: grepTool,
|
|
3236
|
-
glob: globTool,
|
|
3237
|
-
listDirectory: list,
|
|
3238
|
-
readFile: read_file,
|
|
3239
|
-
bash: bashTool,
|
|
3240
|
-
stringReplace: apply_patch,
|
|
3241
|
-
editFile: editFiles
|
|
3242
|
-
};
|
|
3243
|
-
function withTimeout(promise, ms) {
|
|
3244
|
-
return new Promise((resolve, reject) => {
|
|
3245
|
-
const timer = setTimeout(() => {
|
|
3246
|
-
reject(new Error(`BATCH_CALL_TIMEOUT: exceeded ${ms}ms`));
|
|
3247
|
-
}, ms);
|
|
3248
|
-
promise.then((v) => {
|
|
3249
|
-
clearTimeout(timer);
|
|
3250
|
-
resolve(v);
|
|
3251
|
-
}).catch((e) => {
|
|
3252
|
-
clearTimeout(timer);
|
|
3253
|
-
reject(e);
|
|
3254
|
-
});
|
|
3255
|
-
});
|
|
3256
|
-
}
|
|
3257
|
-
async function runWithConcurrencyLimit(tasks, limit) {
|
|
3258
|
-
const results = new Array(tasks.length);
|
|
3259
|
-
let nextIndex = 0;
|
|
3260
|
-
async function worker() {
|
|
3261
|
-
while (nextIndex < tasks.length) {
|
|
3262
|
-
const idx = nextIndex++;
|
|
3263
|
-
results[idx] = await tasks[idx]();
|
|
3264
|
-
}
|
|
3265
|
-
}
|
|
3266
|
-
const workers = Array.from({ length: Math.min(limit, tasks.length) }, () => worker());
|
|
3267
|
-
await Promise.all(workers);
|
|
3268
|
-
return results;
|
|
3269
|
-
}
|
|
3270
|
-
var batchTool = async function(input, projectCwd) {
|
|
3271
|
-
const { tool_calls } = input;
|
|
3272
|
-
const callsToExecute = tool_calls.slice(0, MAX_BATCH_SIZE);
|
|
3273
|
-
const discardedCalls = tool_calls.slice(MAX_BATCH_SIZE);
|
|
3274
|
-
const executeCall = async (call) => {
|
|
3275
|
-
const start = performance.now();
|
|
3276
|
-
try {
|
|
3277
|
-
if (DISALLOWED_TOOLS.has(call.tool)) {
|
|
3278
|
-
return {
|
|
3279
|
-
tool: call.tool,
|
|
3280
|
-
success: false,
|
|
3281
|
-
error: `Tool '${call.tool}' is not allowed in batch. Disallowed tools: ${Array.from(DISALLOWED_TOOLS).join(", ")}`,
|
|
3282
|
-
durationMs: 0
|
|
3283
|
-
};
|
|
3284
|
-
}
|
|
3285
|
-
const executor = batchableToolExecutors[call.tool];
|
|
3286
|
-
if (!executor) {
|
|
3287
|
-
const availableTools = Object.keys(batchableToolExecutors).join(", ");
|
|
3288
|
-
return {
|
|
3289
|
-
tool: call.tool,
|
|
3290
|
-
success: false,
|
|
3291
|
-
error: `Tool '${call.tool}' not found. Available tools for batching: ${availableTools}`,
|
|
3292
|
-
durationMs: 0
|
|
3293
|
-
};
|
|
3294
|
-
}
|
|
3295
|
-
const result = await withTimeout(executor(call.parameters, projectCwd), PER_CALL_TIMEOUT);
|
|
3296
|
-
const durationMs = Math.round(performance.now() - start);
|
|
3297
|
-
return {
|
|
3298
|
-
tool: call.tool,
|
|
3299
|
-
success: result.success !== false,
|
|
3300
|
-
result,
|
|
3301
|
-
durationMs
|
|
3302
|
-
};
|
|
3303
|
-
} catch (error) {
|
|
3304
|
-
const durationMs = Math.round(performance.now() - start);
|
|
3305
|
-
const timedOut = error.message?.includes("BATCH_CALL_TIMEOUT");
|
|
3306
|
-
return {
|
|
3307
|
-
tool: call.tool,
|
|
3308
|
-
success: false,
|
|
3309
|
-
error: error.message || String(error),
|
|
3310
|
-
durationMs,
|
|
3311
|
-
timedOut
|
|
3312
|
-
};
|
|
3313
|
-
}
|
|
3314
|
-
};
|
|
3315
|
-
const tasks = callsToExecute.map(
|
|
3316
|
-
(call) => () => executeCall(call)
|
|
3317
|
-
);
|
|
3318
|
-
const results = await runWithConcurrencyLimit(tasks, MAX_CONCURRENCY);
|
|
3319
|
-
for (const call of discardedCalls) {
|
|
3320
|
-
results.push({
|
|
3321
|
-
tool: call.tool,
|
|
3322
|
-
success: false,
|
|
3323
|
-
error: `Maximum of ${MAX_BATCH_SIZE} tools allowed in batch`,
|
|
3324
|
-
durationMs: 0
|
|
3325
|
-
});
|
|
3326
|
-
}
|
|
3327
|
-
const successfulCalls = results.filter((r) => r.success).length;
|
|
3328
|
-
const failedCalls = results.length - successfulCalls;
|
|
3329
|
-
const outputMessage = failedCalls > 0 ? `Executed ${successfulCalls}/${results.length} tools successfully. ${failedCalls} failed.` : `All ${successfulCalls} tools executed successfully.
|
|
3330
|
-
|
|
3331
|
-
Keep using the batch tool for optimal performance in your next response!`;
|
|
3332
|
-
return {
|
|
3333
|
-
success: failedCalls === 0,
|
|
3334
|
-
message: outputMessage,
|
|
3335
|
-
totalCalls: results.length,
|
|
3336
|
-
successful: successfulCalls,
|
|
3337
|
-
failed: failedCalls,
|
|
3338
|
-
results
|
|
3339
|
-
};
|
|
3340
|
-
};
|
|
3341
|
-
var toolCallSchema2 = z.object({
|
|
3342
1286
|
type: z.literal("tool_call"),
|
|
3343
1287
|
id: z.string(),
|
|
3344
1288
|
tool: z.string(),
|
|
@@ -3361,7 +1305,7 @@ var TOOL_TIMEOUTS = {
|
|
|
3361
1305
|
function getTimeoutForTool(tool) {
|
|
3362
1306
|
return TOOL_TIMEOUTS[tool] ?? DEFAULT_TIMEOUT_MS;
|
|
3363
1307
|
}
|
|
3364
|
-
function
|
|
1308
|
+
function withTimeout(promise, ms, tool) {
|
|
3365
1309
|
return new Promise((resolve, reject) => {
|
|
3366
1310
|
const timer = setTimeout(() => {
|
|
3367
1311
|
reject(new ToolTimeoutError(tool, ms));
|
|
@@ -3411,7 +1355,7 @@ async function executeTool(toolName, args, projectCwd, executors) {
|
|
|
3411
1355
|
}
|
|
3412
1356
|
try {
|
|
3413
1357
|
const timeoutMs = getTimeoutForTool(toolName);
|
|
3414
|
-
const result = await
|
|
1358
|
+
const result = await withTimeout(executor(args, projectCwd), timeoutMs, toolName);
|
|
3415
1359
|
const durationMs = Math.round(performance.now() - start);
|
|
3416
1360
|
return {
|
|
3417
1361
|
success: result?.success !== false,
|
|
@@ -3438,7 +1382,7 @@ async function executeTool(toolName, args, projectCwd, executors) {
|
|
|
3438
1382
|
}
|
|
3439
1383
|
}
|
|
3440
1384
|
function parseToolCall(raw) {
|
|
3441
|
-
const result =
|
|
1385
|
+
const result = toolCallSchema.safeParse(raw);
|
|
3442
1386
|
if (!result.success) {
|
|
3443
1387
|
return new ValidationError(
|
|
3444
1388
|
`Invalid tool_call payload: ${result.error.issues.map((i) => i.message).join(", ")}`
|