@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.
- package/README.md +93 -19
- package/dist/node/index.cjs +725 -309
- package/dist/node/index.d.cts +173 -39
- package/dist/node/index.d.ts +173 -39
- package/dist/node/index.js +716 -308
- package/package.json +15 -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");
|
|
@@ -215,7 +492,9 @@ function zodToJsonSchema(schema, options = {}) {
|
|
|
215
492
|
type: "object",
|
|
216
493
|
properties,
|
|
217
494
|
required,
|
|
218
|
-
...options.allowAdditionalProperties
|
|
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 =
|
|
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
|
|
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
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
1305
|
+
fileStats = await (0, import_promises2.stat)(filePath);
|
|
943
1306
|
} catch (err) {
|
|
944
|
-
const
|
|
1307
|
+
const result = {
|
|
945
1308
|
success: false,
|
|
946
1309
|
output: "",
|
|
947
1310
|
error: `File not found: ${filePath}`
|
|
948
1311
|
};
|
|
949
|
-
return JSON.stringify(
|
|
1312
|
+
return JSON.stringify(result);
|
|
950
1313
|
}
|
|
951
1314
|
if (!fileStats.isFile()) {
|
|
952
|
-
const
|
|
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(
|
|
1320
|
+
return JSON.stringify(result);
|
|
958
1321
|
}
|
|
959
1322
|
let buffer;
|
|
960
1323
|
try {
|
|
961
|
-
buffer = await (0,
|
|
1324
|
+
buffer = await (0, import_promises2.readFile)(filePath);
|
|
962
1325
|
} catch (err) {
|
|
963
|
-
const
|
|
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(
|
|
1331
|
+
return JSON.stringify(result);
|
|
969
1332
|
}
|
|
970
1333
|
if (isBinary(buffer)) {
|
|
971
|
-
const
|
|
1334
|
+
const result = {
|
|
972
1335
|
success: false,
|
|
973
1336
|
output: "",
|
|
974
1337
|
error: `Binary file not supported: ${filePath}`
|
|
975
1338
|
};
|
|
976
|
-
return JSON.stringify(
|
|
1339
|
+
return JSON.stringify(result);
|
|
977
1340
|
}
|
|
978
1341
|
const content = buffer.toString("utf8");
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
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
|
-
|
|
998
|
-
"
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
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
|
-
|
|
1018
|
-
|
|
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
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
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
|
|
1116
|
-
var
|
|
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,
|
|
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,
|
|
1558
|
+
const absPath = (0, import_node_path3.resolve)(cwd, p);
|
|
1151
1559
|
try {
|
|
1152
|
-
const s = await (0,
|
|
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
|
|
1188
|
-
var
|
|
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,
|
|
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,
|
|
1630
|
+
const fullPath = (0, import_node_path4.join)(current, name);
|
|
1223
1631
|
let fileStat;
|
|
1224
1632
|
try {
|
|
1225
|
-
fileStat = await (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|