@robota-sdk/agent-tools 3.0.0-beta.7 → 3.0.0-beta.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,1494 +1,1452 @@
1
- "use strict";
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region \0rolldown/runtime.js
2
3
  var __create = Object.create;
3
4
  var __defProp = Object.defineProperty;
4
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
7
  var __getProtoOf = Object.getPrototypeOf;
7
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
9
  var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
16
+ }
17
+ return to;
19
18
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
-
30
- // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
33
- FunctionTool: () => FunctionTool,
34
- OpenAPITool: () => OpenAPITool,
35
- ToolRegistry: () => ToolRegistry,
36
- bashTool: () => bashTool,
37
- createFunctionTool: () => createFunctionTool,
38
- createOpenAPITool: () => createOpenAPITool,
39
- createZodFunctionTool: () => createZodFunctionTool,
40
- editTool: () => editTool,
41
- globTool: () => globTool,
42
- grepTool: () => grepTool,
43
- readTool: () => readTool,
44
- webFetchTool: () => webFetchTool,
45
- webSearchTool: () => webSearchTool,
46
- writeTool: () => writeTool,
47
- zodToJsonSchema: () => zodToJsonSchema
48
- });
49
- module.exports = __toCommonJS(index_exports);
50
-
51
- // src/registry/tool-registry.ts
52
- var import_agent_core = require("@robota-sdk/agent-core");
53
- var import_agent_core2 = require("@robota-sdk/agent-core");
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
+ value: mod,
21
+ enumerable: true
22
+ }) : target, mod));
23
+ //#endregion
24
+ let node_fs_promises = require("node:fs/promises");
25
+ let node_path = require("node:path");
26
+ let _robota_sdk_agent_core = require("@robota-sdk/agent-core");
27
+ let node_child_process = require("node:child_process");
28
+ let zod = require("zod");
29
+ let node_crypto = require("node:crypto");
30
+ let fast_glob = require("fast-glob");
31
+ fast_glob = __toESM(fast_glob, 1);
32
+ let p_limit = require("p-limit");
33
+ p_limit = __toESM(p_limit, 1);
34
+ //#region src/sandbox/e2b-sandbox-client.ts
35
+ var E2BSandboxClient = class {
36
+ sandbox;
37
+ connectSandbox;
38
+ createSandboxFromSnapshot;
39
+ constructor(options) {
40
+ this.sandbox = options.sandbox;
41
+ this.connectSandbox = options.connectSandbox;
42
+ this.createSandboxFromSnapshot = options.createSandboxFromSnapshot;
43
+ }
44
+ async run(command, options) {
45
+ const result = await this.sandbox.commands.run(command, {
46
+ background: false,
47
+ timeoutMs: options?.timeoutMs,
48
+ cwd: options?.workingDirectory
49
+ });
50
+ return {
51
+ stdout: result.stdout ?? "",
52
+ stderr: result.stderr ?? "",
53
+ exitCode: result.exitCode ?? result.exit_code ?? 0
54
+ };
55
+ }
56
+ async readFile(path) {
57
+ const content = await this.sandbox.files.read(path);
58
+ return typeof content === "string" ? content : Buffer.from(content).toString("utf8");
59
+ }
60
+ async writeFile(path, content) {
61
+ await this.sandbox.files.write(path, content);
62
+ }
63
+ async snapshot() {
64
+ if (this.sandbox.createSnapshot) {
65
+ const snapshot = await this.sandbox.createSnapshot();
66
+ const snapshotId = snapshot.snapshotId ?? snapshot.id;
67
+ if (!snapshotId) throw new Error("E2B createSnapshot() did not return a snapshot id.");
68
+ return snapshotId;
69
+ }
70
+ const sandboxId = this.sandbox.sandboxId;
71
+ if (!sandboxId) throw new Error("E2B sandboxId is required to create a resumable sandbox snapshot.");
72
+ if (!this.sandbox.pause) throw new Error("E2B sandbox adapter does not expose pause().");
73
+ await this.sandbox.pause();
74
+ return sandboxId;
75
+ }
76
+ async restore(snapshotId) {
77
+ if (this.createSandboxFromSnapshot) {
78
+ this.sandbox = await this.createSandboxFromSnapshot(snapshotId);
79
+ return;
80
+ }
81
+ if (this.connectSandbox) {
82
+ this.sandbox = await this.connectSandbox(snapshotId);
83
+ return;
84
+ }
85
+ if (this.sandbox.sandboxId === snapshotId && this.sandbox.connect) {
86
+ this.sandbox = await this.sandbox.connect();
87
+ return;
88
+ }
89
+ throw new Error("E2B sandbox restore requires connectSandbox(snapshotId) or sandbox.connect().");
90
+ }
91
+ };
92
+ //#endregion
93
+ //#region src/sandbox/in-memory-sandbox-client.ts
94
+ var InMemorySandboxClient = class {
95
+ files = /* @__PURE__ */ new Map();
96
+ snapshots = /* @__PURE__ */ new Map();
97
+ runHandler;
98
+ snapshotSequence = 0;
99
+ constructor(options = {}) {
100
+ for (const [path, content] of Object.entries(options.files ?? {})) this.files.set(path, content);
101
+ this.runHandler = options.runHandler;
102
+ }
103
+ async run(command, options) {
104
+ if (this.runHandler) return this.runHandler(command, options, this.files);
105
+ return {
106
+ stdout: "",
107
+ stderr: "",
108
+ exitCode: 0
109
+ };
110
+ }
111
+ async readFile(path) {
112
+ const content = this.files.get(path);
113
+ if (content === void 0) throw new Error(`Sandbox file not found: ${path}`);
114
+ return content;
115
+ }
116
+ async writeFile(path, content) {
117
+ this.files.set(path, content);
118
+ }
119
+ async snapshot() {
120
+ const snapshotId = `snapshot-${++this.snapshotSequence}`;
121
+ this.snapshots.set(snapshotId, new Map(this.files));
122
+ return snapshotId;
123
+ }
124
+ async restore(snapshotId) {
125
+ const snapshot = this.snapshots.get(snapshotId);
126
+ if (!snapshot) throw new Error(`Sandbox snapshot not found: ${snapshotId}`);
127
+ this.files.clear();
128
+ for (const [path, content] of snapshot.entries()) this.files.set(path, content);
129
+ }
130
+ getFile(path) {
131
+ return this.files.get(path);
132
+ }
133
+ };
134
+ //#endregion
135
+ //#region src/sandbox/workspace-manifest.ts
136
+ const DEFAULT_TARGET_ROOT = "/workspace";
137
+ const WINDOWS_ABSOLUTE_PATH_PATTERN = /^[A-Za-z]:[\\/]/;
138
+ const SHELL_QUOTE_PATTERN = /'/g;
139
+ async function applyWorkspaceManifest(sandboxClient, manifest, options = {}) {
140
+ if (sandboxClient.applyManifest) return sandboxClient.applyManifest(manifest, options);
141
+ const targetRoot = normalizeSandboxRoot(options.targetRoot ?? DEFAULT_TARGET_ROOT);
142
+ const appliedEntries = [];
143
+ for (const [rawPath, entry] of Object.entries(manifest.entries)) {
144
+ const path = validateWorkspaceManifestPath(rawPath);
145
+ const targetPath = joinSandboxPath(targetRoot, path);
146
+ appliedEntries.push(await applyManifestEntry(sandboxClient, path, targetPath, targetRoot, entry, options));
147
+ }
148
+ return { entries: appliedEntries };
149
+ }
150
+ function validateWorkspaceManifestPath(path) {
151
+ if (path.length === 0) throw new Error("workspace manifest path must not be empty");
152
+ if (path.includes("\0")) throw new Error("workspace manifest path must not contain NUL bytes");
153
+ if (path.startsWith("/") || path.startsWith("\\") || WINDOWS_ABSOLUTE_PATH_PATTERN.test(path)) throw new Error("workspace manifest path must be workspace-relative");
154
+ const parts = path.replace(/\\/g, "/").split("/").filter(Boolean);
155
+ if (parts.length === 0) throw new Error("workspace manifest path must not resolve to the workspace root");
156
+ if (parts.some((part) => part === "..")) throw new Error("workspace manifest path cannot contain traversal segments");
157
+ const normalizedParts = parts.filter((part) => part !== ".");
158
+ if (normalizedParts.length === 0) throw new Error("workspace manifest path must not resolve to the workspace root");
159
+ return normalizedParts.join("/");
160
+ }
161
+ async function applyManifestEntry(sandboxClient, path, targetPath, targetRoot, entry, options) {
162
+ switch (entry.type) {
163
+ case "file":
164
+ await writeSandboxFile(sandboxClient, targetPath, targetRoot, entry.content);
165
+ return createAppliedEntry(path, entry.type);
166
+ case "dir":
167
+ await createSandboxDirectory(sandboxClient, targetPath);
168
+ return createAppliedEntry(path, entry.type);
169
+ case "localFile":
170
+ await copyLocalFile(sandboxClient, entry.src, targetPath, targetRoot, options);
171
+ return createAppliedEntry(path, entry.type);
172
+ case "localDir":
173
+ await copyLocalDirectory(sandboxClient, entry.src, targetPath, options);
174
+ return createAppliedEntry(path, entry.type);
175
+ case "gitRepo":
176
+ await cloneGitRepository(sandboxClient, entry, targetPath);
177
+ return createAppliedEntry(path, entry.type);
178
+ case "s3Mount":
179
+ case "gcsMount":
180
+ case "r2Mount":
181
+ case "azureBlobMount": return {
182
+ path,
183
+ type: entry.type,
184
+ status: "unsupported",
185
+ message: `${entry.type} requires a provider-specific sandbox adapter.`
186
+ };
187
+ default: return assertUnreachable(entry);
188
+ }
189
+ }
190
+ function createAppliedEntry(path, type) {
191
+ return {
192
+ path,
193
+ type,
194
+ status: "applied"
195
+ };
196
+ }
197
+ async function copyLocalFile(sandboxClient, source, targetPath, targetRoot, options) {
198
+ await writeSandboxFile(sandboxClient, targetPath, targetRoot, await (0, node_fs_promises.readFile)(resolveHostSourcePath(source, options.hostRoot), "utf8"));
199
+ }
200
+ async function copyLocalDirectory(sandboxClient, source, targetPath, options) {
201
+ await copyLocalDirectoryRecursive(sandboxClient, resolveHostSourcePath(source, options.hostRoot), targetPath);
202
+ }
203
+ async function copyLocalDirectoryRecursive(sandboxClient, sourcePath, targetPath) {
204
+ await createSandboxDirectory(sandboxClient, targetPath);
205
+ const entries = await (0, node_fs_promises.readdir)(sourcePath, { withFileTypes: true });
206
+ for (const entry of entries) {
207
+ const childSourcePath = (0, node_path.join)(sourcePath, entry.name);
208
+ const childTargetPath = joinSandboxPath(targetPath, entry.name);
209
+ if (entry.isDirectory()) {
210
+ await copyLocalDirectoryRecursive(sandboxClient, childSourcePath, childTargetPath);
211
+ continue;
212
+ }
213
+ if (entry.isFile()) {
214
+ const content = await (0, node_fs_promises.readFile)(childSourcePath, "utf8");
215
+ await sandboxClient.writeFile(childTargetPath, content);
216
+ }
217
+ }
218
+ }
219
+ async function cloneGitRepository(sandboxClient, entry, targetPath) {
220
+ await runSandboxCommand(sandboxClient, `git clone${entry.shallow === false ? "" : " --depth 1"}${entry.ref ? ` --branch ${quoteShellArg(entry.ref)}` : ""} ${quoteShellArg(entry.url)} ${quoteShellArg(targetPath)}`);
221
+ }
222
+ async function writeSandboxFile(sandboxClient, targetPath, targetRoot, content) {
223
+ const parentPath = node_path.posix.dirname(targetPath);
224
+ if (parentPath !== targetRoot) await createSandboxDirectory(sandboxClient, parentPath);
225
+ await sandboxClient.writeFile(targetPath, content);
226
+ }
227
+ async function createSandboxDirectory(sandboxClient, targetPath) {
228
+ await runSandboxCommand(sandboxClient, `mkdir -p ${quoteShellArg(targetPath)}`);
229
+ }
230
+ async function runSandboxCommand(sandboxClient, command) {
231
+ const result = await sandboxClient.run(command);
232
+ if (result.exitCode !== 0) throw new Error(`workspace manifest command failed: ${command}\n${result.stderr ?? result.stdout}`);
233
+ }
234
+ function resolveHostSourcePath(source, hostRoot) {
235
+ return (0, node_path.isAbsolute)(source) ? (0, node_path.resolve)(source) : (0, node_path.resolve)(hostRoot ?? process.cwd(), source);
236
+ }
237
+ function normalizeSandboxRoot(root) {
238
+ const normalized = root.replace(/\\/g, "/").replace(/\/+$/, "");
239
+ if (!normalized.startsWith("/")) throw new Error("workspace manifest targetRoot must be an absolute sandbox path");
240
+ return normalized.length === 0 ? "/" : normalized;
241
+ }
242
+ function joinSandboxPath(root, path) {
243
+ const normalizedRoot = normalizeSandboxRoot(root);
244
+ if (normalizedRoot === "/") return `/${path}`;
245
+ return `${normalizedRoot}/${path}`;
246
+ }
247
+ function quoteShellArg(value) {
248
+ return `'${value.replace(SHELL_QUOTE_PATTERN, "'\\''")}'`;
249
+ }
250
+ function assertUnreachable(value) {
251
+ throw new Error(`unsupported workspace manifest entry: ${JSON.stringify(value)}`);
252
+ }
253
+ //#endregion
254
+ //#region src/registry/tool-registry.ts
255
+ /**
256
+ * Tool registry implementation
257
+ * Manages tool registration, validation, and retrieval
258
+ */
54
259
  var ToolRegistry = class {
55
- tools = /* @__PURE__ */ new Map();
56
- /**
57
- * Register a tool
58
- */
59
- register(tool) {
60
- if (!tool.schema?.name) {
61
- throw new import_agent_core.ValidationError("Tool must have a valid schema with name");
62
- }
63
- const toolName = tool.schema.name;
64
- this.validateToolSchema(tool.schema);
65
- if (this.tools.has(toolName)) {
66
- import_agent_core2.logger.warn(`Tool "${toolName}" is already registered, overriding`, {
67
- toolName,
68
- existingTool: this.tools.get(toolName)?.constructor.name
69
- });
70
- }
71
- this.tools.set(toolName, tool);
72
- import_agent_core2.logger.debug(`Tool "${toolName}" registered successfully`, {
73
- toolName,
74
- toolType: tool.constructor.name,
75
- parameters: Object.keys(tool.schema.parameters?.properties || {})
76
- });
77
- }
78
- /**
79
- * Unregister a tool
80
- */
81
- unregister(name) {
82
- if (!this.tools.has(name)) {
83
- import_agent_core2.logger.warn(`Attempted to unregister non-existent tool "${name}"`);
84
- return;
85
- }
86
- this.tools.delete(name);
87
- import_agent_core2.logger.debug(`Tool "${name}" unregistered successfully`);
88
- }
89
- /**
90
- * Get tool by name
91
- */
92
- get(name) {
93
- return this.tools.get(name);
94
- }
95
- /**
96
- * Get all registered tools
97
- */
98
- getAll() {
99
- return Array.from(this.tools.values());
100
- }
101
- /**
102
- * Get tool schemas
103
- */
104
- getSchemas() {
105
- const tools = this.getAll();
106
- import_agent_core2.logger.debug("[TOOL-FLOW] ToolRegistry.getSchemas() - Tools before schema extraction", {
107
- count: tools.length,
108
- tools: tools.map((t) => ({
109
- name: t.schema?.name ?? "unnamed",
110
- hasSchema: !!t.schema,
111
- schemaType: typeof t.schema,
112
- toolType: t.constructor?.name || "unknown"
113
- }))
114
- });
115
- return this.getAll().map((tool) => tool.schema);
116
- }
117
- /**
118
- * Check if tool exists
119
- */
120
- has(name) {
121
- return this.tools.has(name);
122
- }
123
- /**
124
- * Clear all tools
125
- */
126
- clear() {
127
- const toolCount = this.tools.size;
128
- this.tools.clear();
129
- import_agent_core2.logger.debug(`Cleared ${toolCount} tools from registry`);
130
- }
131
- /**
132
- * Get tool names
133
- */
134
- getToolNames() {
135
- return Array.from(this.tools.keys());
136
- }
137
- /**
138
- * Get tools by pattern
139
- */
140
- getToolsByPattern(pattern) {
141
- const regex = typeof pattern === "string" ? new RegExp(pattern) : pattern;
142
- return this.getAll().filter((tool) => regex.test(tool.schema.name));
143
- }
144
- /**
145
- * Get tool count
146
- */
147
- size() {
148
- return this.tools.size;
149
- }
150
- /**
151
- * Validate tool schema
152
- */
153
- validateToolSchema(schema) {
154
- if (!schema.name || typeof schema.name !== "string") {
155
- throw new import_agent_core.ValidationError("Tool schema must have a valid name");
156
- }
157
- if (!schema.description || typeof schema.description !== "string") {
158
- throw new import_agent_core.ValidationError("Tool schema must have a description");
159
- }
160
- if (!schema.parameters || typeof schema.parameters !== "object" || schema.parameters === null || Array.isArray(schema.parameters)) {
161
- throw new import_agent_core.ValidationError("Tool schema must have parameters object");
162
- }
163
- if (schema.parameters.type !== "object") {
164
- throw new import_agent_core.ValidationError('Tool parameters type must be "object"');
165
- }
166
- if (schema.parameters.properties) {
167
- for (const propName of Object.keys(schema.parameters.properties)) {
168
- const propSchema = schema.parameters.properties[propName];
169
- if (!propSchema?.type) {
170
- throw new import_agent_core.ValidationError(`Parameter "${propName}" must have a type`);
171
- }
172
- const validTypes = ["string", "number", "boolean", "array", "object"];
173
- if (!validTypes.includes(propSchema.type)) {
174
- throw new import_agent_core.ValidationError(
175
- `Parameter "${propName}" has invalid type "${propSchema.type}"`
176
- );
177
- }
178
- }
179
- }
180
- if (schema.parameters.required) {
181
- const properties = schema.parameters.properties || {};
182
- for (const requiredField of schema.parameters.required) {
183
- if (!properties[requiredField]) {
184
- throw new import_agent_core.ValidationError(
185
- `Required parameter "${requiredField}" is not defined in properties`
186
- );
187
- }
188
- }
189
- }
190
- }
260
+ tools = /* @__PURE__ */ new Map();
261
+ /**
262
+ * Register a tool
263
+ */
264
+ register(tool) {
265
+ if (!tool.schema?.name) throw new _robota_sdk_agent_core.ValidationError("Tool must have a valid schema with name");
266
+ const toolName = tool.schema.name;
267
+ this.validateToolSchema(tool.schema);
268
+ if (this.tools.has(toolName)) _robota_sdk_agent_core.logger.warn(`Tool "${toolName}" is already registered, overriding`, {
269
+ toolName,
270
+ existingTool: this.tools.get(toolName)?.constructor.name
271
+ });
272
+ this.tools.set(toolName, tool);
273
+ _robota_sdk_agent_core.logger.debug(`Tool "${toolName}" registered successfully`, {
274
+ toolName,
275
+ toolType: tool.constructor.name,
276
+ parameters: Object.keys(tool.schema.parameters?.properties || {})
277
+ });
278
+ }
279
+ /**
280
+ * Unregister a tool
281
+ */
282
+ unregister(name) {
283
+ if (!this.tools.has(name)) {
284
+ _robota_sdk_agent_core.logger.warn(`Attempted to unregister non-existent tool "${name}"`);
285
+ return;
286
+ }
287
+ this.tools.delete(name);
288
+ _robota_sdk_agent_core.logger.debug(`Tool "${name}" unregistered successfully`);
289
+ }
290
+ /**
291
+ * Get tool by name
292
+ */
293
+ get(name) {
294
+ return this.tools.get(name);
295
+ }
296
+ /**
297
+ * Get all registered tools
298
+ */
299
+ getAll() {
300
+ return Array.from(this.tools.values());
301
+ }
302
+ /**
303
+ * Get tool schemas
304
+ */
305
+ getSchemas() {
306
+ const tools = this.getAll();
307
+ _robota_sdk_agent_core.logger.debug("[TOOL-FLOW] ToolRegistry.getSchemas() - Tools before schema extraction", {
308
+ count: tools.length,
309
+ tools: tools.map((t) => ({
310
+ name: t.schema?.name ?? "unnamed",
311
+ hasSchema: !!t.schema,
312
+ schemaType: typeof t.schema,
313
+ toolType: t.constructor?.name || "unknown"
314
+ }))
315
+ });
316
+ return this.getAll().map((tool) => tool.schema);
317
+ }
318
+ /**
319
+ * Check if tool exists
320
+ */
321
+ has(name) {
322
+ return this.tools.has(name);
323
+ }
324
+ /**
325
+ * Clear all tools
326
+ */
327
+ clear() {
328
+ const toolCount = this.tools.size;
329
+ this.tools.clear();
330
+ _robota_sdk_agent_core.logger.debug(`Cleared ${toolCount} tools from registry`);
331
+ }
332
+ /**
333
+ * Get tool names
334
+ */
335
+ getToolNames() {
336
+ return Array.from(this.tools.keys());
337
+ }
338
+ /**
339
+ * Get tools by pattern
340
+ */
341
+ getToolsByPattern(pattern) {
342
+ const regex = typeof pattern === "string" ? new RegExp(pattern) : pattern;
343
+ return this.getAll().filter((tool) => regex.test(tool.schema.name));
344
+ }
345
+ /**
346
+ * Get tool count
347
+ */
348
+ size() {
349
+ return this.tools.size;
350
+ }
351
+ /**
352
+ * Validate tool schema
353
+ */
354
+ validateToolSchema(schema) {
355
+ if (!schema.name || typeof schema.name !== "string") throw new _robota_sdk_agent_core.ValidationError("Tool schema must have a valid name");
356
+ if (!schema.description || typeof schema.description !== "string") throw new _robota_sdk_agent_core.ValidationError("Tool schema must have a description");
357
+ if (!schema.parameters || typeof schema.parameters !== "object" || schema.parameters === null || Array.isArray(schema.parameters)) throw new _robota_sdk_agent_core.ValidationError("Tool schema must have parameters object");
358
+ if (schema.parameters.type !== "object") throw new _robota_sdk_agent_core.ValidationError("Tool parameters type must be \"object\"");
359
+ if (schema.parameters.properties) for (const propName of Object.keys(schema.parameters.properties)) {
360
+ const propSchema = schema.parameters.properties[propName];
361
+ if (!propSchema?.type) throw new _robota_sdk_agent_core.ValidationError(`Parameter "${propName}" must have a type`);
362
+ if (![
363
+ "string",
364
+ "number",
365
+ "boolean",
366
+ "array",
367
+ "object"
368
+ ].includes(propSchema.type)) throw new _robota_sdk_agent_core.ValidationError(`Parameter "${propName}" has invalid type "${propSchema.type}"`);
369
+ }
370
+ if (schema.parameters.required) {
371
+ const properties = schema.parameters.properties || {};
372
+ for (const requiredField of schema.parameters.required) if (!properties[requiredField]) throw new _robota_sdk_agent_core.ValidationError(`Required parameter "${requiredField}" is not defined in properties`);
373
+ }
374
+ }
191
375
  };
