@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.
@@ -1,3 +1,272 @@
1
+ // src/sandbox/e2b-sandbox-client.ts
2
+ var E2BSandboxClient = class {
3
+ sandbox;
4
+ connectSandbox;
5
+ createSandboxFromSnapshot;
6
+ constructor(options) {
7
+ this.sandbox = options.sandbox;
8
+ this.connectSandbox = options.connectSandbox;
9
+ this.createSandboxFromSnapshot = options.createSandboxFromSnapshot;
10
+ }
11
+ async run(command, options) {
12
+ const result = await this.sandbox.commands.run(command, {
13
+ background: false,
14
+ timeoutMs: options?.timeoutMs,
15
+ cwd: options?.workingDirectory
16
+ });
17
+ return {
18
+ stdout: result.stdout ?? "",
19
+ stderr: result.stderr ?? "",
20
+ exitCode: result.exitCode ?? result.exit_code ?? 0
21
+ };
22
+ }
23
+ async readFile(path) {
24
+ const content = await this.sandbox.files.read(path);
25
+ return typeof content === "string" ? content : Buffer.from(content).toString("utf8");
26
+ }
27
+ async writeFile(path, content) {
28
+ await this.sandbox.files.write(path, content);
29
+ }
30
+ async snapshot() {
31
+ if (this.sandbox.createSnapshot) {
32
+ const snapshot = await this.sandbox.createSnapshot();
33
+ const snapshotId = snapshot.snapshotId ?? snapshot.id;
34
+ if (!snapshotId) {
35
+ throw new Error("E2B createSnapshot() did not return a snapshot id.");
36
+ }
37
+ return snapshotId;
38
+ }
39
+ const sandboxId = this.sandbox.sandboxId;
40
+ if (!sandboxId) {
41
+ throw new Error("E2B sandboxId is required to create a resumable sandbox snapshot.");
42
+ }
43
+ if (!this.sandbox.pause) {
44
+ throw new Error("E2B sandbox adapter does not expose pause().");
45
+ }
46
+ await this.sandbox.pause();
47
+ return sandboxId;
48
+ }
49
+ async restore(snapshotId) {
50
+ if (this.createSandboxFromSnapshot) {
51
+ this.sandbox = await this.createSandboxFromSnapshot(snapshotId);
52
+ return;
53
+ }
54
+ if (this.connectSandbox) {
55
+ this.sandbox = await this.connectSandbox(snapshotId);
56
+ return;
57
+ }
58
+ if (this.sandbox.sandboxId === snapshotId && this.sandbox.connect) {
59
+ this.sandbox = await this.sandbox.connect();
60
+ return;
61
+ }
62
+ throw new Error(
63
+ "E2B sandbox restore requires connectSandbox(snapshotId) or sandbox.connect()."
64
+ );
65
+ }
66
+ };
67
+
68
+ // src/sandbox/in-memory-sandbox-client.ts
69
+ var InMemorySandboxClient = class {
70
+ files = /* @__PURE__ */ new Map();
71
+ snapshots = /* @__PURE__ */ new Map();
72
+ runHandler;
73
+ snapshotSequence = 0;
74
+ constructor(options = {}) {
75
+ for (const [path, content] of Object.entries(options.files ?? {})) {
76
+ this.files.set(path, content);
77
+ }
78
+ this.runHandler = options.runHandler;
79
+ }
80
+ async run(command, options) {
81
+ if (this.runHandler) {
82
+ return this.runHandler(command, options, this.files);
83
+ }
84
+ return { stdout: "", stderr: "", exitCode: 0 };
85
+ }
86
+ async readFile(path) {
87
+ const content = this.files.get(path);
88
+ if (content === void 0) {
89
+ throw new Error(`Sandbox file not found: ${path}`);
90
+ }
91
+ return content;
92
+ }
93
+ async writeFile(path, content) {
94
+ this.files.set(path, content);
95
+ }
96
+ async snapshot() {
97
+ const snapshotId = `snapshot-${++this.snapshotSequence}`;
98
+ this.snapshots.set(snapshotId, new Map(this.files));
99
+ return snapshotId;
100
+ }
101
+ async restore(snapshotId) {
102
+ const snapshot = this.snapshots.get(snapshotId);
103
+ if (!snapshot) {
104
+ throw new Error(`Sandbox snapshot not found: ${snapshotId}`);
105
+ }
106
+ this.files.clear();
107
+ for (const [path, content] of snapshot.entries()) {
108
+ this.files.set(path, content);
109
+ }
110
+ }
111
+ getFile(path) {
112
+ return this.files.get(path);
113
+ }
114
+ };
115
+
116
+ // src/sandbox/workspace-manifest.ts
117
+ import { readdir, readFile } from "fs/promises";
118
+ import { isAbsolute, join, posix, resolve } from "path";
119
+ var DEFAULT_TARGET_ROOT = "/workspace";
120
+ var WINDOWS_ABSOLUTE_PATH_PATTERN = /^[A-Za-z]:[\\/]/;
121
+ var SHELL_QUOTE_PATTERN = /'/g;
122
+ async function applyWorkspaceManifest(sandboxClient, manifest, options = {}) {
123
+ if (sandboxClient.applyManifest) {
124
+ return sandboxClient.applyManifest(manifest, options);
125
+ }
126
+ const targetRoot = normalizeSandboxRoot(options.targetRoot ?? DEFAULT_TARGET_ROOT);
127
+ const appliedEntries = [];
128
+ for (const [rawPath, entry] of Object.entries(manifest.entries)) {
129
+ const path = validateWorkspaceManifestPath(rawPath);
130
+ const targetPath = joinSandboxPath(targetRoot, path);
131
+ appliedEntries.push(
132
+ await applyManifestEntry(sandboxClient, path, targetPath, targetRoot, entry, options)
133
+ );
134
+ }
135
+ return { entries: appliedEntries };
136
+ }
137
+ function validateWorkspaceManifestPath(path) {
138
+ if (path.length === 0) {
139
+ throw new Error("workspace manifest path must not be empty");
140
+ }
141
+ if (path.includes("\0")) {
142
+ throw new Error("workspace manifest path must not contain NUL bytes");
143
+ }
144
+ if (path.startsWith("/") || path.startsWith("\\") || WINDOWS_ABSOLUTE_PATH_PATTERN.test(path)) {
145
+ throw new Error("workspace manifest path must be workspace-relative");
146
+ }
147
+ const parts = path.replace(/\\/g, "/").split("/").filter(Boolean);
148
+ if (parts.length === 0) {
149
+ throw new Error("workspace manifest path must not resolve to the workspace root");
150
+ }
151
+ if (parts.some((part) => part === "..")) {
152
+ throw new Error("workspace manifest path cannot contain traversal segments");
153
+ }
154
+ const normalizedParts = parts.filter((part) => part !== ".");
155
+ if (normalizedParts.length === 0) {
156
+ throw new Error("workspace manifest path must not resolve to the workspace root");
157
+ }
158
+ return normalizedParts.join("/");
159
+ }
160
+ async function applyManifestEntry(sandboxClient, path, targetPath, targetRoot, entry, options) {
161
+ switch (entry.type) {
162
+ case "file":
163
+ await writeSandboxFile(sandboxClient, targetPath, targetRoot, entry.content);
164
+ return createAppliedEntry(path, entry.type);
165
+ case "dir":
166
+ await createSandboxDirectory(sandboxClient, targetPath);
167
+ return createAppliedEntry(path, entry.type);
168
+ case "localFile":
169
+ await copyLocalFile(sandboxClient, entry.src, targetPath, targetRoot, options);
170
+ return createAppliedEntry(path, entry.type);
171
+ case "localDir":
172
+ await copyLocalDirectory(sandboxClient, entry.src, targetPath, options);
173
+ return createAppliedEntry(path, entry.type);
174
+ case "gitRepo":
175
+ await cloneGitRepository(sandboxClient, entry, targetPath);
176
+ return createAppliedEntry(path, entry.type);
177
+ case "s3Mount":
178
+ case "gcsMount":
179
+ case "r2Mount":
180
+ case "azureBlobMount":
181
+ return {
182
+ path,
183
+ type: entry.type,
184
+ status: "unsupported",
185
+ message: `${entry.type} requires a provider-specific sandbox adapter.`
186
+ };
187
+ default:
188
+ return assertUnreachable(entry);
189
+ }
190
+ }
191
+ function createAppliedEntry(path, type) {
192
+ return { path, type, status: "applied" };
193
+ }
194
+ async function copyLocalFile(sandboxClient, source, targetPath, targetRoot, options) {
195
+ const hostSourcePath = resolveHostSourcePath(source, options.hostRoot);
196
+ const content = await readFile(hostSourcePath, "utf8");
197
+ await writeSandboxFile(sandboxClient, targetPath, targetRoot, content);
198
+ }
199
+ async function copyLocalDirectory(sandboxClient, source, targetPath, options) {
200
+ const hostSourcePath = resolveHostSourcePath(source, options.hostRoot);
201
+ await copyLocalDirectoryRecursive(sandboxClient, hostSourcePath, targetPath);
202
+ }
203
+ async function copyLocalDirectoryRecursive(sandboxClient, sourcePath, targetPath) {
204
+ await createSandboxDirectory(sandboxClient, targetPath);
205
+ const entries = await readdir(sourcePath, { withFileTypes: true });
206
+ for (const entry of entries) {
207
+ const childSourcePath = join(sourcePath, entry.name);
208
+ const childTargetPath = joinSandboxPath(targetPath, entry.name);
209
+ if (entry.isDirectory()) {
210
+ await copyLocalDirectoryRecursive(sandboxClient, childSourcePath, childTargetPath);
211
+ continue;
212
+ }
213
+ if (entry.isFile()) {
214
+ const content = await readFile(childSourcePath, "utf8");
215
+ await sandboxClient.writeFile(childTargetPath, content);
216
+ }
217
+ }
218
+ }
219
+ async function cloneGitRepository(sandboxClient, entry, targetPath) {
220
+ const shallowArgs = entry.shallow === false ? "" : " --depth 1";
221
+ const refArgs = entry.ref ? ` --branch ${quoteShellArg(entry.ref)}` : "";
222
+ await runSandboxCommand(
223
+ sandboxClient,
224
+ `git clone${shallowArgs}${refArgs} ${quoteShellArg(entry.url)} ${quoteShellArg(targetPath)}`
225
+ );
226
+ }
227
+ async function writeSandboxFile(sandboxClient, targetPath, targetRoot, content) {
228
+ const parentPath = posix.dirname(targetPath);
229
+ if (parentPath !== targetRoot) {
230
+ await createSandboxDirectory(sandboxClient, parentPath);
231
+ }
232
+ await sandboxClient.writeFile(targetPath, content);
233
+ }
234
+ async function createSandboxDirectory(sandboxClient, targetPath) {
235
+ await runSandboxCommand(sandboxClient, `mkdir -p ${quoteShellArg(targetPath)}`);
236
+ }
237
+ async function runSandboxCommand(sandboxClient, command) {
238
+ const result = await sandboxClient.run(command);
239
+ if (result.exitCode !== 0) {
240
+ throw new Error(
241
+ `workspace manifest command failed: ${command}
242
+ ${result.stderr ?? result.stdout}`
243
+ );
244
+ }
245
+ }
246
+ function resolveHostSourcePath(source, hostRoot) {
247
+ return isAbsolute(source) ? resolve(source) : resolve(hostRoot ?? process.cwd(), source);
248
+ }
249
+ function normalizeSandboxRoot(root) {
250
+ const normalized = root.replace(/\\/g, "/").replace(/\/+$/, "");
251
+ if (!normalized.startsWith("/")) {
252
+ throw new Error("workspace manifest targetRoot must be an absolute sandbox path");
253
+ }
254
+ return normalized.length === 0 ? "/" : normalized;
255
+ }
256
+ function joinSandboxPath(root, path) {
257
+ const normalizedRoot = normalizeSandboxRoot(root);
258
+ if (normalizedRoot === "/") {
259
+ return `/${path}`;
260
+ }
261
+ return `${normalizedRoot}/${path}`;
262
+ }
263
+ function quoteShellArg(value) {
264
+ return `'${value.replace(SHELL_QUOTE_PATTERN, "'\\''")}'`;
265
+ }
266
+ function assertUnreachable(value) {
267
+ throw new Error(`unsupported workspace manifest entry: ${JSON.stringify(value)}`);
268
+ }
269
+
1
270
  // src/registry/tool-registry.ts
