@robota-sdk/agent-tools 3.0.0-beta.60 → 3.0.0-beta.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -21
- package/dist/node/index.cjs +440 -95
- package/dist/node/index.d.cts +170 -15
- package/dist/node/index.d.ts +170 -15
- package/dist/node/index.js +417 -80
- package/package.json +4 -3
package/dist/node/index.cjs
CHANGED
|
@@ -30,17 +30,25 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
E2BSandboxClient: () => E2BSandboxClient,
|
|
33
34
|
FunctionTool: () => FunctionTool,
|
|
35
|
+
InMemorySandboxClient: () => InMemorySandboxClient,
|
|
34
36
|
OpenAPITool: () => OpenAPITool,
|
|
35
37
|
ToolRegistry: () => ToolRegistry,
|
|
38
|
+
applyWorkspaceManifest: () => applyWorkspaceManifest,
|
|
36
39
|
bashTool: () => bashTool,
|
|
40
|
+
createBashTool: () => createBashTool,
|
|
41
|
+
createEditTool: () => createEditTool,
|
|
37
42
|
createFunctionTool: () => createFunctionTool,
|
|
38
43
|
createOpenAPITool: () => createOpenAPITool,
|
|
44
|
+
createReadTool: () => createReadTool,
|
|
45
|
+
createWriteTool: () => createWriteTool,
|
|
39
46
|
createZodFunctionTool: () => createZodFunctionTool,
|
|
40
47
|
editTool: () => editTool,
|
|
41
48
|
globTool: () => globTool,
|
|
42
49
|
grepTool: () => grepTool,
|
|
43
50
|
readTool: () => readTool,
|
|
51
|
+
validateWorkspaceManifestPath: () => validateWorkspaceManifestPath,
|
|
44
52
|
webFetchTool: () => webFetchTool,
|
|
45
53
|
webSearchTool: () => webSearchTool,
|
|
46
54
|
writeTool: () => writeTool,
|
|
@@ -48,6 +56,275 @@ __export(index_exports, {
|
|
|
48
56
|
});
|
|
49
57
|
module.exports = __toCommonJS(index_exports);
|
|
50
58
|
|
|
59
|
+
// src/sandbox/e2b-sandbox-client.ts
|
|
60
|
+
var E2BSandboxClient = class {
|
|
61
|
+
sandbox;
|
|
62
|
+
connectSandbox;
|
|
63
|
+
createSandboxFromSnapshot;
|
|
64
|
+
constructor(options) {
|
|
65
|
+
this.sandbox = options.sandbox;
|
|
66
|
+
this.connectSandbox = options.connectSandbox;
|
|
67
|
+
this.createSandboxFromSnapshot = options.createSandboxFromSnapshot;
|
|
68
|
+
}
|
|
69
|
+
async run(command, options) {
|
|
70
|
+
const result = await this.sandbox.commands.run(command, {
|
|
71
|
+
background: false,
|
|
72
|
+
timeoutMs: options?.timeoutMs,
|
|
73
|
+
cwd: options?.workingDirectory
|
|
74
|
+
});
|
|
75
|
+
return {
|
|
76
|
+
stdout: result.stdout ?? "",
|
|
77
|
+
stderr: result.stderr ?? "",
|
|
78
|
+
exitCode: result.exitCode ?? result.exit_code ?? 0
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
async readFile(path) {
|
|
82
|
+
const content = await this.sandbox.files.read(path);
|
|
83
|
+
return typeof content === "string" ? content : Buffer.from(content).toString("utf8");
|
|
84
|
+
}
|
|
85
|
+
async writeFile(path, content) {
|
|
86
|
+
await this.sandbox.files.write(path, content);
|
|
87
|
+
}
|
|
88
|
+
async snapshot() {
|
|
89
|
+
if (this.sandbox.createSnapshot) {
|
|
90
|
+
const snapshot = await this.sandbox.createSnapshot();
|
|
91
|
+
const snapshotId = snapshot.snapshotId ?? snapshot.id;
|
|
92
|
+
if (!snapshotId) {
|
|
93
|
+
throw new Error("E2B createSnapshot() did not return a snapshot id.");
|
|
94
|
+
}
|
|
95
|
+
return snapshotId;
|
|
96
|
+
}
|
|
97
|
+
const sandboxId = this.sandbox.sandboxId;
|
|
98
|
+
if (!sandboxId) {
|
|
99
|
+
throw new Error("E2B sandboxId is required to create a resumable sandbox snapshot.");
|
|
100
|
+
}
|
|
101
|
+
if (!this.sandbox.pause) {
|
|
102
|
+
throw new Error("E2B sandbox adapter does not expose pause().");
|
|
103
|
+
}
|
|
104
|
+
await this.sandbox.pause();
|
|
105
|
+
return sandboxId;
|
|
106
|
+
}
|
|
107
|
+
async restore(snapshotId) {
|
|
108
|
+
if (this.createSandboxFromSnapshot) {
|
|
109
|
+
this.sandbox = await this.createSandboxFromSnapshot(snapshotId);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (this.connectSandbox) {
|
|
113
|
+
this.sandbox = await this.connectSandbox(snapshotId);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (this.sandbox.sandboxId === snapshotId && this.sandbox.connect) {
|
|
117
|
+
this.sandbox = await this.sandbox.connect();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
throw new Error(
|
|
121
|
+
"E2B sandbox restore requires connectSandbox(snapshotId) or sandbox.connect()."
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// src/sandbox/in-memory-sandbox-client.ts
|
|
127
|
+
var InMemorySandboxClient = class {
|
|
128
|
+
files = /* @__PURE__ */ new Map();
|
|
129
|
+
snapshots = /* @__PURE__ */ new Map();
|
|
130
|
+
runHandler;
|
|
131
|
+
snapshotSequence = 0;
|
|
132
|
+
constructor(options = {}) {
|
|
133
|
+
for (const [path, content] of Object.entries(options.files ?? {})) {
|
|
134
|
+
this.files.set(path, content);
|
|
135
|
+
}
|
|
136
|
+
this.runHandler = options.runHandler;
|
|
137
|
+
}
|
|
138
|
+
async run(command, options) {
|
|
139
|
+
if (this.runHandler) {
|
|
140
|
+
return this.runHandler(command, options, this.files);
|
|
141
|
+
}
|
|
142
|
+
return { stdout: "", stderr: "", exitCode: 0 };
|
|
143
|
+
}
|
|
144
|
+
async readFile(path) {
|
|
145
|
+
const content = this.files.get(path);
|
|
146
|
+
if (content === void 0) {
|
|
147
|
+
throw new Error(`Sandbox file not found: ${path}`);
|
|
148
|
+
}
|
|
149
|
+
return content;
|
|
150
|
+
}
|
|
151
|
+
async writeFile(path, content) {
|
|
152
|
+
this.files.set(path, content);
|
|
153
|
+
}
|
|
154
|
+
async snapshot() {
|
|
155
|
+
const snapshotId = `snapshot-${++this.snapshotSequence}`;
|
|
156
|
+
this.snapshots.set(snapshotId, new Map(this.files));
|
|
157
|
+
return snapshotId;
|
|
158
|
+
}
|
|
159
|
+
async restore(snapshotId) {
|
|
160
|
+
const snapshot = this.snapshots.get(snapshotId);
|
|
161
|
+
if (!snapshot) {
|
|
162
|
+
throw new Error(`Sandbox snapshot not found: ${snapshotId}`);
|
|
163
|
+
}
|
|
164
|
+
this.files.clear();
|
|
165
|
+
for (const [path, content] of snapshot.entries()) {
|
|
166
|
+
this.files.set(path, content);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
getFile(path) {
|
|
170
|
+
return this.files.get(path);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// src/sandbox/workspace-manifest.ts
|
|
175
|
+
var import_promises = require("fs/promises");
|
|
176
|
+
var import_node_path = require("path");
|
|
177
|
+
var DEFAULT_TARGET_ROOT = "/workspace";
|
|
178
|
+
var WINDOWS_ABSOLUTE_PATH_PATTERN = /^[A-Za-z]:[\\/]/;
|
|
179
|
+
var SHELL_QUOTE_PATTERN = /'/g;
|
|
180
|
+
async function applyWorkspaceManifest(sandboxClient, manifest, options = {}) {
|
|
181
|
+
if (sandboxClient.applyManifest) {
|
|
182
|
+
return sandboxClient.applyManifest(manifest, options);
|
|
183
|
+
}
|
|
184
|
+
const targetRoot = normalizeSandboxRoot(options.targetRoot ?? DEFAULT_TARGET_ROOT);
|
|
185
|
+
const appliedEntries = [];
|
|
186
|
+
for (const [rawPath, entry] of Object.entries(manifest.entries)) {
|
|
187
|
+
const path = validateWorkspaceManifestPath(rawPath);
|
|
188
|
+
const targetPath = joinSandboxPath(targetRoot, path);
|
|
189
|
+
appliedEntries.push(
|
|
190
|
+
await applyManifestEntry(sandboxClient, path, targetPath, targetRoot, entry, options)
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
return { entries: appliedEntries };
|
|
194
|
+
}
|
|
195
|
+
function validateWorkspaceManifestPath(path) {
|
|
196
|
+
if (path.length === 0) {
|
|
197
|
+
throw new Error("workspace manifest path must not be empty");
|
|
198
|
+
}
|
|
199
|
+
if (path.includes("\0")) {
|
|
200
|
+
throw new Error("workspace manifest path must not contain NUL bytes");
|
|
201
|
+
}
|
|
202
|
+
if (path.startsWith("/") || path.startsWith("\\") || WINDOWS_ABSOLUTE_PATH_PATTERN.test(path)) {
|
|
203
|
+
throw new Error("workspace manifest path must be workspace-relative");
|
|
204
|
+
}
|
|
205
|
+
const parts = path.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
206
|
+
if (parts.length === 0) {
|
|
207
|
+
throw new Error("workspace manifest path must not resolve to the workspace root");
|
|
208
|
+
}
|
|
209
|
+
if (parts.some((part) => part === "..")) {
|
|
210
|
+
throw new Error("workspace manifest path cannot contain traversal segments");
|
|
211
|
+
}
|
|
212
|
+
const normalizedParts = parts.filter((part) => part !== ".");
|
|
213
|
+
if (normalizedParts.length === 0) {
|
|
214
|
+
throw new Error("workspace manifest path must not resolve to the workspace root");
|
|
215
|
+
}
|
|
216
|
+
return normalizedParts.join("/");
|
|
217
|
+
}
|
|
218
|
+
async function applyManifestEntry(sandboxClient, path, targetPath, targetRoot, entry, options) {
|
|
219
|
+
switch (entry.type) {
|
|
220
|
+
case "file":
|
|
221
|
+
await writeSandboxFile(sandboxClient, targetPath, targetRoot, entry.content);
|
|
222
|
+
return createAppliedEntry(path, entry.type);
|
|
223
|
+
case "dir":
|
|
224
|
+
await createSandboxDirectory(sandboxClient, targetPath);
|
|
225
|
+
return createAppliedEntry(path, entry.type);
|
|
226
|
+
case "localFile":
|
|
227
|
+
await copyLocalFile(sandboxClient, entry.src, targetPath, targetRoot, options);
|
|
228
|
+
return createAppliedEntry(path, entry.type);
|
|
229
|
+
case "localDir":
|
|
230
|
+
await copyLocalDirectory(sandboxClient, entry.src, targetPath, options);
|
|
231
|
+
return createAppliedEntry(path, entry.type);
|
|
232
|
+
case "gitRepo":
|
|
233
|
+
await cloneGitRepository(sandboxClient, entry, targetPath);
|
|
234
|
+
return createAppliedEntry(path, entry.type);
|
|
235
|
+
case "s3Mount":
|
|
236
|
+
case "gcsMount":
|
|
237
|
+
case "r2Mount":
|
|
238
|
+
case "azureBlobMount":
|
|
239
|
+
return {
|
|
240
|
+
path,
|
|
241
|
+
type: entry.type,
|
|
242
|
+
status: "unsupported",
|
|
243
|
+
message: `${entry.type} requires a provider-specific sandbox adapter.`
|
|
244
|
+
};
|
|
245
|
+
default:
|
|
246
|
+
return assertUnreachable(entry);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function createAppliedEntry(path, type) {
|
|
250
|
+
return { path, type, status: "applied" };
|
|
251
|
+
}
|
|
252
|
+
async function copyLocalFile(sandboxClient, source, targetPath, targetRoot, options) {
|
|
253
|
+
const hostSourcePath = resolveHostSourcePath(source, options.hostRoot);
|
|
254
|
+
const content = await (0, import_promises.readFile)(hostSourcePath, "utf8");
|
|
255
|
+
await writeSandboxFile(sandboxClient, targetPath, targetRoot, content);
|
|
256
|
+
}
|
|
257
|
+
async function copyLocalDirectory(sandboxClient, source, targetPath, options) {
|
|
258
|
+
const hostSourcePath = resolveHostSourcePath(source, options.hostRoot);
|
|
259
|
+
await copyLocalDirectoryRecursive(sandboxClient, hostSourcePath, targetPath);
|
|
260
|
+
}
|
|
261
|
+
async function copyLocalDirectoryRecursive(sandboxClient, sourcePath, targetPath) {
|
|
262
|
+
await createSandboxDirectory(sandboxClient, targetPath);
|
|
263
|
+
const entries = await (0, import_promises.readdir)(sourcePath, { withFileTypes: true });
|
|
264
|
+
for (const entry of entries) {
|
|
265
|
+
const childSourcePath = (0, import_node_path.join)(sourcePath, entry.name);
|
|
266
|
+
const childTargetPath = joinSandboxPath(targetPath, entry.name);
|
|
267
|
+
if (entry.isDirectory()) {
|
|
268
|
+
await copyLocalDirectoryRecursive(sandboxClient, childSourcePath, childTargetPath);
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (entry.isFile()) {
|
|
272
|
+
const content = await (0, import_promises.readFile)(childSourcePath, "utf8");
|
|
273
|
+
await sandboxClient.writeFile(childTargetPath, content);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
async function cloneGitRepository(sandboxClient, entry, targetPath) {
|
|
278
|
+
const shallowArgs = entry.shallow === false ? "" : " --depth 1";
|
|
279
|
+
const refArgs = entry.ref ? ` --branch ${quoteShellArg(entry.ref)}` : "";
|
|
280
|
+
await runSandboxCommand(
|
|
281
|
+
sandboxClient,
|
|
282
|
+
`git clone${shallowArgs}${refArgs} ${quoteShellArg(entry.url)} ${quoteShellArg(targetPath)}`
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
async function writeSandboxFile(sandboxClient, targetPath, targetRoot, content) {
|
|
286
|
+
const parentPath = import_node_path.posix.dirname(targetPath);
|
|
287
|
+
if (parentPath !== targetRoot) {
|
|
288
|
+
await createSandboxDirectory(sandboxClient, parentPath);
|
|
289
|
+
}
|
|
290
|
+
await sandboxClient.writeFile(targetPath, content);
|
|
291
|
+
}
|
|
292
|
+
async function createSandboxDirectory(sandboxClient, targetPath) {
|
|
293
|
+
await runSandboxCommand(sandboxClient, `mkdir -p ${quoteShellArg(targetPath)}`);
|
|
294
|
+
}
|
|
295
|
+
async function runSandboxCommand(sandboxClient, command) {
|
|
296
|
+
const result = await sandboxClient.run(command);
|
|
297
|
+
if (result.exitCode !== 0) {
|
|
298
|
+
throw new Error(
|
|
299
|
+
`workspace manifest command failed: ${command}
|
|
300
|
+
${result.stderr ?? result.stdout}`
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function resolveHostSourcePath(source, hostRoot) {
|
|
305
|
+
return (0, import_node_path.isAbsolute)(source) ? (0, import_node_path.resolve)(source) : (0, import_node_path.resolve)(hostRoot ?? process.cwd(), source);
|
|
306
|
+
}
|
|
307
|
+
function normalizeSandboxRoot(root) {
|
|
308
|
+
const normalized = root.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
309
|
+
if (!normalized.startsWith("/")) {
|
|
310
|
+
throw new Error("workspace manifest targetRoot must be an absolute sandbox path");
|
|
311
|
+
}
|
|
312
|
+
return normalized.length === 0 ? "/" : normalized;
|
|
313
|
+
}
|
|
314
|
+
function joinSandboxPath(root, path) {
|
|
315
|
+
const normalizedRoot = normalizeSandboxRoot(root);
|
|
316
|
+
if (normalizedRoot === "/") {
|
|
317
|
+
return `/${path}`;
|
|
318
|
+
}
|
|
319
|
+
return `${normalizedRoot}/${path}`;
|
|
320
|
+
}
|
|
321
|
+
function quoteShellArg(value) {
|
|
322
|
+
return `'${value.replace(SHELL_QUOTE_PATTERN, "'\\''")}'`;
|
|
323
|
+
}
|
|
324
|
+
function assertUnreachable(value) {
|
|
325
|
+
throw new Error(`unsupported workspace manifest entry: ${JSON.stringify(value)}`);
|
|
326
|
+
}
|
|
327
|
+
|
|
51
328
|
// src/registry/tool-registry.ts
|
|
52
329
|
var import_agent_core = require("@robota-sdk/agent-core");
|
|
53
330
|
var import_agent_core2 = require("@robota-sdk/agent-core");
|
|
@@ -860,9 +1137,33 @@ var BashSchema = import_zod.z.object({
|
|
|
860
1137
|
timeout: import_zod.z.number().optional().describe("Optional timeout in milliseconds (max 600000). Default is 120000 (2 minutes)"),
|
|
861
1138
|
workingDirectory: import_zod.z.string().optional().describe("Working directory for the command. Defaults to the current working directory")
|
|
862
1139
|
});
|
|
863
|
-
async function runBash(args) {
|
|
1140
|
+
async function runBash(args, options = {}) {
|
|
864
1141
|
const { command, timeout = DEFAULT_TIMEOUT_MS, workingDirectory } = args;
|
|
865
|
-
|
|
1142
|
+
if (options.sandboxClient) {
|
|
1143
|
+
try {
|
|
1144
|
+
const sandboxResult = await options.sandboxClient.run(command, {
|
|
1145
|
+
timeoutMs: timeout,
|
|
1146
|
+
workingDirectory
|
|
1147
|
+
});
|
|
1148
|
+
const output = sandboxResult.stderr ? `${sandboxResult.stdout}
|
|
1149
|
+
stderr:
|
|
1150
|
+
${sandboxResult.stderr}` : sandboxResult.stdout;
|
|
1151
|
+
const result = {
|
|
1152
|
+
success: true,
|
|
1153
|
+
output,
|
|
1154
|
+
exitCode: sandboxResult.exitCode
|
|
1155
|
+
};
|
|
1156
|
+
return JSON.stringify(result);
|
|
1157
|
+
} catch (err) {
|
|
1158
|
+
const result = {
|
|
1159
|
+
success: false,
|
|
1160
|
+
output: "",
|
|
1161
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1162
|
+
};
|
|
1163
|
+
return JSON.stringify(result);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
return new Promise((resolve4) => {
|
|
866
1167
|
const stdoutChunks = [];
|
|
867
1168
|
const stderrChunks = [];
|
|
868
1169
|
let timedOut = false;
|
|
@@ -891,7 +1192,7 @@ async function runBash(args) {
|
|
|
891
1192
|
if (settled) return;
|
|
892
1193
|
settled = true;
|
|
893
1194
|
clearTimeout(timer);
|
|
894
|
-
|
|
1195
|
+
resolve4(JSON.stringify(result));
|
|
895
1196
|
}
|
|
896
1197
|
child.on("error", (err) => {
|
|
897
1198
|
settle({
|
|
@@ -924,17 +1225,20 @@ ${stderr}` : stdout;
|
|
|
924
1225
|
});
|
|
925
1226
|
});
|
|
926
1227
|
}
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
1228
|
+
function createBashTool(options = {}) {
|
|
1229
|
+
return createZodFunctionTool(
|
|
1230
|
+
"Bash",
|
|
1231
|
+
"Executes a given bash command and returns its output.\n\nThe working directory persists between commands, but shell state does not.\n\nIMPORTANT: Avoid using this tool to run `find`, `grep`, `cat`, `head`, `tail`, `sed`, `awk`, or `echo` commands. Instead, use the appropriate dedicated tool:\n - File search: Use Glob (NOT find or ls)\n - Content search: Use Grep (NOT grep or rg)\n - Read files: Use Read (NOT cat/head/tail)\n - Edit files: Use Edit (NOT sed/awk)\n\nFor simple commands, keep the description brief (5-10 words). For complex commands, include enough context to clarify what the command does.\n\nOutput is limited to 30,000 characters. Longer output will be middle-truncated.",
|
|
1232
|
+
BashSchema,
|
|
1233
|
+
async (params) => {
|
|
1234
|
+
return runBash(params, options);
|
|
1235
|
+
}
|
|
1236
|
+
);
|
|
1237
|
+
}
|
|
1238
|
+
var bashTool = createBashTool();
|
|
935
1239
|
|
|
936
1240
|
// src/builtins/read-tool.ts
|
|
937
|
-
var
|
|
1241
|
+
var import_promises2 = require("fs/promises");
|
|
938
1242
|
var import_zod2 = require("zod");
|
|
939
1243
|
var DEFAULT_LIMIT = 2e3;
|
|
940
1244
|
var ReadSchema = import_zod2.z.object({
|
|
@@ -961,94 +1265,113 @@ function formatWithLineNumbers(lines, startLine) {
|
|
|
961
1265
|
return `${lineNum} ${line}`;
|
|
962
1266
|
}).join("\n");
|
|
963
1267
|
}
|
|
964
|
-
|
|
1268
|
+
function formatReadResult(filePath, content, startLine, limit) {
|
|
1269
|
+
const allLines = content.split("\n");
|
|
1270
|
+
if (allLines[allLines.length - 1] === "") {
|
|
1271
|
+
allLines.pop();
|
|
1272
|
+
}
|
|
1273
|
+
const zeroBasedStart = startLine - 1;
|
|
1274
|
+
const selectedLines = allLines.slice(zeroBasedStart, zeroBasedStart + limit);
|
|
1275
|
+
const output = formatWithLineNumbers(selectedLines, startLine);
|
|
1276
|
+
const totalLines = allLines.length;
|
|
1277
|
+
const returnedLines = selectedLines.length;
|
|
1278
|
+
const header = returnedLines < totalLines ? `[File: ${filePath} (lines ${startLine}-${startLine + returnedLines - 1} of ${totalLines})]
|
|
1279
|
+
` : `[File: ${filePath} (${totalLines} lines)]
|
|
1280
|
+
`;
|
|
1281
|
+
const result = {
|
|
1282
|
+
success: true,
|
|
1283
|
+
output: header + output
|
|
1284
|
+
};
|
|
1285
|
+
return JSON.stringify(result);
|
|
1286
|
+
}
|
|
1287
|
+
async function readFileTool(args, options = {}) {
|
|
965
1288
|
const { filePath, offset, limit = DEFAULT_LIMIT } = args;
|
|
966
1289
|
const startLine = offset !== void 0 && offset > 0 ? offset : 1;
|
|
1290
|
+
if (options.sandboxClient) {
|
|
1291
|
+
try {
|
|
1292
|
+
const content2 = await options.sandboxClient.readFile(filePath);
|
|
1293
|
+
return formatReadResult(filePath, content2, startLine, limit);
|
|
1294
|
+
} catch (err) {
|
|
1295
|
+
const result = {
|
|
1296
|
+
success: false,
|
|
1297
|
+
output: "",
|
|
1298
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1299
|
+
};
|
|
1300
|
+
return JSON.stringify(result);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
967
1303
|
let fileStats;
|
|
968
1304
|
try {
|
|
969
|
-
fileStats = await (0,
|
|
1305
|
+
fileStats = await (0, import_promises2.stat)(filePath);
|
|
970
1306
|
} catch (err) {
|
|
971
|
-
const
|
|
1307
|
+
const result = {
|
|
972
1308
|
success: false,
|
|
973
1309
|
output: "",
|
|
974
1310
|
error: `File not found: ${filePath}`
|
|
975
1311
|
};
|
|
976
|
-
return JSON.stringify(
|
|
1312
|
+
return JSON.stringify(result);
|
|
977
1313
|
}
|
|
978
1314
|
if (!fileStats.isFile()) {
|
|
979
|
-
const
|
|
1315
|
+
const result = {
|
|
980
1316
|
success: false,
|
|
981
1317
|
output: "",
|
|
982
1318
|
error: `Path is not a file: ${filePath}`
|
|
983
1319
|
};
|
|
984
|
-
return JSON.stringify(
|
|
1320
|
+
return JSON.stringify(result);
|
|
985
1321
|
}
|
|
986
1322
|
let buffer;
|
|
987
1323
|
try {
|
|
988
|
-
buffer = await (0,
|
|
1324
|
+
buffer = await (0, import_promises2.readFile)(filePath);
|
|
989
1325
|
} catch (err) {
|
|
990
|
-
const
|
|
1326
|
+
const result = {
|
|
991
1327
|
success: false,
|
|
992
1328
|
output: "",
|
|
993
1329
|
error: err instanceof Error ? err.message : String(err)
|
|
994
1330
|
};
|
|
995
|
-
return JSON.stringify(
|
|
1331
|
+
return JSON.stringify(result);
|
|
996
1332
|
}
|
|
997
1333
|
if (isBinary(buffer)) {
|
|
998
|
-
const
|
|
1334
|
+
const result = {
|
|
999
1335
|
success: false,
|
|
1000
1336
|
output: "",
|
|
1001
1337
|
error: `Binary file not supported: ${filePath}`
|
|
1002
1338
|
};
|
|
1003
|
-
return JSON.stringify(
|
|
1339
|
+
return JSON.stringify(result);
|
|
1004
1340
|
}
|
|
1005
1341
|
const content = buffer.toString("utf8");
|
|
1006
|
-
|
|
1007
|
-
if (allLines[allLines.length - 1] === "") {
|
|
1008
|
-
allLines.pop();
|
|
1009
|
-
}
|
|
1010
|
-
const zeroBasedStart = startLine - 1;
|
|
1011
|
-
const selectedLines = allLines.slice(zeroBasedStart, zeroBasedStart + limit);
|
|
1012
|
-
const output = formatWithLineNumbers(selectedLines, startLine);
|
|
1013
|
-
const totalLines = allLines.length;
|
|
1014
|
-
const returnedLines = selectedLines.length;
|
|
1015
|
-
const header = returnedLines < totalLines ? `[File: ${filePath} (lines ${startLine}-${startLine + returnedLines - 1} of ${totalLines})]
|
|
1016
|
-
` : `[File: ${filePath} (${totalLines} lines)]
|
|
1017
|
-
`;
|
|
1018
|
-
const result = {
|
|
1019
|
-
success: true,
|
|
1020
|
-
output: header + output
|
|
1021
|
-
};
|
|
1022
|
-
return JSON.stringify(result);
|
|
1342
|
+
return formatReadResult(filePath, content, startLine, limit);
|
|
1023
1343
|
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1344
|
+
function createReadTool(options = {}) {
|
|
1345
|
+
return createZodFunctionTool(
|
|
1346
|
+
"Read",
|
|
1347
|
+
"Reads a file from the local filesystem.\n\nBy default, reads up to 2000 lines from the beginning of the file. You can optionally specify offset and limit for partial reads.\n\nResults are returned using cat -n format, with line numbers starting at 1.\n\nThe file_path parameter must be an absolute path, not a relative path.",
|
|
1348
|
+
ReadSchema,
|
|
1349
|
+
async (params) => {
|
|
1350
|
+
return readFileTool(params, options);
|
|
1351
|
+
}
|
|
1352
|
+
);
|
|
1353
|
+
}
|
|
1354
|
+
var readTool = createReadTool();
|
|
1032
1355
|
|
|
1033
1356
|
// src/builtins/write-tool.ts
|
|
1034
1357
|
var import_zod3 = require("zod");
|
|
1035
1358
|
|
|
1036
1359
|
// src/builtins/atomic-file-write.ts
|
|
1037
1360
|
var import_node_crypto = require("crypto");
|
|
1038
|
-
var
|
|
1039
|
-
var
|
|
1361
|
+
var import_promises3 = require("fs/promises");
|
|
1362
|
+
var import_node_path2 = require("path");
|
|
1040
1363
|
var TEMP_RANDOM_BYTES = 6;
|
|
1041
1364
|
var PRESERVED_MODE_BITS = 4095;
|
|
1042
1365
|
var MISSING_FILE_ERROR_CODE = "ENOENT";
|
|
1043
1366
|
function createTempFilePath(filePath) {
|
|
1044
|
-
const dir = (0,
|
|
1045
|
-
const name = (0,
|
|
1367
|
+
const dir = (0, import_node_path2.dirname)(filePath);
|
|
1368
|
+
const name = (0, import_node_path2.basename)(filePath);
|
|
1046
1369
|
const suffix = (0, import_node_crypto.randomBytes)(TEMP_RANDOM_BYTES).toString("hex");
|
|
1047
|
-
return (0,
|
|
1370
|
+
return (0, import_node_path2.join)(dir, `.${name}.robota-tmp-${process.pid}-${Date.now()}-${suffix}`);
|
|
1048
1371
|
}
|
|
1049
1372
|
async function readExistingMode(filePath) {
|
|
1050
1373
|
try {
|
|
1051
|
-
const fileStats = await (0,
|
|
1374
|
+
const fileStats = await (0, import_promises3.stat)(filePath);
|
|
1052
1375
|
return fileStats.mode & PRESERVED_MODE_BITS;
|
|
1053
1376
|
} catch (error) {
|
|
1054
1377
|
if (error instanceof Error && hasErrorCode(error, MISSING_FILE_ERROR_CODE)) return void 0;
|
|
@@ -1059,18 +1382,18 @@ function hasErrorCode(error, code) {
|
|
|
1059
1382
|
return "code" in error && error.code === code;
|
|
1060
1383
|
}
|
|
1061
1384
|
async function atomicWriteUtf8File(filePath, content) {
|
|
1062
|
-
const dir = (0,
|
|
1063
|
-
await (0,
|
|
1385
|
+
const dir = (0, import_node_path2.dirname)(filePath);
|
|
1386
|
+
await (0, import_promises3.mkdir)(dir, { recursive: true });
|
|
1064
1387
|
const existingMode = await readExistingMode(filePath);
|
|
1065
1388
|
const tempFilePath = createTempFilePath(filePath);
|
|
1066
1389
|
try {
|
|
1067
|
-
await (0,
|
|
1390
|
+
await (0, import_promises3.writeFile)(tempFilePath, content, "utf8");
|
|
1068
1391
|
if (existingMode !== void 0) {
|
|
1069
|
-
await (0,
|
|
1392
|
+
await (0, import_promises3.chmod)(tempFilePath, existingMode);
|
|
1070
1393
|
}
|
|
1071
|
-
await (0,
|
|
1394
|
+
await (0, import_promises3.rename)(tempFilePath, filePath);
|
|
1072
1395
|
} catch (error) {
|
|
1073
|
-
await (0,
|
|
1396
|
+
await (0, import_promises3.rm)(tempFilePath, { force: true }).catch(() => void 0);
|
|
1074
1397
|
throw error;
|
|
1075
1398
|
}
|
|
1076
1399
|
}
|
|
@@ -1080,10 +1403,14 @@ var WriteSchema = import_zod3.z.object({
|
|
|
1080
1403
|
filePath: import_zod3.z.string().describe("The absolute path to the file to write"),
|
|
1081
1404
|
content: import_zod3.z.string().describe("The content to write to the file")
|
|
1082
1405
|
});
|
|
1083
|
-
async function writeFileTool(args) {
|
|
1406
|
+
async function writeFileTool(args, options = {}) {
|
|
1084
1407
|
const { filePath, content } = args;
|
|
1085
1408
|
try {
|
|
1086
|
-
|
|
1409
|
+
if (options.sandboxClient) {
|
|
1410
|
+
await options.sandboxClient.writeFile(filePath, content);
|
|
1411
|
+
} else {
|
|
1412
|
+
await atomicWriteUtf8File(filePath, content);
|
|
1413
|
+
}
|
|
1087
1414
|
const result = {
|
|
1088
1415
|
success: true,
|
|
1089
1416
|
output: `Written ${Buffer.byteLength(content, "utf8")} bytes to ${filePath}`
|
|
@@ -1098,17 +1425,20 @@ async function writeFileTool(args) {
|
|
|
1098
1425
|
return JSON.stringify(result);
|
|
1099
1426
|
}
|
|
1100
1427
|
}
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1428
|
+
function createWriteTool(options = {}) {
|
|
1429
|
+
return createZodFunctionTool(
|
|
1430
|
+
"Write",
|
|
1431
|
+
"Writes a file to the local filesystem. This will overwrite an existing file if one exists.\n\nALWAYS prefer the Edit tool for modifying existing files \u2014 it only sends the diff. Only use this tool to create new files or for complete rewrites.\n\nNEVER create documentation files (*.md) or README files unless explicitly requested by the user.",
|
|
1432
|
+
WriteSchema,
|
|
1433
|
+
async (params) => {
|
|
1434
|
+
return writeFileTool(params, options);
|
|
1435
|
+
}
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1438
|
+
var writeTool = createWriteTool();
|
|
1109
1439
|
|
|
1110
1440
|
// src/builtins/edit-tool.ts
|
|
1111
|
-
var
|
|
1441
|
+
var import_promises4 = require("fs/promises");
|
|
1112
1442
|
var import_zod4 = require("zod");
|
|
1113
1443
|
var EditSchema = import_zod4.z.object({
|
|
1114
1444
|
filePath: import_zod4.z.string().describe("The absolute path to the file to modify"),
|
|
@@ -1118,11 +1448,11 @@ var EditSchema = import_zod4.z.object({
|
|
|
1118
1448
|
"Replace all occurrences of old_string (default: false). Useful for renaming variables"
|
|
1119
1449
|
)
|
|
1120
1450
|
});
|
|
1121
|
-
async function editFileTool(args) {
|
|
1451
|
+
async function editFileTool(args, options = {}) {
|
|
1122
1452
|
const { filePath, oldString, newString, replaceAll = false } = args;
|
|
1123
1453
|
let content;
|
|
1124
1454
|
try {
|
|
1125
|
-
content = await (0,
|
|
1455
|
+
content = options.sandboxClient ? await options.sandboxClient.readFile(filePath) : await (0, import_promises4.readFile)(filePath, "utf8");
|
|
1126
1456
|
} catch (err) {
|
|
1127
1457
|
const result2 = {
|
|
1128
1458
|
success: false,
|
|
@@ -1154,7 +1484,11 @@ async function editFileTool(args) {
|
|
|
1154
1484
|
}
|
|
1155
1485
|
const updated = replaceAll ? content.split(oldString).join(newString) : content.slice(0, content.indexOf(oldString)) + newString + content.slice(content.indexOf(oldString) + oldString.length);
|
|
1156
1486
|
try {
|
|
1157
|
-
|
|
1487
|
+
if (options.sandboxClient) {
|
|
1488
|
+
await options.sandboxClient.writeFile(filePath, updated);
|
|
1489
|
+
} else {
|
|
1490
|
+
await atomicWriteUtf8File(filePath, updated);
|
|
1491
|
+
}
|
|
1158
1492
|
} catch (err) {
|
|
1159
1493
|
const result2 = {
|
|
1160
1494
|
success: false,
|
|
@@ -1173,18 +1507,21 @@ async function editFileTool(args) {
|
|
|
1173
1507
|
};
|
|
1174
1508
|
return JSON.stringify(result);
|
|
1175
1509
|
}
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1510
|
+
function createEditTool(options = {}) {
|
|
1511
|
+
return createZodFunctionTool(
|
|
1512
|
+
"Edit",
|
|
1513
|
+
"Performs exact string replacements in files.\n\nYou must use the Read tool at least once before editing. When editing text from Read output, preserve the exact indentation.\n\nThe edit will FAIL if old_string is not unique in the file. Either provide more surrounding context to make it unique, or use replace_all to change every instance.\n\nALWAYS prefer editing existing files over creating new ones.",
|
|
1514
|
+
EditSchema,
|
|
1515
|
+
async (params) => {
|
|
1516
|
+
return editFileTool(params, options);
|
|
1517
|
+
}
|
|
1518
|
+
);
|
|
1519
|
+
}
|
|
1520
|
+
var editTool = createEditTool();
|
|
1184
1521
|
|
|
1185
1522
|
// src/builtins/glob-tool.ts
|
|
1186
|
-
var
|
|
1187
|
-
var
|
|
1523
|
+
var import_promises5 = require("fs/promises");
|
|
1524
|
+
var import_node_path3 = require("path");
|
|
1188
1525
|
var import_fast_glob = __toESM(require("fast-glob"), 1);
|
|
1189
1526
|
var import_zod5 = require("zod");
|
|
1190
1527
|
var DEFAULT_MAX_RESULTS = 1e3;
|
|
@@ -1199,7 +1536,7 @@ var GlobSchema = import_zod5.z.object({
|
|
|
1199
1536
|
});
|
|
1200
1537
|
async function globFileTool(args) {
|
|
1201
1538
|
const { pattern, path: basePath } = args;
|
|
1202
|
-
const cwd = basePath ? (0,
|
|
1539
|
+
const cwd = basePath ? (0, import_node_path3.resolve)(basePath) : process.cwd();
|
|
1203
1540
|
let matches;
|
|
1204
1541
|
try {
|
|
1205
1542
|
matches = await (0, import_fast_glob.default)(pattern, {
|
|
@@ -1218,9 +1555,9 @@ async function globFileTool(args) {
|
|
|
1218
1555
|
}
|
|
1219
1556
|
const withMtime = await Promise.all(
|
|
1220
1557
|
matches.map(async (p) => {
|
|
1221
|
-
const absPath = (0,
|
|
1558
|
+
const absPath = (0, import_node_path3.resolve)(cwd, p);
|
|
1222
1559
|
try {
|
|
1223
|
-
const s = await (0,
|
|
1560
|
+
const s = await (0, import_promises5.stat)(absPath);
|
|
1224
1561
|
return { path: p, mtime: s.mtimeMs };
|
|
1225
1562
|
} catch {
|
|
1226
1563
|
return { path: p, mtime: 0 };
|
|
@@ -1255,8 +1592,8 @@ var globTool = createZodFunctionTool(
|
|
|
1255
1592
|
);
|
|
1256
1593
|
|
|
1257
1594
|
// src/builtins/grep-tool.ts
|
|
1258
|
-
var
|
|
1259
|
-
var
|
|
1595
|
+
var import_promises6 = require("fs/promises");
|
|
1596
|
+
var import_node_path4 = require("path");
|
|
1260
1597
|
var import_zod6 = require("zod");
|
|
1261
1598
|
var GrepSchema = import_zod6.z.object({
|
|
1262
1599
|
pattern: import_zod6.z.string().describe("The regular expression pattern to search for in file contents"),
|
|
@@ -1284,16 +1621,16 @@ async function collectFiles(dirPath, glob) {
|
|
|
1284
1621
|
async function walk(current) {
|
|
1285
1622
|
let entryNames;
|
|
1286
1623
|
try {
|
|
1287
|
-
entryNames = await (0,
|
|
1624
|
+
entryNames = await (0, import_promises6.readdir)(current);
|
|
1288
1625
|
} catch {
|
|
1289
1626
|
return;
|
|
1290
1627
|
}
|
|
1291
1628
|
for (const name of entryNames) {
|
|
1292
1629
|
if (name === "node_modules" || name === ".git") continue;
|
|
1293
|
-
const fullPath = (0,
|
|
1630
|
+
const fullPath = (0, import_node_path4.join)(current, name);
|
|
1294
1631
|
let fileStat;
|
|
1295
1632
|
try {
|
|
1296
|
-
fileStat = await (0,
|
|
1633
|
+
fileStat = await (0, import_promises6.stat)(fullPath);
|
|
1297
1634
|
} catch {
|
|
1298
1635
|
continue;
|
|
1299
1636
|
}
|
|
@@ -1349,7 +1686,7 @@ async function grepFileTool(args) {
|
|
|
1349
1686
|
contextLines = 0,
|
|
1350
1687
|
outputMode = "files_with_matches"
|
|
1351
1688
|
} = args;
|
|
1352
|
-
const targetPath = searchPath ? (0,
|
|
1689
|
+
const targetPath = searchPath ? (0, import_node_path4.resolve)(searchPath) : process.cwd();
|
|
1353
1690
|
let regex;
|
|
1354
1691
|
try {
|
|
1355
1692
|
regex = new RegExp(pattern);
|
|
@@ -1363,7 +1700,7 @@ async function grepFileTool(args) {
|
|
|
1363
1700
|
}
|
|
1364
1701
|
let targetStat;
|
|
1365
1702
|
try {
|
|
1366
|
-
targetStat = await (0,
|
|
1703
|
+
targetStat = await (0, import_promises6.stat)(targetPath);
|
|
1367
1704
|
} catch {
|
|
1368
1705
|
const result2 = {
|
|
1369
1706
|
success: false,
|
|
@@ -1382,7 +1719,7 @@ async function grepFileTool(args) {
|
|
|
1382
1719
|
for (const filePath of files) {
|
|
1383
1720
|
let content;
|
|
1384
1721
|
try {
|
|
1385
|
-
const buffer = await (0,
|
|
1722
|
+
const buffer = await (0, import_promises6.readFile)(filePath);
|
|
1386
1723
|
const checkLen = Math.min(buffer.length, 8192);
|
|
1387
1724
|
let hasBinary = false;
|
|
1388
1725
|
for (let i = 0; i < checkLen; i++) {
|
|
@@ -1547,17 +1884,25 @@ var webSearchTool = createZodFunctionTool(
|
|
|
1547
1884
|
);
|
|
1548
1885
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1549
1886
|
0 && (module.exports = {
|
|
1887
|
+
E2BSandboxClient,
|
|
1550
1888
|
FunctionTool,
|
|
1889
|
+
InMemorySandboxClient,
|
|
1551
1890
|
OpenAPITool,
|
|
1552
1891
|
ToolRegistry,
|
|
1892
|
+
applyWorkspaceManifest,
|
|
1553
1893
|
bashTool,
|
|
1894
|
+
createBashTool,
|
|
1895
|
+
createEditTool,
|
|
1554
1896
|
createFunctionTool,
|
|
1555
1897
|
createOpenAPITool,
|
|
1898
|
+
createReadTool,
|
|
1899
|
+
createWriteTool,
|
|
1556
1900
|
createZodFunctionTool,
|
|
1557
1901
|
editTool,
|
|
1558
1902
|
globTool,
|
|
1559
1903
|
grepTool,
|
|
1560
1904
|
readTool,
|
|
1905
|
+
validateWorkspaceManifestPath,
|
|
1561
1906
|
webFetchTool,
|
|
1562
1907
|
webSearchTool,
|
|
1563
1908
|
writeTool,
|