192
-
193
- // src/implementations/function-tool.ts
194
- var import_agent_core3 = require("@robota-sdk/agent-core");
195
-
196
- // src/implementations/function-tool/schema-converter.ts
376
+ //#endregion
377
+ //#region src/implementations/function-tool/parameter-validator.ts
378
+ /**
379
+ * Validate individual parameter type against its schema.
380
+ * Returns an error string if invalid, undefined if valid.
381
+ */
382
+ function validateParameterType(key, value, schema) {
383
+ switch (schema["type"]) {
384
+ case "string":
385
+ if (typeof value !== "string") return `Parameter "${key}" must be a string, got ${typeof value}`;
386
+ break;
387
+ case "number":
388
+ if (typeof value !== "number" || isNaN(value)) return `Parameter "${key}" must be a number, got ${typeof value}`;
389
+ break;
390
+ case "boolean":
391
+ if (typeof value !== "boolean") return `Parameter "${key}" must be a boolean, got ${typeof value}`;
392
+ break;
393
+ case "array":
394
+ if (!Array.isArray(value)) return `Parameter "${key}" must be an array, got ${typeof value}`;
395
+ if (schema.items) for (let i = 0; i < value.length; i++) {
396
+ const itemError = validateParameterType(`${key}[${i}]`, value[i], schema.items);
397
+ if (itemError) return itemError;
398
+ }
399
+ break;
400
+ case "object":
401
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return `Parameter "${key}" must be an object, got ${typeof value}`;
402
+ break;
403
+ }
404
+ if (schema.enum && schema.enum.length > 0) {
405
+ const enumValues = schema.enum;
406
+ let isValidEnum = false;
407
+ for (const enumValue of enumValues) if (value === enumValue) {
408
+ isValidEnum = true;
409
+ break;
410
+ }
411
+ if (!isValidEnum) return `Parameter "${key}" must be one of: ${enumValues.join(", ")}, got ${value}`;
412
+ }
413
+ }
414
+ /**
415
+ * Collect all validation errors for the given parameters against a schema.
416
+ */
417
+ function getValidationErrors(parameters, schemaRequired, schemaProperties, additionalProperties) {
418
+ const errors = [];
419
+ for (const field of schemaRequired) if (!(field in parameters)) errors.push(`Missing required parameter: ${field}`);
420
+ for (const [key, value] of Object.entries(parameters)) {
421
+ const paramSchema = schemaProperties[key];
422
+ if (!paramSchema) {
423
+ if (additionalProperties === true) continue;
424
+ if (additionalProperties && typeof additionalProperties === "object") {
425
+ const additionalTypeError = validateParameterType(key, value, additionalProperties);
426
+ if (additionalTypeError) errors.push(additionalTypeError);
427
+ continue;
428
+ }
429
+ errors.push(`Unknown parameter: ${key}`);
430
+ continue;
431
+ }
432
+ const typeError = validateParameterType(key, value, paramSchema);
433
+ if (typeError) errors.push(typeError);
434
+ }
435
+ return errors;
436
+ }
437
+ /**
438
+ * Validate parameters and return a structured result.
439
+ */
440
+ function validateToolParameters(parameters, schemaRequired, schemaProperties, additionalProperties) {
441
+ const errors = getValidationErrors(parameters, schemaRequired, schemaProperties, additionalProperties);
442
+ return {
443
+ isValid: errors.length === 0,
444
+ errors
445
+ };
446
+ }
447
+ //#endregion
448
+ //#region src/implementations/function-tool/schema-converter.ts
449
+ /**
450
+ * Convert Zod schema to JSON Schema format with safe undefined handling
451
+ */
197
452
  function zodToJsonSchema(schema, options = {}) {
198
- const properties = {};
199
- const required = [];
200
- const schemaDef = schema._def;
201
- if (!schemaDef) {
202
- throw new Error("Zod schema is missing _def; cannot convert to JSON schema.");
203
- }
204
- if (schemaDef.typeName === "ZodObject" && schemaDef.shape) {
205
- const shape = typeof schemaDef.shape === "function" ? schemaDef.shape() : schemaDef.shape;
206
- for (const [key, typeObj] of Object.entries(shape)) {
207
- const property = convertZodTypeToProperty(typeObj);
208
- properties[key] = property;
209
- if (isRequiredField(typeObj)) {
210
- required.push(key);
211
- }
212
- }
213
- }
214
- return {
215
- type: "object",
216
- properties,
217
- required,
218
- ...options.allowAdditionalProperties && { additionalProperties: true }
219
- };
453
+ const properties = {};
454
+ const required = [];
455
+ const schemaDef = schema._def;
456
+ if (!schemaDef) throw new Error("Zod schema is missing _def; cannot convert to JSON schema.");
457
+ if (schemaDef.typeName === "ZodObject" && schemaDef.shape) {
458
+ const shape = typeof schemaDef.shape === "function" ? schemaDef.shape() : schemaDef.shape;
459
+ for (const [key, typeObj] of Object.entries(shape)) {
460
+ properties[key] = convertZodTypeToProperty(typeObj);
461
+ if (isRequiredField(typeObj)) required.push(key);
462
+ }
463
+ }
464
+ return {
465
+ type: "object",
466
+ properties,
467
+ required,
468
+ ...(options.allowAdditionalProperties || schemaDef.unknownKeys === "passthrough") && { additionalProperties: true }
469
+ };
220
470
  }
