bashkit 0.1.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/AGENTS.md +442 -0
- package/LICENSE +21 -0
- package/README.md +713 -0
- package/dist/cli/init.d.ts +2 -0
- package/dist/cli/init.js +179 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +1805 -0
- package/dist/middleware/anthropic-cache.d.ts +17 -0
- package/dist/middleware/index.d.ts +1 -0
- package/dist/sandbox/e2b.d.ts +9 -0
- package/dist/sandbox/index.d.ts +4 -0
- package/dist/sandbox/interface.d.ts +21 -0
- package/dist/sandbox/local.d.ts +5 -0
- package/dist/sandbox/vercel.d.ts +13 -0
- package/dist/setup/index.d.ts +2 -0
- package/dist/setup/setup-environment.d.ts +36 -0
- package/dist/setup/types.d.ts +47 -0
- package/dist/skills/discovery.d.ts +9 -0
- package/dist/skills/fetch.d.ts +56 -0
- package/dist/skills/index.d.ts +6 -0
- package/dist/skills/loader.d.ts +11 -0
- package/dist/skills/types.d.ts +29 -0
- package/dist/skills/xml.d.ts +26 -0
- package/dist/tools/bash.d.ts +18 -0
- package/dist/tools/edit.d.ts +16 -0
- package/dist/tools/exit-plan-mode.d.ts +11 -0
- package/dist/tools/glob.d.ts +14 -0
- package/dist/tools/grep.d.ts +42 -0
- package/dist/tools/index.d.ts +45 -0
- package/dist/tools/read.d.ts +25 -0
- package/dist/tools/task.d.ts +50 -0
- package/dist/tools/todo-write.d.ts +28 -0
- package/dist/tools/web-fetch.d.ts +20 -0
- package/dist/tools/web-search.d.ts +24 -0
- package/dist/tools/write.d.ts +14 -0
- package/dist/types.d.ts +32 -0
- package/dist/utils/compact-conversation.d.ts +85 -0
- package/dist/utils/context-status.d.ts +71 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/prune-messages.d.ts +32 -0
- package/package.json +84 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1805 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
|
+
|
|
20
|
+
// src/middleware/anthropic-cache.ts
|
|
21
|
+
function ensureCacheMarker(message) {
|
|
22
|
+
if (!message)
|
|
23
|
+
return;
|
|
24
|
+
if (!("content" in message))
|
|
25
|
+
return;
|
|
26
|
+
const content = message.content;
|
|
27
|
+
if (!content || !Array.isArray(content))
|
|
28
|
+
return;
|
|
29
|
+
const lastPart = content.at(-1);
|
|
30
|
+
if (!lastPart || typeof lastPart === "string")
|
|
31
|
+
return;
|
|
32
|
+
lastPart.providerOptions = {
|
|
33
|
+
...lastPart.providerOptions,
|
|
34
|
+
anthropic: {
|
|
35
|
+
cacheControl: { type: "ephemeral" }
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
var anthropicPromptCacheMiddleware = {
|
|
40
|
+
transformParams: async ({
|
|
41
|
+
params
|
|
42
|
+
}) => {
|
|
43
|
+
const messages = params.prompt;
|
|
44
|
+
if (!messages || messages.length === 0) {
|
|
45
|
+
return params;
|
|
46
|
+
}
|
|
47
|
+
ensureCacheMarker(messages.at(-1));
|
|
48
|
+
ensureCacheMarker(messages.slice(0, -1).findLast((m) => m.role !== "assistant"));
|
|
49
|
+
return {
|
|
50
|
+
...params,
|
|
51
|
+
prompt: messages
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
// src/sandbox/e2b.ts
|
|
56
|
+
import { Sandbox as E2BSandboxSDK } from "@e2b/code-interpreter";
|
|
57
|
+
function createE2BSandbox(config = {}) {
|
|
58
|
+
let sandbox = null;
|
|
59
|
+
const workingDirectory = config.cwd || "/home/user";
|
|
60
|
+
const timeout = config.timeout ?? 300000;
|
|
61
|
+
const ensureSandbox = async () => {
|
|
62
|
+
if (sandbox)
|
|
63
|
+
return sandbox;
|
|
64
|
+
sandbox = await E2BSandboxSDK.create({
|
|
65
|
+
apiKey: config.apiKey,
|
|
66
|
+
timeoutMs: timeout,
|
|
67
|
+
metadata: config.metadata
|
|
68
|
+
});
|
|
69
|
+
return sandbox;
|
|
70
|
+
};
|
|
71
|
+
const exec = async (command, options) => {
|
|
72
|
+
const sbx = await ensureSandbox();
|
|
73
|
+
const startTime = performance.now();
|
|
74
|
+
try {
|
|
75
|
+
const result = await sbx.commands.run(command, {
|
|
76
|
+
cwd: options?.cwd || workingDirectory,
|
|
77
|
+
timeoutMs: options?.timeout
|
|
78
|
+
});
|
|
79
|
+
const durationMs = Math.round(performance.now() - startTime);
|
|
80
|
+
return {
|
|
81
|
+
stdout: result.stdout,
|
|
82
|
+
stderr: result.stderr,
|
|
83
|
+
exitCode: result.exitCode,
|
|
84
|
+
durationMs,
|
|
85
|
+
interrupted: false
|
|
86
|
+
};
|
|
87
|
+
} catch (error) {
|
|
88
|
+
const durationMs = Math.round(performance.now() - startTime);
|
|
89
|
+
if (error instanceof Error && error.message.toLowerCase().includes("timeout")) {
|
|
90
|
+
return {
|
|
91
|
+
stdout: "",
|
|
92
|
+
stderr: "Command timed out",
|
|
93
|
+
exitCode: 124,
|
|
94
|
+
durationMs,
|
|
95
|
+
interrupted: true
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
if (error instanceof Error) {
|
|
99
|
+
const exitMatch = error.message.match(/exit status (\d+)/i);
|
|
100
|
+
const exitCode = exitMatch ? parseInt(exitMatch[1], 10) : 1;
|
|
101
|
+
return {
|
|
102
|
+
stdout: "",
|
|
103
|
+
stderr: error.message,
|
|
104
|
+
exitCode,
|
|
105
|
+
durationMs,
|
|
106
|
+
interrupted: false
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
return {
|
|
113
|
+
exec,
|
|
114
|
+
async readFile(path) {
|
|
115
|
+
const result = await exec(`cat "${path}"`);
|
|
116
|
+
if (result.exitCode !== 0) {
|
|
117
|
+
throw new Error(`Failed to read file: ${result.stderr}`);
|
|
118
|
+
}
|
|
119
|
+
return result.stdout;
|
|
120
|
+
},
|
|
121
|
+
async writeFile(path, content) {
|
|
122
|
+
const sbx = await ensureSandbox();
|
|
123
|
+
await sbx.files.write(path, content);
|
|
124
|
+
},
|
|
125
|
+
async readDir(path) {
|
|
126
|
+
const result = await exec(`ls -1 "${path}"`);
|
|
127
|
+
if (result.exitCode !== 0) {
|
|
128
|
+
throw new Error(`Failed to read directory: ${result.stderr}`);
|
|
129
|
+
}
|
|
130
|
+
return result.stdout.split(`
|
|
131
|
+
`).filter(Boolean);
|
|
132
|
+
},
|
|
133
|
+
async fileExists(path) {
|
|
134
|
+
const result = await exec(`test -e "${path}"`);
|
|
135
|
+
return result.exitCode === 0;
|
|
136
|
+
},
|
|
137
|
+
async isDirectory(path) {
|
|
138
|
+
const result = await exec(`test -d "${path}"`);
|
|
139
|
+
return result.exitCode === 0;
|
|
140
|
+
},
|
|
141
|
+
async destroy() {
|
|
142
|
+
if (sandbox) {
|
|
143
|
+
await sandbox.kill();
|
|
144
|
+
sandbox = null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
// src/sandbox/local.ts
|
|
150
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
151
|
+
function createLocalSandbox(config = {}) {
|
|
152
|
+
const workingDirectory = config.cwd || "/tmp";
|
|
153
|
+
if (!existsSync(workingDirectory)) {
|
|
154
|
+
mkdirSync(workingDirectory, { recursive: true });
|
|
155
|
+
}
|
|
156
|
+
const exec = async (command, options) => {
|
|
157
|
+
const startTime = performance.now();
|
|
158
|
+
let interrupted = false;
|
|
159
|
+
const cwd = options?.cwd || workingDirectory;
|
|
160
|
+
if (!existsSync(cwd)) {
|
|
161
|
+
mkdirSync(cwd, { recursive: true });
|
|
162
|
+
}
|
|
163
|
+
const proc = Bun.spawn(["sh", "-c", command], {
|
|
164
|
+
cwd,
|
|
165
|
+
stdout: "pipe",
|
|
166
|
+
stderr: "pipe"
|
|
167
|
+
});
|
|
168
|
+
let timeoutId;
|
|
169
|
+
if (options?.timeout) {
|
|
170
|
+
timeoutId = setTimeout(() => {
|
|
171
|
+
interrupted = true;
|
|
172
|
+
proc.kill();
|
|
173
|
+
}, options.timeout);
|
|
174
|
+
}
|
|
175
|
+
const stdout = await new Response(proc.stdout).text();
|
|
176
|
+
const stderr = await new Response(proc.stderr).text();
|
|
177
|
+
const exitCode = await proc.exited;
|
|
178
|
+
if (timeoutId) {
|
|
179
|
+
clearTimeout(timeoutId);
|
|
180
|
+
}
|
|
181
|
+
const durationMs = Math.round(performance.now() - startTime);
|
|
182
|
+
return {
|
|
183
|
+
stdout,
|
|
184
|
+
stderr,
|
|
185
|
+
exitCode,
|
|
186
|
+
durationMs,
|
|
187
|
+
interrupted
|
|
188
|
+
};
|
|
189
|
+
};
|
|
190
|
+
return {
|
|
191
|
+
exec,
|
|
192
|
+
async readFile(path) {
|
|
193
|
+
const fullPath = path.startsWith("/") ? path : `${workingDirectory}/${path}`;
|
|
194
|
+
const file = Bun.file(fullPath);
|
|
195
|
+
return await file.text();
|
|
196
|
+
},
|
|
197
|
+
async writeFile(path, content) {
|
|
198
|
+
const fullPath = path.startsWith("/") ? path : `${workingDirectory}/${path}`;
|
|
199
|
+
await Bun.write(fullPath, content);
|
|
200
|
+
},
|
|
201
|
+
async readDir(path) {
|
|
202
|
+
const fullPath = path.startsWith("/") ? path : `${workingDirectory}/${path}`;
|
|
203
|
+
const fs = await import("fs/promises");
|
|
204
|
+
return await fs.readdir(fullPath);
|
|
205
|
+
},
|
|
206
|
+
async fileExists(path) {
|
|
207
|
+
const fullPath = path.startsWith("/") ? path : `${workingDirectory}/${path}`;
|
|
208
|
+
const fs = await import("fs/promises");
|
|
209
|
+
try {
|
|
210
|
+
await fs.stat(fullPath);
|
|
211
|
+
return true;
|
|
212
|
+
} catch {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
async isDirectory(path) {
|
|
217
|
+
const fullPath = path.startsWith("/") ? path : `${workingDirectory}/${path}`;
|
|
218
|
+
const fs = await import("fs/promises");
|
|
219
|
+
try {
|
|
220
|
+
const stat = await fs.stat(fullPath);
|
|
221
|
+
return stat.isDirectory();
|
|
222
|
+
} catch {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
async destroy() {}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
// src/sandbox/vercel.ts
|
|
230
|
+
import { Sandbox as VercelSandboxSDK } from "@vercel/sandbox";
|
|
231
|
+
function createVercelSandbox(config = {}) {
|
|
232
|
+
let sandbox = null;
|
|
233
|
+
const workingDirectory = config.cwd || "/vercel/sandbox";
|
|
234
|
+
const resolvedConfig = {
|
|
235
|
+
runtime: config.runtime ?? "node22",
|
|
236
|
+
resources: config.resources ?? { vcpus: 2 },
|
|
237
|
+
timeout: config.timeout ?? 300000
|
|
238
|
+
};
|
|
239
|
+
const ensureSandbox = async () => {
|
|
240
|
+
if (sandbox)
|
|
241
|
+
return sandbox;
|
|
242
|
+
const createOptions = {
|
|
243
|
+
runtime: resolvedConfig.runtime,
|
|
244
|
+
resources: resolvedConfig.resources,
|
|
245
|
+
timeout: resolvedConfig.timeout
|
|
246
|
+
};
|
|
247
|
+
if (config.teamId && config.token) {
|
|
248
|
+
Object.assign(createOptions, {
|
|
249
|
+
teamId: config.teamId,
|
|
250
|
+
token: config.token
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
sandbox = await VercelSandboxSDK.create(createOptions);
|
|
254
|
+
return sandbox;
|
|
255
|
+
};
|
|
256
|
+
const exec = async (command, options) => {
|
|
257
|
+
const sbx = await ensureSandbox();
|
|
258
|
+
const startTime = performance.now();
|
|
259
|
+
let interrupted = false;
|
|
260
|
+
const abortController = new AbortController;
|
|
261
|
+
let timeoutId;
|
|
262
|
+
if (options?.timeout) {
|
|
263
|
+
timeoutId = setTimeout(() => {
|
|
264
|
+
interrupted = true;
|
|
265
|
+
abortController.abort();
|
|
266
|
+
}, options.timeout);
|
|
267
|
+
}
|
|
268
|
+
try {
|
|
269
|
+
const result = await sbx.runCommand({
|
|
270
|
+
cmd: "bash",
|
|
271
|
+
args: ["-c", command],
|
|
272
|
+
cwd: options?.cwd || workingDirectory,
|
|
273
|
+
signal: abortController.signal
|
|
274
|
+
});
|
|
275
|
+
if (timeoutId) {
|
|
276
|
+
clearTimeout(timeoutId);
|
|
277
|
+
}
|
|
278
|
+
const stdout = await result.stdout();
|
|
279
|
+
const stderr = await result.stderr();
|
|
280
|
+
const durationMs = Math.round(performance.now() - startTime);
|
|
281
|
+
return {
|
|
282
|
+
stdout,
|
|
283
|
+
stderr,
|
|
284
|
+
exitCode: result.exitCode,
|
|
285
|
+
durationMs,
|
|
286
|
+
interrupted
|
|
287
|
+
};
|
|
288
|
+
} catch (error) {
|
|
289
|
+
if (timeoutId) {
|
|
290
|
+
clearTimeout(timeoutId);
|
|
291
|
+
}
|
|
292
|
+
const durationMs = Math.round(performance.now() - startTime);
|
|
293
|
+
if (interrupted) {
|
|
294
|
+
return {
|
|
295
|
+
stdout: "",
|
|
296
|
+
stderr: "Command timed out",
|
|
297
|
+
exitCode: 124,
|
|
298
|
+
durationMs,
|
|
299
|
+
interrupted: true
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
throw error;
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
return {
|
|
306
|
+
exec,
|
|
307
|
+
async readFile(path) {
|
|
308
|
+
const sbx = await ensureSandbox();
|
|
309
|
+
const stream = await sbx.readFile({ path });
|
|
310
|
+
if (!stream) {
|
|
311
|
+
throw new Error(`File not found: ${path}`);
|
|
312
|
+
}
|
|
313
|
+
const chunks = [];
|
|
314
|
+
for await (const chunk of stream) {
|
|
315
|
+
chunks.push(Buffer.from(chunk));
|
|
316
|
+
}
|
|
317
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
318
|
+
},
|
|
319
|
+
async writeFile(path, content) {
|
|
320
|
+
const sbx = await ensureSandbox();
|
|
321
|
+
await sbx.writeFiles([
|
|
322
|
+
{
|
|
323
|
+
path,
|
|
324
|
+
content: Buffer.from(content, "utf-8")
|
|
325
|
+
}
|
|
326
|
+
]);
|
|
327
|
+
},
|
|
328
|
+
async readDir(path) {
|
|
329
|
+
const result = await exec(`ls -1 ${path}`);
|
|
330
|
+
if (result.exitCode !== 0) {
|
|
331
|
+
throw new Error(`Failed to read directory: ${result.stderr}`);
|
|
332
|
+
}
|
|
333
|
+
return result.stdout.split(`
|
|
334
|
+
`).filter(Boolean);
|
|
335
|
+
},
|
|
336
|
+
async fileExists(path) {
|
|
337
|
+
const result = await exec(`test -e ${path}`);
|
|
338
|
+
return result.exitCode === 0;
|
|
339
|
+
},
|
|
340
|
+
async isDirectory(path) {
|
|
341
|
+
const result = await exec(`test -d ${path}`);
|
|
342
|
+
return result.exitCode === 0;
|
|
343
|
+
},
|
|
344
|
+
async destroy() {
|
|
345
|
+
if (sandbox) {
|
|
346
|
+
await sandbox.stop();
|
|
347
|
+
sandbox = null;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
// src/types.ts
|
|
353
|
+
var DEFAULT_CONFIG = {
|
|
354
|
+
defaultTimeout: 120000,
|
|
355
|
+
workingDirectory: "/tmp",
|
|
356
|
+
tools: {
|
|
357
|
+
Bash: { maxOutputLength: 30000 }
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
// src/tools/bash.ts
|
|
362
|
+
import { tool, zodSchema } from "ai";
|
|
363
|
+
import { z } from "zod";
|
|
364
|
+
var bashInputSchema = z.object({
|
|
365
|
+
command: z.string().describe("The command to execute"),
|
|
366
|
+
timeout: z.number().optional().describe("Optional timeout in milliseconds (max 600000)"),
|
|
367
|
+
description: z.string().optional().describe("Clear, concise description of what this command does in 5-10 words"),
|
|
368
|
+
run_in_background: z.boolean().optional().describe("Set to true to run this command in the background")
|
|
369
|
+
});
|
|
370
|
+
var BASH_DESCRIPTION = `Executes bash commands in a persistent shell session with optional timeout and background execution.
|
|
371
|
+
|
|
372
|
+
**Important guidelines:**
|
|
373
|
+
- Always quote file paths containing spaces with double quotes (e.g., cd "/path/with spaces")
|
|
374
|
+
- Avoid using search commands like \`find\` and \`grep\` - use the Glob and Grep tools instead
|
|
375
|
+
- Avoid using \`cat\`, \`head\`, \`tail\` - use the Read tool instead
|
|
376
|
+
- When issuing multiple commands, use \`;\` or \`&&\` to separate them (not newlines)
|
|
377
|
+
- If output exceeds 30000 characters, it will be truncated
|
|
378
|
+
- Default timeout is 2 minutes; maximum is 10 minutes`;
|
|
379
|
+
function createBashTool(sandbox, config) {
|
|
380
|
+
const maxOutputLength = config?.maxOutputLength ?? 30000;
|
|
381
|
+
const defaultTimeout = config?.timeout ?? 120000;
|
|
382
|
+
return tool({
|
|
383
|
+
description: BASH_DESCRIPTION,
|
|
384
|
+
inputSchema: zodSchema(bashInputSchema),
|
|
385
|
+
execute: async ({
|
|
386
|
+
command,
|
|
387
|
+
timeout,
|
|
388
|
+
description: _description,
|
|
389
|
+
run_in_background: _run_in_background
|
|
390
|
+
}) => {
|
|
391
|
+
if (config?.blockedCommands) {
|
|
392
|
+
for (const blocked of config.blockedCommands) {
|
|
393
|
+
if (command.includes(blocked)) {
|
|
394
|
+
return {
|
|
395
|
+
error: `Command blocked: contains '${blocked}'`
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
try {
|
|
401
|
+
const effectiveTimeout = Math.min(timeout ?? defaultTimeout, 600000);
|
|
402
|
+
const result = await sandbox.exec(command, {
|
|
403
|
+
timeout: effectiveTimeout
|
|
404
|
+
});
|
|
405
|
+
let stdout = result.stdout;
|
|
406
|
+
let stderr = result.stderr;
|
|
407
|
+
if (stdout.length > maxOutputLength) {
|
|
408
|
+
stdout = stdout.slice(0, maxOutputLength) + `
|
|
409
|
+
[output truncated, ${stdout.length - maxOutputLength} chars omitted]`;
|
|
410
|
+
}
|
|
411
|
+
if (stderr.length > maxOutputLength) {
|
|
412
|
+
stderr = stderr.slice(0, maxOutputLength) + `
|
|
413
|
+
[output truncated, ${stderr.length - maxOutputLength} chars omitted]`;
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
stdout,
|
|
417
|
+
stderr,
|
|
418
|
+
exit_code: result.exitCode,
|
|
419
|
+
interrupted: result.interrupted,
|
|
420
|
+
duration_ms: result.durationMs
|
|
421
|
+
};
|
|
422
|
+
} catch (error) {
|
|
423
|
+
return {
|
|
424
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/tools/edit.ts
|
|
432
|
+
import { tool as tool2, zodSchema as zodSchema2 } from "ai";
|
|
433
|
+
import { z as z2 } from "zod";
|
|
434
|
+
var editInputSchema = z2.object({
|
|
435
|
+
file_path: z2.string().describe("The absolute path to the file to modify"),
|
|
436
|
+
old_string: z2.string().describe("The text to replace"),
|
|
437
|
+
new_string: z2.string().describe("The text to replace it with (must be different from old_string)"),
|
|
438
|
+
replace_all: z2.boolean().optional().describe("Replace all occurrences of old_string (default false)")
|
|
439
|
+
});
|
|
440
|
+
function createEditTool(sandbox, config) {
|
|
441
|
+
return tool2({
|
|
442
|
+
description: "Performs exact string replacements in files.",
|
|
443
|
+
inputSchema: zodSchema2(editInputSchema),
|
|
444
|
+
execute: async ({
|
|
445
|
+
file_path,
|
|
446
|
+
old_string,
|
|
447
|
+
new_string,
|
|
448
|
+
replace_all = false
|
|
449
|
+
}) => {
|
|
450
|
+
if (old_string === new_string) {
|
|
451
|
+
return { error: "old_string and new_string must be different" };
|
|
452
|
+
}
|
|
453
|
+
if (config?.allowedPaths) {
|
|
454
|
+
const isAllowed = config.allowedPaths.some((allowed) => file_path.startsWith(allowed));
|
|
455
|
+
if (!isAllowed) {
|
|
456
|
+
return { error: `Path not allowed: ${file_path}` };
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
try {
|
|
460
|
+
const exists = await sandbox.fileExists(file_path);
|
|
461
|
+
if (!exists) {
|
|
462
|
+
return { error: `File not found: ${file_path}` };
|
|
463
|
+
}
|
|
464
|
+
const content = await sandbox.readFile(file_path);
|
|
465
|
+
const occurrences = content.split(old_string).length - 1;
|
|
466
|
+
if (occurrences === 0) {
|
|
467
|
+
return { error: `String not found in file: "${old_string}"` };
|
|
468
|
+
}
|
|
469
|
+
if (!replace_all && occurrences > 1) {
|
|
470
|
+
return {
|
|
471
|
+
error: `String appears ${occurrences} times in file. Use replace_all=true to replace all, or provide a more unique string.`
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
let newContent;
|
|
475
|
+
let replacements;
|
|
476
|
+
if (replace_all) {
|
|
477
|
+
newContent = content.split(old_string).join(new_string);
|
|
478
|
+
replacements = occurrences;
|
|
479
|
+
} else {
|
|
480
|
+
newContent = content.replace(old_string, new_string);
|
|
481
|
+
replacements = 1;
|
|
482
|
+
}
|
|
483
|
+
await sandbox.writeFile(file_path, newContent);
|
|
484
|
+
return {
|
|
485
|
+
message: `Successfully edited ${file_path}`,
|
|
486
|
+
file_path,
|
|
487
|
+
replacements
|
|
488
|
+
};
|
|
489
|
+
} catch (error) {
|
|
490
|
+
return {
|
|
491
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// src/tools/glob.ts
|
|
499
|
+
import { tool as tool3, zodSchema as zodSchema3 } from "ai";
|
|
500
|
+
import { z as z3 } from "zod";
|
|
501
|
+
var globInputSchema = z3.object({
|
|
502
|
+
pattern: z3.string().describe('Glob pattern to match files (e.g., "**/*.ts", "src/**/*.js", "*.md")'),
|
|
503
|
+
path: z3.string().optional().describe("Directory to search in (defaults to working directory)")
|
|
504
|
+
});
|
|
505
|
+
function createGlobTool(sandbox, config) {
|
|
506
|
+
return tool3({
|
|
507
|
+
description: "Search for files matching a glob pattern. Returns file paths sorted by modification time. Use this instead of `find` command.",
|
|
508
|
+
inputSchema: zodSchema3(globInputSchema),
|
|
509
|
+
execute: async ({
|
|
510
|
+
pattern,
|
|
511
|
+
path
|
|
512
|
+
}) => {
|
|
513
|
+
const searchPath = path || ".";
|
|
514
|
+
if (config?.allowedPaths) {
|
|
515
|
+
const isAllowed = config.allowedPaths.some((allowed) => searchPath.startsWith(allowed));
|
|
516
|
+
if (!isAllowed) {
|
|
517
|
+
return { error: `Path not allowed: ${searchPath}` };
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
try {
|
|
521
|
+
const result = await sandbox.exec(`find ${searchPath} -type f -name "${pattern}" 2>/dev/null | head -1000`, { timeout: config?.timeout });
|
|
522
|
+
if (result.exitCode !== 0 && result.stderr) {
|
|
523
|
+
return { error: result.stderr };
|
|
524
|
+
}
|
|
525
|
+
const matches = result.stdout.split(`
|
|
526
|
+
`).filter(Boolean).map((p) => p.trim());
|
|
527
|
+
return {
|
|
528
|
+
matches,
|
|
529
|
+
count: matches.length,
|
|
530
|
+
search_path: searchPath
|
|
531
|
+
};
|
|
532
|
+
} catch (error) {
|
|
533
|
+
return {
|
|
534
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// src/tools/grep.ts
|
|
542
|
+
import { tool as tool4, zodSchema as zodSchema4 } from "ai";
|
|
543
|
+
import { z as z4 } from "zod";
|
|
544
|
+
var grepInputSchema = z4.object({
|
|
545
|
+
pattern: z4.string().describe("The regular expression pattern to search for"),
|
|
546
|
+
path: z4.string().optional().describe("File or directory to search in (defaults to cwd)"),
|
|
547
|
+
glob: z4.string().optional().describe('Glob pattern to filter files (e.g. "*.js")'),
|
|
548
|
+
type: z4.string().optional().describe('File type to search (e.g. "js", "py", "rust")'),
|
|
549
|
+
output_mode: z4.enum(["content", "files_with_matches", "count"]).optional().describe('Output mode: "content", "files_with_matches", or "count"'),
|
|
550
|
+
"-i": z4.boolean().optional().describe("Case insensitive search"),
|
|
551
|
+
"-n": z4.boolean().optional().describe("Show line numbers (for content mode)"),
|
|
552
|
+
"-B": z4.number().optional().describe("Lines to show before each match"),
|
|
553
|
+
"-A": z4.number().optional().describe("Lines to show after each match"),
|
|
554
|
+
"-C": z4.number().optional().describe("Lines to show before and after each match"),
|
|
555
|
+
head_limit: z4.number().optional().describe("Limit output to first N lines/entries"),
|
|
556
|
+
multiline: z4.boolean().optional().describe("Enable multiline mode")
|
|
557
|
+
});
|
|
558
|
+
function createGrepTool(sandbox, config) {
|
|
559
|
+
return tool4({
|
|
560
|
+
description: "Powerful search tool built on ripgrep with regex support. Use this instead of the grep command.",
|
|
561
|
+
inputSchema: zodSchema4(grepInputSchema),
|
|
562
|
+
execute: async (input) => {
|
|
563
|
+
const {
|
|
564
|
+
pattern,
|
|
565
|
+
path,
|
|
566
|
+
glob,
|
|
567
|
+
type,
|
|
568
|
+
output_mode = "content",
|
|
569
|
+
"-i": caseInsensitive,
|
|
570
|
+
"-n": showLineNumbers = true,
|
|
571
|
+
"-B": beforeContext,
|
|
572
|
+
"-A": afterContext,
|
|
573
|
+
"-C": context,
|
|
574
|
+
head_limit,
|
|
575
|
+
multiline
|
|
576
|
+
} = input;
|
|
577
|
+
const searchPath = path || ".";
|
|
578
|
+
if (config?.allowedPaths) {
|
|
579
|
+
const isAllowed = config.allowedPaths.some((allowed) => searchPath.startsWith(allowed));
|
|
580
|
+
if (!isAllowed) {
|
|
581
|
+
return { error: `Path not allowed: ${searchPath}` };
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
try {
|
|
585
|
+
const flags = [];
|
|
586
|
+
if (caseInsensitive)
|
|
587
|
+
flags.push("-i");
|
|
588
|
+
if (showLineNumbers && output_mode === "content")
|
|
589
|
+
flags.push("-n");
|
|
590
|
+
if (multiline)
|
|
591
|
+
flags.push("-U");
|
|
592
|
+
if (context) {
|
|
593
|
+
flags.push(`-C ${context}`);
|
|
594
|
+
} else {
|
|
595
|
+
if (beforeContext)
|
|
596
|
+
flags.push(`-B ${beforeContext}`);
|
|
597
|
+
if (afterContext)
|
|
598
|
+
flags.push(`-A ${afterContext}`);
|
|
599
|
+
}
|
|
600
|
+
if (glob)
|
|
601
|
+
flags.push(`--include="${glob}"`);
|
|
602
|
+
if (type)
|
|
603
|
+
flags.push(`--include="*.${type}"`);
|
|
604
|
+
const flagStr = flags.join(" ");
|
|
605
|
+
const limit = head_limit || 1000;
|
|
606
|
+
let cmd;
|
|
607
|
+
if (output_mode === "files_with_matches") {
|
|
608
|
+
cmd = `grep -rl ${flagStr} "${pattern}" ${searchPath} 2>/dev/null | head -${limit}`;
|
|
609
|
+
const result = await sandbox.exec(cmd, { timeout: config?.timeout });
|
|
610
|
+
const files = result.stdout.split(`
|
|
611
|
+
`).filter(Boolean);
|
|
612
|
+
return {
|
|
613
|
+
files,
|
|
614
|
+
count: files.length
|
|
615
|
+
};
|
|
616
|
+
} else if (output_mode === "count") {
|
|
617
|
+
cmd = `grep -rc ${flagStr} "${pattern}" ${searchPath} 2>/dev/null | grep -v ':0$' | head -${limit}`;
|
|
618
|
+
const result = await sandbox.exec(cmd, { timeout: config?.timeout });
|
|
619
|
+
const lines = result.stdout.split(`
|
|
620
|
+
`).filter(Boolean);
|
|
621
|
+
const counts = lines.map((line) => {
|
|
622
|
+
const lastColon = line.lastIndexOf(":");
|
|
623
|
+
return {
|
|
624
|
+
file: line.slice(0, lastColon),
|
|
625
|
+
count: parseInt(line.slice(lastColon + 1), 10)
|
|
626
|
+
};
|
|
627
|
+
});
|
|
628
|
+
const total = counts.reduce((sum, c) => sum + c.count, 0);
|
|
629
|
+
return {
|
|
630
|
+
counts,
|
|
631
|
+
total
|
|
632
|
+
};
|
|
633
|
+
} else {
|
|
634
|
+
cmd = `grep -rn ${flagStr} "${pattern}" ${searchPath} 2>/dev/null | head -${limit}`;
|
|
635
|
+
const result = await sandbox.exec(cmd, { timeout: config?.timeout });
|
|
636
|
+
if (!result.stdout.trim()) {
|
|
637
|
+
return {
|
|
638
|
+
matches: [],
|
|
639
|
+
total_matches: 0
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
const lines = result.stdout.split(`
|
|
643
|
+
`).filter(Boolean);
|
|
644
|
+
const matches = [];
|
|
645
|
+
for (const line of lines) {
|
|
646
|
+
const colonMatch = line.match(/^(.+?):(\d+)[:|-](.*)$/);
|
|
647
|
+
if (colonMatch) {
|
|
648
|
+
const [, file, lineNum, content] = colonMatch;
|
|
649
|
+
matches.push({
|
|
650
|
+
file,
|
|
651
|
+
line_number: parseInt(lineNum, 10),
|
|
652
|
+
line: content
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return {
|
|
657
|
+
matches,
|
|
658
|
+
total_matches: matches.length
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
} catch (error) {
|
|
662
|
+
return {
|
|
663
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// src/tools/read.ts
|
|
671
|
+
import { tool as tool5, zodSchema as zodSchema5 } from "ai";
|
|
672
|
+
import { z as z5 } from "zod";
|
|
673
|
+
var readInputSchema = z5.object({
|
|
674
|
+
file_path: z5.string().describe("Absolute path to file or directory"),
|
|
675
|
+
offset: z5.number().optional().describe("Line number to start reading from (1-indexed)"),
|
|
676
|
+
limit: z5.number().optional().describe("Maximum number of lines to read")
|
|
677
|
+
});
|
|
678
|
+
function createReadTool(sandbox, config) {
|
|
679
|
+
return tool5({
|
|
680
|
+
description: "Read the contents of a file or list directory entries. For text files, returns numbered lines with total line count. For directories, returns file/folder names. Use this instead of `cat`, `head`, or `tail` commands.",
|
|
681
|
+
inputSchema: zodSchema5(readInputSchema),
|
|
682
|
+
execute: async ({
|
|
683
|
+
file_path,
|
|
684
|
+
offset,
|
|
685
|
+
limit
|
|
686
|
+
}) => {
|
|
687
|
+
if (config?.allowedPaths) {
|
|
688
|
+
const isAllowed = config.allowedPaths.some((allowed) => file_path.startsWith(allowed));
|
|
689
|
+
if (!isAllowed) {
|
|
690
|
+
return { error: `Path not allowed: ${file_path}` };
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
try {
|
|
694
|
+
const exists = await sandbox.fileExists(file_path);
|
|
695
|
+
if (!exists) {
|
|
696
|
+
return { error: `Path not found: ${file_path}` };
|
|
697
|
+
}
|
|
698
|
+
const isDir = await sandbox.isDirectory(file_path);
|
|
699
|
+
if (isDir) {
|
|
700
|
+
const entries = await sandbox.readDir(file_path);
|
|
701
|
+
return {
|
|
702
|
+
type: "directory",
|
|
703
|
+
entries,
|
|
704
|
+
count: entries.length
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
const content = await sandbox.readFile(file_path);
|
|
708
|
+
const nullByteIndex = content.indexOf("\x00");
|
|
709
|
+
if (nullByteIndex !== -1 && nullByteIndex < 1000) {
|
|
710
|
+
const ext = file_path.split(".").pop()?.toLowerCase();
|
|
711
|
+
const binaryExtensions = [
|
|
712
|
+
"pdf",
|
|
713
|
+
"png",
|
|
714
|
+
"jpg",
|
|
715
|
+
"jpeg",
|
|
716
|
+
"gif",
|
|
717
|
+
"zip",
|
|
718
|
+
"tar",
|
|
719
|
+
"gz",
|
|
720
|
+
"exe",
|
|
721
|
+
"bin",
|
|
722
|
+
"so",
|
|
723
|
+
"dylib"
|
|
724
|
+
];
|
|
725
|
+
if (binaryExtensions.includes(ext || "")) {
|
|
726
|
+
return {
|
|
727
|
+
error: `Cannot read binary file: ${file_path} (file exists, ${content.length} bytes). Use appropriate tools to process ${ext?.toUpperCase()} files (e.g., Python scripts for PDFs).`
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
const allLines = content.split(`
|
|
732
|
+
`);
|
|
733
|
+
const totalLines = allLines.length;
|
|
734
|
+
const maxLinesWithoutLimit = config?.maxFileSize || 500;
|
|
735
|
+
if (!limit && totalLines > maxLinesWithoutLimit) {
|
|
736
|
+
return {
|
|
737
|
+
error: `File is large (${totalLines} lines). Use 'offset' and 'limit' to read in chunks. Example: offset=1, limit=100 for first 100 lines.`
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
const startLine = offset ? offset - 1 : 0;
|
|
741
|
+
const endLine = limit ? startLine + limit : allLines.length;
|
|
742
|
+
const selectedLines = allLines.slice(startLine, endLine);
|
|
743
|
+
const lines = selectedLines.map((line, i) => ({
|
|
744
|
+
line_number: startLine + i + 1,
|
|
745
|
+
content: line
|
|
746
|
+
}));
|
|
747
|
+
return {
|
|
748
|
+
type: "text",
|
|
749
|
+
content: selectedLines.join(`
|
|
750
|
+
`),
|
|
751
|
+
lines,
|
|
752
|
+
total_lines: totalLines
|
|
753
|
+
};
|
|
754
|
+
} catch (error) {
|
|
755
|
+
return {
|
|
756
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// src/tools/web-fetch.ts
|
|
764
|
+
import { generateText, tool as tool6, zodSchema as zodSchema6 } from "ai";
|
|
765
|
+
import Parallel from "parallel-web";
|
|
766
|
+
import { z as z6 } from "zod";
|
|
767
|
+
var webFetchInputSchema = z6.object({
|
|
768
|
+
url: z6.string().describe("The URL to fetch content from"),
|
|
769
|
+
prompt: z6.string().describe("The prompt to run on the fetched content")
|
|
770
|
+
});
|
|
771
|
+
var RETRYABLE_CODES = [408, 429, 500, 502, 503];
|
|
772
|
+
function createWebFetchTool(config) {
|
|
773
|
+
const { apiKey, model } = config;
|
|
774
|
+
return tool6({
|
|
775
|
+
description: "Fetches content from a URL and processes it with an AI model. Use this to analyze web pages, extract information, or summarize content.",
|
|
776
|
+
inputSchema: zodSchema6(webFetchInputSchema),
|
|
777
|
+
execute: async (input) => {
|
|
778
|
+
const { url, prompt } = input;
|
|
779
|
+
try {
|
|
780
|
+
const client = new Parallel({ apiKey });
|
|
781
|
+
const extract = await client.beta.extract({
|
|
782
|
+
urls: [url],
|
|
783
|
+
excerpts: true,
|
|
784
|
+
full_content: true
|
|
785
|
+
});
|
|
786
|
+
if (!extract.results || extract.results.length === 0) {
|
|
787
|
+
return {
|
|
788
|
+
error: "No content extracted from URL",
|
|
789
|
+
status_code: 404,
|
|
790
|
+
retryable: false
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
const extractedResult = extract.results[0];
|
|
794
|
+
const content = extractedResult.full_content || extractedResult.excerpts?.join(`
|
|
795
|
+
|
|
796
|
+
`) || "";
|
|
797
|
+
if (!content) {
|
|
798
|
+
return {
|
|
799
|
+
error: "No content available from URL",
|
|
800
|
+
status_code: 404,
|
|
801
|
+
retryable: false
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
const result = await generateText({
|
|
805
|
+
model,
|
|
806
|
+
prompt: `${prompt}
|
|
807
|
+
|
|
808
|
+
Content from ${url}:
|
|
809
|
+
|
|
810
|
+
${content}`
|
|
811
|
+
});
|
|
812
|
+
return {
|
|
813
|
+
response: result.text,
|
|
814
|
+
url,
|
|
815
|
+
final_url: extractedResult.url || url
|
|
816
|
+
};
|
|
817
|
+
} catch (error) {
|
|
818
|
+
if (error && typeof error === "object" && "status" in error) {
|
|
819
|
+
const statusCode = error.status;
|
|
820
|
+
const message = error.message || "API request failed";
|
|
821
|
+
return {
|
|
822
|
+
error: message,
|
|
823
|
+
status_code: statusCode,
|
|
824
|
+
retryable: RETRYABLE_CODES.includes(statusCode)
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
return {
|
|
828
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// src/tools/web-search.ts
|
|
836
|
+
import { tool as tool7, zodSchema as zodSchema7 } from "ai";
|
|
837
|
+
import Parallel2 from "parallel-web";
|
|
838
|
+
import { z as z7 } from "zod";
|
|
839
|
+
var webSearchInputSchema = z7.object({
|
|
840
|
+
query: z7.string().describe("The search query to use"),
|
|
841
|
+
allowed_domains: z7.array(z7.string()).optional().describe("Only include results from these domains"),
|
|
842
|
+
blocked_domains: z7.array(z7.string()).optional().describe("Never include results from these domains")
|
|
843
|
+
});
|
|
844
|
+
var RETRYABLE_CODES2 = [408, 429, 500, 502, 503];
|
|
845
|
+
function createWebSearchTool(config) {
|
|
846
|
+
const { apiKey } = config;
|
|
847
|
+
return tool7({
|
|
848
|
+
description: "Searches the web and returns formatted results. Use this to find current information, documentation, articles, and more.",
|
|
849
|
+
inputSchema: zodSchema7(webSearchInputSchema),
|
|
850
|
+
execute: async (input) => {
|
|
851
|
+
const { query, allowed_domains, blocked_domains } = input;
|
|
852
|
+
try {
|
|
853
|
+
const client = new Parallel2({ apiKey });
|
|
854
|
+
const sourcePolicy = allowed_domains || blocked_domains ? {
|
|
855
|
+
...allowed_domains && { include_domains: allowed_domains },
|
|
856
|
+
...blocked_domains && { exclude_domains: blocked_domains }
|
|
857
|
+
} : undefined;
|
|
858
|
+
const search = await client.beta.search({
|
|
859
|
+
mode: "agentic",
|
|
860
|
+
objective: query,
|
|
861
|
+
max_results: 10,
|
|
862
|
+
...sourcePolicy && { source_policy: sourcePolicy }
|
|
863
|
+
});
|
|
864
|
+
const results = (search.results || []).map((result) => ({
|
|
865
|
+
title: result.title ?? "",
|
|
866
|
+
url: result.url ?? "",
|
|
867
|
+
snippet: result.excerpts?.join(`
|
|
868
|
+
`) ?? "",
|
|
869
|
+
metadata: result.publish_date ? { publish_date: result.publish_date } : undefined
|
|
870
|
+
}));
|
|
871
|
+
return {
|
|
872
|
+
results,
|
|
873
|
+
total_results: results.length,
|
|
874
|
+
query
|
|
875
|
+
};
|
|
876
|
+
} catch (error) {
|
|
877
|
+
if (error && typeof error === "object" && "status" in error) {
|
|
878
|
+
const statusCode = error.status;
|
|
879
|
+
const message = error.message || "API request failed";
|
|
880
|
+
return {
|
|
881
|
+
error: message,
|
|
882
|
+
status_code: statusCode,
|
|
883
|
+
retryable: RETRYABLE_CODES2.includes(statusCode)
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
return {
|
|
887
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// src/tools/write.ts
|
|
895
|
+
import { tool as tool8, zodSchema as zodSchema8 } from "ai";
|
|
896
|
+
import { z as z8 } from "zod";
|
|
897
|
+
var writeInputSchema = z8.object({
|
|
898
|
+
file_path: z8.string().describe("Path to the file to write"),
|
|
899
|
+
content: z8.string().describe("Content to write to the file")
|
|
900
|
+
});
|
|
901
|
+
function createWriteTool(sandbox, config) {
|
|
902
|
+
return tool8({
|
|
903
|
+
description: "Write content to a file. Creates the file if it does not exist, overwrites if it does.",
|
|
904
|
+
inputSchema: zodSchema8(writeInputSchema),
|
|
905
|
+
execute: async ({
|
|
906
|
+
file_path,
|
|
907
|
+
content
|
|
908
|
+
}) => {
|
|
909
|
+
const byteLength = Buffer.byteLength(content, "utf-8");
|
|
910
|
+
if (config?.maxFileSize && byteLength > config.maxFileSize) {
|
|
911
|
+
return {
|
|
912
|
+
error: `File content exceeds maximum size of ${config.maxFileSize} bytes (got ${byteLength})`
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
if (config?.allowedPaths) {
|
|
916
|
+
const isAllowed = config.allowedPaths.some((allowed) => file_path.startsWith(allowed));
|
|
917
|
+
if (!isAllowed) {
|
|
918
|
+
return { error: `Path not allowed: ${file_path}` };
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
try {
|
|
922
|
+
await sandbox.writeFile(file_path, content);
|
|
923
|
+
return {
|
|
924
|
+
message: `Successfully wrote to ${file_path}`,
|
|
925
|
+
bytes_written: byteLength,
|
|
926
|
+
file_path
|
|
927
|
+
};
|
|
928
|
+
} catch (error) {
|
|
929
|
+
return {
|
|
930
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
// src/tools/exit-plan-mode.ts
|
|
937
|
+
import { tool as tool9, zodSchema as zodSchema9 } from "ai";
|
|
938
|
+
import { z as z9 } from "zod";
|
|
939
|
+
var exitPlanModeInputSchema = z9.object({
|
|
940
|
+
plan: z9.string().describe("The plan to present to the user for approval")
|
|
941
|
+
});
|
|
942
|
+
function createExitPlanModeTool(config, onPlanSubmit) {
|
|
943
|
+
return tool9({
|
|
944
|
+
description: "Exits planning mode and prompts the user to approve the plan. Use this when you have finished planning and want user confirmation before proceeding.",
|
|
945
|
+
inputSchema: zodSchema9(exitPlanModeInputSchema),
|
|
946
|
+
execute: async ({
|
|
947
|
+
plan
|
|
948
|
+
}) => {
|
|
949
|
+
try {
|
|
950
|
+
let approved;
|
|
951
|
+
if (onPlanSubmit) {
|
|
952
|
+
approved = await onPlanSubmit(plan);
|
|
953
|
+
}
|
|
954
|
+
return {
|
|
955
|
+
message: approved ? "Plan approved, proceeding with execution" : "Plan submitted for review",
|
|
956
|
+
approved
|
|
957
|
+
};
|
|
958
|
+
} catch (error) {
|
|
959
|
+
return {
|
|
960
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
// src/tools/task.ts
|
|
967
|
+
import {
|
|
968
|
+
generateText as generateText2,
|
|
969
|
+
stepCountIs,
|
|
970
|
+
tool as tool10,
|
|
971
|
+
zodSchema as zodSchema10
|
|
972
|
+
} from "ai";
|
|
973
|
+
import { z as z10 } from "zod";
|
|
974
|
+
var taskInputSchema = z10.object({
|
|
975
|
+
description: z10.string().describe("A short (3-5 word) description of the task"),
|
|
976
|
+
prompt: z10.string().describe("The task for the agent to perform"),
|
|
977
|
+
subagent_type: z10.string().describe("The type of specialized agent to use for this task")
|
|
978
|
+
});
|
|
979
|
+
function filterTools(allTools, allowedTools) {
|
|
980
|
+
if (!allowedTools)
|
|
981
|
+
return allTools;
|
|
982
|
+
const filtered = {};
|
|
983
|
+
for (const name of allowedTools) {
|
|
984
|
+
if (allTools[name]) {
|
|
985
|
+
filtered[name] = allTools[name];
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return filtered;
|
|
989
|
+
}
|
|
990
|
+
function createTaskTool(config) {
|
|
991
|
+
const {
|
|
992
|
+
model: defaultModel,
|
|
993
|
+
tools: allTools,
|
|
994
|
+
subagentTypes = {},
|
|
995
|
+
costPerInputToken = 0.000003,
|
|
996
|
+
costPerOutputToken = 0.000015,
|
|
997
|
+
defaultStopWhen,
|
|
998
|
+
defaultOnStepFinish
|
|
999
|
+
} = config;
|
|
1000
|
+
return tool10({
|
|
1001
|
+
description: "Launches a new agent to handle complex, multi-step tasks autonomously. Use this for tasks that require multiple steps, research, or specialized expertise.",
|
|
1002
|
+
inputSchema: zodSchema10(taskInputSchema),
|
|
1003
|
+
execute: async ({
|
|
1004
|
+
description,
|
|
1005
|
+
prompt,
|
|
1006
|
+
subagent_type
|
|
1007
|
+
}) => {
|
|
1008
|
+
const startTime = performance.now();
|
|
1009
|
+
try {
|
|
1010
|
+
const typeConfig = subagentTypes[subagent_type] || {};
|
|
1011
|
+
const model = typeConfig.model || defaultModel;
|
|
1012
|
+
const tools = filterTools(allTools, typeConfig.tools);
|
|
1013
|
+
const systemPrompt = typeConfig.systemPrompt;
|
|
1014
|
+
const result = await generateText2({
|
|
1015
|
+
model,
|
|
1016
|
+
tools,
|
|
1017
|
+
system: systemPrompt,
|
|
1018
|
+
prompt,
|
|
1019
|
+
stopWhen: typeConfig.stopWhen ?? defaultStopWhen ?? stepCountIs(15),
|
|
1020
|
+
prepareStep: typeConfig.prepareStep,
|
|
1021
|
+
onStepFinish: async (step) => {
|
|
1022
|
+
await typeConfig.onStepFinish?.(step);
|
|
1023
|
+
await defaultOnStepFinish?.({
|
|
1024
|
+
subagentType: subagent_type,
|
|
1025
|
+
description,
|
|
1026
|
+
step
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
});
|
|
1030
|
+
const durationMs = Math.round(performance.now() - startTime);
|
|
1031
|
+
const usage = result.usage.inputTokens !== undefined && result.usage.outputTokens !== undefined ? {
|
|
1032
|
+
input_tokens: result.usage.inputTokens,
|
|
1033
|
+
output_tokens: result.usage.outputTokens
|
|
1034
|
+
} : undefined;
|
|
1035
|
+
let totalCostUsd;
|
|
1036
|
+
if (usage) {
|
|
1037
|
+
totalCostUsd = usage.input_tokens * costPerInputToken + usage.output_tokens * costPerOutputToken;
|
|
1038
|
+
}
|
|
1039
|
+
return {
|
|
1040
|
+
result: result.text,
|
|
1041
|
+
usage,
|
|
1042
|
+
total_cost_usd: totalCostUsd,
|
|
1043
|
+
duration_ms: durationMs
|
|
1044
|
+
};
|
|
1045
|
+
} catch (error) {
|
|
1046
|
+
return {
|
|
1047
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
// src/tools/todo-write.ts
|
|
1054
|
+
import { tool as tool11, zodSchema as zodSchema11 } from "ai";
|
|
1055
|
+
import { z as z11 } from "zod";
|
|
1056
|
+
var todoWriteInputSchema = z11.object({
|
|
1057
|
+
todos: z11.array(z11.object({
|
|
1058
|
+
content: z11.string().describe("The task description"),
|
|
1059
|
+
status: z11.enum(["pending", "in_progress", "completed"]).describe("The task status"),
|
|
1060
|
+
activeForm: z11.string().describe("Active form of the task description")
|
|
1061
|
+
})).describe("The updated todo list")
|
|
1062
|
+
});
|
|
1063
|
+
function createTodoWriteTool(state, config, onUpdate) {
|
|
1064
|
+
return tool11({
|
|
1065
|
+
description: "Creates and manages a structured task list for tracking progress. Use this to plan complex tasks and track completion.",
|
|
1066
|
+
inputSchema: zodSchema11(todoWriteInputSchema),
|
|
1067
|
+
execute: async ({
|
|
1068
|
+
todos
|
|
1069
|
+
}) => {
|
|
1070
|
+
try {
|
|
1071
|
+
state.todos = todos;
|
|
1072
|
+
if (onUpdate) {
|
|
1073
|
+
onUpdate(todos);
|
|
1074
|
+
}
|
|
1075
|
+
const stats = {
|
|
1076
|
+
total: todos.length,
|
|
1077
|
+
pending: todos.filter((t) => t.status === "pending").length,
|
|
1078
|
+
in_progress: todos.filter((t) => t.status === "in_progress").length,
|
|
1079
|
+
completed: todos.filter((t) => t.status === "completed").length
|
|
1080
|
+
};
|
|
1081
|
+
return {
|
|
1082
|
+
message: "Todo list updated successfully",
|
|
1083
|
+
stats
|
|
1084
|
+
};
|
|
1085
|
+
} catch (error) {
|
|
1086
|
+
return {
|
|
1087
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// src/tools/index.ts
|
|
1095
|
+
function createAgentTools(sandbox, config) {
|
|
1096
|
+
const toolsConfig = {
|
|
1097
|
+
...DEFAULT_CONFIG.tools,
|
|
1098
|
+
...config?.tools
|
|
1099
|
+
};
|
|
1100
|
+
const tools = {
|
|
1101
|
+
Bash: createBashTool(sandbox, toolsConfig.Bash),
|
|
1102
|
+
Read: createReadTool(sandbox, toolsConfig.Read),
|
|
1103
|
+
Write: createWriteTool(sandbox, toolsConfig.Write),
|
|
1104
|
+
Edit: createEditTool(sandbox, toolsConfig.Edit),
|
|
1105
|
+
Glob: createGlobTool(sandbox, toolsConfig.Glob),
|
|
1106
|
+
Grep: createGrepTool(sandbox, toolsConfig.Grep)
|
|
1107
|
+
};
|
|
1108
|
+
if (config?.webSearch) {
|
|
1109
|
+
tools.WebSearch = createWebSearchTool(config.webSearch);
|
|
1110
|
+
}
|
|
1111
|
+
if (config?.webFetch) {
|
|
1112
|
+
tools.WebFetch = createWebFetchTool(config.webFetch);
|
|
1113
|
+
}
|
|
1114
|
+
return tools;
|
|
1115
|
+
}
|
|
1116
|
+
// src/utils/compact-conversation.ts
|
|
1117
|
+
import { generateText as generateText3 } from "ai";
|
|
1118
|
+
|
|
1119
|
+
// src/utils/prune-messages.ts
|
|
1120
|
+
var DEFAULT_CONFIG2 = {
|
|
1121
|
+
targetTokens: 40000,
|
|
1122
|
+
minSavingsThreshold: 20000,
|
|
1123
|
+
protectLastNUserMessages: 3
|
|
1124
|
+
};
|
|
1125
|
+
function estimateTokens(text) {
|
|
1126
|
+
return Math.ceil(text.length / 4);
|
|
1127
|
+
}
|
|
1128
|
+
function estimateMessageTokens(message) {
|
|
1129
|
+
let tokens = 0;
|
|
1130
|
+
if (typeof message.content === "string") {
|
|
1131
|
+
tokens += estimateTokens(message.content);
|
|
1132
|
+
} else if (Array.isArray(message.content)) {
|
|
1133
|
+
for (const part of message.content) {
|
|
1134
|
+
if (typeof part === "string") {
|
|
1135
|
+
tokens += estimateTokens(part);
|
|
1136
|
+
} else if ("text" in part && typeof part.text === "string") {
|
|
1137
|
+
tokens += estimateTokens(part.text);
|
|
1138
|
+
} else if ("result" in part) {
|
|
1139
|
+
tokens += estimateTokens(JSON.stringify(part.result));
|
|
1140
|
+
} else if ("args" in part) {
|
|
1141
|
+
tokens += estimateTokens(JSON.stringify(part.args));
|
|
1142
|
+
} else {
|
|
1143
|
+
tokens += estimateTokens(JSON.stringify(part));
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
tokens += 4;
|
|
1148
|
+
return tokens;
|
|
1149
|
+
}
|
|
1150
|
+
function estimateMessagesTokens(messages) {
|
|
1151
|
+
return messages.reduce((sum, msg) => sum + estimateMessageTokens(msg), 0);
|
|
1152
|
+
}
|
|
1153
|
+
function findLastNUserMessageIndices(messages, n) {
|
|
1154
|
+
const indices = new Set;
|
|
1155
|
+
let count = 0;
|
|
1156
|
+
for (let i = messages.length - 1;i >= 0 && count < n; i--) {
|
|
1157
|
+
if (messages[i].role === "user") {
|
|
1158
|
+
indices.add(i);
|
|
1159
|
+
count++;
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
return indices;
|
|
1163
|
+
}
|
|
1164
|
+
function findProtectedIndices(messages, protectLastN) {
|
|
1165
|
+
const userIndices = findLastNUserMessageIndices(messages, protectLastN);
|
|
1166
|
+
const protected_ = new Set;
|
|
1167
|
+
if (userIndices.size === 0)
|
|
1168
|
+
return protected_;
|
|
1169
|
+
const earliestProtected = Math.min(...userIndices);
|
|
1170
|
+
for (let i = earliestProtected;i < messages.length; i++) {
|
|
1171
|
+
protected_.add(i);
|
|
1172
|
+
}
|
|
1173
|
+
return protected_;
|
|
1174
|
+
}
|
|
1175
|
+
function pruneMessageContent(message) {
|
|
1176
|
+
if (message.role !== "assistant" || typeof message.content === "string") {
|
|
1177
|
+
return message;
|
|
1178
|
+
}
|
|
1179
|
+
if (!Array.isArray(message.content)) {
|
|
1180
|
+
return message;
|
|
1181
|
+
}
|
|
1182
|
+
const prunedContent = message.content.map((part) => {
|
|
1183
|
+
if (typeof part === "object" && "toolName" in part && "args" in part) {
|
|
1184
|
+
return {
|
|
1185
|
+
...part,
|
|
1186
|
+
args: { _pruned: true, toolName: part.toolName }
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
return part;
|
|
1190
|
+
});
|
|
1191
|
+
return { ...message, content: prunedContent };
|
|
1192
|
+
}
|
|
1193
|
+
function pruneToolMessage(message) {
|
|
1194
|
+
if (message.role !== "tool") {
|
|
1195
|
+
return message;
|
|
1196
|
+
}
|
|
1197
|
+
if (!Array.isArray(message.content)) {
|
|
1198
|
+
return message;
|
|
1199
|
+
}
|
|
1200
|
+
const prunedContent = message.content.map((part) => {
|
|
1201
|
+
if (typeof part === "object" && "result" in part) {
|
|
1202
|
+
return {
|
|
1203
|
+
...part,
|
|
1204
|
+
result: { _pruned: true }
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
return part;
|
|
1208
|
+
});
|
|
1209
|
+
return { ...message, content: prunedContent };
|
|
1210
|
+
}
|
|
1211
|
+
function pruneMessagesByTokens(messages, config) {
|
|
1212
|
+
const { targetTokens, minSavingsThreshold, protectLastNUserMessages } = {
|
|
1213
|
+
...DEFAULT_CONFIG2,
|
|
1214
|
+
...config
|
|
1215
|
+
};
|
|
1216
|
+
const totalTokens = estimateMessagesTokens(messages);
|
|
1217
|
+
const potentialSavings = totalTokens - targetTokens;
|
|
1218
|
+
if (potentialSavings <= minSavingsThreshold) {
|
|
1219
|
+
return messages;
|
|
1220
|
+
}
|
|
1221
|
+
const protectedIndices = findProtectedIndices(messages, protectLastNUserMessages);
|
|
1222
|
+
const prunedMessages = [];
|
|
1223
|
+
let currentTokens = 0;
|
|
1224
|
+
let savedTokens = 0;
|
|
1225
|
+
for (let i = 0;i < messages.length; i++) {
|
|
1226
|
+
const message = messages[i];
|
|
1227
|
+
const isProtected = protectedIndices.has(i);
|
|
1228
|
+
if (isProtected) {
|
|
1229
|
+
prunedMessages.push(message);
|
|
1230
|
+
currentTokens += estimateMessageTokens(message);
|
|
1231
|
+
} else {
|
|
1232
|
+
const originalTokens = estimateMessageTokens(message);
|
|
1233
|
+
if (currentTokens + originalTokens > targetTokens) {
|
|
1234
|
+
let prunedMessage = message;
|
|
1235
|
+
if (message.role === "assistant") {
|
|
1236
|
+
prunedMessage = pruneMessageContent(message);
|
|
1237
|
+
} else if (message.role === "tool") {
|
|
1238
|
+
prunedMessage = pruneToolMessage(message);
|
|
1239
|
+
}
|
|
1240
|
+
const prunedTokens = estimateMessageTokens(prunedMessage);
|
|
1241
|
+
savedTokens += originalTokens - prunedTokens;
|
|
1242
|
+
prunedMessages.push(prunedMessage);
|
|
1243
|
+
currentTokens += prunedTokens;
|
|
1244
|
+
} else {
|
|
1245
|
+
prunedMessages.push(message);
|
|
1246
|
+
currentTokens += originalTokens;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
return prunedMessages;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
// src/utils/compact-conversation.ts
|
|
1254
|
+
async function compactConversation(messages, config, state = { conversationSummary: "" }) {
|
|
1255
|
+
const currentTokens = estimateMessagesTokens(messages);
|
|
1256
|
+
const threshold = config.compactionThreshold ?? 0.85;
|
|
1257
|
+
const limit = config.maxTokens * threshold;
|
|
1258
|
+
if (currentTokens < limit) {
|
|
1259
|
+
return { messages, state, didCompact: false };
|
|
1260
|
+
}
|
|
1261
|
+
const protectCount = config.protectRecentMessages ?? 10;
|
|
1262
|
+
const recentMessages = messages.slice(-protectCount);
|
|
1263
|
+
const oldMessages = messages.slice(0, -protectCount);
|
|
1264
|
+
if (oldMessages.length === 0) {
|
|
1265
|
+
return { messages, state, didCompact: false };
|
|
1266
|
+
}
|
|
1267
|
+
const newSummary = await summarizeMessages(oldMessages, config.summarizerModel, config.taskContext, state.conversationSummary);
|
|
1268
|
+
const compactedMessages = [
|
|
1269
|
+
{
|
|
1270
|
+
role: "user",
|
|
1271
|
+
content: `[Previous conversation summary]
|
|
1272
|
+
|
|
1273
|
+
${newSummary}
|
|
1274
|
+
|
|
1275
|
+
[Continuing from recent messages below...]`
|
|
1276
|
+
},
|
|
1277
|
+
{
|
|
1278
|
+
role: "assistant",
|
|
1279
|
+
content: "I understand the context from the previous conversation. Continuing from where we left off."
|
|
1280
|
+
},
|
|
1281
|
+
...recentMessages
|
|
1282
|
+
];
|
|
1283
|
+
return {
|
|
1284
|
+
messages: compactedMessages,
|
|
1285
|
+
state: { conversationSummary: newSummary },
|
|
1286
|
+
didCompact: true
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
var SUMMARIZATION_PROMPT = `<context>
|
|
1290
|
+
You are a conversation summarizer for an AI coding agent. The agent has been working on a task and the conversation has grown too long to fit in context. Your job is to create a comprehensive summary that allows the conversation to continue seamlessly.
|
|
1291
|
+
</context>
|
|
1292
|
+
|
|
1293
|
+
<task>
|
|
1294
|
+
Create a structured summary of the conversation below. This summary will replace the old messages, so it MUST preserve all information needed to continue the work.
|
|
1295
|
+
</task>
|
|
1296
|
+
|
|
1297
|
+
<original-goal>
|
|
1298
|
+
{{TASK_CONTEXT}}
|
|
1299
|
+
</original-goal>
|
|
1300
|
+
|
|
1301
|
+
<previous-summary>
|
|
1302
|
+
{{PREVIOUS_SUMMARY}}
|
|
1303
|
+
</previous-summary>
|
|
1304
|
+
|
|
1305
|
+
<conversation-to-summarize>
|
|
1306
|
+
{{CONVERSATION}}
|
|
1307
|
+
</conversation-to-summarize>
|
|
1308
|
+
|
|
1309
|
+
<output-format>
|
|
1310
|
+
Structure your summary with these sections:
|
|
1311
|
+
|
|
1312
|
+
## Task Overview
|
|
1313
|
+
Brief description of what the user asked for and the current goal.
|
|
1314
|
+
|
|
1315
|
+
## Progress Made
|
|
1316
|
+
- What has been accomplished so far
|
|
1317
|
+
- Key milestones reached
|
|
1318
|
+
|
|
1319
|
+
## Files & Code
|
|
1320
|
+
- Files created: list with paths
|
|
1321
|
+
- Files modified: list with paths and what changed
|
|
1322
|
+
- Files read/analyzed: list with paths
|
|
1323
|
+
- Key code patterns or architecture decisions
|
|
1324
|
+
|
|
1325
|
+
## Technical Decisions
|
|
1326
|
+
- Important choices made and why
|
|
1327
|
+
- Configurations or settings established
|
|
1328
|
+
- Dependencies or tools being used
|
|
1329
|
+
|
|
1330
|
+
## Errors & Resolutions
|
|
1331
|
+
- Problems encountered
|
|
1332
|
+
- How they were solved (or if still unresolved)
|
|
1333
|
+
|
|
1334
|
+
## Current State
|
|
1335
|
+
- Where the work left off
|
|
1336
|
+
- What was being worked on when summarized
|
|
1337
|
+
- Any pending questions or blockers
|
|
1338
|
+
|
|
1339
|
+
## Key Context
|
|
1340
|
+
- Important facts, names, or values that must not be forgotten
|
|
1341
|
+
- User preferences or requirements mentioned
|
|
1342
|
+
</output-format>
|
|
1343
|
+
|
|
1344
|
+
<instructions>
|
|
1345
|
+
- Be thorough. Missing information cannot be recovered.
|
|
1346
|
+
- Preserve exact file paths, variable names, and code snippets where relevant.
|
|
1347
|
+
- If tool calls were made, note what tools were used and their outcomes.
|
|
1348
|
+
- Maintain the user's original terminology and naming.
|
|
1349
|
+
- Do not editorialize or add suggestions - just capture what happened.
|
|
1350
|
+
- Omit sections that have no relevant information.
|
|
1351
|
+
</instructions>`;
|
|
1352
|
+
async function summarizeMessages(messages, model, taskContext, previousSummary) {
|
|
1353
|
+
const prompt = SUMMARIZATION_PROMPT.replace("{{TASK_CONTEXT}}", taskContext || "Not specified").replace("{{PREVIOUS_SUMMARY}}", previousSummary || "None - this is the first compaction").replace("{{CONVERSATION}}", formatMessagesForSummary(messages));
|
|
1354
|
+
const result = await generateText3({
|
|
1355
|
+
model,
|
|
1356
|
+
messages: [
|
|
1357
|
+
{
|
|
1358
|
+
role: "user",
|
|
1359
|
+
content: prompt
|
|
1360
|
+
}
|
|
1361
|
+
]
|
|
1362
|
+
});
|
|
1363
|
+
return result.text;
|
|
1364
|
+
}
|
|
1365
|
+
function formatMessagesForSummary(messages) {
|
|
1366
|
+
return messages.map((msg, index) => {
|
|
1367
|
+
const role = msg.role.toUpperCase();
|
|
1368
|
+
if (typeof msg.content === "string") {
|
|
1369
|
+
return `<message index="${index}" role="${role}">
|
|
1370
|
+
${msg.content}
|
|
1371
|
+
</message>`;
|
|
1372
|
+
}
|
|
1373
|
+
if (Array.isArray(msg.content)) {
|
|
1374
|
+
const parts = msg.content.map((part) => {
|
|
1375
|
+
if (typeof part === "string") {
|
|
1376
|
+
return part;
|
|
1377
|
+
}
|
|
1378
|
+
if ("text" in part && typeof part.text === "string") {
|
|
1379
|
+
return part.text;
|
|
1380
|
+
}
|
|
1381
|
+
if ("toolName" in part && "args" in part) {
|
|
1382
|
+
return `[Tool Call: ${part.toolName}]
|
|
1383
|
+
Args: ${JSON.stringify(part.args, null, 2)}`;
|
|
1384
|
+
}
|
|
1385
|
+
if ("result" in part) {
|
|
1386
|
+
const resultStr = typeof part.result === "string" ? part.result : JSON.stringify(part.result, null, 2);
|
|
1387
|
+
return `[Tool Result]
|
|
1388
|
+
${resultStr}`;
|
|
1389
|
+
}
|
|
1390
|
+
return JSON.stringify(part, null, 2);
|
|
1391
|
+
}).join(`
|
|
1392
|
+
|
|
1393
|
+
`);
|
|
1394
|
+
return `<message index="${index}" role="${role}">
|
|
1395
|
+
${parts}
|
|
1396
|
+
</message>`;
|
|
1397
|
+
}
|
|
1398
|
+
return `<message index="${index}" role="${role}">
|
|
1399
|
+
${JSON.stringify(msg.content, null, 2)}
|
|
1400
|
+
</message>`;
|
|
1401
|
+
}).join(`
|
|
1402
|
+
|
|
1403
|
+
`);
|
|
1404
|
+
}
|
|
1405
|
+
var MODEL_CONTEXT_LIMITS = {
|
|
1406
|
+
"claude-opus-4-5": 200000,
|
|
1407
|
+
"claude-sonnet-4-5": 200000,
|
|
1408
|
+
"claude-haiku-4": 200000,
|
|
1409
|
+
"gpt-4o": 128000,
|
|
1410
|
+
"gpt-4-turbo": 128000,
|
|
1411
|
+
"gpt-4": 8192,
|
|
1412
|
+
"gpt-3.5-turbo": 16385,
|
|
1413
|
+
"gemini-2.5-pro": 1e6,
|
|
1414
|
+
"gemini-2.5-flash": 1e6
|
|
1415
|
+
};
|
|
1416
|
+
function createCompactConfig(modelId, summarizerModel, overrides) {
|
|
1417
|
+
const maxTokens = MODEL_CONTEXT_LIMITS[modelId];
|
|
1418
|
+
return {
|
|
1419
|
+
maxTokens,
|
|
1420
|
+
summarizerModel,
|
|
1421
|
+
...overrides
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
// src/utils/context-status.ts
|
|
1425
|
+
var DEFAULT_CONFIG3 = {
|
|
1426
|
+
elevatedThreshold: 0.5,
|
|
1427
|
+
highThreshold: 0.7,
|
|
1428
|
+
criticalThreshold: 0.85
|
|
1429
|
+
};
|
|
1430
|
+
function defaultHighGuidance(metrics) {
|
|
1431
|
+
const used = Math.round(metrics.usagePercent * 100);
|
|
1432
|
+
const remaining = Math.round((1 - metrics.usagePercent) * 100);
|
|
1433
|
+
return `Context usage: ${used}%. You still have ${remaining}% remaining—no need to rush. Continue working thoroughly.`;
|
|
1434
|
+
}
|
|
1435
|
+
function defaultCriticalGuidance(metrics) {
|
|
1436
|
+
const used = Math.round(metrics.usagePercent * 100);
|
|
1437
|
+
return `Context usage: ${used}%. Consider wrapping up the current task or summarizing progress before continuing.`;
|
|
1438
|
+
}
|
|
1439
|
+
function getContextStatus(messages, maxTokens, config) {
|
|
1440
|
+
const { elevatedThreshold, highThreshold, criticalThreshold } = {
|
|
1441
|
+
...DEFAULT_CONFIG3,
|
|
1442
|
+
...config
|
|
1443
|
+
};
|
|
1444
|
+
const usedTokens = estimateMessagesTokens(messages);
|
|
1445
|
+
const usagePercent = usedTokens / maxTokens;
|
|
1446
|
+
const baseStatus = { usedTokens, maxTokens, usagePercent };
|
|
1447
|
+
if (usagePercent < elevatedThreshold) {
|
|
1448
|
+
return { ...baseStatus, status: "comfortable" };
|
|
1449
|
+
}
|
|
1450
|
+
if (usagePercent < highThreshold) {
|
|
1451
|
+
return { ...baseStatus, status: "elevated" };
|
|
1452
|
+
}
|
|
1453
|
+
if (usagePercent < criticalThreshold) {
|
|
1454
|
+
const guidance2 = typeof config?.highGuidance === "function" ? config.highGuidance(baseStatus) : config?.highGuidance ?? defaultHighGuidance(baseStatus);
|
|
1455
|
+
return { ...baseStatus, status: "high", guidance: guidance2 };
|
|
1456
|
+
}
|
|
1457
|
+
const guidance = typeof config?.criticalGuidance === "function" ? config.criticalGuidance(baseStatus) : config?.criticalGuidance ?? defaultCriticalGuidance(baseStatus);
|
|
1458
|
+
return { ...baseStatus, status: "critical", guidance };
|
|
1459
|
+
}
|
|
1460
|
+
function contextNeedsAttention(status) {
|
|
1461
|
+
return status.status === "high" || status.status === "critical";
|
|
1462
|
+
}
|
|
1463
|
+
function contextNeedsCompaction(status) {
|
|
1464
|
+
return status.status === "critical";
|
|
1465
|
+
}
|
|
1466
|
+
// src/skills/discovery.ts
|
|
1467
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
1468
|
+
import { homedir } from "node:os";
|
|
1469
|
+
import { join, resolve } from "node:path";
|
|
1470
|
+
|
|
1471
|
+
// src/skills/loader.ts
|
|
1472
|
+
function parseSkillMetadata(content, skillPath) {
|
|
1473
|
+
const frontmatter = extractFrontmatter(content);
|
|
1474
|
+
if (!frontmatter) {
|
|
1475
|
+
throw new Error(`No YAML frontmatter found in ${skillPath}`);
|
|
1476
|
+
}
|
|
1477
|
+
const parsed = parseYaml(frontmatter);
|
|
1478
|
+
if (!parsed.name || typeof parsed.name !== "string") {
|
|
1479
|
+
throw new Error(`Missing or invalid 'name' field in ${skillPath}`);
|
|
1480
|
+
}
|
|
1481
|
+
if (!parsed.description || typeof parsed.description !== "string") {
|
|
1482
|
+
throw new Error(`Missing or invalid 'description' field in ${skillPath}`);
|
|
1483
|
+
}
|
|
1484
|
+
const nameRegex = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
|
|
1485
|
+
if (parsed.name.length > 64 || parsed.name.length > 1 && !nameRegex.test(parsed.name) || parsed.name.includes("--")) {
|
|
1486
|
+
throw new Error(`Invalid 'name' format in ${skillPath}: must be 1-64 lowercase chars/hyphens, no start/end/consecutive hyphens`);
|
|
1487
|
+
}
|
|
1488
|
+
let allowedTools;
|
|
1489
|
+
if (parsed["allowed-tools"]) {
|
|
1490
|
+
const toolsStr = String(parsed["allowed-tools"]);
|
|
1491
|
+
allowedTools = toolsStr.split(/\s+/).filter(Boolean);
|
|
1492
|
+
}
|
|
1493
|
+
let metadata;
|
|
1494
|
+
if (parsed.metadata && typeof parsed.metadata === "object") {
|
|
1495
|
+
metadata = {};
|
|
1496
|
+
for (const [key, value] of Object.entries(parsed.metadata)) {
|
|
1497
|
+
metadata[key] = String(value);
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
return {
|
|
1501
|
+
name: parsed.name,
|
|
1502
|
+
description: parsed.description,
|
|
1503
|
+
path: skillPath,
|
|
1504
|
+
license: parsed.license ? String(parsed.license) : undefined,
|
|
1505
|
+
compatibility: parsed.compatibility ? String(parsed.compatibility) : undefined,
|
|
1506
|
+
metadata,
|
|
1507
|
+
allowedTools
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
function extractFrontmatter(content) {
|
|
1511
|
+
const trimmed = content.trimStart();
|
|
1512
|
+
if (!trimmed.startsWith("---")) {
|
|
1513
|
+
return null;
|
|
1514
|
+
}
|
|
1515
|
+
const endIndex = trimmed.indexOf(`
|
|
1516
|
+
---`, 3);
|
|
1517
|
+
if (endIndex === -1) {
|
|
1518
|
+
return null;
|
|
1519
|
+
}
|
|
1520
|
+
return trimmed.slice(3, endIndex).trim();
|
|
1521
|
+
}
|
|
1522
|
+
function parseYaml(yaml) {
|
|
1523
|
+
const result = {};
|
|
1524
|
+
const lines = yaml.split(`
|
|
1525
|
+
`);
|
|
1526
|
+
let currentKey = null;
|
|
1527
|
+
let currentObject = null;
|
|
1528
|
+
for (const line of lines) {
|
|
1529
|
+
if (!line.trim() || line.trim().startsWith("#")) {
|
|
1530
|
+
continue;
|
|
1531
|
+
}
|
|
1532
|
+
const nestedMatch = line.match(/^(\s{2,})(\w+):\s*(.*)$/);
|
|
1533
|
+
if (nestedMatch && currentKey && currentObject) {
|
|
1534
|
+
const [, , key, value] = nestedMatch;
|
|
1535
|
+
currentObject[key] = value.trim().replace(/^["']|["']$/g, "");
|
|
1536
|
+
continue;
|
|
1537
|
+
}
|
|
1538
|
+
const topMatch = line.match(/^(\w[\w-]*):\s*(.*)$/);
|
|
1539
|
+
if (topMatch) {
|
|
1540
|
+
if (currentKey && currentObject) {
|
|
1541
|
+
result[currentKey] = currentObject;
|
|
1542
|
+
currentObject = null;
|
|
1543
|
+
}
|
|
1544
|
+
const [, key, value] = topMatch;
|
|
1545
|
+
const trimmedValue = value.trim();
|
|
1546
|
+
if (trimmedValue === "" || trimmedValue === "|" || trimmedValue === ">") {
|
|
1547
|
+
currentKey = key;
|
|
1548
|
+
currentObject = {};
|
|
1549
|
+
} else {
|
|
1550
|
+
result[key] = trimmedValue.replace(/^["']|["']$/g, "");
|
|
1551
|
+
currentKey = null;
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
if (currentKey && currentObject && Object.keys(currentObject).length > 0) {
|
|
1556
|
+
result[currentKey] = currentObject;
|
|
1557
|
+
}
|
|
1558
|
+
return result;
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
// src/skills/discovery.ts
|
|
1562
|
+
var DEFAULT_SKILL_PATHS = [".skills", "~/.bashkit/skills"];
|
|
1563
|
+
async function discoverSkills(options) {
|
|
1564
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
1565
|
+
const searchPaths = options?.paths ?? DEFAULT_SKILL_PATHS;
|
|
1566
|
+
const skills = [];
|
|
1567
|
+
const seenNames = new Set;
|
|
1568
|
+
for (const searchPath of searchPaths) {
|
|
1569
|
+
const resolvedPath = resolvePath(searchPath, cwd);
|
|
1570
|
+
const foundSkills = await scanDirectory(resolvedPath);
|
|
1571
|
+
for (const skill of foundSkills) {
|
|
1572
|
+
if (!seenNames.has(skill.name)) {
|
|
1573
|
+
seenNames.add(skill.name);
|
|
1574
|
+
skills.push(skill);
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
return skills;
|
|
1579
|
+
}
|
|
1580
|
+
function resolvePath(path, cwd) {
|
|
1581
|
+
if (path.startsWith("~/")) {
|
|
1582
|
+
return join(homedir(), path.slice(2));
|
|
1583
|
+
}
|
|
1584
|
+
if (path.startsWith("/")) {
|
|
1585
|
+
return path;
|
|
1586
|
+
}
|
|
1587
|
+
return resolve(cwd, path);
|
|
1588
|
+
}
|
|
1589
|
+
async function scanDirectory(dirPath) {
|
|
1590
|
+
const skills = [];
|
|
1591
|
+
try {
|
|
1592
|
+
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
1593
|
+
for (const entry of entries) {
|
|
1594
|
+
if (!entry.isDirectory()) {
|
|
1595
|
+
continue;
|
|
1596
|
+
}
|
|
1597
|
+
const skillPath = join(dirPath, entry.name, "SKILL.md");
|
|
1598
|
+
try {
|
|
1599
|
+
const skillStat = await stat(skillPath);
|
|
1600
|
+
if (!skillStat.isFile()) {
|
|
1601
|
+
continue;
|
|
1602
|
+
}
|
|
1603
|
+
const content = await readFile(skillPath, "utf-8");
|
|
1604
|
+
const metadata = parseSkillMetadata(content, skillPath);
|
|
1605
|
+
if (metadata.name !== entry.name) {
|
|
1606
|
+
console.warn(`Skill name "${metadata.name}" does not match folder name "${entry.name}" in ${skillPath}`);
|
|
1607
|
+
}
|
|
1608
|
+
skills.push(metadata);
|
|
1609
|
+
} catch {}
|
|
1610
|
+
}
|
|
1611
|
+
} catch {
|
|
1612
|
+
return [];
|
|
1613
|
+
}
|
|
1614
|
+
return skills;
|
|
1615
|
+
}
|
|
1616
|
+
// src/skills/fetch.ts
|
|
1617
|
+
function parseGitHubRef(ref) {
|
|
1618
|
+
const parts = ref.split("/");
|
|
1619
|
+
if (parts.length < 3) {
|
|
1620
|
+
throw new Error(`Invalid skill reference "${ref}". Expected format: owner/repo/skillName (e.g., anthropics/skills/pdf)`);
|
|
1621
|
+
}
|
|
1622
|
+
const owner = parts[0];
|
|
1623
|
+
const repo = parts[1];
|
|
1624
|
+
const skillName = parts[parts.length - 1];
|
|
1625
|
+
return { owner, repo, skillName };
|
|
1626
|
+
}
|
|
1627
|
+
async function fetchDirectoryContents(owner, repo, path, basePath, branch = "main") {
|
|
1628
|
+
const apiUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${branch}`;
|
|
1629
|
+
const response = await fetch(apiUrl, {
|
|
1630
|
+
headers: {
|
|
1631
|
+
Accept: "application/vnd.github.v3+json",
|
|
1632
|
+
"User-Agent": "bashkit"
|
|
1633
|
+
}
|
|
1634
|
+
});
|
|
1635
|
+
if (!response.ok) {
|
|
1636
|
+
throw new Error(`Failed to fetch directory contents from "${path}": ${response.status} ${response.statusText}`);
|
|
1637
|
+
}
|
|
1638
|
+
const items = await response.json();
|
|
1639
|
+
const files = {};
|
|
1640
|
+
await Promise.all(items.map(async (item) => {
|
|
1641
|
+
if (item.type === "file" && item.download_url) {
|
|
1642
|
+
const fileResponse = await fetch(item.download_url);
|
|
1643
|
+
if (fileResponse.ok) {
|
|
1644
|
+
const relativePath = item.path.slice(basePath.length + 1);
|
|
1645
|
+
files[relativePath] = await fileResponse.text();
|
|
1646
|
+
}
|
|
1647
|
+
} else if (item.type === "dir") {
|
|
1648
|
+
const subFiles = await fetchDirectoryContents(owner, repo, item.path, basePath, branch);
|
|
1649
|
+
for (const [subPath, content] of Object.entries(subFiles)) {
|
|
1650
|
+
files[subPath] = content;
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
}));
|
|
1654
|
+
return files;
|
|
1655
|
+
}
|
|
1656
|
+
async function fetchSkill(ref) {
|
|
1657
|
+
const { owner, repo, skillName } = parseGitHubRef(ref);
|
|
1658
|
+
const skillPath = `skills/${skillName}`;
|
|
1659
|
+
const files = await fetchDirectoryContents(owner, repo, skillPath, skillPath);
|
|
1660
|
+
if (!files["SKILL.md"]) {
|
|
1661
|
+
throw new Error(`Skill "${ref}" does not contain a SKILL.md file. Found files: ${Object.keys(files).join(", ")}`);
|
|
1662
|
+
}
|
|
1663
|
+
return {
|
|
1664
|
+
name: skillName,
|
|
1665
|
+
files
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
async function fetchSkills(refs) {
|
|
1669
|
+
const results = await Promise.all(refs.map(async (ref) => {
|
|
1670
|
+
const bundle = await fetchSkill(ref);
|
|
1671
|
+
return { name: bundle.name, bundle };
|
|
1672
|
+
}));
|
|
1673
|
+
return Object.fromEntries(results.map(({ name, bundle }) => [name, bundle]));
|
|
1674
|
+
}
|
|
1675
|
+
// src/skills/xml.ts
|
|
1676
|
+
function skillsToXml(skills) {
|
|
1677
|
+
if (skills.length === 0) {
|
|
1678
|
+
return `<available_skills>
|
|
1679
|
+
</available_skills>`;
|
|
1680
|
+
}
|
|
1681
|
+
const skillElements = skills.map((skill) => {
|
|
1682
|
+
const name = escapeXml(skill.name);
|
|
1683
|
+
const description = escapeXml(skill.description);
|
|
1684
|
+
const location = escapeXml(skill.path);
|
|
1685
|
+
return ` <skill>
|
|
1686
|
+
<name>${name}</name>
|
|
1687
|
+
<description>${description}</description>
|
|
1688
|
+
<location>${location}</location>
|
|
1689
|
+
</skill>`;
|
|
1690
|
+
}).join(`
|
|
1691
|
+
`);
|
|
1692
|
+
return `<available_skills>
|
|
1693
|
+
${skillElements}
|
|
1694
|
+
</available_skills>`;
|
|
1695
|
+
}
|
|
1696
|
+
function escapeXml(str) {
|
|
1697
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1698
|
+
}
|
|
1699
|
+
// src/setup/setup-environment.ts
|
|
1700
|
+
function isSkillBundle(content) {
|
|
1701
|
+
return typeof content === "object" && "files" in content;
|
|
1702
|
+
}
|
|
1703
|
+
async function setupAgentEnvironment(sandbox, config) {
|
|
1704
|
+
const skills = [];
|
|
1705
|
+
if (config.workspace) {
|
|
1706
|
+
for (const path of Object.values(config.workspace)) {
|
|
1707
|
+
await createDirectory(sandbox, path);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
if (config.skills) {
|
|
1711
|
+
await createDirectory(sandbox, ".skills");
|
|
1712
|
+
for (const [name, content] of Object.entries(config.skills)) {
|
|
1713
|
+
const skillDir = `.skills/${name}`;
|
|
1714
|
+
if (isSkillBundle(content)) {
|
|
1715
|
+
await seedSkillBundle(sandbox, skillDir, content);
|
|
1716
|
+
const skillMdContent = content.files["SKILL.md"];
|
|
1717
|
+
if (skillMdContent) {
|
|
1718
|
+
try {
|
|
1719
|
+
const metadata = parseSkillMetadata(skillMdContent, `${skillDir}/SKILL.md`);
|
|
1720
|
+
skills.push(metadata);
|
|
1721
|
+
} catch {
|
|
1722
|
+
skills.push({
|
|
1723
|
+
name,
|
|
1724
|
+
description: `Skill: ${name}`,
|
|
1725
|
+
path: `${skillDir}/SKILL.md`
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
} else {
|
|
1730
|
+
const skillPath = `${skillDir}/SKILL.md`;
|
|
1731
|
+
await createDirectory(sandbox, skillDir);
|
|
1732
|
+
await sandbox.writeFile(skillPath, content);
|
|
1733
|
+
try {
|
|
1734
|
+
const metadata = parseSkillMetadata(content, skillPath);
|
|
1735
|
+
skills.push(metadata);
|
|
1736
|
+
} catch {
|
|
1737
|
+
skills.push({
|
|
1738
|
+
name,
|
|
1739
|
+
description: `Skill: ${name}`,
|
|
1740
|
+
path: skillPath
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
return { skills };
|
|
1747
|
+
}
|
|
1748
|
+
async function seedSkillBundle(sandbox, skillDir, bundle) {
|
|
1749
|
+
await createDirectory(sandbox, skillDir);
|
|
1750
|
+
for (const [relativePath, content] of Object.entries(bundle.files)) {
|
|
1751
|
+
const fullPath = `${skillDir}/${relativePath}`;
|
|
1752
|
+
const parentDir = fullPath.substring(0, fullPath.lastIndexOf("/"));
|
|
1753
|
+
if (parentDir && parentDir !== skillDir) {
|
|
1754
|
+
await createDirectory(sandbox, parentDir);
|
|
1755
|
+
}
|
|
1756
|
+
await sandbox.writeFile(fullPath, content);
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
async function createDirectory(sandbox, path) {
|
|
1760
|
+
const normalizedPath = path.replace(/\/+$/, "");
|
|
1761
|
+
if (!normalizedPath)
|
|
1762
|
+
return;
|
|
1763
|
+
const exists = await sandbox.fileExists(normalizedPath);
|
|
1764
|
+
if (exists) {
|
|
1765
|
+
const isDir = await sandbox.isDirectory(normalizedPath);
|
|
1766
|
+
if (isDir)
|
|
1767
|
+
return;
|
|
1768
|
+
}
|
|
1769
|
+
await sandbox.exec(`mkdir -p "${normalizedPath}"`);
|
|
1770
|
+
}
|
|
1771
|
+
export {
|
|
1772
|
+
skillsToXml,
|
|
1773
|
+
setupAgentEnvironment,
|
|
1774
|
+
pruneMessagesByTokens,
|
|
1775
|
+
parseSkillMetadata,
|
|
1776
|
+
getContextStatus,
|
|
1777
|
+
fetchSkills,
|
|
1778
|
+
fetchSkill,
|
|
1779
|
+
estimateTokens,
|
|
1780
|
+
estimateMessagesTokens,
|
|
1781
|
+
estimateMessageTokens,
|
|
1782
|
+
discoverSkills,
|
|
1783
|
+
createWriteTool,
|
|
1784
|
+
createWebSearchTool,
|
|
1785
|
+
createWebFetchTool,
|
|
1786
|
+
createVercelSandbox,
|
|
1787
|
+
createTodoWriteTool,
|
|
1788
|
+
createTaskTool,
|
|
1789
|
+
createReadTool,
|
|
1790
|
+
createLocalSandbox,
|
|
1791
|
+
createGrepTool,
|
|
1792
|
+
createGlobTool,
|
|
1793
|
+
createExitPlanModeTool,
|
|
1794
|
+
createEditTool,
|
|
1795
|
+
createE2BSandbox,
|
|
1796
|
+
createCompactConfig,
|
|
1797
|
+
createBashTool,
|
|
1798
|
+
createAgentTools,
|
|
1799
|
+
contextNeedsCompaction,
|
|
1800
|
+
contextNeedsAttention,
|
|
1801
|
+
compactConversation,
|
|
1802
|
+
anthropicPromptCacheMiddleware,
|
|
1803
|
+
MODEL_CONTEXT_LIMITS,
|
|
1804
|
+
DEFAULT_CONFIG
|
|
1805
|
+
};
|