@robota-sdk/agent-tools 3.0.0-beta.6 → 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.
@@ -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");
@@ -215,7 +492,9 @@ function zodToJsonSchema(schema, options = {}) {
215
492
  type: "object",
216
493
  properties,
217
494
  required,
218
- ...options.allowAdditionalProperties && { additionalProperties: true }
495
+ ...(options.allowAdditionalProperties || schemaDef.unknownKeys === "passthrough") && {
496
+ additionalProperties: true
497
+ }
219
498
  };
220
499
  }
221
500
  function convertZodTypeToProperty(typeObj) {
@@ -294,6 +573,100 @@ function isRequiredField(typeObj) {
294
573
  return typeDef.typeName !== "ZodOptional" && typeDef.typeName !== "ZodNullable" && typeDef.typeName !== "ZodDefault";
295
574
  }
296
575
 
576
+ // src/implementations/function-tool/parameter-validator.ts
577
+ function validateParameterType(key, value, schema) {
578
+ const expectedType = schema["type"];
579
+ switch (expectedType) {
580
+ case "string":
581
+ if (typeof value !== "string") {
582
+ return `Parameter "${key}" must be a string, got ${typeof value}`;
583
+ }
584
+ break;
585
+ case "number":
586
+ if (typeof value !== "number" || isNaN(value)) {
587
+ return `Parameter "${key}" must be a number, got ${typeof value}`;
588
+ }
589
+ break;
590
+ case "boolean":
591
+ if (typeof value !== "boolean") {
592
+ return `Parameter "${key}" must be a boolean, got ${typeof value}`;
593
+ }
594
+ break;
595
+ case "array":
596
+ if (!Array.isArray(value)) {
597
+ return `Parameter "${key}" must be an array, got ${typeof value}`;
598
+ }
599
+ if (schema.items) {
600
+ for (let i = 0; i < value.length; i++) {
601
+ const itemError = validateParameterType(`${key}[${i}]`, value[i], schema.items);
602
+ if (itemError) {
603
+ return itemError;
604
+ }
605
+ }
606
+ }
607
+ break;
608
+ case "object":
609
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
610
+ return `Parameter "${key}" must be an object, got ${typeof value}`;
611
+ }
612
+ break;
613
+ }
614
+ if (schema.enum && schema.enum.length > 0) {
615
+ const enumValues = schema.enum;
616
+ let isValidEnum = false;
617
+ for (const enumValue of enumValues) {
618
+ if (value === enumValue) {
619
+ isValidEnum = true;
620
+ break;
621
+ }
622
+ }
623
+ if (!isValidEnum) {
624
+ return `Parameter "${key}" must be one of: ${enumValues.join(", ")}, got ${value}`;
625
+ }
626
+ }
627
+ return void 0;
628
+ }
629
+ function getValidationErrors(parameters, schemaRequired, schemaProperties, additionalProperties) {
630
+ const errors = [];
631
+ for (const field of schemaRequired) {
632
+ if (!(field in parameters)) {
633
+ errors.push(`Missing required parameter: ${field}`);
634
+ }
635
+ }
636
+ for (const [key, value] of Object.entries(parameters)) {
637
+ const paramSchema = schemaProperties[key];
638
+ if (!paramSchema) {
639
+ if (additionalProperties === true) {
640
+ continue;
641
+ }
642
+ if (additionalProperties && typeof additionalProperties === "object") {
643
+ const additionalTypeError = validateParameterType(key, value, additionalProperties);
644
+ if (additionalTypeError) errors.push(additionalTypeError);
645
+ continue;
646
+ }
647
+ errors.push(`Unknown parameter: ${key}`);
648
+ continue;
649
+ }
650
+ const typeError = validateParameterType(key, value, paramSchema);
651
+ if (typeError) {
652
+ errors.push(typeError);
653
+ }
654
+ }
655
+ return errors;
656
+ }
657
+ function validateToolParameters(parameters, schemaRequired, schemaProperties, additionalProperties) {
658
+ const errors = getValidationErrors(
659
+ parameters,
660
+ schemaRequired,
661
+ schemaProperties,
662
+ additionalProperties
663
+ );
664
+ return {
665
+ isValid: errors.length === 0,
666
+ errors
667
+ };
668
+ }
669
+
297
670
  // src/implementations/function-tool.ts