471
+ /**
472
+ * Convert individual Zod type to parameter schema with safe undefined handling
473
+ */
221
474
  function convertZodTypeToProperty(typeObj) {
222
- const typeDef = typeObj._def;
223
- if (!typeDef) {
224
- throw new Error("Zod type is missing _def; cannot convert to JSON schema.");
225
- }
226
- const base = {};
227
- if (typeDef.description) {
228
- base.description = typeDef.description;
229
- }
230
- switch (typeDef.typeName) {
231
- case "ZodString":
232
- return { type: "string", ...base };
233
- case "ZodNumber":
234
- return { type: "number", ...base };
235
- case "ZodBoolean":
236
- return { type: "boolean", ...base };
237
- case "ZodArray": {
238
- if (!typeDef.type) {
239
- throw new Error("ZodArray is missing item type; cannot convert to JSON schema.");
240
- }
241
- const arrayItems = convertZodTypeToProperty(typeDef.type);
242
- return {
243
- type: "array",
244
- items: arrayItems,
245
- ...base
246
- };
247
- }
248
- case "ZodObject":
249
- return { type: "object", ...base };
250
- case "ZodEnum": {
251
- const enumValues = typeDef.values;
252
- if (!enumValues || !Array.isArray(enumValues)) {
253
- throw new Error("ZodEnum is missing enum values; cannot convert to JSON schema.");
254
- }
255
- return {
256
- type: "string",
257
- enum: enumValues,
258
- ...base
259
- };
260
- }
261
- case "ZodOptional":
262
- if (typeDef.innerType) {
263
- const innerProperty = convertZodTypeToProperty(typeDef.innerType);
264
- return { ...innerProperty, ...base };
265
- }
266
- throw new Error("ZodOptional is missing innerType; cannot convert to JSON schema.");
267
- case "ZodNullable":
268
- if (typeDef.innerType) {
269
- const innerProperty = convertZodTypeToProperty(typeDef.innerType);
270
- return { ...innerProperty, ...base };
271
- }
272
- throw new Error("ZodNullable is missing innerType; cannot convert to JSON schema.");
273
- case "ZodDefault":
274
- if (typeDef.innerType) {
275
- const innerProperty = convertZodTypeToProperty(typeDef.innerType);
276
- return { ...innerProperty, ...base };
277
- }
278
- throw new Error("ZodDefault is missing innerType; cannot convert to JSON schema.");
279
- case "ZodRecord":
280
- if (typeDef.valueType) {
281
- const valueProperty = convertZodTypeToProperty(typeDef.valueType);
282
- return { type: "object", additionalProperties: valueProperty, ...base };
283
- }
284
- return { type: "object", additionalProperties: { type: "string" }, ...base };
285
- default:
286
- throw new Error(`Unsupported Zod type: ${String(typeDef.typeName)}`);
287
- }
475
+ const typeDef = typeObj._def;
476
+ if (!typeDef) throw new Error("Zod type is missing _def; cannot convert to JSON schema.");
477
+ const base = {};
478
+ if (typeDef.description) base.description = typeDef.description;
479
+ switch (typeDef.typeName) {
480
+ case "ZodString": return {
481
+ type: "string",
482
+ ...base
483
+ };
484
+ case "ZodNumber": return {
485
+ type: "number",
486
+ ...base
487
+ };
488
+ case "ZodBoolean": return {
489
+ type: "boolean",
490
+ ...base
491
+ };
492
+ case "ZodArray":
493
+ if (!typeDef.type) throw new Error("ZodArray is missing item type; cannot convert to JSON schema.");
494
+ return {
495
+ type: "array",
496
+ items: convertZodTypeToProperty(typeDef.type),
497
+ ...base
498
+ };
499
+ case "ZodObject": return {
500
+ type: "object",
501
+ ...base
502
+ };
503
+ case "ZodEnum": {
504
+ const enumValues = typeDef.values;
505
+ if (!enumValues || !Array.isArray(enumValues)) throw new Error("ZodEnum is missing enum values; cannot convert to JSON schema.");
506
+ return {
507
+ type: "string",
508
+ enum: enumValues,
509
+ ...base
510
+ };
511
+ }
512
+ case "ZodOptional":
513
+ if (typeDef.innerType) return {
514
+ ...convertZodTypeToProperty(typeDef.innerType),
515
+ ...base
516
+ };
517
+ throw new Error("ZodOptional is missing innerType; cannot convert to JSON schema.");
518
+ case "ZodNullable":
519
+ if (typeDef.innerType) return {
520
+ ...convertZodTypeToProperty(typeDef.innerType),
521
+ ...base
522
+ };
523
+ throw new Error("ZodNullable is missing innerType; cannot convert to JSON schema.");
524
+ case "ZodDefault":
525
+ if (typeDef.innerType) return {
526
+ ...convertZodTypeToProperty(typeDef.innerType),
527
+ ...base
528
+ };
529
+ throw new Error("ZodDefault is missing innerType; cannot convert to JSON schema.");
530
+ case "ZodRecord":
531
+ if (typeDef.valueType) return {
532
+ type: "object",
533
+ additionalProperties: convertZodTypeToProperty(typeDef.valueType),
534
+ ...base
535
+ };
536
+ return {
537
+ type: "object",
538
+ additionalProperties: { type: "string" },
539
+ ...base
540
+ };
541
+ default: throw new Error(`Unsupported Zod type: ${String(typeDef.typeName)}`);
542
+ }
288
543
  }
544
+ /**
545
+ * Check if a Zod field is required (not optional or nullable)
546
+ */
289
547
  function isRequiredField(typeObj) {
290
- const typeDef = typeObj._def;
291
- if (!typeDef) {
292
- throw new Error("Zod schema is missing _def; cannot determine required fields.");
293
- }
294
- return typeDef.typeName !== "ZodOptional" && typeDef.typeName !== "ZodNullable" && typeDef.typeName !== "ZodDefault";
548
+ const typeDef = typeObj._def;
549
+ if (!typeDef) throw new Error("Zod schema is missing _def; cannot determine required fields.");
550
+ return typeDef.typeName !== "ZodOptional" && typeDef.typeName !== "ZodNullable" && typeDef.typeName !== "ZodDefault";
295
551
  }
296
-
297
- // src/implementations/function-tool.ts
552
+ //#endregion
553
+ //#region src/implementations/function-tool.ts
554
+ /**
555
+ * Function tool implementation
556
+ * Wraps a JavaScript function as a tool with schema validation
557
+ *
558
+ * Implements IFunctionTool without extending AbstractTool to avoid
559
+ * circular runtime dependency (tools → agents → tools).
560
+ */
298
561
  var FunctionTool = class {
299
- schema;
300
- fn;
301
- eventService;
302
- constructor(schema, fn) {
303
- this.schema = schema;
304
- this.fn = fn;
305
- this.validateConstructorInputs();
306
- }
307
- /**
308
- * Get tool name
309
- */
310
- getName() {
311
- return this.schema.name;
312
- }
313
- /**
314
- * Set EventService for post-construction injection.
315
- * Accepts EventService as-is without transformation.
316
- * Caller is responsible for providing properly configured EventService.
317
- */
318
- setEventService(eventService) {
319
- this.eventService = eventService;
320
- }
321
- /**
322
- * Execute the function tool
323
- */
324
- async execute(parameters, context) {
325
- const toolName = this.schema.name;
326
- if (!this.validate(parameters)) {
327
- const errors = this.getValidationErrors(parameters);
328
- throw new import_agent_core3.ValidationError(`Invalid parameters for tool "${toolName}": ${errors.join(", ")}`);
329
- }
330
- const startTime = Date.now();
331
- let result;
332
- try {
333
- result = await this.fn(parameters, context);
334
- } catch (error) {
335
- if (error instanceof import_agent_core3.ToolExecutionError || error instanceof import_agent_core3.ValidationError) {
336
- throw error;
337
- }
338
- throw new import_agent_core3.ToolExecutionError(
339
- `Function tool execution failed: ${error instanceof Error ? error.message : String(error)}`,
340
- toolName,
341
- error instanceof Error ? error : new Error(String(error)),
342
- {
343
- parameterCount: Object.keys(parameters || {}).length,
344
- hasContext: !!context
345
- }
346
- );
347
- }
348
- const executionTime = Date.now() - startTime;
349
- return {
350
- success: true,
351
- data: result,
352
- metadata: {
353
- executionTime,
354
- toolName,
355
- parameters
356
- }
357
- };
358
- }
359
- /**
360
- * Validate parameters (simple boolean result)
361
- */
362
- validate(parameters) {
363
- return this.getValidationErrors(parameters).length === 0;
364
- }
365
- /**
366
- * Validate tool parameters with detailed result
367
- */
368
- validateParameters(parameters) {
369
- const errors = this.getValidationErrors(parameters);
370
- return {
371
- isValid: errors.length === 0,
372
- errors
373
- };
374
- }
375
- /**
376
- * Get tool description
377
- */
378
- getDescription() {
379
- return this.schema.description;
380
- }
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
- /**
462
- * Validate constructor inputs
463
- */
464
- validateConstructorInputs() {
465
- if (!this.schema) {
466
- throw new import_agent_core3.ValidationError("Tool schema is required");
467
- }
468
- if (!this.fn || typeof this.fn !== "function") {
469
- throw new import_agent_core3.ValidationError("Tool function is required and must be a function");
470
- }
471
- if (!this.schema.name) {
472
- throw new import_agent_core3.ValidationError("Tool schema must have a name");
473
- }
474
- }
562
+ schema;
563
+ fn;
564
+ eventService;
565
+ constructor(schema, fn) {
566
+ this.schema = schema;
567
+ this.fn = fn;
568
+ this.validateConstructorInputs();
569
+ }
570
+ /**
571
+ * Get tool name
572
+ */
573
+ getName() {
574
+ return this.schema.name;
575
+ }
576
+ /**
577
+ * Set EventService for post-construction injection.
578
+ * Accepts EventService as-is without transformation.
579
+ * Caller is responsible for providing properly configured EventService.
580
+ */
581
+ setEventService(eventService) {
582
+ this.eventService = eventService;
583
+ }
584
+ /**
585
+ * Execute the function tool
586
+ */
587
+ async execute(parameters, context) {
588
+ const toolName = this.schema.name;
589
+ if (!this.validate(parameters)) throw new _robota_sdk_agent_core.ValidationError(`Invalid parameters for tool "${toolName}": ${getValidationErrors(parameters, this.schema.parameters.required || [], this.schema.parameters.properties || {}, this.schema.parameters.additionalProperties).join(", ")}`);
590
+ const startTime = Date.now();
591
+ let result;
592
+ try {
593
+ result = await this.fn(parameters, context);
594
+ } catch (error) {
595
+ if (error instanceof _robota_sdk_agent_core.ToolExecutionError || error instanceof _robota_sdk_agent_core.ValidationError) throw error;
596
+ throw new _robota_sdk_agent_core.ToolExecutionError(`Function tool execution failed: ${error instanceof Error ? error.message : String(error)}`, toolName, error instanceof Error ? error : new Error(String(error)), {
597
+ parameterCount: Object.keys(parameters || {}).length,
598
+ hasContext: !!context
599
+ });
600
+ }
601
+ const executionTime = Date.now() - startTime;
602
+ return {
603
+ success: true,
604
+ data: result,
605
+ metadata: {
606
+ executionTime,
607
+ toolName,
608
+ parameters
609
+ }
610
+ };
611
+ }
612
+ /**
613
+ * Validate parameters (simple boolean result)
614
+ */
615
+ validate(parameters) {
616
+ return getValidationErrors(parameters, this.schema.parameters.required || [], this.schema.parameters.properties || {}, this.schema.parameters.additionalProperties).length === 0;
617
+ }
618
+ /**
619
+ * Validate tool parameters with detailed result
620
+ */
621
+ validateParameters(parameters) {
622
+ return validateToolParameters(parameters, this.schema.parameters.required || [], this.schema.parameters.properties || {}, this.schema.parameters.additionalProperties);
623
+ }
624
+ /**
625
+ * Get tool description
626
+ */
627
+ getDescription() {
628
+ return this.schema.description;
629
+ }
630
+ /**
631
+ * Validate constructor inputs
632
+ */
633
+ validateConstructorInputs() {
634
+ if (!this.schema) throw new _robota_sdk_agent_core.ValidationError("Tool schema is required");
635
+ if (!this.fn || typeof this.fn !== "function") throw new _robota_sdk_agent_core.ValidationError("Tool function is required and must be a function");
636
+ if (!this.schema.name) throw new _robota_sdk_agent_core.ValidationError("Tool schema must have a name");
637
+ }
475
638
  };