2
271
  import { ValidationError } from "@robota-sdk/agent-core";
3
272
  import { logger } from "@robota-sdk/agent-core";
@@ -810,9 +1079,33 @@ var BashSchema = z.object({
810
1079
  timeout: z.number().optional().describe("Optional timeout in milliseconds (max 600000). Default is 120000 (2 minutes)"),
811
1080
  workingDirectory: z.string().optional().describe("Working directory for the command. Defaults to the current working directory")
812
1081
  });
813
- async function runBash(args) {
1082
+ async function runBash(args, options = {}) {
814
1083
  const { command, timeout = DEFAULT_TIMEOUT_MS, workingDirectory } = args;
815
- return new Promise((resolve3) => {
1084
+ if (options.sandboxClient) {
1085
+ try {
1086
+ const sandboxResult = await options.sandboxClient.run(command, {
1087
+ timeoutMs: timeout,
1088
+ workingDirectory
1089
+ });
1090
+ const output = sandboxResult.stderr ? `${sandboxResult.stdout}
1091
+ stderr:
1092
+ ${sandboxResult.stderr}` : sandboxResult.stdout;
1093
+ const result = {
1094
+ success: true,
1095
+ output,
1096
+ exitCode: sandboxResult.exitCode
1097
+ };
1098
+ return JSON.stringify(result);
1099
+ } catch (err) {
1100
+ const result = {
1101
+ success: false,
1102
+ output: "",
1103
+ error: err instanceof Error ? err.message : String(err)
1104
+ };
1105
+ return JSON.stringify(result);
1106
+ }
1107
+ }
1108
+ return new Promise((resolve4) => {
816
1109
  const stdoutChunks = [];
817
1110
  const stderrChunks = [];
818
1111
  let timedOut = false;
@@ -841,7 +1134,7 @@ async function runBash(args) {
841
1134
  if (settled) return;
842
1135
  settled = true;
843
1136
  clearTimeout(timer);
844
- resolve3(JSON.stringify(result));
1137
+ resolve4(JSON.stringify(result));
845
1138
  }
846
1139
  child.on("error", (err) => {
847
1140
  settle({
@@ -874,17 +1167,20 @@ ${stderr}` : stdout;
874
1167
  });
875
1168
  });
876
1169
  }
877
- var bashTool = createZodFunctionTool(
878
- "Bash",
879
- "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.",
880
- BashSchema,
881
- async (params) => {
882
- return runBash(params);
883
- }
884
- );
1170
+ function createBashTool(options = {}) {
1171
+ return createZodFunctionTool(
1172
+ "Bash",
1173
+ "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.",
1174
+ BashSchema,
1175
+ async (params) => {
1176
+ return runBash(params, options);
1177
+ }
1178
+ );
1179
+ }
1180
+ var bashTool = createBashTool();
885
1181
 
886
1182
  // src/builtins/read-tool.ts
887
- import { readFile, stat } from "fs/promises";
1183
+ import { readFile as readFile2, stat } from "fs/promises";
888
1184
  import { z as z2 } from "zod";
889
1185
  var DEFAULT_LIMIT = 2e3;
890
1186
  var ReadSchema = z2.object({
@@ -911,74 +1207,93 @@ function formatWithLineNumbers(lines, startLine) {
911
1207
  return `${lineNum} ${line}`;
912
1208
  }).join("\n");
913
1209
  }
914
- async function readFileTool(args) {
1210
+ function formatReadResult(filePath, content, startLine, limit) {
1211
+ const allLines = content.split("\n");
1212
+ if (allLines[allLines.length - 1] === "") {
1213
+ allLines.pop();
1214
+ }
1215
+ const zeroBasedStart = startLine - 1;
1216
+ const selectedLines = allLines.slice(zeroBasedStart, zeroBasedStart + limit);
1217
+ const output = formatWithLineNumbers(selectedLines, startLine);
1218
+ const totalLines = allLines.length;
1219
+ const returnedLines = selectedLines.length;
1220
+ const header = returnedLines < totalLines ? `[File: ${filePath} (lines ${startLine}-${startLine + returnedLines - 1} of ${totalLines})]
1221
+ ` : `[File: ${filePath} (${totalLines} lines)]
1222
+ `;
1223
+ const result = {
1224
+ success: true,
1225
+ output: header + output
1226
+ };
1227
+ return JSON.stringify(result);
1228
+ }
1229
+ async function readFileTool(args, options = {}) {
915
1230
  const { filePath, offset, limit = DEFAULT_LIMIT } = args;
916
1231
  const startLine = offset !== void 0 && offset > 0 ? offset : 1;
1232
+ if (options.sandboxClient) {
1233
+ try {
1234
+ const content2 = await options.sandboxClient.readFile(filePath);
1235
+ return formatReadResult(filePath, content2, startLine, limit);
1236
+ } catch (err) {
1237
+ const result = {
1238
+ success: false,
1239
+ output: "",
1240
+ error: err instanceof Error ? err.message : String(err)
1241
+ };
1242
+ return JSON.stringify(result);
1243
+ }
1244
+ }
917
1245
  let fileStats;
918
1246
  try {
919
1247
  fileStats = await stat(filePath);
920
1248
  } catch (err) {
921
- const result2 = {
1249
+ const result = {
922
1250
  success: false,
923
1251
  output: "",
924
1252
  error: `File not found: ${filePath}`
925
1253
  };
926
- return JSON.stringify(result2);
1254
+ return JSON.stringify(result);
927
1255
  }
928
1256
  if (!fileStats.isFile()) {
929
- const result2 = {
1257
+ const result = {
930
1258
  success: false,
931
1259
  output: "",
932
1260
  error: `Path is not a file: ${filePath}`
933
1261
  };
934
- return JSON.stringify(result2);
1262
+ return JSON.stringify(result);
935
1263
  }
936
1264
  let buffer;
937
1265
  try {
938
- buffer = await readFile(filePath);
1266
+ buffer = await readFile2(filePath);
939
1267
  } catch (err) {
940
- const result2 = {
1268
+ const result = {
941
1269
  success: false,
942
1270
  output: "",
943
1271
  error: err instanceof Error ? err.message : String(err)
944
1272
  };
945
- return JSON.stringify(result2);
1273
+ return JSON.stringify(result);
946
1274
  }
947
1275
  if (isBinary(buffer)) {
948
- const result2 = {
1276
+ const result = {
949
1277
  success: false,
950
1278
  output: "",
951
1279
  error: `Binary file not supported: ${filePath}`
952
1280
  };
953
- return JSON.stringify(result2);
1281
+ return JSON.stringify(result);
954
1282
  }
955
1283
  const content = buffer.toString("utf8");
956
- const allLines = content.split("\n");
957
- if (allLines[allLines.length - 1] === "") {
958
- allLines.pop();
959
- }
960
- const zeroBasedStart = startLine - 1;
961
- const selectedLines = allLines.slice(zeroBasedStart, zeroBasedStart + limit);
962
- const output = formatWithLineNumbers(selectedLines, startLine);
963
- const totalLines = allLines.length;
964
- const returnedLines = selectedLines.length;
965
- const header = returnedLines < totalLines ? `[File: ${filePath} (lines ${startLine}-${startLine + returnedLines - 1} of ${totalLines})]
966
- ` : `[File: ${filePath} (${totalLines} lines)]
967
- `;
968
- const result = {
969
- success: true,
970
- output: header + output
971
- };
972
- return JSON.stringify(result);
1284
+ return formatReadResult(filePath, content, startLine, limit);
973
1285
  }
974
- var readTool = createZodFunctionTool(
975
- "Read",
976
- "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.",
977
- ReadSchema,
978
- async (params) => {
979
- return readFileTool(params);
980
- }
981
- );
1286
+ function createReadTool(options = {}) {
1287
+ return createZodFunctionTool(
1288
+ "Read",
1289
+ "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.",
1290
+ ReadSchema,
1291
+ async (params) => {
1292
+ return readFileTool(params, options);
1293
+ }
1294
+ );
1295
+ }
1296
+ var readTool = createReadTool();
982
1297
 
983
1298
  // src/builtins/write-tool.ts
984
1299
  import { z as z3 } from "zod";
@@ -986,7 +1301,7 @@ import { z as z3 } from "zod";
986
1301
  // src/builtins/atomic-file-write.ts
987
1302
  import { randomBytes } from "crypto";
988
1303
  import { chmod, mkdir, rename, rm, stat as stat2, writeFile } from "fs/promises";
989
- import { basename, dirname, join } from "path";
1304
+ import { basename, dirname, join as join2 } from "path";
990
1305
  var TEMP_RANDOM_BYTES = 6;
991
1306
  var PRESERVED_MODE_BITS = 4095;
992
1307
  var MISSING_FILE_ERROR_CODE = "ENOENT";
@@ -994,7 +1309,7 @@ function createTempFilePath(filePath) {
994
1309
  const dir = dirname(filePath);
995
1310
  const name = basename(filePath);
996
1311
  const suffix = randomBytes(TEMP_RANDOM_BYTES).toString("hex");
997
- return join(dir, `.${name}.robota-tmp-${process.pid}-${Date.now()}-${suffix}`);
1312
+ return join2(dir, `.${name}.robota-tmp-${process.pid}-${Date.now()}-${suffix}`);
998
1313
  }
999
1314
  async function readExistingMode(filePath) {
1000
1315
  try {
@@ -1030,10 +1345,14 @@ var WriteSchema = z3.object({
1030
1345
  filePath: z3.string().describe("The absolute path to the file to write"),
1031
1346
  content: z3.string().describe("The content to write to the file")
1032
1347
  });
1033
- async function writeFileTool(args) {
1348
+ async function writeFileTool(args, options = {}) {
1034
1349
  const { filePath, content } = args;
1035
1350
  try {
1036
- await atomicWriteUtf8File(filePath, content);
1351
+ if (options.sandboxClient) {
1352
+ await options.sandboxClient.writeFile(filePath, content);
1353
+ } else {
1354
+ await atomicWriteUtf8File(filePath, content);
1355
+ }
1037
1356
  const result = {
1038
1357
  success: true,
1039
1358
  output: `Written ${Buffer.byteLength(content, "utf8")} bytes to ${filePath}`
@@ -1048,17 +1367,20 @@ async function writeFileTool(args) {
1048
1367
  return JSON.stringify(result);
1049
1368
  }
1050
1369
  }
1051
- var writeTool = createZodFunctionTool(
1052
- "Write",
1053
- "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.",
1054
- WriteSchema,
1055
- async (params) => {
1056
- return writeFileTool(params);
1057
- }
1058
- );
1370
+ function createWriteTool(options = {}) {
1371
+ return createZodFunctionTool(
1372
+ "Write",
1373
+ "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.",
1374
+ WriteSchema,
1375
+ async (params) => {
1376
+ return writeFileTool(params, options);
1377
+ }
1378
+ );
1379
+ }
1380
+ var writeTool = createWriteTool();
1059
1381
 
1060
1382
  // src/builtins/edit-tool.ts
1061
- import { readFile as readFile2 } from "fs/promises";
1383
+ import { readFile as readFile3 } from "fs/promises";
1062
1384
  import { z as z4 } from "zod";
1063
1385
  var EditSchema = z4.object({
1064
1386
  filePath: z4.string().describe("The absolute path to the file to modify"),
@@ -1068,11 +1390,11 @@ var EditSchema = z4.object({
1068
1390
  "Replace all occurrences of old_string (default: false). Useful for renaming variables"
1069
1391
  )
1070
1392
  });
1071
- async function editFileTool(args) {
1393
+ async function editFileTool(args, options = {}) {
1072
1394
  const { filePath, oldString, newString, replaceAll = false } = args;
1073
1395
  let content;
1074
1396
  try {
1075
- content = await readFile2(filePath, "utf8");
1397
+ content = options.sandboxClient ? await options.sandboxClient.readFile(filePath) : await readFile3(filePath, "utf8");
1076
1398
  } catch (err) {
1077
1399
  const result2 = {
1078
1400
  success: false,
@@ -1104,7 +1426,11 @@ async function editFileTool(args) {
1104
1426
  }
1105
1427
  const updated = replaceAll ? content.split(oldString).join(newString) : content.slice(0, content.indexOf(oldString)) + newString + content.slice(content.indexOf(oldString) + oldString.length);
1106
1428
  try {
1107
- await atomicWriteUtf8File(filePath, updated);
1429
+ if (options.sandboxClient) {
1430
+ await options.sandboxClient.writeFile(filePath, updated);
1431
+ } else {
1432
+ await atomicWriteUtf8File(filePath, updated);
1433
+ }
1108
1434
  } catch (err) {
1109
1435
  const result2 = {
1110
1436
  success: false,
@@ -1123,18 +1449,21 @@ async function editFileTool(args) {
1123
1449
  };
1124
1450
  return JSON.stringify(result);
1125
1451
  }
1126
- var editTool = createZodFunctionTool(
1127
- "Edit",
1128
- "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.",
1129
- EditSchema,
1130
- async (params) => {
1131
- return editFileTool(params);
1132
- }
1133
- );
1452
+ function createEditTool(options = {}) {
1453
+ return createZodFunctionTool(
1454
+ "Edit",
1455
+ "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.",
1456
+ EditSchema,
1457
+ async (params) => {
1458
+ return editFileTool(params, options);
1459
+ }
1460
+ );
1461
+ }
1462
+ var editTool = createEditTool();
1134
1463
 
1135
1464
  // src/builtins/glob-tool.ts
1136
1465
  import { stat as stat3 } from "fs/promises";
1137
- import { resolve } from "path";
1466
+ import { resolve as resolve2 } from "path";
1138
1467
  import fg from "fast-glob";
1139
1468
  import { z as z5 } from "zod";
1140
1469
  var DEFAULT_MAX_RESULTS = 1e3;
@@ -1149,7 +1478,7 @@ var GlobSchema = z5.object({
1149
1478
  });
1150
1479
  async function globFileTool(args) {
1151
1480
  const { pattern, path: basePath } = args;
1152
- const cwd = basePath ? resolve(basePath) : process.cwd();
1481
+ const cwd = basePath ? resolve2(basePath) : process.cwd();
1153
1482
  let matches;
1154
1483
  try {
1155
1484
  matches = await fg(pattern, {
@@ -1168,7 +1497,7 @@ async function globFileTool(args) {
1168
1497
  }
1169
1498
  const withMtime = await Promise.all(
1170
1499
  matches.map(async (p) => {
1171
- const absPath = resolve(cwd, p);
1500
+ const absPath = resolve2(cwd, p);
1172
1501
  try {
1173
1502
  const s = await stat3(absPath);
1174
1503
  return { path: p, mtime: s.mtimeMs };
@@ -1205,8 +1534,8 @@ var globTool = createZodFunctionTool(
1205
1534
  );
1206
1535
 
1207
1536
  // src/builtins/grep-tool.ts
1208
- import { readFile as readFile3, readdir, stat as stat4 } from "fs/promises";
1209
- import { join as join2, resolve as resolve2 } from "path";
1537
+ import { readFile as readFile4, readdir as readdir2, stat as stat4 } from "fs/promises";
1538
+ import { join as join3, resolve as resolve3 } from "path";
1210
1539
  import { z as z6 } from "zod";
1211
1540
  var GrepSchema = z6.object({
1212
1541
  pattern: z6.string().describe("The regular expression pattern to search for in file contents"),
@@ -1234,13 +1563,13 @@ async function collectFiles(dirPath, glob) {
1234
1563
  async function walk(current) {
1235
1564
  let entryNames;
1236
1565
  try {
1237
- entryNames = await readdir(current);
1566
+ entryNames = await readdir2(current);
1238
1567
  } catch {
1239
1568
  return;
1240
1569
  }
1241
1570
  for (const name of entryNames) {
1242
1571
  if (name === "node_modules" || name === ".git") continue;
1243
- const fullPath = join2(current, name);
1572
+ const fullPath = join3(current, name);
1244
1573
  let fileStat;
1245
1574
  try {
1246
1575
  fileStat = await stat4(fullPath);
@@ -1299,7 +1628,7 @@ async function grepFileTool(args) {
1299
1628
  contextLines = 0,
1300
1629
  outputMode = "files_with_matches"
1301
1630
  } = args;
1302
- const targetPath = searchPath ? resolve2(searchPath) : process.cwd();
1631
+ const targetPath = searchPath ? resolve3(searchPath) : process.cwd();
1303
1632
  let regex;
1304
1633
  try {
1305
1634
  regex = new RegExp(pattern);
@@ -1332,7 +1661,7 @@ async function grepFileTool(args) {
1332
1661
  for (const filePath of files) {
1333
1662
  let content;
1334
1663
  try {
1335
- const buffer = await readFile3(filePath);
1664
+ const buffer = await readFile4(filePath);
1336
1665
  const checkLen = Math.min(buffer.length, 8192);
1337
1666
  let hasBinary = false;
1338
1667
  for (let i = 0; i < checkLen; i++) {
@@ -1496,17 +1825,25 @@ var webSearchTool = createZodFunctionTool(
1496
1825
  async (params) => runWebSearch(params)
1497
1826
  );
1498
1827
  export {
1828
+ E2BSandboxClient,
1499
1829
  FunctionTool,
1830
+ InMemorySandboxClient,
1500
1831
  OpenAPITool,
1501
1832
  ToolRegistry,
1833
+ applyWorkspaceManifest,
1502
1834
  bashTool,
1835
+ createBashTool,
1836
+ createEditTool,
1503
1837
  createFunctionTool,
1504
1838
  createOpenAPITool,
1839
+ createReadTool,
1840
+ createWriteTool,
1505
1841
  createZodFunctionTool,
1506
1842
  editTool,
1507
1843
  globTool,
1508
1844
  grepTool,
1509
1845
  readTool,
1846
+ validateWorkspaceManifestPath,
1510
1847
  webFetchTool,
1511
1848
  webSearchTool,
1512
1849
  writeTool,