298
671
  var FunctionTool = class {
299
672
  schema;
@@ -324,7 +697,12 @@ var FunctionTool = class {
324
697
  async execute(parameters, context) {
325
698
  const toolName = this.schema.name;
326
699
  if (!this.validate(parameters)) {
327
- const errors = this.getValidationErrors(parameters);
700
+ const errors = getValidationErrors(
701
+ parameters,
702
+ this.schema.parameters.required || [],
703
+ this.schema.parameters.properties || {},
704
+ this.schema.parameters.additionalProperties
705
+ );
328
706
  throw new import_agent_core3.ValidationError(`Invalid parameters for tool "${toolName}": ${errors.join(", ")}`);
329
707
  }
330
708
  const startTime = Date.now();
@@ -360,17 +738,23 @@ var FunctionTool = class {
360
738
  * Validate parameters (simple boolean result)
361
739
  */
362
740
  validate(parameters) {
363
- return this.getValidationErrors(parameters).length === 0;
741
+ return getValidationErrors(
742
+ parameters,
743
+ this.schema.parameters.required || [],
744
+ this.schema.parameters.properties || {},
745
+ this.schema.parameters.additionalProperties
746
+ ).length === 0;
364
747
  }
365
748
  /**
366
749
  * Validate tool parameters with detailed result
367
750
  */
368
751
  validateParameters(parameters) {
369
- const errors = this.getValidationErrors(parameters);
370
- return {
371
- isValid: errors.length === 0,
372
- errors
373
- };
752
+ return validateToolParameters(
753
+ parameters,
754
+ this.schema.parameters.required || [],
755
+ this.schema.parameters.properties || {},
756
+ this.schema.parameters.additionalProperties
757
+ );
374
758
  }
375
759
  /**
376
760
  * Get tool description
@@ -378,86 +762,6 @@ var FunctionTool = class {
378
762
  getDescription() {
379
763
  return this.schema.description;
380
764
  }
381
- /**
382
- * Get detailed validation errors
383
- */
384
- getValidationErrors(parameters) {
385
- const errors = [];
386
- const required = this.schema.parameters.required || [];
387
- const properties = this.schema.parameters.properties || {};
388
- for (const field of required) {
389
- if (!(field in parameters)) {
390
- errors.push(`Missing required parameter: ${field}`);
391
- }
392
- }
393
- for (const [key, value] of Object.entries(parameters)) {
394
- const paramSchema = properties[key];
395
- if (!paramSchema) {
396
- errors.push(`Unknown parameter: ${key}`);
397
- continue;
398
- }
399
- const typeError = this.validateParameterType(key, value, paramSchema);
400
- if (typeError) {
401
- errors.push(typeError);
402
- }
403
- }
404
- return errors;
405
- }
406
- /**
407
- * Validate individual parameter type
408
- */
409
- validateParameterType(key, value, schema) {
410
- const expectedType = schema["type"];
411
- switch (expectedType) {
412
- case "string":
413
- if (typeof value !== "string") {
414
- return `Parameter "${key}" must be a string, got ${typeof value}`;
415
- }
416
- break;
417
- case "number":
418
- if (typeof value !== "number" || isNaN(value)) {
419
- return `Parameter "${key}" must be a number, got ${typeof value}`;
420
- }
421
- break;
422
- case "boolean":
423
- if (typeof value !== "boolean") {
424
- return `Parameter "${key}" must be a boolean, got ${typeof value}`;
425
- }
426
- break;
427
- case "array":
428
- if (!Array.isArray(value)) {
429
- return `Parameter "${key}" must be an array, got ${typeof value}`;
430
- }
431
- if (schema.items) {
432
- for (let i = 0; i < value.length; i++) {
433
- const itemError = this.validateParameterType(`${key}[${i}]`, value[i], schema.items);
434
- if (itemError) {
435
- return itemError;
436
- }
437
- }
438
- }
439
- break;
440
- case "object":
441
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
442
- return `Parameter "${key}" must be an object, got ${typeof value}`;
443
- }
444
- break;
445
- }
446
- if (schema.enum && schema.enum.length > 0) {
447
- const enumValues = schema.enum;
448
- let isValidEnum = false;
449
- for (const enumValue of enumValues) {
450
- if (value === enumValue) {
451
- isValidEnum = true;
452
- break;
453
- }
454
- }
455
- if (!isValidEnum) {
456
- return `Parameter "${key}" must be one of: ${enumValues.join(", ")}, got ${value}`;
457
- }
458
- }
459
- return void 0;
460
- }
461
765
  /**
462
766
  * Validate constructor inputs
463
767
  */
@@ -501,6 +805,132 @@ function createZodFunctionTool(name, description, zodSchema, fn) {
501
805
 
502
806
  // src/implementations/openapi-tool.ts
503
807
  var import_agent_core4 = require("@robota-sdk/agent-core");
808
+
809
+ // src/implementations/openapi-schema-converter.ts
810
+ var HTTP_METHODS = [
811
+ "get",
812
+ "post",
813
+ "put",
814
+ "delete",
815
+ "patch",
816
+ "head",
817
+ "options"
818
+ ];
819
+ function findOperation(apiSpec, operationId) {
820
+ for (const [path, pathItem] of Object.entries(apiSpec.paths || {})) {
821
+ if (!pathItem) continue;
822
+ for (const method of HTTP_METHODS) {
823
+ const operation = pathItem[method];
824
+ if (operation?.operationId === operationId) {
825
+ return { method, path, operation };
826
+ }
827
+ }
828
+ }
829
+ return void 0;
830
+ }
831
+ function mapOpenAPIType(type) {
832
+ switch (type) {
833
+ case "string":
834
+ return "string";
835
+ case "number":
836
+ return "number";
837
+ case "integer":
838
+ return "integer";
839
+ case "boolean":
840
+ return "boolean";
841
+ case "array":
842
+ return "array";
843
+ case "object":
844
+ return "object";
845
+ default:
846
+ return "string";
847
+ }
848
+ }
849
+ function convertOpenAPISchemaToParameterSchema(schema) {
850
+ if ("$ref" in schema) {
851
+ return { type: "object" };
852
+ }
853
+ const result = {
854
+ type: mapOpenAPIType(schema.type)
855
+ };
856
+ if (schema.description) {
857
+ result.description = schema.description;
858
+ }
859
+ if (schema.enum) {
860
+ result.enum = schema.enum;
861
+ }
862
+ if (schema.minimum !== void 0) {
863
+ result.minimum = schema.minimum;
864
+ }
865
+ if (schema.maximum !== void 0) {
866
+ result.maximum = schema.maximum;
867
+ }
868
+ if (schema.pattern) {
869
+ result.pattern = schema.pattern;
870
+ }
871
+ if (schema.format) {
872
+ result.format = schema.format;
873
+ }
874
+ if (schema.default !== void 0) {
875
+ result.default = schema.default;
876
+ }
877
+ if (schema.type === "array" && schema.items) {
878
+ result.items = convertOpenAPISchemaToParameterSchema(schema.items);
879
+ }
880
+ if (schema.type === "object" && schema.properties) {
881
+ result.properties = {};
882
+ for (const [propName, propSchema] of Object.entries(schema.properties)) {
883
+ result.properties[propName] = convertOpenAPISchemaToParameterSchema(propSchema);
884
+ }
885
+ if (schema.required && schema.required.length > 0) {
886
+ result.required = schema.required;
887
+ }
888
+ }
889
+ return result;
890
+ }
891
+ function convertOpenAPIParamToSchema(param) {
892
+ const schema = param.schema;
893
+ return convertOpenAPISchemaToParameterSchema(schema);
894
+ }
895
+ function createSchemaFromOperation(operationId, opSpec) {
896
+ const properties = {};
897
+ const required = [];
898
+ const params = opSpec.parameters || [];
899
+ for (const param of params) {
900
+ properties[param.name] = convertOpenAPIParamToSchema(param);
901
+ if (param.required) {
902
+ required.push(param.name);
903
+ }
904
+ }
905
+ if (opSpec.requestBody) {
906
+ const requestBody = opSpec.requestBody;
907
+ const jsonContent = requestBody.content?.["application/json"];
908
+ if (jsonContent?.schema) {
909
+ const bodySchema = convertOpenAPISchemaToParameterSchema(jsonContent.schema);
910
+ if (bodySchema.type === "object" && bodySchema.properties) {
911
+ Object.assign(properties, bodySchema.properties);
912
+ const schemaWithRequired = bodySchema;
913
+ if (schemaWithRequired.required) {
914
+ required.push(...schemaWithRequired.required);
915
+ }
916
+ }
917
+ }
918
+ }
919
+ const schemaParams = {
920
+ type: "object",
921
+ properties
922
+ };
923
+ if (required.length > 0) {
924
+ schemaParams.required = required;
925
+ }
926
+ return {
927
+ name: operationId,
928
+ description: opSpec.summary || opSpec.description || `OpenAPI operation: ${operationId}`,
929
+ parameters: schemaParams
930
+ };
931
+ }
932
+
933
+ // src/implementations/openapi-tool.ts
504
934
  var OpenAPITool = class {
505
935
  schema;
506
936
  apiSpec;
@@ -607,36 +1037,13 @@ var OpenAPITool = class {
607
1037
  * @private
608
1038
  */
609
1039
  async executeAPICall(parameters, _context) {
610
- const operation = this.findOperation();
1040
+ const operation = findOperation(this.apiSpec, this.operationId);
611
1041
  if (!operation) {
612
1042
  throw new Error(`Operation ${this.operationId} not found in OpenAPI spec`);
613
1043
  }
614
- const requestConfig = this.buildRequestConfig(operation, parameters);
1044
+ this.buildRequestConfig(operation, parameters);
615
1045
  throw new Error("Not implemented: actual API execution is not yet available");
616
1046
  }
617
- /**
618
- * Find the operation in the OpenAPI specification
619
- */
620
- findOperation() {
621
- for (const [path, pathItem] of Object.entries(this.apiSpec.paths || {})) {
622
- if (!pathItem) continue;
623
- for (const method of [
624
- "get",
625
- "post",
626
- "put",
627
- "delete",
628
- "patch",
629
- "head",
630
- "options"
631
- ]) {
632
- const operation = pathItem[method];
633
- if (operation?.operationId === this.operationId) {
634
- return { method, path, operation };
635
- }
636
- }
637
- }
638
- return void 0;
639
- }
640
1047
  /**
641
1048
  * Build HTTP request configuration from OpenAPI operation and parameters
642
1049
  */
@@ -708,121 +1115,13 @@ var OpenAPITool = class {
708
1115
  * Create tool schema from OpenAPI operation specification
709
1116
  */
710
1117
  createSchemaFromOpenAPI() {
711
- const operation = this.findOperation();
1118
+ const operation = findOperation(this.apiSpec, this.operationId);
712
1119
  if (!operation) {
713
1120
  throw new Error(
714
1121
  `[STRICT-POLICY][EMITTER-CONTRACT] OpenAPI operation not found: ${this.operationId}. Emitter contract must provide a valid operationId present in the OpenAPI document.`
715
1122
  );
716
1123
  }
717
- const { operation: opSpec } = operation;
718
- const properties = {};
719
- const required = [];
720
- const params = opSpec.parameters || [];
721
- for (const param of params) {
722
- properties[param.name] = this.convertOpenAPIParamToSchema(param);
723
- if (param.required) {
724
- required.push(param.name);
725
- }
726
- }
727
- if (opSpec.requestBody) {
728
- const requestBody = opSpec.requestBody;
729
- const jsonContent = requestBody.content?.["application/json"];
730
- if (jsonContent?.schema) {
731
- const bodySchema = this.convertOpenAPISchemaToParameterSchema(jsonContent.schema);
732
- if (bodySchema.type === "object" && bodySchema.properties) {
733
- Object.assign(properties, bodySchema.properties);
734
- const schemaWithRequired = bodySchema;
735
- if (schemaWithRequired.required) {
736
- required.push(...schemaWithRequired.required);
737
- }
738
- }
739
- }
740
- }
741
- const schemaParams = {
742
- type: "object",
743
- properties
744
- };
745
- if (required.length > 0) {
746
- schemaParams.required = required;
747
- }
748
- return {
749
- name: this.operationId,
750
- description: opSpec.summary || opSpec.description || `OpenAPI operation: ${this.operationId}`,
751
- parameters: schemaParams
752
- };
753
- }
754
- /**
755
- * Convert OpenAPI parameter to tool parameter schema
756
- */
757
- convertOpenAPIParamToSchema(param) {
758
- const schema = param.schema;
759
- return this.convertOpenAPISchemaToParameterSchema(schema);
760
- }
761
- /**
762
- * Convert OpenAPI schema to parameter schema
763
- */
764
- convertOpenAPISchemaToParameterSchema(schema) {
765
- if ("$ref" in schema) {
766
- return { type: "object" };
767
- }
768
- const result = {
769
- type: this.mapOpenAPIType(schema.type)
770
- };
771
- if (schema.description) {
772
- result.description = schema.description;
773
- }
774
- if (schema.enum) {
775
- result.enum = schema.enum;
776
- }
777
- if (schema.minimum !== void 0) {
778
- result.minimum = schema.minimum;
779
- }
780
- if (schema.maximum !== void 0) {
781
- result.maximum = schema.maximum;
782
- }
783
- if (schema.pattern) {
784
- result.pattern = schema.pattern;
785
- }
786
- if (schema.format) {
787
- result.format = schema.format;
788
- }
789
- if (schema.default !== void 0) {
790
- result.default = schema.default;
791
- }
792
- if (schema.type === "array" && schema.items) {
793
- result.items = this.convertOpenAPISchemaToParameterSchema(schema.items);
794
- }
795
- if (schema.type === "object" && schema.properties) {
796
- result.properties = {};
797
- for (const [propName, propSchema] of Object.entries(schema.properties)) {
798
- result.properties[propName] = this.convertOpenAPISchemaToParameterSchema(propSchema);
799
- }
800
- if (schema.required && schema.required.length > 0) {
801
- result.required = schema.required;
802
- }
803
- }
804
- return result;
805
- }
806
- /**
807
- * Map OpenAPI type to JSON schema type
808
- */
809
- mapOpenAPIType(type) {
810
- switch (type) {
811
- case "string":
812
- return "string";
813
- case "number":
814
- return "number";
815
- case "integer":
816
- return "integer";
817
- case "boolean":
818
- return "boolean";
819
- case "array":
820
- return "array";
821
- case "object":
822
- return "object";
823
- default:
824
- return "string";
825
- }
1124
+ return createSchemaFromOperation(this.operationId, operation.operation);
826
1125
  }
827
1126
  };
828
1127
  function createOpenAPITool(config) {
@@ -838,9 +1137,33 @@ var BashSchema = import_zod.z.object({
838
1137
  timeout: import_zod.z.number().optional().describe("Optional timeout in milliseconds (max 600000). Default is 120000 (2 minutes)"),
839
1138
  workingDirectory: import_zod.z.string().optional().describe("Working directory for the command. Defaults to the current working directory")
840
1139
  });
841
- async function runBash(args) {
1140
+ async function runBash(args, options = {}) {
842
1141
  const { command, timeout = DEFAULT_TIMEOUT_MS, workingDirectory } = args;
843
- return new Promise((resolve3) => {
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) => {
844
1167
  const stdoutChunks = [];
845
1168
  const stderrChunks = [];
846
1169
  let timedOut = false;
@@ -859,12 +1182,17 @@ async function runBash(args) {
859
1182
  const timer = setTimeout(() => {
860
1183
  timedOut = true;
861
1184
  child.kill("SIGTERM");
1185
+ settle({
1186
+ success: false,
1187
+ output: Buffer.concat(stdoutChunks).toString("utf8"),
1188
+ error: `Command timed out after ${timeout}ms`
1189
+ });
862
1190
  }, timeout);
863
1191
  function settle(result) {
864
1192
  if (settled) return;
865
1193
  settled = true;
866
1194
  clearTimeout(timer);
867
- resolve3(JSON.stringify(result));
1195
+ resolve4(JSON.stringify(result));
868
1196
  }
869
1197
  child.on("error", (err) => {
870
1198
  settle({
@@ -897,17 +1225,20 @@ ${stderr}` : stdout;
897
1225
  });
898
1226
  });
899
1227
  }
900
- var bashTool = createZodFunctionTool(
901
- "Bash",
902
- "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.",
903
- BashSchema,
904
- async (params) => {
905
- return runBash(params);
906
- }
907
- );
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();
908
1239
 
909
1240
  // src/builtins/read-tool.ts
910
- var import_promises = require("fs/promises");
1241
+ var import_promises2 = require("fs/promises");
911
1242
  var import_zod2 = require("zod");
912
1243
  var DEFAULT_LIMIT = 2e3;
913
1244
  var ReadSchema = import_zod2.z.object({
@@ -934,88 +1265,152 @@ function formatWithLineNumbers(lines, startLine) {
934
1265
  return `${lineNum} ${line}`;
935
1266
  }).join("\n");
936
1267
  }
937
- async function readFileTool(args) {
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 = {}) {
938
1288
  const { filePath, offset, limit = DEFAULT_LIMIT } = args;
939
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
+ }
940
1303
  let fileStats;
941
1304
  try {
942
- fileStats = await (0, import_promises.stat)(filePath);
1305
+ fileStats = await (0, import_promises2.stat)(filePath);
943
1306
  } catch (err) {
944
- const result2 = {
1307
+ const result = {
945
1308
  success: false,
946
1309
  output: "",
947
1310
  error: `File not found: ${filePath}`
948
1311
  };
949
- return JSON.stringify(result2);
1312
+ return JSON.stringify(result);
950
1313
  }
951
1314
  if (!fileStats.isFile()) {
952
- const result2 = {
1315
+ const result = {
953
1316
  success: false,
954
1317
  output: "",
955
1318
  error: `Path is not a file: ${filePath}`
956
1319
  };
957
- return JSON.stringify(result2);
1320
+ return JSON.stringify(result);
958
1321
  }
959
1322
  let buffer;
960
1323
  try {
961
- buffer = await (0, import_promises.readFile)(filePath);
1324
+ buffer = await (0, import_promises2.readFile)(filePath);
962
1325
  } catch (err) {
963
- const result2 = {
1326
+ const result = {
964
1327
  success: false,
965
1328
  output: "",
966
1329
  error: err instanceof Error ? err.message : String(err)
967
1330
  };
968
- return JSON.stringify(result2);
1331
+ return JSON.stringify(result);
969
1332
  }
970
1333
  if (isBinary(buffer)) {
971
- const result2 = {
1334
+ const result = {
972
1335
  success: false,
973
1336
  output: "",
974
1337
  error: `Binary file not supported: ${filePath}`
975
1338
  };
976
- return JSON.stringify(result2);
1339
+ return JSON.stringify(result);
977
1340
  }
978
1341
  const content = buffer.toString("utf8");
979
- const allLines = content.split("\n");
980
- if (allLines[allLines.length - 1] === "") {
981
- allLines.pop();
1342
+ return formatReadResult(filePath, content, startLine, limit);
1343
+ }
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();
1355
+
1356
+ // src/builtins/write-tool.ts
1357
+ var import_zod3 = require("zod");
1358
+
1359
+ // src/builtins/atomic-file-write.ts
1360
+ var import_node_crypto = require("crypto");
1361
+ var import_promises3 = require("fs/promises");
1362
+ var import_node_path2 = require("path");
1363
+ var TEMP_RANDOM_BYTES = 6;
1364
+ var PRESERVED_MODE_BITS = 4095;
1365
+ var MISSING_FILE_ERROR_CODE = "ENOENT";
1366
+ function createTempFilePath(filePath) {
1367
+ const dir = (0, import_node_path2.dirname)(filePath);
1368
+ const name = (0, import_node_path2.basename)(filePath);
1369
+ const suffix = (0, import_node_crypto.randomBytes)(TEMP_RANDOM_BYTES).toString("hex");
1370
+ return (0, import_node_path2.join)(dir, `.${name}.robota-tmp-${process.pid}-${Date.now()}-${suffix}`);
1371
+ }
1372
+ async function readExistingMode(filePath) {
1373
+ try {
1374
+ const fileStats = await (0, import_promises3.stat)(filePath);
1375
+ return fileStats.mode & PRESERVED_MODE_BITS;
1376
+ } catch (error) {
1377
+ if (error instanceof Error && hasErrorCode(error, MISSING_FILE_ERROR_CODE)) return void 0;
1378
+ throw error;
982
1379
  }
983
- const zeroBasedStart = startLine - 1;
984
- const selectedLines = allLines.slice(zeroBasedStart, zeroBasedStart + limit);
985
- const output = formatWithLineNumbers(selectedLines, startLine);
986
- const totalLines = allLines.length;
987
- const returnedLines = selectedLines.length;
988
- const header = returnedLines < totalLines ? `[File: ${filePath} (lines ${startLine}-${startLine + returnedLines - 1} of ${totalLines})]
989
- ` : `[File: ${filePath} (${totalLines} lines)]
990
- `;
991
- const result = {
992
- success: true,
993
- output: header + output
994
- };
995
- return JSON.stringify(result);
996
1380
  }
997
- var readTool = createZodFunctionTool(
998
- "Read",
999
- "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.",
1000
- ReadSchema,
1001
- async (params) => {
1002
- return readFileTool(params);
1381
+ function hasErrorCode(error, code) {
1382
+ return "code" in error && error.code === code;
1383
+ }
1384
+ async function atomicWriteUtf8File(filePath, content) {
1385
+ const dir = (0, import_node_path2.dirname)(filePath);
1386
+ await (0, import_promises3.mkdir)(dir, { recursive: true });
1387
+ const existingMode = await readExistingMode(filePath);
1388
+ const tempFilePath = createTempFilePath(filePath);
1389
+ try {
1390
+ await (0, import_promises3.writeFile)(tempFilePath, content, "utf8");
1391
+ if (existingMode !== void 0) {
1392
+ await (0, import_promises3.chmod)(tempFilePath, existingMode);
1393
+ }
1394
+ await (0, import_promises3.rename)(tempFilePath, filePath);
1395
+ } catch (error) {
1396
+ await (0, import_promises3.rm)(tempFilePath, { force: true }).catch(() => void 0);
1397
+ throw error;
1003
1398
  }
1004
- );
1399
+ }
1005
1400
 
1006
1401
  // src/builtins/write-tool.ts
1007
- var import_promises2 = require("fs/promises");
1008
- var import_node_path = require("path");
1009
- var import_zod3 = require("zod");
1010
1402
  var WriteSchema = import_zod3.z.object({
1011
1403
  filePath: import_zod3.z.string().describe("The absolute path to the file to write"),
1012
1404
  content: import_zod3.z.string().describe("The content to write to the file")
1013
1405
  });
1014
- async function writeFileTool(args) {
1406
+ async function writeFileTool(args, options = {}) {
1015
1407
  const { filePath, content } = args;
1016
1408
  try {
1017
- await (0, import_promises2.mkdir)((0, import_node_path.dirname)(filePath), { recursive: true });
1018
- await (0, import_promises2.writeFile)(filePath, content, "utf8");
1409
+ if (options.sandboxClient) {
1410
+ await options.sandboxClient.writeFile(filePath, content);
1411
+ } else {
1412
+ await atomicWriteUtf8File(filePath, content);
1413
+ }
1019
1414
  const result = {
1020
1415
  success: true,
1021
1416
  output: `Written ${Buffer.byteLength(content, "utf8")} bytes to ${filePath}`
@@ -1030,17 +1425,20 @@ async function writeFileTool(args) {
1030
1425
  return JSON.stringify(result);
1031
1426
  }
1032
1427
  }
1033
- var writeTool = createZodFunctionTool(
1034
- "Write",
1035
- "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.",
1036
- WriteSchema,
1037
- async (params) => {
1038
- return writeFileTool(params);
1039
- }
1040
- );
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();
1041
1439
 
1042
1440
  // src/builtins/edit-tool.ts
1043
- var import_promises3 = require("fs/promises");
1441
+ var import_promises4 = require("fs/promises");
1044
1442
  var import_zod4 = require("zod");
1045
1443
  var EditSchema = import_zod4.z.object({
1046
1444
  filePath: import_zod4.z.string().describe("The absolute path to the file to modify"),
@@ -1050,11 +1448,11 @@ var EditSchema = import_zod4.z.object({
1050
1448
  "Replace all occurrences of old_string (default: false). Useful for renaming variables"
1051
1449
  )
1052
1450
  });
1053
- async function editFileTool(args) {
1451
+ async function editFileTool(args, options = {}) {
1054
1452
  const { filePath, oldString, newString, replaceAll = false } = args;
1055
1453
  let content;
1056
1454
  try {
1057
- content = await (0, import_promises3.readFile)(filePath, "utf8");
1455
+ content = options.sandboxClient ? await options.sandboxClient.readFile(filePath) : await (0, import_promises4.readFile)(filePath, "utf8");
1058
1456
  } catch (err) {
1059
1457
  const result2 = {
1060
1458
  success: false,
@@ -1086,7 +1484,11 @@ async function editFileTool(args) {
1086
1484
  }
1087
1485
  const updated = replaceAll ? content.split(oldString).join(newString) : content.slice(0, content.indexOf(oldString)) + newString + content.slice(content.indexOf(oldString) + oldString.length);
1088
1486
  try {
1089
- await (0, import_promises3.writeFile)(filePath, updated, "utf8");
1487
+ if (options.sandboxClient) {
1488
+ await options.sandboxClient.writeFile(filePath, updated);
1489
+ } else {
1490
+ await atomicWriteUtf8File(filePath, updated);
1491
+ }
1090
1492
  } catch (err) {
1091
1493
  const result2 = {
1092
1494
  success: false,
@@ -1096,24 +1498,30 @@ async function editFileTool(args) {
1096
1498
  return JSON.stringify(result2);
1097
1499
  }
1098
1500
  const count = replaceAll ? content.split(oldString).length - 1 : 1;
1501
+ const matchIdx = content.indexOf(oldString);
1502
+ const startLine = matchIdx >= 0 ? content.substring(0, matchIdx).split("\n").length : 1;
1099
1503
  const result = {
1100
1504
  success: true,
1101
- output: `Replaced ${count} occurrence(s) in ${filePath}`
1505
+ output: `Replaced ${count} occurrence(s) in ${filePath}`,
1506
+ startLine
1102
1507
  };
1103
1508
  return JSON.stringify(result);
1104
1509
  }
1105
- var editTool = createZodFunctionTool(
1106
- "Edit",
1107
- "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.",
1108
- EditSchema,
1109
- async (params) => {
1110
- return editFileTool(params);
1111
- }
1112
- );
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();
1113
1521
 
1114
1522
  // src/builtins/glob-tool.ts
1115
- var import_promises4 = require("fs/promises");
1116
- var import_node_path2 = require("path");
1523
+ var import_promises5 = require("fs/promises");
1524
+ var import_node_path3 = require("path");
1117
1525
  var import_fast_glob = __toESM(require("fast-glob"), 1);
1118
1526
  var import_zod5 = require("zod");
1119
1527
  var DEFAULT_MAX_RESULTS = 1e3;
@@ -1128,7 +1536,7 @@ var GlobSchema = import_zod5.z.object({
1128
1536
  });
1129
1537
  async function globFileTool(args) {
1130
1538
  const { pattern, path: basePath } = args;
1131
- const cwd = basePath ? (0, import_node_path2.resolve)(basePath) : process.cwd();
1539
+ const cwd = basePath ? (0, import_node_path3.resolve)(basePath) : process.cwd();
1132
1540
  let matches;
1133
1541
  try {
1134
1542
  matches = await (0, import_fast_glob.default)(pattern, {
@@ -1147,9 +1555,9 @@ async function globFileTool(args) {
1147
1555
  }
1148
1556
  const withMtime = await Promise.all(
1149
1557
  matches.map(async (p) => {
1150
- const absPath = (0, import_node_path2.resolve)(cwd, p);
1558
+ const absPath = (0, import_node_path3.resolve)(cwd, p);
1151
1559
  try {
1152
- const s = await (0, import_promises4.stat)(absPath);
1560
+ const s = await (0, import_promises5.stat)(absPath);
1153
1561
  return { path: p, mtime: s.mtimeMs };
1154
1562
  } catch {
1155
1563
  return { path: p, mtime: 0 };
@@ -1184,8 +1592,8 @@ var globTool = createZodFunctionTool(
1184
1592
  );
1185
1593
 
1186
1594
  // src/builtins/grep-tool.ts
1187
- var import_promises5 = require("fs/promises");
1188
- var import_node_path3 = require("path");
1595
+ var import_promises6 = require("fs/promises");
1596
+ var import_node_path4 = require("path");
1189
1597
  var import_zod6 = require("zod");
1190
1598
  var GrepSchema = import_zod6.z.object({
1191
1599
  pattern: import_zod6.z.string().describe("The regular expression pattern to search for in file contents"),
@@ -1213,16 +1621,16 @@ async function collectFiles(dirPath, glob) {
1213
1621
  async function walk(current) {
1214
1622
  let entryNames;
1215
1623
  try {
1216
- entryNames = await (0, import_promises5.readdir)(current);
1624
+ entryNames = await (0, import_promises6.readdir)(current);
1217
1625
  } catch {
1218
1626
  return;
1219
1627
  }
1220
1628
  for (const name of entryNames) {
1221
1629
  if (name === "node_modules" || name === ".git") continue;
1222
- const fullPath = (0, import_node_path3.join)(current, name);
1630
+ const fullPath = (0, import_node_path4.join)(current, name);
1223
1631
  let fileStat;
1224
1632
  try {
1225
- fileStat = await (0, import_promises5.stat)(fullPath);
1633
+ fileStat = await (0, import_promises6.stat)(fullPath);
1226
1634
  } catch {
1227
1635
  continue;
1228
1636
  }
@@ -1278,7 +1686,7 @@ async function grepFileTool(args) {
1278
1686
  contextLines = 0,
1279
1687
  outputMode = "files_with_matches"
1280
1688
  } = args;
1281
- const targetPath = searchPath ? (0, import_node_path3.resolve)(searchPath) : process.cwd();
1689
+ const targetPath = searchPath ? (0, import_node_path4.resolve)(searchPath) : process.cwd();
1282
1690
  let regex;
1283
1691
  try {
1284
1692
  regex = new RegExp(pattern);
@@ -1292,7 +1700,7 @@ async function grepFileTool(args) {
1292
1700
  }
1293
1701
  let targetStat;
1294
1702
  try {
1295
- targetStat = await (0, import_promises5.stat)(targetPath);
1703
+ targetStat = await (0, import_promises6.stat)(targetPath);
1296
1704
  } catch {
1297
1705
  const result2 = {
1298
1706
  success: false,
@@ -1311,7 +1719,7 @@ async function grepFileTool(args) {
1311
1719
  for (const filePath of files) {
1312
1720
  let content;
1313
1721
  try {
1314
- const buffer = await (0, import_promises5.readFile)(filePath);
1722
+ const buffer = await (0, import_promises6.readFile)(filePath);
1315
1723
  const checkLen = Math.min(buffer.length, 8192);
1316
1724
  let hasBinary = false;
1317
1725
  for (let i = 0; i < checkLen; i++) {
@@ -1476,17 +1884,25 @@ var webSearchTool = createZodFunctionTool(
1476
1884
  );
1477
1885
  // Annotate the CommonJS export names for ESM import in node:
1478
1886
  0 && (module.exports = {
1887
+ E2BSandboxClient,
1479
1888
  FunctionTool,
1889
+ InMemorySandboxClient,
1480
1890
  OpenAPITool,
1481
1891
  ToolRegistry,
1892
+ applyWorkspaceManifest,
1482
1893
  bashTool,
1894
+ createBashTool,
1895
+ createEditTool,
1483
1896
  createFunctionTool,
1484
1897
  createOpenAPITool,
1898
+ createReadTool,
1899
+ createWriteTool,
1485
1900
  createZodFunctionTool,
1486
1901
  editTool,
1487
1902
  globTool,
1488
1903
  grepTool,
1489
1904
  readTool,
1905
+ validateWorkspaceManifestPath,
1490
1906
  webFetchTool,
1491
1907
  webSearchTool,
1492
1908
  writeTool,