639
+ /**
640
+ * Helper function to create a function tool from a simple function
641
+ */
476
642
  function createFunctionTool(name, description, parameters, fn) {
477
- const schema = {
478
- name,
479
- description,
480
- parameters
481
- };
482
- return new FunctionTool(schema, fn);
643
+ return new FunctionTool({
644
+ name,
645
+ description,
646
+ parameters
647
+ }, fn);
483
648
  }
649
+ /**
650
+ * Helper function to create a function tool from Zod schema
651
+ */
484
652
  function createZodFunctionTool(name, description, zodSchema, fn) {
485
- const parameters = zodToJsonSchema(zodSchema);
486
- const schema = {
487
- name,
488
- description,
489
- parameters
490
- };
491
- const wrappedFn = async (parameters2, context) => {
492
- const parseResult = zodSchema.safeParse(parameters2);
493
- if (!parseResult.success) {
494
- throw new import_agent_core3.ValidationError(`Zod validation failed: ${parseResult.error}`);
495
- }
496
- const result = await fn(parseResult.data || parameters2, context);
497
- return typeof result === "string" ? result : JSON.stringify(result);
498
- };
499
- return new FunctionTool(schema, wrappedFn);
500
- }
501
-
502
- // src/implementations/openapi-tool.ts
503
- var import_agent_core4 = require("@robota-sdk/agent-core");
504
- var OpenAPITool = class {
505
- schema;
506
- apiSpec;
507
- operationId;
508
- baseURL;
509
- config;
510
- eventService;
511
- constructor(config) {
512
- this.config = config;
513
- if (typeof config.spec !== "object" || config.spec === null || typeof config.spec.openapi !== "string" || typeof config.spec.paths !== "object") {
514
- throw new Error(
515
- 'Invalid OpenAPI spec: must contain "openapi" (string) and "paths" (object) fields'
516
- );
517
- }
518
- this.apiSpec = config.spec;
519
- this.operationId = config.operationId;
520
- this.baseURL = config.baseURL;
521
- this.schema = this.createSchemaFromOpenAPI();
522
- }
523
- /**
524
- * Execute the OpenAPI tool
525
- */
526
- async execute(parameters, context) {
527
- const toolName = this.schema.name;
528
- const validation = this.validateParameters(parameters);
529
- if (!validation.isValid) {
530
- throw new import_agent_core4.ValidationError(
531
- `Invalid parameters for OpenAPI tool "${toolName}": ${validation.errors.join(", ")}`
532
- );
533
- }
534
- try {
535
- const startTime = Date.now();
536
- const result = await this.executeAPICall(parameters, context);
537
- const executionTime = Date.now() - startTime;
538
- return {
539
- success: true,
540
- data: result,
541
- metadata: {
542
- executionTime,
543
- toolName,
544
- operationId: this.operationId,
545
- baseURL: this.baseURL
546
- }
547
- };
548
- } catch (error) {
549
- if (error instanceof import_agent_core4.ToolExecutionError || error instanceof import_agent_core4.ValidationError) {
550
- throw error;
551
- }
552
- const safeError = error instanceof Error ? error : new Error(String(error));
553
- throw new import_agent_core4.ToolExecutionError(
554
- `OpenAPI tool execution failed: ${safeError.message}`,
555
- toolName,
556
- safeError,
557
- {
558
- operationId: this.operationId,
559
- baseURL: this.baseURL,
560
- parametersCount: Object.keys(parameters).length
561
- }
562
- );
563
- }
564
- }
565
- /**
566
- * Validate tool parameters
567
- */
568
- validate(parameters) {
569
- return this.validateParameters(parameters).isValid;
570
- }
571
- /**
572
- * Validate tool parameters with detailed result
573
- */
574
- validateParameters(parameters) {
575
- const required = this.schema.parameters.required || [];
576
- const errors = [];
577
- for (const field of required) {
578
- if (!(field in parameters)) {
579
- errors.push(`Missing required parameter: ${field}`);
580
- }
581
- }
582
- return {
583
- isValid: errors.length === 0,
584
- errors
585
- };
586
- }
587
- /**
588
- * Get tool name
589
- */
590
- getName() {
591
- return this.schema.name;
592
- }
593
- /**
594
- * Set EventService for post-construction injection.
595
- */
596
- setEventService(eventService) {
597
- this.eventService = eventService;
598
- }
599
- /**
600
- * Get tool description
601
- */
602
- getDescription() {
603
- return this.schema.description;
604
- }
605
- /**
606
- * Execute the actual API call
607
- * @private
608
- */
609
- async executeAPICall(parameters, _context) {
610
- const operation = this.findOperation();
611
- if (!operation) {
612
- throw new Error(`Operation ${this.operationId} not found in OpenAPI spec`);
613
- }
614
- const requestConfig = this.buildRequestConfig(operation, parameters);
615
- throw new Error("Not implemented: actual API execution is not yet available");
616
- }
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
- /**
641
- * Build HTTP request configuration from OpenAPI operation and parameters
642
- */
643
- buildRequestConfig(opInfo, parameters) {
644
- const { method, path, operation } = opInfo;
645
- let url = this.baseURL + path;
646
- const headers = {};
647
- let body;
648
- const params = operation.parameters || [];
649
- for (const param of params) {
650
- const value = parameters[param.name];
651
- if (value === void 0 && param.required) {
652
- throw new Error(`Required parameter ${param.name} is missing`);
653
- }
654
- if (value !== void 0) {
655
- switch (param.in) {
656
- case "path":
657
- url = url.replace(`{${param.name}}`, encodeURIComponent(String(value)));
658
- break;
659
- case "query": {
660
- const separator = url.includes("?") ? "&" : "?";
661
- url += `${separator}${param.name}=${encodeURIComponent(String(value))}`;
662
- break;
663
- }
664
- case "header":
665
- headers[param.name] = String(value);
666
- break;
667
- }
668
- }
669
- }
670
- if (["post", "put", "patch"].includes(method) && operation.requestBody) {
671
- const requestBody = operation.requestBody;
672
- const jsonContent = requestBody.content?.["application/json"];
673
- if (jsonContent) {
674
- headers["Content-Type"] = "application/json";
675
- const bodyParams = {};
676
- for (const [key, value] of Object.entries(parameters)) {
677
- const isParamUsed = params.some((p) => p.name === key);
678
- if (!isParamUsed) {
679
- bodyParams[key] = value;
680
- }
681
- }
682
- body = JSON.stringify(bodyParams);
683
- }
684
- }
685
- if (this.config.auth) {
686
- switch (this.config.auth.type) {
687
- case "bearer":
688
- headers["Authorization"] = `Bearer ${this.config.auth.token}`;
689
- break;
690
- case "apiKey": {
691
- const headerName = this.config.auth.header || "X-API-Key";
692
- headers[headerName] = this.config.auth.apiKey || "";
693
- break;
694
- }
695
- }
696
- }
697
- const result = {
698
- method,
699
- url,
700
- headers
701
- };
702
- if (body !== void 0) {
703
- result.body = body;
704
- }
705
- return result;
706
- }
707
- /**
708
- * Create tool schema from OpenAPI operation specification
709
- */
710
- createSchemaFromOpenAPI() {
711
- const operation = this.findOperation();
712
- if (!operation) {
713
- throw new Error(
714
- `[STRICT-POLICY][EMITTER-CONTRACT] OpenAPI operation not found: ${this.operationId}. Emitter contract must provide a valid operationId present in the OpenAPI document.`
715
- );
716
- }
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
- }
826
- }
827
- };
828
- function createOpenAPITool(config) {
829
- return new OpenAPITool(config);
653
+ const schema = {
654
+ name,
655
+ description,
656
+ parameters: zodToJsonSchema(zodSchema)
657
+ };
658
+ const wrappedFn = async (parameters, context) => {
659
+ const parseResult = zodSchema.safeParse(parameters);
660
+ if (!parseResult.success) throw new _robota_sdk_agent_core.ValidationError(`Zod validation failed: ${parseResult.error}`);
661
+ const result = await fn(parseResult.data || parameters, context);
662
+ return typeof result === "string" ? result : JSON.stringify(result);
663
+ };
664
+ return new FunctionTool(schema, wrappedFn);
830
665
  }
