@robota-sdk/agent-tools 3.0.0-beta.62 → 3.0.0-beta.64

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