@avasis-ai/synthcode 1.0.0
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/LICENSE +21 -0
- package/README.md +292 -0
- package/dist/chunk-53ZOIXM4.js +624 -0
- package/dist/chunk-53ZOIXM4.js.map +1 -0
- package/dist/chunk-BWXHO6UJ.js +115 -0
- package/dist/chunk-BWXHO6UJ.js.map +1 -0
- package/dist/chunk-CARUMOML.js +123 -0
- package/dist/chunk-CARUMOML.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/chunk-DGUM43GV.js.map +1 -0
- package/dist/chunk-F34HO4RA.js +487 -0
- package/dist/chunk-F34HO4RA.js.map +1 -0
- package/dist/chunk-FK7S2S7V.js +132 -0
- package/dist/chunk-FK7S2S7V.js.map +1 -0
- package/dist/chunk-MQ7XP6VT.js +174 -0
- package/dist/chunk-MQ7XP6VT.js.map +1 -0
- package/dist/chunk-TLPOO6C3.js +176 -0
- package/dist/chunk-TLPOO6C3.js.map +1 -0
- package/dist/chunk-W6OLZ2OI.js +56 -0
- package/dist/chunk-W6OLZ2OI.js.map +1 -0
- package/dist/cli/index.cjs +151 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +8 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/run.cjs +128 -0
- package/dist/cli/run.cjs.map +1 -0
- package/dist/cli/run.d.cts +1 -0
- package/dist/cli/run.d.ts +1 -0
- package/dist/cli/run.js +126 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/index-D-K6sx8s.d.cts +8 -0
- package/dist/index-D-K6sx8s.d.ts +8 -0
- package/dist/index.cjs +2909 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +274 -0
- package/dist/index.d.ts +274 -0
- package/dist/index.js +1048 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/index.cjs +531 -0
- package/dist/llm/index.cjs.map +1 -0
- package/dist/llm/index.d.cts +70 -0
- package/dist/llm/index.d.ts +70 -0
- package/dist/llm/index.js +24 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/mcp/index.cjs +323 -0
- package/dist/mcp/index.cjs.map +1 -0
- package/dist/mcp/index.d.cts +39 -0
- package/dist/mcp/index.d.ts +39 -0
- package/dist/mcp/index.js +11 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/memory/index.cjs +146 -0
- package/dist/memory/index.cjs.map +1 -0
- package/dist/memory/index.d.cts +51 -0
- package/dist/memory/index.d.ts +51 -0
- package/dist/memory/index.js +10 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/tools/fuzzy-edit.cjs +200 -0
- package/dist/tools/fuzzy-edit.cjs.map +1 -0
- package/dist/tools/fuzzy-edit.d.cts +9 -0
- package/dist/tools/fuzzy-edit.d.ts +9 -0
- package/dist/tools/fuzzy-edit.js +12 -0
- package/dist/tools/fuzzy-edit.js.map +1 -0
- package/dist/tools/index.cjs +1032 -0
- package/dist/tools/index.cjs.map +1 -0
- package/dist/tools/index.d.cts +4 -0
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.js +39 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types-C11cw5ZD.d.cts +177 -0
- package/dist/types-C11cw5ZD.d.ts +177 -0
- package/dist/utils-TF4TBXQJ.js +10 -0
- package/dist/utils-TF4TBXQJ.js.map +1 -0
- package/dist/web-fetch-B42QzYD2.d.cts +85 -0
- package/dist/web-fetch-EDdhxmEf.d.ts +85 -0
- package/package.json +134 -0
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
import {
|
|
2
|
+
globToRegExp
|
|
3
|
+
} from "./chunk-W6OLZ2OI.js";
|
|
4
|
+
import {
|
|
5
|
+
defineTool
|
|
6
|
+
} from "./chunk-FK7S2S7V.js";
|
|
7
|
+
import {
|
|
8
|
+
fuzzyReplace
|
|
9
|
+
} from "./chunk-MQ7XP6VT.js";
|
|
10
|
+
|
|
11
|
+
// src/tools/orchestrator.ts
|
|
12
|
+
var Semaphore = class {
|
|
13
|
+
constructor(max) {
|
|
14
|
+
this.max = max;
|
|
15
|
+
}
|
|
16
|
+
max;
|
|
17
|
+
current = 0;
|
|
18
|
+
queue = [];
|
|
19
|
+
acquire() {
|
|
20
|
+
if (this.current < this.max) {
|
|
21
|
+
this.current++;
|
|
22
|
+
return Promise.resolve();
|
|
23
|
+
}
|
|
24
|
+
return new Promise((resolve) => {
|
|
25
|
+
this.queue.push(resolve);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
release() {
|
|
29
|
+
this.current--;
|
|
30
|
+
const next = this.queue.shift();
|
|
31
|
+
if (next) {
|
|
32
|
+
this.current++;
|
|
33
|
+
next();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
function partitionToolCalls(toolCalls, registry) {
|
|
38
|
+
const batches = [];
|
|
39
|
+
let currentBatch = [];
|
|
40
|
+
let currentBatchIsConcurrent = false;
|
|
41
|
+
for (const call of toolCalls) {
|
|
42
|
+
const tool = registry.get(call.name);
|
|
43
|
+
const isConcurrent = tool ? tool.isReadOnly && tool.isConcurrencySafe : false;
|
|
44
|
+
if (isConcurrent && currentBatchIsConcurrent) {
|
|
45
|
+
currentBatch.push(call);
|
|
46
|
+
} else {
|
|
47
|
+
if (currentBatch.length > 0) {
|
|
48
|
+
batches.push(currentBatch);
|
|
49
|
+
}
|
|
50
|
+
currentBatch = [call];
|
|
51
|
+
currentBatchIsConcurrent = isConcurrent;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (currentBatch.length > 0) {
|
|
55
|
+
batches.push(currentBatch);
|
|
56
|
+
}
|
|
57
|
+
return batches;
|
|
58
|
+
}
|
|
59
|
+
async function executeToolCall(call, registry, context, permissionCheck, abortSignal) {
|
|
60
|
+
if (abortSignal?.aborted) {
|
|
61
|
+
return {
|
|
62
|
+
id: call.id,
|
|
63
|
+
name: call.name,
|
|
64
|
+
output: "Execution aborted",
|
|
65
|
+
isError: true,
|
|
66
|
+
durationMs: 0
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const tool = registry.get(call.name);
|
|
70
|
+
if (!tool) {
|
|
71
|
+
return {
|
|
72
|
+
id: call.id,
|
|
73
|
+
name: call.name,
|
|
74
|
+
output: `Unknown tool: ${call.name}`,
|
|
75
|
+
isError: true,
|
|
76
|
+
durationMs: 0
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const start = performance.now();
|
|
80
|
+
try {
|
|
81
|
+
const parseResult = tool.inputSchema.safeParse(call.input);
|
|
82
|
+
if (!parseResult.success) {
|
|
83
|
+
return {
|
|
84
|
+
id: call.id,
|
|
85
|
+
name: call.name,
|
|
86
|
+
output: `Invalid input for ${call.name}: ${parseResult.error.issues.map((i) => i.message).join(", ")}`,
|
|
87
|
+
isError: true,
|
|
88
|
+
durationMs: Math.round(performance.now() - start)
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (permissionCheck) {
|
|
92
|
+
const allowed = await permissionCheck(call.name, call.input);
|
|
93
|
+
if (!allowed) {
|
|
94
|
+
return {
|
|
95
|
+
id: call.id,
|
|
96
|
+
name: call.name,
|
|
97
|
+
output: `Permission denied for tool: ${call.name}`,
|
|
98
|
+
isError: true,
|
|
99
|
+
durationMs: Math.round(performance.now() - start)
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const output = await tool.execute(parseResult.data, context);
|
|
104
|
+
return {
|
|
105
|
+
id: call.id,
|
|
106
|
+
name: call.name,
|
|
107
|
+
output,
|
|
108
|
+
isError: false,
|
|
109
|
+
durationMs: Math.round(performance.now() - start)
|
|
110
|
+
};
|
|
111
|
+
} catch (err) {
|
|
112
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
113
|
+
return {
|
|
114
|
+
id: call.id,
|
|
115
|
+
name: call.name,
|
|
116
|
+
output: `Error executing ${call.name}: ${message}`,
|
|
117
|
+
isError: true,
|
|
118
|
+
durationMs: Math.round(performance.now() - start)
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function orchestrateTools(tools, toolCalls, context, permissionCheck, abortSignal) {
|
|
123
|
+
const batches = partitionToolCalls(toolCalls, tools);
|
|
124
|
+
const results = [];
|
|
125
|
+
for (const batch of batches) {
|
|
126
|
+
if (abortSignal?.aborted) {
|
|
127
|
+
for (const call of batch) {
|
|
128
|
+
results.push({
|
|
129
|
+
id: call.id,
|
|
130
|
+
name: call.name,
|
|
131
|
+
output: "Execution aborted",
|
|
132
|
+
isError: true,
|
|
133
|
+
durationMs: 0
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (batch.length > 1) {
|
|
139
|
+
const semaphore = new Semaphore(10);
|
|
140
|
+
const promises = batch.map(
|
|
141
|
+
(call) => semaphore.acquire().then(async () => {
|
|
142
|
+
try {
|
|
143
|
+
return await executeToolCall(call, tools, context, permissionCheck, abortSignal);
|
|
144
|
+
} finally {
|
|
145
|
+
semaphore.release();
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
);
|
|
149
|
+
const settled = await Promise.allSettled(promises);
|
|
150
|
+
for (let i = 0; i < settled.length; i++) {
|
|
151
|
+
const s = settled[i];
|
|
152
|
+
if (s.status === "fulfilled") {
|
|
153
|
+
results.push(s.value);
|
|
154
|
+
} else {
|
|
155
|
+
results.push({
|
|
156
|
+
id: batch[i].id,
|
|
157
|
+
name: batch[i].name,
|
|
158
|
+
output: `Unexpected error: ${s.reason instanceof Error ? s.reason.message : String(s.reason)}`,
|
|
159
|
+
isError: true,
|
|
160
|
+
durationMs: 0
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
const result = await executeToolCall(batch[0], tools, context, permissionCheck, abortSignal);
|
|
166
|
+
results.push(result);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return results;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/tools/registry.ts
|
|
173
|
+
var ToolRegistry = class {
|
|
174
|
+
tools = /* @__PURE__ */ new Map();
|
|
175
|
+
constructor(tools) {
|
|
176
|
+
if (tools) {
|
|
177
|
+
for (const tool of tools) {
|
|
178
|
+
this.add(tool);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/** Add a tool to the registry. Duplicate names are ignored (first one wins). */
|
|
183
|
+
add(tool) {
|
|
184
|
+
if (this.tools.has(tool.name)) {
|
|
185
|
+
console.warn(`[ToolRegistry] Duplicate tool "${tool.name}" ignored (first one wins)`);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
this.tools.set(tool.name, tool);
|
|
189
|
+
}
|
|
190
|
+
/** Get a tool by name. */
|
|
191
|
+
get(name) {
|
|
192
|
+
return this.tools.get(name);
|
|
193
|
+
}
|
|
194
|
+
/** Check if a tool exists by name. */
|
|
195
|
+
has(name) {
|
|
196
|
+
return this.tools.has(name);
|
|
197
|
+
}
|
|
198
|
+
/** Find tool by case-insensitive name. Returns undefined if not found. */
|
|
199
|
+
findCaseInsensitive(name) {
|
|
200
|
+
const lower = name.toLowerCase();
|
|
201
|
+
if (this.tools.has(name)) return name;
|
|
202
|
+
for (const key of this.tools.keys()) {
|
|
203
|
+
if (key.toLowerCase() === lower) return key;
|
|
204
|
+
}
|
|
205
|
+
return void 0;
|
|
206
|
+
}
|
|
207
|
+
/** List all registered tool names. */
|
|
208
|
+
listNames() {
|
|
209
|
+
return Array.from(this.tools.keys());
|
|
210
|
+
}
|
|
211
|
+
/** Get all registered tools. */
|
|
212
|
+
getAll() {
|
|
213
|
+
return Array.from(this.tools.values());
|
|
214
|
+
}
|
|
215
|
+
/** Get all tools in API format, sorted by name for cache stability. */
|
|
216
|
+
getAPI() {
|
|
217
|
+
return this.getAll().sort((a, b) => a.name.localeCompare(b.name)).map((t) => t.toAPI());
|
|
218
|
+
}
|
|
219
|
+
/** Number of registered tools. */
|
|
220
|
+
get size() {
|
|
221
|
+
return this.tools.size;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// src/tools/bash.ts
|
|
226
|
+
import { exec } from "child_process";
|
|
227
|
+
import { z } from "zod";
|
|
228
|
+
var MAX_OUTPUT = 50 * 1024;
|
|
229
|
+
var TRUNCATE_HALF = 25 * 1024;
|
|
230
|
+
function truncateOutput(output) {
|
|
231
|
+
if (output.length <= MAX_OUTPUT) return output;
|
|
232
|
+
const notice = `
|
|
233
|
+
|
|
234
|
+
... [output truncated: showing first ${TRUNCATE_HALF} and last ${TRUNCATE_HALF} of ${output.length} characters] ...
|
|
235
|
+
|
|
236
|
+
`;
|
|
237
|
+
return output.slice(0, TRUNCATE_HALF) + notice + output.slice(-TRUNCATE_HALF);
|
|
238
|
+
}
|
|
239
|
+
var BashTool = defineTool({
|
|
240
|
+
name: "bash",
|
|
241
|
+
description: "Execute a shell command. Returns stdout and stderr. Use for running build commands, git operations, package managers, and other CLI tools.",
|
|
242
|
+
inputSchema: z.object({
|
|
243
|
+
command: z.string().describe("The shell command to execute"),
|
|
244
|
+
timeout: z.number().optional().describe("Timeout in milliseconds (default 120000)"),
|
|
245
|
+
workdir: z.string().optional().describe("Working directory for the command")
|
|
246
|
+
}),
|
|
247
|
+
isReadOnly: false,
|
|
248
|
+
isConcurrencySafe: false,
|
|
249
|
+
execute: async ({ command, timeout = 12e4, workdir }, context) => {
|
|
250
|
+
return new Promise((resolve) => {
|
|
251
|
+
let settled = false;
|
|
252
|
+
const done = (result) => {
|
|
253
|
+
if (settled) return;
|
|
254
|
+
settled = true;
|
|
255
|
+
resolve(result);
|
|
256
|
+
};
|
|
257
|
+
if (context.abortSignal?.aborted) {
|
|
258
|
+
done("Command aborted before execution");
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const child = exec(
|
|
262
|
+
command,
|
|
263
|
+
{
|
|
264
|
+
cwd: workdir ?? context.cwd,
|
|
265
|
+
timeout,
|
|
266
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
267
|
+
killSignal: "SIGKILL"
|
|
268
|
+
},
|
|
269
|
+
(error, stdout, stderr) => {
|
|
270
|
+
let output = "";
|
|
271
|
+
if (stdout) output += `STDOUT:
|
|
272
|
+
${stdout}
|
|
273
|
+
|
|
274
|
+
`;
|
|
275
|
+
if (stderr) output += `STDERR:
|
|
276
|
+
${stderr}
|
|
277
|
+
|
|
278
|
+
`;
|
|
279
|
+
output += `Exit code: ${error?.code ?? 0}`;
|
|
280
|
+
if (error && error.killed) {
|
|
281
|
+
output = `Command timed out after ${timeout}ms
|
|
282
|
+
|
|
283
|
+
${output}`;
|
|
284
|
+
}
|
|
285
|
+
done(truncateOutput(output));
|
|
286
|
+
}
|
|
287
|
+
);
|
|
288
|
+
child.on("error", (err) => {
|
|
289
|
+
done(`Failed to execute command: ${err.message}`);
|
|
290
|
+
});
|
|
291
|
+
if (context.abortSignal) {
|
|
292
|
+
context.abortSignal.addEventListener(
|
|
293
|
+
"abort",
|
|
294
|
+
() => {
|
|
295
|
+
child.kill("SIGKILL");
|
|
296
|
+
done("Command aborted");
|
|
297
|
+
},
|
|
298
|
+
{ once: true }
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// src/tools/file-read.ts
|
|
306
|
+
import fs from "fs/promises";
|
|
307
|
+
import path from "path";
|
|
308
|
+
import { z as z2 } from "zod";
|
|
309
|
+
var MAX_LINE_LENGTH = 2e3;
|
|
310
|
+
var FileReadTool = defineTool({
|
|
311
|
+
name: "file_read",
|
|
312
|
+
description: "Read the contents of a file. Returns the file content with line numbers. Use GlobTool to find files first.",
|
|
313
|
+
inputSchema: z2.object({
|
|
314
|
+
path: z2.string().describe("Absolute or relative path to the file"),
|
|
315
|
+
offset: z2.number().optional().describe("Line number to start reading from (1-indexed)"),
|
|
316
|
+
limit: z2.number().optional().describe("Maximum number of lines to read")
|
|
317
|
+
}),
|
|
318
|
+
isReadOnly: true,
|
|
319
|
+
isConcurrencySafe: true,
|
|
320
|
+
execute: async ({ path: filePath, offset, limit }, context) => {
|
|
321
|
+
try {
|
|
322
|
+
const resolved = path.isAbsolute(filePath) ? filePath : path.resolve(context.cwd, filePath);
|
|
323
|
+
const content = await fs.readFile(resolved, "utf-8");
|
|
324
|
+
const lines = content.split("\n");
|
|
325
|
+
const startLine = offset ? Math.max(1, offset) : 1;
|
|
326
|
+
const startIndex = startLine - 1;
|
|
327
|
+
const endIndex = limit != null ? startIndex + limit : lines.length;
|
|
328
|
+
const selected = lines.slice(startIndex, endIndex);
|
|
329
|
+
const formatted = selected.map((line, i) => {
|
|
330
|
+
const lineNum = startLine + i;
|
|
331
|
+
const truncated = line.length > MAX_LINE_LENGTH ? line.slice(0, MAX_LINE_LENGTH) + "..." : line;
|
|
332
|
+
return `${lineNum}: ${truncated}`;
|
|
333
|
+
}).join("\n");
|
|
334
|
+
return formatted;
|
|
335
|
+
} catch (err) {
|
|
336
|
+
if (err.code === "ENOENT") {
|
|
337
|
+
return `Error: File not found: ${filePath}`;
|
|
338
|
+
}
|
|
339
|
+
if (err.code === "EISDIR") {
|
|
340
|
+
return `Error: Path is a directory: ${filePath}`;
|
|
341
|
+
}
|
|
342
|
+
return `Error reading file: ${err instanceof Error ? err.message : String(err)}`;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// src/tools/file-write.ts
|
|
348
|
+
import fs2 from "fs/promises";
|
|
349
|
+
import path2 from "path";
|
|
350
|
+
import { z as z3 } from "zod";
|
|
351
|
+
var FileWriteTool = defineTool({
|
|
352
|
+
name: "file_write",
|
|
353
|
+
description: "Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Parent directories are created automatically.",
|
|
354
|
+
inputSchema: z3.object({
|
|
355
|
+
path: z3.string().describe("Path to the file"),
|
|
356
|
+
content: z3.string().describe("Content to write")
|
|
357
|
+
}),
|
|
358
|
+
isReadOnly: false,
|
|
359
|
+
isConcurrencySafe: false,
|
|
360
|
+
execute: async ({ path: filePath, content }, context) => {
|
|
361
|
+
try {
|
|
362
|
+
const resolved = path2.isAbsolute(filePath) ? filePath : path2.resolve(context.cwd, filePath);
|
|
363
|
+
const dir = path2.dirname(resolved);
|
|
364
|
+
await fs2.mkdir(dir, { recursive: true });
|
|
365
|
+
await fs2.writeFile(resolved, content, "utf-8");
|
|
366
|
+
return `Wrote ${content.length} characters to ${filePath}`;
|
|
367
|
+
} catch (err) {
|
|
368
|
+
return `Error writing file: ${err instanceof Error ? err.message : String(err)}`;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// src/tools/file-edit.ts
|
|
374
|
+
import fs3 from "fs/promises";
|
|
375
|
+
import path3 from "path";
|
|
376
|
+
import { z as z4 } from "zod";
|
|
377
|
+
var FileEditTool = defineTool({
|
|
378
|
+
name: "file_edit",
|
|
379
|
+
description: "Edit an existing file by replacing a string match with new content. Uses 9-strategy fuzzy matching (exact, line-trimmed, block-anchor with Levenshtein, whitespace-normalized, indentation-flexible, escape-normalized, trimmed-boundary, context-aware, multi-occurrence) so the oldString does not need to be a perfect character-for-character match. For multi-line edits, include enough surrounding context to ensure uniqueness.",
|
|
380
|
+
inputSchema: z4.object({
|
|
381
|
+
path: z4.string().describe("Path to the file"),
|
|
382
|
+
oldString: z4.string().describe("The string to find and replace (fuzzy-matched)"),
|
|
383
|
+
newString: z4.string().describe("The replacement string"),
|
|
384
|
+
replaceAll: z4.boolean().optional().describe("Replace all occurrences instead of just the first")
|
|
385
|
+
}),
|
|
386
|
+
isReadOnly: false,
|
|
387
|
+
isConcurrencySafe: false,
|
|
388
|
+
execute: async ({ path: filePath, oldString, newString, replaceAll = false }, context) => {
|
|
389
|
+
try {
|
|
390
|
+
const resolved = path3.isAbsolute(filePath) ? filePath : path3.resolve(context.cwd, filePath);
|
|
391
|
+
const content = await fs3.readFile(resolved, "utf-8");
|
|
392
|
+
let newContent;
|
|
393
|
+
let count = 0;
|
|
394
|
+
if (replaceAll) {
|
|
395
|
+
const occurrences = content.split(oldString).length - 1;
|
|
396
|
+
if (occurrences === 0) {
|
|
397
|
+
try {
|
|
398
|
+
newContent = fuzzyReplace(content, oldString, newString, true);
|
|
399
|
+
count = 1;
|
|
400
|
+
} catch {
|
|
401
|
+
return `Error: oldString not found in ${filePath} (tried 9 fuzzy strategies)`;
|
|
402
|
+
}
|
|
403
|
+
} else {
|
|
404
|
+
newContent = content.split(oldString).join(newString);
|
|
405
|
+
count = occurrences;
|
|
406
|
+
}
|
|
407
|
+
} else {
|
|
408
|
+
try {
|
|
409
|
+
newContent = fuzzyReplace(content, oldString, newString, false);
|
|
410
|
+
count = 1;
|
|
411
|
+
} catch (err) {
|
|
412
|
+
const kind = err.kind;
|
|
413
|
+
if (kind === "ambiguous") {
|
|
414
|
+
return `Error: Multiple matches for oldString in ${filePath}. Provide more surrounding context or set replaceAll to true.`;
|
|
415
|
+
}
|
|
416
|
+
return `Error: oldString not found in ${filePath} (tried 9 fuzzy strategies)`;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
await fs3.writeFile(resolved, newContent, "utf-8");
|
|
420
|
+
return `Replaced ${count} occurrence(s) in ${filePath}`;
|
|
421
|
+
} catch (err) {
|
|
422
|
+
if (err.code === "ENOENT") {
|
|
423
|
+
return `Error: File not found: ${filePath}`;
|
|
424
|
+
}
|
|
425
|
+
return `Error editing file: ${err instanceof Error ? err.message : String(err)}`;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// src/tools/glob.ts
|
|
431
|
+
import fs4 from "fs/promises";
|
|
432
|
+
import path4 from "path";
|
|
433
|
+
import { z as z5 } from "zod";
|
|
434
|
+
var MAX_RESULTS = 200;
|
|
435
|
+
async function walkDir(dir) {
|
|
436
|
+
const { walkDir: walk } = await import("./utils-TF4TBXQJ.js");
|
|
437
|
+
const entries = await walk(dir);
|
|
438
|
+
return entries.map((e) => e.path);
|
|
439
|
+
}
|
|
440
|
+
function matchInclude(filePath, includePattern) {
|
|
441
|
+
if (!includePattern) return true;
|
|
442
|
+
const base = path4.basename(filePath);
|
|
443
|
+
const re = globToRegExp(includePattern);
|
|
444
|
+
return re.test(base);
|
|
445
|
+
}
|
|
446
|
+
var GlobTool = defineTool({
|
|
447
|
+
name: "glob",
|
|
448
|
+
description: "Find files matching a glob pattern. Returns matching file paths sorted by modification time.",
|
|
449
|
+
inputSchema: z5.object({
|
|
450
|
+
pattern: z5.string().describe('Glob pattern (e.g., "**/*.ts", "src/**/*.tsx")'),
|
|
451
|
+
path: z5.string().optional().describe("Directory to search in (default: current directory)"),
|
|
452
|
+
include: z5.string().optional().describe('File extension or pattern to include (e.g., "*.ts")')
|
|
453
|
+
}),
|
|
454
|
+
isReadOnly: true,
|
|
455
|
+
isConcurrencySafe: true,
|
|
456
|
+
execute: async ({ pattern, path: searchPath, include }, context) => {
|
|
457
|
+
try {
|
|
458
|
+
const searchDir = searchPath ? path4.isAbsolute(searchPath) ? searchPath : path4.resolve(context.cwd, searchPath) : context.cwd;
|
|
459
|
+
const allFiles = await walkDir(searchDir);
|
|
460
|
+
const re = globToRegExp(pattern);
|
|
461
|
+
const matched = [];
|
|
462
|
+
for (const filePath of allFiles) {
|
|
463
|
+
const relative = path4.relative(searchDir, filePath);
|
|
464
|
+
if (re.test(relative) && matchInclude(relative, include)) {
|
|
465
|
+
try {
|
|
466
|
+
const stat = await fs4.stat(filePath);
|
|
467
|
+
matched.push({ filePath: relative, mtimeMs: stat.mtimeMs });
|
|
468
|
+
} catch {
|
|
469
|
+
matched.push({ filePath: relative, mtimeMs: 0 });
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
matched.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
474
|
+
const limited = matched.slice(0, MAX_RESULTS);
|
|
475
|
+
return limited.map((m) => m.filePath).join("\n");
|
|
476
|
+
} catch (err) {
|
|
477
|
+
return `Error searching files: ${err instanceof Error ? err.message : String(err)}`;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
// src/tools/grep.ts
|
|
483
|
+
import fs5 from "fs/promises";
|
|
484
|
+
import path5 from "path";
|
|
485
|
+
import { z as z6 } from "zod";
|
|
486
|
+
var MAX_MATCHES = 200;
|
|
487
|
+
async function walkDir2(dir) {
|
|
488
|
+
const { walkDir: walk } = await import("./utils-TF4TBXQJ.js");
|
|
489
|
+
const entries = await walk(dir);
|
|
490
|
+
return entries.map((e) => e.path);
|
|
491
|
+
}
|
|
492
|
+
var GrepTool = defineTool({
|
|
493
|
+
name: "grep",
|
|
494
|
+
description: "Search file contents using a regular expression. Returns matching file paths with line numbers.",
|
|
495
|
+
inputSchema: z6.object({
|
|
496
|
+
pattern: z6.string().describe("Regular expression to search for"),
|
|
497
|
+
path: z6.string().optional().describe("Directory to search in"),
|
|
498
|
+
include: z6.string().optional().describe('File pattern to include (e.g., "*.ts")')
|
|
499
|
+
}),
|
|
500
|
+
isReadOnly: true,
|
|
501
|
+
isConcurrencySafe: true,
|
|
502
|
+
execute: async ({ pattern, path: searchPath, include }, context) => {
|
|
503
|
+
try {
|
|
504
|
+
let regex;
|
|
505
|
+
try {
|
|
506
|
+
regex = new RegExp(pattern);
|
|
507
|
+
} catch {
|
|
508
|
+
return `Error: Invalid regular expression: ${pattern}`;
|
|
509
|
+
}
|
|
510
|
+
const searchDir = searchPath ? path5.isAbsolute(searchPath) ? searchPath : path5.resolve(context.cwd, searchPath) : context.cwd;
|
|
511
|
+
const includeRe = include ? globToRegExp(include) : null;
|
|
512
|
+
const allFiles = await walkDir2(searchDir);
|
|
513
|
+
const matches = [];
|
|
514
|
+
for (const filePath of allFiles) {
|
|
515
|
+
if (matches.length >= MAX_MATCHES) break;
|
|
516
|
+
if (includeRe) {
|
|
517
|
+
const base = path5.basename(filePath);
|
|
518
|
+
if (!includeRe.test(base)) continue;
|
|
519
|
+
}
|
|
520
|
+
let content;
|
|
521
|
+
try {
|
|
522
|
+
content = await fs5.readFile(filePath, "utf-8");
|
|
523
|
+
} catch {
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
const lines = content.split("\n");
|
|
527
|
+
const relative = path5.relative(searchDir, filePath);
|
|
528
|
+
for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
|
|
529
|
+
if (matches.length >= MAX_MATCHES) break;
|
|
530
|
+
const line = lines[lineIdx];
|
|
531
|
+
if (regex.test(line)) {
|
|
532
|
+
matches.push(`${relative}:${lineIdx + 1}: ${line}`);
|
|
533
|
+
regex.lastIndex = 0;
|
|
534
|
+
} else {
|
|
535
|
+
regex.lastIndex = 0;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
if (matches.length === 0) {
|
|
540
|
+
return "No matches found.";
|
|
541
|
+
}
|
|
542
|
+
return matches.join("\n");
|
|
543
|
+
} catch (err) {
|
|
544
|
+
return `Error searching files: ${err instanceof Error ? err.message : String(err)}`;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
// src/tools/web-fetch.ts
|
|
550
|
+
import { z as z7 } from "zod";
|
|
551
|
+
var MAX_RESPONSE_SIZE = 100 * 1024;
|
|
552
|
+
var WebFetchTool = defineTool({
|
|
553
|
+
name: "web_fetch",
|
|
554
|
+
description: "Fetch content from a URL. Returns the response body as text. Supports HTTP and HTTPS.",
|
|
555
|
+
inputSchema: z7.object({
|
|
556
|
+
url: z7.string().url().describe("The URL to fetch"),
|
|
557
|
+
method: z7.enum(["GET", "POST", "PUT", "DELETE"]).optional().describe("HTTP method (default: GET)"),
|
|
558
|
+
headers: z7.record(z7.string()).optional().describe("Request headers"),
|
|
559
|
+
body: z7.string().optional().describe("Request body (for POST/PUT)")
|
|
560
|
+
}),
|
|
561
|
+
isReadOnly: true,
|
|
562
|
+
isConcurrencySafe: true,
|
|
563
|
+
execute: async ({ url, method = "GET", headers, body }, context) => {
|
|
564
|
+
try {
|
|
565
|
+
if (context.abortSignal?.aborted) {
|
|
566
|
+
return "Request aborted before execution";
|
|
567
|
+
}
|
|
568
|
+
const timeoutSignal = AbortSignal.timeout(3e4);
|
|
569
|
+
let signal;
|
|
570
|
+
if (context.abortSignal) {
|
|
571
|
+
signal = AbortSignal.any([context.abortSignal, timeoutSignal]);
|
|
572
|
+
} else {
|
|
573
|
+
signal = timeoutSignal;
|
|
574
|
+
}
|
|
575
|
+
const fetchOptions = {
|
|
576
|
+
method,
|
|
577
|
+
headers,
|
|
578
|
+
signal,
|
|
579
|
+
redirect: "follow"
|
|
580
|
+
};
|
|
581
|
+
if (body && method !== "GET") {
|
|
582
|
+
fetchOptions.body = body;
|
|
583
|
+
}
|
|
584
|
+
const response = await fetch(url, fetchOptions);
|
|
585
|
+
const contentType = response.headers.get("content-type") ?? "unknown";
|
|
586
|
+
const status = response.status;
|
|
587
|
+
const text = await response.text();
|
|
588
|
+
let result = `Status: ${status}
|
|
589
|
+
Content-Type: ${contentType}
|
|
590
|
+
|
|
591
|
+
`;
|
|
592
|
+
if (text.length > MAX_RESPONSE_SIZE) {
|
|
593
|
+
const half = Math.floor(MAX_RESPONSE_SIZE / 2);
|
|
594
|
+
const notice = `
|
|
595
|
+
|
|
596
|
+
... [response truncated: showing first ${half} and last ${half} of ${text.length} characters] ...
|
|
597
|
+
|
|
598
|
+
`;
|
|
599
|
+
result += text.slice(0, half) + notice + text.slice(-half);
|
|
600
|
+
} else {
|
|
601
|
+
result += text;
|
|
602
|
+
}
|
|
603
|
+
return result;
|
|
604
|
+
} catch (err) {
|
|
605
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
606
|
+
return "Request aborted";
|
|
607
|
+
}
|
|
608
|
+
return `Error fetching URL: ${err instanceof Error ? err.message : String(err)}`;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
export {
|
|
614
|
+
orchestrateTools,
|
|
615
|
+
ToolRegistry,
|
|
616
|
+
BashTool,
|
|
617
|
+
FileReadTool,
|
|
618
|
+
FileWriteTool,
|
|
619
|
+
FileEditTool,
|
|
620
|
+
GlobTool,
|
|
621
|
+
GrepTool,
|
|
622
|
+
WebFetchTool
|
|
623
|
+
};
|
|
624
|
+
//# sourceMappingURL=chunk-53ZOIXM4.js.map
|