831
-
832
- // src/builtins/bash-tool.ts
833
- var import_node_child_process = require("child_process");
834
- var import_zod = require("zod");
835
- var DEFAULT_TIMEOUT_MS = 12e4;
836
- var BashSchema = import_zod.z.object({
837
- command: import_zod.z.string().describe("The bash command to execute"),
838
- timeout: import_zod.z.number().optional().describe("Optional timeout in milliseconds (max 600000). Default is 120000 (2 minutes)"),
839
- workingDirectory: import_zod.z.string().optional().describe("Working directory for the command. Defaults to the current working directory")
666
+ //#endregion
667
+ //#region src/builtins/bash-tool.ts
668
+ /**
669
+ * BashTool — execute shell commands via child_process.spawn
670
+ *
671
+ * Returns TToolResult JSON string. Non-zero exit is returned as success:true
672
+ * with exitCode set, matching Claude Code behaviour (the command ran, it just
673
+ * exited non-zero — the LLM can decide what to do with that information).
674
+ */
675
+ const DEFAULT_TIMEOUT_MS$2 = 12e4;
676
+ const BashSchema = zod.z.object({
677
+ command: zod.z.string().describe("The bash command to execute"),
678
+ timeout: zod.z.number().optional().describe("Optional timeout in milliseconds (max 600000). Default is 120000 (2 minutes)"),
679
+ workingDirectory: zod.z.string().optional().describe("Working directory for the command. Defaults to the current working directory")
840
680
  });
841
- async function runBash(args) {
842
- const { command, timeout = DEFAULT_TIMEOUT_MS, workingDirectory } = args;
843
- return new Promise((resolve3) => {
844
- const stdoutChunks = [];
845
- const stderrChunks = [];
846
- let timedOut = false;
847
- let settled = false;
848
- const child = (0, import_node_child_process.spawn)("sh", ["-c", command], {
849
- cwd: workingDirectory ?? process.cwd(),
850
- env: process.env,
851
- stdio: ["pipe", "pipe", "pipe"]
852
- });
853
- child.stdout.on("data", (chunk) => {
854
- stdoutChunks.push(chunk);
855
- });
856
- child.stderr.on("data", (chunk) => {
857
- stderrChunks.push(chunk);
858
- });
859
- const timer = setTimeout(() => {
860
- timedOut = true;
861
- child.kill("SIGTERM");
862
- }, timeout);
863
- function settle(result) {
864
- if (settled) return;
865
- settled = true;
866
- clearTimeout(timer);
867
- resolve3(JSON.stringify(result));
868
- }
869
- child.on("error", (err) => {
870
- settle({
871
- success: false,
872
- output: "",
873
- error: err.message
874
- });
875
- });
876
- child.on("close", (code) => {
877
- if (timedOut) {
878
- settle({
879
- success: false,
880
- output: Buffer.concat(stdoutChunks).toString("utf8"),
881
- error: `Command timed out after ${timeout}ms`,
882
- exitCode: code ?? void 0
883
- });
884
- return;
885
- }
886
- const stdout = Buffer.concat(stdoutChunks).toString("utf8");
887
- const stderr = Buffer.concat(stderrChunks).toString("utf8");
888
- const exitCode = code ?? 0;
889
- const output = stderr ? `${stdout}
890
- stderr:
891
- ${stderr}` : stdout;
892
- settle({
893
- success: true,
894
- output,
895
- exitCode
896
- });
897
- });
898
- });
681
+ /**
682
+ * Run a shell command and return stdout + stderr.
683
+ * Resolves with the TToolResult JSON string.
684
+ */
685
+ async function runBash(args, options = {}) {
686
+ const { command, timeout: rawTimeout = DEFAULT_TIMEOUT_MS$2, workingDirectory } = args;
687
+ const timeout = Math.min(rawTimeout, 6e5);
688
+ if (options.sandboxClient) try {
689
+ const sandboxResult = await options.sandboxClient.run(command, {
690
+ timeoutMs: timeout,
691
+ workingDirectory
692
+ });
693
+ const result = {
694
+ success: true,
695
+ output: sandboxResult.stderr ? `${sandboxResult.stdout}\nstderr:\n${sandboxResult.stderr}` : sandboxResult.stdout,
696
+ exitCode: sandboxResult.exitCode
697
+ };
698
+ return JSON.stringify(result);
699
+ } catch (err) {
700
+ const result = {
701
+ success: false,
702
+ output: "",
703
+ error: err instanceof Error ? err.message : String(err)
704
+ };
705
+ return JSON.stringify(result);
706
+ }
707
+ return new Promise((resolve) => {
708
+ const stdoutChunks = [];
709
+ const stderrChunks = [];
710
+ let timedOut = false;
711
+ let settled = false;
712
+ const child = (0, node_child_process.spawn)("sh", ["-c", command], {
713
+ cwd: workingDirectory ?? process.cwd(),
714
+ env: process.env,
715
+ stdio: [
716
+ "pipe",
717
+ "pipe",
718
+ "pipe"
719
+ ]
720
+ });
721
+ child.stdout.on("data", (chunk) => {
722
+ stdoutChunks.push(chunk);
723
+ });
724
+ child.stderr.on("data", (chunk) => {
725
+ stderrChunks.push(chunk);
726
+ });
727
+ const timer = setTimeout(() => {
728
+ timedOut = true;
729
+ child.kill("SIGTERM");
730
+ settle({
731
+ success: false,
732
+ output: Buffer.concat(stdoutChunks).toString("utf8"),
733
+ error: `Command timed out after ${timeout}ms`
734
+ });
735
+ }, timeout);
736
+ function settle(result) {
737
+ if (settled) return;
738
+ settled = true;
739
+ clearTimeout(timer);
740
+ resolve(JSON.stringify(result));
741
+ }
742
+ child.on("error", (err) => {
743
+ settle({
744
+ success: false,
745
+ output: "",
746
+ error: err.message
747
+ });
748
+ });
749
+ child.on("close", (code) => {
750
+ if (timedOut) {
751
+ settle({
752
+ success: false,
753
+ output: Buffer.concat(stdoutChunks).toString("utf8"),
754
+ error: `Command timed out after ${timeout}ms`,
755
+ exitCode: code ?? void 0
756
+ });
757
+ return;
758
+ }
759
+ const stdout = Buffer.concat(stdoutChunks).toString("utf8");
760
+ const stderr = Buffer.concat(stderrChunks).toString("utf8");
761
+ const exitCode = code ?? 0;
762
+ settle({
763
+ success: true,
764
+ output: stderr ? `${stdout}\nstderr:\n${stderr}` : stdout,
765
+ exitCode
766
+ });
767
+ });
768
+ });
899
769
  }
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
- );
908
-
909
- // src/builtins/read-tool.ts
910
- var import_promises = require("fs/promises");
911
- var import_zod2 = require("zod");
912
- var DEFAULT_LIMIT = 2e3;
913
- var ReadSchema = import_zod2.z.object({
914
- filePath: import_zod2.z.string().describe("The absolute path to the file to read"),
915
- offset: import_zod2.z.number().optional().describe(
916
- "The line number to start reading from (1-based). Only provide if the file is too large to read at once"
917
- ),
918
- limit: import_zod2.z.number().optional().describe(
919
- `The number of lines to read (default: ${DEFAULT_LIMIT}). Only provide if the file is too large to read at once`
920
- )
770
+ /**
771
+ * Create a BashTool instance — register with Robota agent tools registry.
772
+ */
773
+ function createBashTool(options = {}) {
774
+ return createZodFunctionTool("Bash", "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.", BashSchema, async (params) => {
775
+ return runBash(params, options);
776
+ });
777
+ }
778
+ /**
779
+ * BashTool instance — register with Robota agent tools registry.
780
+ */
781
+ const bashTool = createBashTool();
782
+ //#endregion
783
+ //#region src/builtins/path-guard.ts
784
+ /**
785
+ * Returns a JSON-serialized TToolResult error when filePath is outside cwd.
786
+ * Returns undefined when the path is within cwd or cwd is not set.
787
+ */
788
+ function checkPathWithinCwd(filePath, cwd) {
789
+ if (cwd === void 0) return void 0;
790
+ const resolved = (0, node_path.resolve)(filePath);
791
+ const cwdResolved = (0, node_path.resolve)(cwd);
792
+ if (resolved !== cwdResolved && !resolved.startsWith(cwdResolved + node_path.sep)) {
793
+ const result = {
794
+ success: false,
795
+ output: "",
796
+ error: `Access denied: "${filePath}" is outside the working directory`
797
+ };
798
+ return JSON.stringify(result);
799
+ }
800
+ }
801
+ //#endregion
802
+ //#region src/builtins/read-tool.ts
803
+ /**
804
+ * ReadTool — read a file and return its contents with line numbers (cat -n style).
805
+ *
806
+ * Supports offset/limit for partial reads. Detects binary files and refuses to
807
+ * return their raw bytes. Default limit is 2000 lines.
808
+ */
809
+ const DEFAULT_LIMIT$1 = 2e3;
810
+ const ReadSchema = zod.z.object({
811
+ filePath: zod.z.string().describe("The absolute path to the file to read"),
812
+ offset: zod.z.number().optional().describe("The line number to start reading from (1-based). Only provide if the file is too large to read at once"),
813
+ limit: zod.z.number().optional().describe(`The number of lines to read (default: ${DEFAULT_LIMIT$1}). Only provide if the file is too large to read at once`)
921
814
  });
815
+ /**
816
+ * Heuristic binary detection: scan the first 8 KB for null bytes.
817
+ */
922
818
  function isBinary(buffer) {
923
- const checkLength = Math.min(buffer.length, 8192);
924
- for (let i = 0; i < checkLength; i++) {
925
- if (buffer[i] === 0) return true;
926
- }
927
- return false;
819
+ const checkLength = Math.min(buffer.length, 8192);
820
+ for (let i = 0; i < checkLength; i++) if (buffer[i] === 0) return true;
821
+ return false;
928
822
  }
823
+ /**
824
+ * Format lines with 1-based line numbers in cat -n style.
825
+ * Pads line number to the width of the highest line number.
826
+ */
929
827
  function formatWithLineNumbers(lines, startLine) {
930
- const lastLineNum = startLine + lines.length - 1;
931
- const width = String(lastLineNum).length;
932
- return lines.map((line, idx) => {
933
- const lineNum = String(startLine + idx).padStart(width, " ");
934
- return `${lineNum} ${line}`;
935
- }).join("\n");
828
+ const lastLineNum = startLine + lines.length - 1;
829
+ const width = String(lastLineNum).length;
830
+ return lines.map((line, idx) => {
831
+ return `${String(startLine + idx).padStart(width, " ")}\t${line}`;
832
+ }).join("\n");
833
+ }
834
+ function formatReadResult(filePath, content, startLine, limit) {
835
+ const allLines = content.split("\n");
836
+ if (allLines[allLines.length - 1] === "") allLines.pop();
837
+ const zeroBasedStart = startLine - 1;
838
+ const selectedLines = allLines.slice(zeroBasedStart, zeroBasedStart + limit);
839
+ const output = formatWithLineNumbers(selectedLines, startLine);
840
+ const totalLines = allLines.length;
841
+ const returnedLines = selectedLines.length;
842
+ const result = {
843
+ success: true,
844
+ output: (returnedLines < totalLines ? `[File: ${filePath} (lines ${startLine}-${startLine + returnedLines - 1} of ${totalLines})]\n` : `[File: ${filePath} (${totalLines} lines)]\n`) + output
845
+ };
846
+ return JSON.stringify(result);
847
+ }
848
+ async function readFileTool(args, options = {}) {
849
+ const { filePath, offset, limit = DEFAULT_LIMIT$1 } = args;
850
+ const startLine = offset !== void 0 && offset > 0 ? offset : 1;
851
+ if (options.sandboxClient) try {
852
+ return formatReadResult(filePath, await options.sandboxClient.readFile(filePath), startLine, limit);
853
+ } catch (err) {
854
+ const result = {
855
+ success: false,
856
+ output: "",
857
+ error: err instanceof Error ? err.message : String(err)
858
+ };
859
+ return JSON.stringify(result);
860
+ }
861
+ const pathError = checkPathWithinCwd(filePath, options.cwd);
862
+ if (pathError !== void 0) return pathError;
863
+ let fileStats;
864
+ try {
865
+ fileStats = await (0, node_fs_promises.stat)(filePath);
866
+ } catch (err) {
867
+ const result = {
868
+ success: false,
869
+ output: "",
870
+ error: `File not found: ${filePath}`
871
+ };
872
+ return JSON.stringify(result);
873
+ }
874
+ if (!fileStats.isFile()) {
875
+ const result = {
876
+ success: false,
877
+ output: "",
878
+ error: `Path is not a file: ${filePath}`
879
+ };
880
+ return JSON.stringify(result);
881
+ }
882
+ let buffer;
883
+ try {
884
+ buffer = await (0, node_fs_promises.readFile)(filePath);
885
+ } catch (err) {
886
+ const result = {
887
+ success: false,
888
+ output: "",
889
+ error: err instanceof Error ? err.message : String(err)
890
+ };
891
+ return JSON.stringify(result);
892
+ }
893
+ if (isBinary(buffer)) {
894
+ const result = {
895
+ success: false,
896
+ output: "",
897
+ error: `Binary file not supported: ${filePath}`
898
+ };
899
+ return JSON.stringify(result);
900
+ }
901
+ return formatReadResult(filePath, buffer.toString("utf8"), startLine, limit);
902
+ }
903
+ /**
904
+ * Create a ReadTool instance — register with Robota agent tools registry.
905
+ */
906
+ function createReadTool(options = {}) {
907
+ return createZodFunctionTool("Read", "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.", ReadSchema, async (params) => {
908
+ return readFileTool(params, options);
909
+ });
936
910
  }
937
- async function readFileTool(args) {
938
- const { filePath, offset, limit = DEFAULT_LIMIT } = args;
939
- const startLine = offset !== void 0 && offset > 0 ? offset : 1;
940
- let fileStats;
941
- try {
942
- fileStats = await (0, import_promises.stat)(filePath);
943
- } catch (err) {
944
- const result2 = {
945
- success: false,
946
- output: "",
947
- error: `File not found: ${filePath}`
948
- };
949
- return JSON.stringify(result2);
950
- }
951
- if (!fileStats.isFile()) {
952
- const result2 = {
953
- success: false,
954
- output: "",
955
- error: `Path is not a file: ${filePath}`
956
- };
957
- return JSON.stringify(result2);
958
- }
959
- let buffer;
960
- try {
961
- buffer = await (0, import_promises.readFile)(filePath);
962
- } catch (err) {
963
- const result2 = {
964
- success: false,
965
- output: "",
966
- error: err instanceof Error ? err.message : String(err)
967
- };
968
- return JSON.stringify(result2);
969
- }
970
- if (isBinary(buffer)) {
971
- const result2 = {
972
- success: false,
973
- output: "",
974
- error: `Binary file not supported: ${filePath}`
975
- };
976
- return JSON.stringify(result2);
977
- }
978
- const content = buffer.toString("utf8");
979
- const allLines = content.split("\n");
980
- if (allLines[allLines.length - 1] === "") {
981
- allLines.pop();
982
- }
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);
911
+ /**
912
+ * ReadTool instance — register with Robota agent tools registry.
913
+ */
914
+ const readTool = createReadTool();
915
+ //#endregion
916
+ //#region src/builtins/atomic-file-write.ts
917
+ const TEMP_RANDOM_BYTES = 6;
918
+ const PRESERVED_MODE_BITS = 4095;
919
+ const MISSING_FILE_ERROR_CODE = "ENOENT";
920
+ function createTempFilePath(filePath) {
921
+ const dir = (0, node_path.dirname)(filePath);
922
+ const name = (0, node_path.basename)(filePath);
923
+ const suffix = (0, node_crypto.randomBytes)(TEMP_RANDOM_BYTES).toString("hex");
924
+ return (0, node_path.join)(dir, `.${name}.robota-tmp-${process.pid}-${Date.now()}-${suffix}`);
996
925
  }
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);
1003
- }
1004
- );
1005
-
1006
- // 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
- var WriteSchema = import_zod3.z.object({
1011
- filePath: import_zod3.z.string().describe("The absolute path to the file to write"),
1012
- content: import_zod3.z.string().describe("The content to write to the file")
926
+ async function readExistingMode(filePath) {
927
+ try {
928
+ return (await (0, node_fs_promises.stat)(filePath)).mode & PRESERVED_MODE_BITS;
929
+ } catch (error) {
930
+ if (error instanceof Error && hasErrorCode(error, MISSING_FILE_ERROR_CODE)) return void 0;
931
+ throw error;
932
+ }
933
+ }
934
+ function hasErrorCode(error, code) {
935
+ return "code" in error && error.code === code;
936
+ }
937
+ async function atomicWriteUtf8File(filePath, content) {
938
+ await (0, node_fs_promises.mkdir)((0, node_path.dirname)(filePath), { recursive: true });
939
+ const existingMode = await readExistingMode(filePath);
940
+ const tempFilePath = createTempFilePath(filePath);
941
+ try {
942
+ await (0, node_fs_promises.writeFile)(tempFilePath, content, "utf8");
943
+ if (existingMode !== void 0) await (0, node_fs_promises.chmod)(tempFilePath, existingMode);
944
+ await (0, node_fs_promises.rename)(tempFilePath, filePath);
945
+ } catch (error) {
946
+ await (0, node_fs_promises.rm)(tempFilePath, { force: true }).catch(() => void 0);
947
+ throw error;
948
+ }
949
+ }
950
+ //#endregion
951
+ //#region src/builtins/write-tool.ts
952
+ /**
953
+ * WriteTool — write content to a file, auto-creating parent directories.
954
+ */
955
+ const WriteSchema = zod.z.object({
956
+ filePath: zod.z.string().describe("The absolute path to the file to write"),
957
+ content: zod.z.string().describe("The content to write to the file")
1013
958
  });
1014
- async function writeFileTool(args) {
1015
- const { filePath, content } = args;
1016
- try {
1017
- await (0, import_promises2.mkdir)((0, import_node_path.dirname)(filePath), { recursive: true });
1018
- await (0, import_promises2.writeFile)(filePath, content, "utf8");
1019
- const result = {
1020
- success: true,
1021
- output: `Written ${Buffer.byteLength(content, "utf8")} bytes to ${filePath}`
1022
- };
1023
- return JSON.stringify(result);
1024
- } catch (err) {
1025
- const result = {
1026
- success: false,
1027
- output: "",
1028
- error: err instanceof Error ? err.message : String(err)
1029
- };
1030
- return JSON.stringify(result);
1031
- }
959
+ async function writeFileTool(args, options = {}) {
960
+ const { filePath, content } = args;
961
+ if (!options.sandboxClient) {
962
+ const pathError = checkPathWithinCwd(filePath, options.cwd);
963
+ if (pathError !== void 0) return pathError;
964
+ }
965
+ try {
966
+ if (options.sandboxClient) await options.sandboxClient.writeFile(filePath, content);
967
+ else await atomicWriteUtf8File(filePath, content);
968
+ const result = {
969
+ success: true,
970
+ output: `Written ${Buffer.byteLength(content, "utf8")} bytes to ${filePath}`
971
+ };
972
+ return JSON.stringify(result);
973
+ } catch (err) {
974
+ const result = {
975
+ success: false,
976
+ output: "",
977
+ error: err instanceof Error ? err.message : String(err)
978
+ };
979
+ return JSON.stringify(result);
980
+ }
1032
981
  }
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
- );
1041
-
1042
- // src/builtins/edit-tool.ts
1043
- var import_promises3 = require("fs/promises");
1044
- var import_zod4 = require("zod");
1045
- var EditSchema = import_zod4.z.object({
1046
- filePath: import_zod4.z.string().describe("The absolute path to the file to modify"),
1047
- oldString: import_zod4.z.string().describe("The text to replace (must be an exact match of existing content)"),
1048
- newString: import_zod4.z.string().describe("The text to replace it with (must be different from old_string)"),
1049
- replaceAll: import_zod4.z.boolean().optional().describe(
1050
- "Replace all occurrences of old_string (default: false). Useful for renaming variables"
1051
- )
982
+ /**
983
+ * Create a WriteTool instance — register with Robota agent tools registry.
984
+ */
985
+ function createWriteTool(options = {}) {
986
+ return createZodFunctionTool("Write", "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 — 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.", WriteSchema, async (params) => {
987
+ return writeFileTool(params, options);
988
+ });
989
+ }
990
+ /**
991
+ * WriteTool instance — register with Robota agent tools registry.
992
+ */
993
+ const writeTool = createWriteTool();
994
+ //#endregion
995
+ //#region src/builtins/edit-tool.ts
996
+ /**
997
+ * EditTool — perform string-replace edits on a file.
998
+ *
999
+ * By default, requires the oldString to appear exactly once in the file
1000
+ * (ensuring surgical edits). Pass replaceAll:true to replace all occurrences.
1001
+ */
1002
+ const EditSchema = zod.z.object({
1003
+ filePath: zod.z.string().describe("The absolute path to the file to modify"),
1004
+ oldString: zod.z.string().describe("The text to replace (must be an exact match of existing content)"),
1005
+ newString: zod.z.string().describe("The text to replace it with (must be different from old_string)"),
1006
+ replaceAll: zod.z.boolean().optional().describe("Replace all occurrences of old_string (default: false). Useful for renaming variables")
1052
1007
  });
1053
- async function editFileTool(args) {
1054
- const { filePath, oldString, newString, replaceAll = false } = args;
1055
- let content;
1056
- try {
1057
- content = await (0, import_promises3.readFile)(filePath, "utf8");
1058
- } catch (err) {
1059
- const result2 = {
1060
- success: false,
1061
- output: "",
1062
- error: `File not found: ${filePath}`
1063
- };
1064
- return JSON.stringify(result2);
1065
- }
1066
- if (!content.includes(oldString)) {
1067
- const result2 = {
1068
- success: false,
1069
- output: "",
1070
- error: `oldString not found in file: ${filePath}`
1071
- };
1072
- return JSON.stringify(result2);
1073
- }
1074
- if (!replaceAll) {
1075
- const firstIdx = content.indexOf(oldString);
1076
- const lastIdx = content.lastIndexOf(oldString);
1077
- if (firstIdx !== lastIdx) {
1078
- const occurrences = content.split(oldString).length - 1;
1079
- const result2 = {
1080
- success: false,
1081
- output: "",
1082
- error: `oldString is not unique in file (found ${occurrences} occurrences). Provide more context to make it unique, or use replaceAll:true.`
1083
- };
1084
- return JSON.stringify(result2);
1085
- }
1086
- }
1087
- const updated = replaceAll ? content.split(oldString).join(newString) : content.slice(0, content.indexOf(oldString)) + newString + content.slice(content.indexOf(oldString) + oldString.length);
1088
- try {
1089
- await (0, import_promises3.writeFile)(filePath, updated, "utf8");
1090
- } catch (err) {
1091
- const result2 = {
1092
- success: false,
1093
- output: "",
1094
- error: err instanceof Error ? err.message : String(err)
1095
- };
1096
- return JSON.stringify(result2);
1097
- }
1098
- const count = replaceAll ? content.split(oldString).length - 1 : 1;
1099
- const result = {
1100
- success: true,
1101
- output: `Replaced ${count} occurrence(s) in ${filePath}`
1102
- };
1103
- return JSON.stringify(result);
1008
+ async function editFileTool(args, options = {}) {
1009
+ const { filePath, oldString, newString, replaceAll = false } = args;
1010
+ if (!options.sandboxClient) {
1011
+ const pathError = checkPathWithinCwd(filePath, options.cwd);
1012
+ if (pathError !== void 0) return pathError;
1013
+ }
1014
+ let content;
1015
+ try {
1016
+ content = options.sandboxClient ? await options.sandboxClient.readFile(filePath) : await (0, node_fs_promises.readFile)(filePath, "utf8");
1017
+ } catch (err) {
1018
+ const result = {
1019
+ success: false,
1020
+ output: "",
1021
+ error: `File not found: ${filePath}`
1022
+ };
1023
+ return JSON.stringify(result);
1024
+ }
1025
+ if (!content.includes(oldString)) {
1026
+ const result = {
1027
+ success: false,
1028
+ output: "",
1029
+ error: `oldString not found in file: ${filePath}`
1030
+ };
1031
+ return JSON.stringify(result);
1032
+ }
1033
+ if (!replaceAll) {
1034
+ if (content.indexOf(oldString) !== content.lastIndexOf(oldString)) {
1035
+ const result = {
1036
+ success: false,
1037
+ output: "",
1038
+ error: `oldString is not unique in file (found ${content.split(oldString).length - 1} occurrences). Provide more context to make it unique, or use replaceAll:true.`
1039
+ };
1040
+ return JSON.stringify(result);
1041
+ }
1042
+ }
1043
+ const updated = replaceAll ? content.split(oldString).join(newString) : content.slice(0, content.indexOf(oldString)) + newString + content.slice(content.indexOf(oldString) + oldString.length);
1044
+ try {
1045
+ if (options.sandboxClient) await options.sandboxClient.writeFile(filePath, updated);
1046
+ else await atomicWriteUtf8File(filePath, updated);
1047
+ } catch (err) {
1048
+ const result = {
1049
+ success: false,
1050
+ output: "",
1051
+ error: err instanceof Error ? err.message : String(err)
1052
+ };
1053
+ return JSON.stringify(result);
1054
+ }
1055
+ const count = replaceAll ? content.split(oldString).length - 1 : 1;
1056
+ const matchIdx = content.indexOf(oldString);
1057
+ const startLine = matchIdx >= 0 ? content.substring(0, matchIdx).split("\n").length : 1;
1058
+ const result = {
1059
+ success: true,
1060
+ output: `Replaced ${count} occurrence(s) in ${filePath}`,
1061
+ startLine
1062
+ };
1063
+ return JSON.stringify(result);
1064
+ }
1065
+ /**
1066
+ * Create an EditTool instance — register with Robota agent tools registry.
1067
+ */
1068
+ function createEditTool(options = {}) {
1069
+ return createZodFunctionTool("Edit", "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.", EditSchema, async (params) => {
1070
+ return editFileTool(params, options);
1071
+ });
1104
1072
  }
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
- );
1113
-
1114
- // src/builtins/glob-tool.ts
1115
- var import_promises4 = require("fs/promises");
1116
- var import_node_path2 = require("path");
1117
- var import_fast_glob = __toESM(require("fast-glob"), 1);
1118
- var import_zod5 = require("zod");
1119
- var DEFAULT_MAX_RESULTS = 1e3;
1120
- var GlobSchema = import_zod5.z.object({
1121
- pattern: import_zod5.z.string().describe('The glob pattern to match files against (e.g. "**/*.ts", "src/**/*.tsx")'),
1122
- path: import_zod5.z.string().optional().describe(
1123
- "The directory to search in. Defaults to the current working directory. Must be a valid directory path if provided"
1124
- ),
1125
- limit: import_zod5.z.number().optional().describe(
1126
- "Maximum number of results to return (default: 1000). Use a smaller limit to save context space"
1127
- )
1073
+ /**
1074
+ * EditTool instance — register with Robota agent tools registry.
1075
+ */
1076
+ const editTool = createEditTool();
1077
+ //#endregion
1078
+ //#region src/builtins/glob-tool.ts
1079
+ /**
1080
+ * GlobTool — fast file pattern search using fast-glob.
1081
+ *
1082
+ * Excludes node_modules and .git by default.
1083
+ * Results are sorted by modification time (most recently modified first).
1084
+ */
1085
+ const DEFAULT_MAX_RESULTS = 1e3;
1086
+ const GlobSchema = zod.z.object({
1087
+ pattern: zod.z.string().describe("The glob pattern to match files against (e.g. \"**/*.ts\", \"src/**/*.tsx\")"),
1088
+ path: zod.z.string().optional().describe("The directory to search in. Defaults to the current working directory. Must be a valid directory path if provided"),
1089
+ limit: zod.z.number().optional().describe("Maximum number of results to return (default: 1000). Use a smaller limit to save context space")
1128
1090
  });
1129
1091
  async function globFileTool(args) {
1130
- const { pattern, path: basePath } = args;
1131
- const cwd = basePath ? (0, import_node_path2.resolve)(basePath) : process.cwd();
1132
- let matches;
1133
- try {
1134
- matches = await (0, import_fast_glob.default)(pattern, {
1135
- cwd,
1136
- ignore: ["**/node_modules/**", "**/.git/**"],
1137
- dot: true,
1138
- absolute: false
1139
- });
1140
- } catch (err) {
1141
- const result2 = {
1142
- success: false,
1143
- output: "",
1144
- error: err instanceof Error ? err.message : String(err)
1145
- };
1146
- return JSON.stringify(result2);
1147
- }
1148
- const withMtime = await Promise.all(
1149
- matches.map(async (p) => {
1150
- const absPath = (0, import_node_path2.resolve)(cwd, p);
1151
- try {
1152
- const s = await (0, import_promises4.stat)(absPath);
1153
- return { path: p, mtime: s.mtimeMs };
1154
- } catch {
1155
- return { path: p, mtime: 0 };
1156
- }
1157
- })
1158
- );
1159
- withMtime.sort((a, b) => b.mtime - a.mtime);
1160
- const maxResults = args.limit ?? DEFAULT_MAX_RESULTS;
1161
- const totalMatches = withMtime.length;
1162
- const truncated = totalMatches > maxResults;
1163
- const limited = truncated ? withMtime.slice(0, maxResults) : withMtime;
1164
- const sorted = limited.map((f) => f.path);
1165
- let output = sorted.length > 0 ? sorted.join("\n") : "(no matches)";
1166
- if (truncated) {
1167
- output += `
1168
-
1169
- [Showing ${maxResults} of ${totalMatches} matches. Use limit parameter to see more.]`;
1170
- }
1171
- const result = {
1172
- success: true,
1173
- output
1174
- };
1175
- return JSON.stringify(result);
1092
+ const { pattern, path: basePath } = args;
1093
+ const cwd = basePath ? (0, node_path.resolve)(basePath) : process.cwd();
1094
+ let matches;
1095
+ try {
1096
+ matches = await (0, fast_glob.default)(pattern, {
1097
+ cwd,
1098
+ ignore: ["**/node_modules/**", "**/.git/**"],
1099
+ dot: true,
1100
+ absolute: false
1101
+ });
1102
+ } catch (err) {
1103
+ const result = {
1104
+ success: false,
1105
+ output: "",
1106
+ error: err instanceof Error ? err.message : String(err)
1107
+ };
1108
+ return JSON.stringify(result);
1109
+ }
1110
+ const limit = (0, p_limit.default)(100);
1111
+ const withMtime = await Promise.all(matches.map((p) => limit(async () => {
1112
+ const absPath = (0, node_path.resolve)(cwd, p);
1113
+ try {
1114
+ return {
1115
+ path: p,
1116
+ mtime: (await (0, node_fs_promises.stat)(absPath)).mtimeMs
1117
+ };
1118
+ } catch {
1119
+ return {
1120
+ path: p,
1121
+ mtime: 0
1122
+ };
1123
+ }
1124
+ })));
1125
+ withMtime.sort((a, b) => b.mtime - a.mtime);
1126
+ const maxResults = args.limit ?? DEFAULT_MAX_RESULTS;
1127
+ const totalMatches = withMtime.length;
1128
+ const truncated = totalMatches > maxResults;
1129
+ const sorted = (truncated ? withMtime.slice(0, maxResults) : withMtime).map((f) => f.path);
1130
+ let output = sorted.length > 0 ? sorted.join("\n") : "(no matches)";
1131
+ if (truncated) output += `\n\n[Showing ${maxResults} of ${totalMatches} matches. Use limit parameter to see more.]`;
1132
+ return JSON.stringify({
1133
+ success: true,
1134
+ output
1135
+ });
1176
1136
  }
1177
- var globTool = createZodFunctionTool(
1178
- "Glob",
1179
- "Fast file pattern matching tool that works with any codebase size.\n\nSupports glob patterns like '**/*.js' or 'src/**/*.ts'. Returns matching file paths sorted by modification time.\n\nUse this tool when you need to find files by name patterns. When doing an open-ended search that may require multiple rounds, use the Agent tool instead.\n\nDefault limit is 1000 results. Use the limit parameter if you need fewer results to save context space.",
1180
- GlobSchema,
1181
- async (params) => {
1182
- return globFileTool(params);
1183
- }
1184
- );
1185
-
1186
- // src/builtins/grep-tool.ts
1187
- var import_promises5 = require("fs/promises");
1188
- var import_node_path3 = require("path");
1189
- var import_zod6 = require("zod");
1190
- var GrepSchema = import_zod6.z.object({
1191
- pattern: import_zod6.z.string().describe("The regular expression pattern to search for in file contents"),
1192
- path: import_zod6.z.string().optional().describe("File or directory to search in. Defaults to the current working directory"),
1193
- glob: import_zod6.z.string().optional().describe(
1194
- 'Glob pattern to filter files (e.g. "*.ts", "*.{ts,tsx}"). Only files matching this pattern will be searched'
1195
- ),
1196
- contextLines: import_zod6.z.number().optional().describe(
1197
- 'Number of context lines to show before and after each match. Only applies when outputMode is "content". Default: 0'
1198
- ),
1199
- outputMode: import_zod6.z.enum(["files_with_matches", "content"]).optional().describe(
1200
- 'Output mode: "files_with_matches" shows only file paths (default), "content" shows matching lines with context'
1201
- )
1137
+ /**
1138
+ * GlobTool instance — register with Robota agent tools registry.
1139
+ */
1140
+ const globTool = createZodFunctionTool("Glob", "Fast file pattern matching tool that works with any codebase size.\n\nSupports glob patterns like '**/*.js' or 'src/**/*.ts'. Returns matching file paths sorted by modification time.\n\nUse this tool when you need to find files by name patterns. When doing an open-ended search that may require multiple rounds, use the Agent tool instead.\n\nDefault limit is 1000 results. Use the limit parameter if you need fewer results to save context space.", GlobSchema, async (params) => {
1141
+ return globFileTool(params);
1202
1142
  });
1143
+ //#endregion
1144
+ //#region src/builtins/grep-tool.ts
1145
+ /**
1146
+ * GrepTool — recursive regex content search.
1147
+ *
1148
+ * Supports two output modes:
1149
+ * - files_with_matches (default): return only file paths that contain a match
1150
+ * - content: return matching lines with optional context lines
1151
+ */
1152
+ const GrepSchema = zod.z.object({
1153
+ pattern: zod.z.string().describe("The regular expression pattern to search for in file contents"),
1154
+ path: zod.z.string().optional().describe("File or directory to search in. Defaults to the current working directory"),
1155
+ glob: zod.z.string().optional().describe("Glob pattern to filter files (e.g. \"*.ts\", \"*.{ts,tsx}\"). Only files matching this pattern will be searched"),
1156
+ contextLines: zod.z.number().optional().describe("Number of context lines to show before and after each match. Only applies when outputMode is \"content\". Default: 0"),
1157
+ outputMode: zod.z.enum(["files_with_matches", "content"]).optional().describe("Output mode: \"files_with_matches\" shows only file paths (default), \"content\" shows matching lines with context")
1158
+ });
1159
+ /** Convert a simple glob to a RegExp for file name filtering. */
1203
1160
  function globToRegex(glob) {
1204
- const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, ".+").replace(/\*/g, "[^/]*");
1205
- return new RegExp(`^${escaped}$`);
1161
+ const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, ".+").replace(/\*/g, "[^/]*");
1162
+ return new RegExp(`^${escaped}$`);
1206
1163
  }
1164
+ /** Check if a file name matches an optional glob filter. */
1207
1165
  function matchesGlob(filename, glob) {
1208
- if (glob === void 0) return true;
1209
- return globToRegex(glob).test(filename);
1166
+ if (glob === void 0) return true;
1167
+ return globToRegex(glob).test(filename);
1210
1168
  }
1169
+ /** Gather all files under a directory recursively, excluding node_modules/.git. */
1211
1170
  async function collectFiles(dirPath, glob) {
1212
- const results = [];
1213
- async function walk(current) {
1214
- let entryNames;
1215
- try {
1216
- entryNames = await (0, import_promises5.readdir)(current);
1217
- } catch {
1218
- return;
1219
- }
1220
- for (const name of entryNames) {
1221
- if (name === "node_modules" || name === ".git") continue;
1222
- const fullPath = (0, import_node_path3.join)(current, name);
1223
- let fileStat;
1224
- try {
1225
- fileStat = await (0, import_promises5.stat)(fullPath);
1226
- } catch {
1227
- continue;
1228
- }
1229
- if (fileStat.isDirectory()) {
1230
- await walk(fullPath);
1231
- } else if (fileStat.isFile()) {
1232
- if (matchesGlob(name, glob)) {
1233
- results.push(fullPath);
1234
- }
1235
- }
1236
- }
1237
- }
1238
- await walk(dirPath);
1239
- return results;
1171
+ const results = [];
1172
+ async function walk(current) {
1173
+ let entryNames;
1174
+ try {
1175
+ entryNames = await (0, node_fs_promises.readdir)(current);
1176
+ } catch {
1177
+ return;
1178
+ }
1179
+ for (const name of entryNames) {
1180
+ if (name === "node_modules" || name === ".git") continue;
1181
+ const fullPath = (0, node_path.join)(current, name);
1182
+ let fileStat;
1183
+ try {
1184
+ fileStat = await (0, node_fs_promises.stat)(fullPath);
1185
+ } catch {
1186
+ continue;
1187
+ }
1188
+ if (fileStat.isDirectory()) await walk(fullPath);
1189
+ else if (fileStat.isFile()) {
1190
+ if (matchesGlob(name, glob)) results.push(fullPath);
1191
+ }
1192
+ }
1193
+ }
1194
+ await walk(dirPath);
1195
+ return results;
1240
1196
  }
1197
+ /** Search a single file for lines matching the regex. */
1241
1198
  function searchFile(content, filePath, regex, contextLines, outputMode) {
1242
- const lines = content.split("\n");
1243
- const matchingIndices = [];
1244
- for (let i = 0; i < lines.length; i++) {
1245
- if (regex.test(lines[i])) {
1246
- matchingIndices.push(i);
1247
- }
1248
- }
1249
- if (matchingIndices.length === 0) return [];
1250
- if (outputMode === "files_with_matches") {
1251
- return [filePath];
1252
- }
1253
- const includedIndices = /* @__PURE__ */ new Set();
1254
- for (const idx of matchingIndices) {
1255
- for (let c = Math.max(0, idx - contextLines); c <= Math.min(lines.length - 1, idx + contextLines); c++) {
1256
- includedIndices.add(c);
1257
- }
1258
- }
1259
- const outputLines = [];
1260
- const sortedIndices = Array.from(includedIndices).sort((a, b) => a - b);
1261
- let prevIdx;
1262
- for (const idx of sortedIndices) {
1263
- if (prevIdx !== void 0 && idx > prevIdx + 1) {
1264
- outputLines.push("--");
1265
- }
1266
- const lineNum = idx + 1;
1267
- const marker = matchingIndices.includes(idx) ? ":" : "-";
1268
- outputLines.push(`${filePath}:${lineNum}${marker}${lines[idx]}`);
1269
- prevIdx = idx;
1270
- }
1271
- return outputLines;
1199
+ const lines = content.split("\n");
1200
+ const matchingIndices = [];
1201
+ for (let i = 0; i < lines.length; i++) if (regex.test(lines[i])) matchingIndices.push(i);
1202
+ if (matchingIndices.length === 0) return [];
1203
+ if (outputMode === "files_with_matches") return [filePath];
1204
+ const includedIndices = /* @__PURE__ */ new Set();
1205
+ for (const idx of matchingIndices) for (let c = Math.max(0, idx - contextLines); c <= Math.min(lines.length - 1, idx + contextLines); c++) includedIndices.add(c);
1206
+ const outputLines = [];
1207
+ const sortedIndices = Array.from(includedIndices).sort((a, b) => a - b);
1208
+ let prevIdx;
1209
+ for (const idx of sortedIndices) {
1210
+ if (prevIdx !== void 0 && idx > prevIdx + 1) outputLines.push("--");
1211
+ const lineNum = idx + 1;
1212
+ const marker = matchingIndices.includes(idx) ? ":" : "-";
1213
+ outputLines.push(`${filePath}:${lineNum}${marker}${lines[idx]}`);
1214
+ prevIdx = idx;
1215
+ }
1216
+ return outputLines;
1272
1217
  }
1273
1218
  async function grepFileTool(args) {
1274
- const {
1275
- pattern,
1276
- path: searchPath,
1277
- glob,
1278
- contextLines = 0,
1279
- outputMode = "files_with_matches"
1280
- } = args;
1281
- const targetPath = searchPath ? (0, import_node_path3.resolve)(searchPath) : process.cwd();
1282
- let regex;
1283
- try {
1284
- regex = new RegExp(pattern);
1285
- } catch (err) {
1286
- const result2 = {
1287
- success: false,
1288
- output: "",
1289
- error: `Invalid regex pattern: ${pattern}`
1290
- };
1291
- return JSON.stringify(result2);
1292
- }
1293
- let targetStat;
1294
- try {
1295
- targetStat = await (0, import_promises5.stat)(targetPath);
1296
- } catch {
1297
- const result2 = {
1298
- success: false,
1299
- output: "",
1300
- error: `Path not found: ${targetPath}`
1301
- };
1302
- return JSON.stringify(result2);
1303
- }
1304
- let files;
1305
- if (targetStat.isFile()) {
1306
- files = [targetPath];
1307
- } else {
1308
- files = await collectFiles(targetPath, glob);
1309
- }
1310
- const allOutputLines = [];
1311
- for (const filePath of files) {
1312
- let content;
1313
- try {
1314
- const buffer = await (0, import_promises5.readFile)(filePath);
1315
- const checkLen = Math.min(buffer.length, 8192);
1316
- let hasBinary = false;
1317
- for (let i = 0; i < checkLen; i++) {
1318
- if (buffer[i] === 0) {
1319
- hasBinary = true;
1320
- break;
1321
- }
1322
- }
1323
- if (hasBinary) continue;
1324
- content = buffer.toString("utf8");
1325
- } catch {
1326
- continue;
1327
- }
1328
- const fileMatches = searchFile(content, filePath, regex, contextLines, outputMode);
1329
- allOutputLines.push(...fileMatches);
1330
- }
1331
- const result = {
1332
- success: true,
1333
- output: allOutputLines.length > 0 ? allOutputLines.join("\n") : "(no matches)"
1334
- };
1335
- return JSON.stringify(result);
1219
+ const { pattern, path: searchPath, glob, contextLines = 0, outputMode = "files_with_matches" } = args;
1220
+ const targetPath = searchPath ? (0, node_path.resolve)(searchPath) : process.cwd();
1221
+ let regex;
1222
+ try {
1223
+ regex = new RegExp(pattern);
1224
+ } catch (err) {
1225
+ const result = {
1226
+ success: false,
1227
+ output: "",
1228
+ error: `Invalid regex pattern: ${pattern}`
1229
+ };
1230
+ return JSON.stringify(result);
1231
+ }
1232
+ let targetStat;
1233
+ try {
1234
+ targetStat = await (0, node_fs_promises.stat)(targetPath);
1235
+ } catch {
1236
+ const result = {
1237
+ success: false,
1238
+ output: "",
1239
+ error: `Path not found: ${targetPath}`
1240
+ };
1241
+ return JSON.stringify(result);
1242
+ }
1243
+ let files;
1244
+ if (targetStat.isFile()) files = [targetPath];
1245
+ else files = await collectFiles(targetPath, glob);
1246
+ const allOutputLines = [];
1247
+ for (const filePath of files) {
1248
+ let content;
1249
+ try {
1250
+ const buffer = await (0, node_fs_promises.readFile)(filePath);
1251
+ const checkLen = Math.min(buffer.length, 8192);
1252
+ let hasBinary = false;
1253
+ for (let i = 0; i < checkLen; i++) if (buffer[i] === 0) {
1254
+ hasBinary = true;
1255
+ break;
1256
+ }
1257
+ if (hasBinary) continue;
1258
+ content = buffer.toString("utf8");
1259
+ } catch {
1260
+ continue;
1261
+ }
1262
+ const fileMatches = searchFile(content, filePath, regex, contextLines, outputMode);
1263
+ allOutputLines.push(...fileMatches);
1264
+ }
1265
+ const result = {
1266
+ success: true,
1267
+ output: allOutputLines.length > 0 ? allOutputLines.join("\n") : "(no matches)"
1268
+ };
1269
+ return JSON.stringify(result);
1336
1270
  }
1337
- var grepTool = createZodFunctionTool(
1338
- "Grep",
1339
- "A powerful search tool built on regex matching.\n\nSupports full regex syntax (e.g., 'log.*Error', 'function\\\\s+\\\\w+'). Filter files with glob parameter (e.g., '*.js', '**/*.tsx').\n\nOutput modes: 'content' shows matching lines with context, 'files_with_matches' shows only file paths (default), 'count' shows match counts.\n\nUse this tool for ALL search tasks. NEVER invoke grep or rg as a Bash command.\n\nUse head_limit to control result size and save context space.",
1340
- GrepSchema,
1341
- async (params) => {
1342
- return grepFileTool(params);
1343
- }
1344
- );
1345
-
1346
- // src/builtins/web-fetch-tool.ts
1347
- var import_zod7 = require("zod");
1348
- var DEFAULT_TIMEOUT_MS2 = 3e4;
1349
- var MAX_RESPONSE_BYTES = 5e6;
1350
- var WebFetchSchema = import_zod7.z.object({
1351
- url: import_zod7.z.string().describe("The URL to fetch"),
1352
- headers: import_zod7.z.record(import_zod7.z.string()).optional().describe("Optional HTTP headers as key-value pairs")
1271
+ /**
1272
+ * GrepTool instance — register with Robota agent tools registry.
1273
+ */
1274
+ const grepTool = createZodFunctionTool("Grep", "A powerful search tool built on regex matching.\n\nSupports full regex syntax (e.g., 'log.*Error', 'function\\\\s+\\\\w+'). Filter files with glob parameter (e.g., '*.js', '**/*.tsx').\n\nOutput modes: 'content' shows matching lines with context, 'files_with_matches' shows only file paths (default), 'count' shows match counts.\n\nUse this tool for ALL search tasks. NEVER invoke grep or rg as a Bash command.\n\nUse head_limit to control result size and save context space.", GrepSchema, async (params) => {
1275
+ return grepFileTool(params);
1276
+ });
1277
+ //#endregion
1278
+ //#region src/builtins/web-fetch-tool.ts
1279
+ /**
1280
+ * WebFetchTool — fetch a URL and return its content as text.
1281
+ *
1282
+ * HTML is stripped to plain text for readability. Uses Node.js native fetch.
1283
+ * Output is capped at 30K chars (same as other tools).
1284
+ */
1285
+ const DEFAULT_TIMEOUT_MS$1 = 3e4;
1286
+ const MAX_RESPONSE_BYTES = 5e6;
1287
+ const WebFetchSchema = zod.z.object({
1288
+ url: zod.z.string().describe("The URL to fetch"),
1289
+ headers: zod.z.record(zod.z.string()).optional().describe("Optional HTTP headers as key-value pairs")
1353
1290
  });
1291
+ /** Strip HTML tags and decode common entities to produce readable text. */
1354
1292
  function htmlToText(html) {
1355
- return html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&nbsp;/g, " ").replace(/\s+/g, " ").trim();
1293
+ return html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, "\"").replace(/&#39;/g, "'").replace(/&nbsp;/g, " ").replace(/\s+/g, " ").trim();
1294
+ }
1295
+ function classifyFetchError(err) {
1296
+ if (!(err instanceof Error)) return String(err);
1297
+ if (err.name === "AbortError") return `Request timed out after ${DEFAULT_TIMEOUT_MS$1 / 1e3}s. The server did not respond in time.`;
1298
+ const code = err.code;
1299
+ if (code === "ENOTFOUND" || code === "EAI_AGAIN") return `Network error: DNS resolution failed for this host. The URL may be incorrect or the host does not exist. Do not retry with the same URL.`;
1300
+ if (code === "ECONNREFUSED") return `Network error: Connection refused. The server is not accepting connections at this address. Do not retry with the same URL.`;
1301
+ if (code === "ECONNRESET") return `Network error: Connection was reset by the server. The server may be temporarily unavailable.`;
1302
+ if (code === "ETIMEDOUT") return `Network error: Connection timed out. The server is not reachable within the expected time.`;
1303
+ if (code === "CERT_HAS_EXPIRED" || code === "UNABLE_TO_VERIFY_LEAF_SIGNATURE") return `Network error: SSL certificate error (${code}). The server's certificate is invalid. Do not retry with the same URL.`;
1304
+ return `Network error: ${err.message} Check that the URL is correct and the server is reachable.`;
1356
1305
  }
1357
1306
  async function runWebFetch(args) {
1358
- const { url, headers } = args;
1359
- try {
1360
- new URL(url);
1361
- } catch {
1362
- const result = { success: false, output: "", error: `Invalid URL: ${url}` };
1363
- return JSON.stringify(result);
1364
- }
1365
- try {
1366
- const controller = new AbortController();
1367
- const timeout = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS2);
1368
- const response = await fetch(url, {
1369
- headers: {
1370
- "User-Agent": "Robota-CLI/3.0",
1371
- ...headers ?? {}
1372
- },
1373
- signal: controller.signal,
1374
- redirect: "follow"
1375
- });
1376
- clearTimeout(timeout);
1377
- if (!response.ok) {
1378
- const result2 = {
1379
- success: false,
1380
- output: "",
1381
- error: `HTTP ${response.status} ${response.statusText}`
1382
- };
1383
- return JSON.stringify(result2);
1384
- }
1385
- const contentType = response.headers.get("content-type") ?? "";
1386
- const buffer = await response.arrayBuffer();
1387
- if (buffer.byteLength > MAX_RESPONSE_BYTES) {
1388
- const result2 = {
1389
- success: false,
1390
- output: "",
1391
- error: `Response too large: ${buffer.byteLength} bytes (max ${MAX_RESPONSE_BYTES})`
1392
- };
1393
- return JSON.stringify(result2);
1394
- }
1395
- let text = new TextDecoder().decode(buffer);
1396
- if (contentType.includes("html")) {
1397
- text = htmlToText(text);
1398
- }
1399
- const result = { success: true, output: text };
1400
- return JSON.stringify(result);
1401
- } catch (err) {
1402
- const message = err instanceof Error ? err.message : String(err);
1403
- const result = { success: false, output: "", error: message };
1404
- return JSON.stringify(result);
1405
- }
1307
+ const { url, headers } = args;
1308
+ try {
1309
+ new URL(url);
1310
+ } catch {
1311
+ const result = {
1312
+ success: false,
1313
+ output: "",
1314
+ error: `Invalid URL: "${url}". Fix the URL format before retrying.`
1315
+ };
1316
+ return JSON.stringify(result);
1317
+ }
1318
+ try {
1319
+ const controller = new AbortController();
1320
+ const timeout = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS$1);
1321
+ const response = await fetch(url, {
1322
+ headers: {
1323
+ "User-Agent": "Robota-CLI/3.0",
1324
+ ...headers ?? {}
1325
+ },
1326
+ signal: controller.signal,
1327
+ redirect: "follow"
1328
+ });
1329
+ clearTimeout(timeout);
1330
+ if (!response.ok) {
1331
+ const retryHint = response.status >= 500 ? " The server is temporarily unavailable — retrying may help." : " Do not retry with the same URL.";
1332
+ const result = {
1333
+ success: false,
1334
+ output: "",
1335
+ error: `HTTP ${response.status} ${response.statusText}.${retryHint}`
1336
+ };
1337
+ return JSON.stringify(result);
1338
+ }
1339
+ const contentType = response.headers.get("content-type") ?? "";
1340
+ const buffer = await response.arrayBuffer();
1341
+ if (buffer.byteLength > MAX_RESPONSE_BYTES) {
1342
+ const result = {
1343
+ success: false,
1344
+ output: "",
1345
+ error: `Response too large: ${buffer.byteLength} bytes (max ${MAX_RESPONSE_BYTES}). Consider fetching a more specific URL or a paginated endpoint.`
1346
+ };
1347
+ return JSON.stringify(result);
1348
+ }
1349
+ let text = new TextDecoder().decode(buffer);
1350
+ if (contentType.includes("html")) text = htmlToText(text);
1351
+ return JSON.stringify({
1352
+ success: true,
1353
+ output: text
1354
+ });
1355
+ } catch (err) {
1356
+ const result = {
1357
+ success: false,
1358
+ output: "",
1359
+ error: classifyFetchError(err)
1360
+ };
1361
+ return JSON.stringify(result);
1362
+ }
1406
1363
  }
1407
- var webFetchTool = createZodFunctionTool(
1408
- "WebFetch",
1409
- "Fetch a URL and return its content as text. HTML pages are converted to plain text.",
1410
- WebFetchSchema,
1411
- async (params) => runWebFetch(params)
1412
- );
1413
-
1414
- // src/builtins/web-search-tool.ts
1415
- var import_zod8 = require("zod");
1416
- var DEFAULT_LIMIT2 = 10;
1417
- var DEFAULT_TIMEOUT_MS3 = 15e3;
1418
- var WebSearchSchema = import_zod8.z.object({
1419
- query: import_zod8.z.string().describe("The search query"),
1420
- limit: import_zod8.z.number().optional().describe(`Maximum number of results to return (default: ${DEFAULT_LIMIT2})`)
1364
+ const webFetchTool = createZodFunctionTool("WebFetch", "Fetch a URL and return its content as text. HTML pages are converted to plain text.", WebFetchSchema, async (params) => runWebFetch(params));
1365
+ //#endregion
1366
+ //#region src/builtins/web-search-tool.ts
1367
+ /**
1368
+ * WebSearchTool — search the web and return results.
1369
+ *
1370
+ * Uses Brave Search API when BRAVE_API_KEY is set.
1371
+ * Returns an error with setup instructions otherwise.
1372
+ */
1373
+ const DEFAULT_LIMIT = 10;
1374
+ const DEFAULT_TIMEOUT_MS = 15e3;
1375
+ const WebSearchSchema = zod.z.object({
1376
+ query: zod.z.string().describe("The search query"),
1377
+ limit: zod.z.number().optional().describe(`Maximum number of results to return (default: ${DEFAULT_LIMIT})`)
1421
1378
  });
1422
1379
  async function runWebSearch(args) {
1423
- const { query, limit = DEFAULT_LIMIT2 } = args;
1424
- const apiKey = process.env["BRAVE_API_KEY"];
1425
- if (!apiKey) {
1426
- const result = {
1427
- success: false,
1428
- output: "",
1429
- error: "Web search requires BRAVE_API_KEY environment variable. Get a free API key at https://brave.com/search/api/ (2,000 queries/month free)."
1430
- };
1431
- return JSON.stringify(result);
1432
- }
1433
- try {
1434
- const controller = new AbortController();
1435
- const timeout = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS3);
1436
- const params = new URLSearchParams({
1437
- q: query,
1438
- count: String(Math.min(limit, 20))
1439
- });
1440
- const response = await fetch(`https://api.search.brave.com/res/v1/web/search?${params}`, {
1441
- headers: {
1442
- Accept: "application/json",
1443
- "Accept-Encoding": "gzip",
1444
- "X-Subscription-Token": apiKey
1445
- },
1446
- signal: controller.signal
1447
- });
1448
- clearTimeout(timeout);
1449
- if (!response.ok) {
1450
- const result2 = {
1451
- success: false,
1452
- output: "",
1453
- error: `Brave Search API error: HTTP ${response.status} ${response.statusText}`
1454
- };
1455
- return JSON.stringify(result2);
1456
- }
1457
- const data = await response.json();
1458
- const results = (data.web?.results ?? []).map((r) => ({
1459
- title: r.title,
1460
- url: r.url,
1461
- snippet: r.description
1462
- }));
1463
- const result = { success: true, output: JSON.stringify(results, null, 2) };
1464
- return JSON.stringify(result);
1465
- } catch (err) {
1466
- const message = err instanceof Error ? err.message : String(err);
1467
- const result = { success: false, output: "", error: message };
1468
- return JSON.stringify(result);
1469
- }
1380
+ const { query, limit = DEFAULT_LIMIT } = args;
1381
+ const apiKey = process.env["BRAVE_API_KEY"];
1382
+ if (!apiKey) return JSON.stringify({
1383
+ success: false,
1384
+ output: "",
1385
+ error: "Web search requires BRAVE_API_KEY environment variable. Get a free API key at https://brave.com/search/api/ (2,000 queries/month free)."
1386
+ });
1387
+ try {
1388
+ const controller = new AbortController();
1389
+ const timeout = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);
1390
+ const params = new URLSearchParams({
1391
+ q: query,
1392
+ count: String(Math.min(limit, 20))
1393
+ });
1394
+ const response = await fetch(`https://api.search.brave.com/res/v1/web/search?${params}`, {
1395
+ headers: {
1396
+ Accept: "application/json",
1397
+ "Accept-Encoding": "gzip",
1398
+ "X-Subscription-Token": apiKey
1399
+ },
1400
+ signal: controller.signal
1401
+ });
1402
+ clearTimeout(timeout);
1403
+ if (!response.ok) {
1404
+ const result = {
1405
+ success: false,
1406
+ output: "",
1407
+ error: `Brave Search API error: HTTP ${response.status} ${response.statusText}`
1408
+ };
1409
+ return JSON.stringify(result);
1410
+ }
1411
+ const results = ((await response.json()).web?.results ?? []).map((r) => ({
1412
+ title: r.title,
1413
+ url: r.url,
1414
+ snippet: r.description
1415
+ }));
1416
+ const result = {
1417
+ success: true,
1418
+ output: JSON.stringify(results, null, 2)
1419
+ };
1420
+ return JSON.stringify(result);
1421
+ } catch (err) {
1422
+ const result = {
1423
+ success: false,
1424
+ output: "",
1425
+ error: err instanceof Error ? err.message : String(err)
1426
+ };
1427
+ return JSON.stringify(result);
1428
+ }
1470
1429
  }
1471
- var webSearchTool = createZodFunctionTool(
1472
- "WebSearch",
1473
- "Search the web and return results with title, URL, and snippet.",
1474
- WebSearchSchema,
1475
- async (params) => runWebSearch(params)
1476
- );
1477
- // Annotate the CommonJS export names for ESM import in node:
1478
- 0 && (module.exports = {
1479
- FunctionTool,
1480
- OpenAPITool,
1481
- ToolRegistry,
1482
- bashTool,
1483
- createFunctionTool,
1484
- createOpenAPITool,
1485
- createZodFunctionTool,
1486
- editTool,
1487
- globTool,
1488
- grepTool,
1489
- readTool,
1490
- webFetchTool,
1491
- webSearchTool,
1492
- writeTool,
1493
- zodToJsonSchema
1494
- });
1430
+ const webSearchTool = createZodFunctionTool("WebSearch", "Search the web and return results with title, URL, and snippet.", WebSearchSchema, async (params) => runWebSearch(params));
1431
+ //#endregion
1432
+ exports.E2BSandboxClient = E2BSandboxClient;
1433
+ exports.FunctionTool = FunctionTool;
1434
+ exports.InMemorySandboxClient = InMemorySandboxClient;
1435
+ exports.ToolRegistry = ToolRegistry;
1436
+ exports.applyWorkspaceManifest = applyWorkspaceManifest;
1437
+ exports.bashTool = bashTool;
1438
+ exports.createBashTool = createBashTool;
1439
+ exports.createEditTool = createEditTool;
1440
+ exports.createFunctionTool = createFunctionTool;
1441
+ exports.createReadTool = createReadTool;
1442
+ exports.createWriteTool = createWriteTool;
1443
+ exports.createZodFunctionTool = createZodFunctionTool;
1444
+ exports.editTool = editTool;
1445
+ exports.globTool = globTool;
1446
+ exports.grepTool = grepTool;
1447
+ exports.readTool = readTool;
1448
+ exports.validateWorkspaceManifestPath = validateWorkspaceManifestPath;
1449
+ exports.webFetchTool = webFetchTool;
1450
+ exports.webSearchTool = webSearchTool;
1451
+ exports.writeTool = writeTool;
1452
+ exports.zodToJsonSchema = zodToJsonSchema;