@inkeep/agents-run-api 0.39.2 → 0.39.3

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.
Files changed (67) hide show
  1. package/dist/SandboxExecutorFactory.cjs +895 -0
  2. package/dist/SandboxExecutorFactory.js +893 -0
  3. package/dist/SandboxExecutorFactory.js.map +1 -0
  4. package/dist/chunk-VBDAOXYI.cjs +927 -0
  5. package/dist/chunk-VBDAOXYI.js +832 -0
  6. package/dist/chunk-VBDAOXYI.js.map +1 -0
  7. package/dist/chunk.cjs +34 -0
  8. package/dist/conversations.cjs +7 -0
  9. package/dist/conversations.js +7 -0
  10. package/dist/conversations2.cjs +209 -0
  11. package/dist/conversations2.js +180 -0
  12. package/dist/conversations2.js.map +1 -0
  13. package/dist/dbClient.cjs +9676 -0
  14. package/dist/dbClient.js +9670 -0
  15. package/dist/dbClient.js.map +1 -0
  16. package/dist/dbClient2.cjs +5 -0
  17. package/dist/dbClient2.js +5 -0
  18. package/dist/env.cjs +59 -0
  19. package/dist/env.js +54 -0
  20. package/dist/env.js.map +1 -0
  21. package/dist/execution-limits.cjs +260 -0
  22. package/dist/execution-limits.js +63 -0
  23. package/dist/execution-limits.js.map +1 -0
  24. package/dist/index.cjs +10548 -20592
  25. package/dist/index.d.cts +26 -22
  26. package/dist/index.d.cts.map +1 -0
  27. package/dist/index.d.ts +27 -22
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +10548 -12917
  30. package/dist/index.js.map +1 -0
  31. package/dist/instrumentation.cjs +12 -121
  32. package/dist/instrumentation.d.cts +9 -7
  33. package/dist/instrumentation.d.cts.map +1 -0
  34. package/dist/instrumentation.d.ts +9 -7
  35. package/dist/instrumentation.d.ts.map +1 -0
  36. package/dist/instrumentation.js +5 -1
  37. package/dist/instrumentation2.cjs +116 -0
  38. package/dist/instrumentation2.js +69 -0
  39. package/dist/instrumentation2.js.map +1 -0
  40. package/dist/json-postprocessor.cjs +20 -0
  41. package/dist/json-postprocessor.js +20 -0
  42. package/dist/json-postprocessor.js.map +1 -0
  43. package/dist/logger.cjs +5 -0
  44. package/dist/logger.js +3 -0
  45. package/dist/logger2.cjs +1 -0
  46. package/dist/logger2.js +3 -0
  47. package/dist/nodefs.cjs +29 -0
  48. package/dist/nodefs.js +27 -0
  49. package/dist/nodefs.js.map +1 -0
  50. package/dist/opfs-ahp.cjs +367 -0
  51. package/dist/opfs-ahp.js +368 -0
  52. package/dist/opfs-ahp.js.map +1 -0
  53. package/package.json +3 -3
  54. package/dist/SandboxExecutorFactory-2N27SE3B.js +0 -943
  55. package/dist/chunk-A2S7GSHL.js +0 -1
  56. package/dist/chunk-EVOISBFH.js +0 -5070
  57. package/dist/chunk-JCVMVG3J.js +0 -592
  58. package/dist/chunk-KBZIYCPJ.js +0 -40
  59. package/dist/chunk-KCJWSIDZ.js +0 -246
  60. package/dist/chunk-THWNUGWP.js +0 -204
  61. package/dist/chunk-UC2EPLSW.js +0 -75
  62. package/dist/conversations-XPSTWUMK.js +0 -1
  63. package/dist/dbClient-MAHUR4TO.js +0 -1
  64. package/dist/json-postprocessor-IGYTSWFB.js +0 -12
  65. package/dist/logger-3EE6BUSU.js +0 -1
  66. package/dist/nodefs-RPE52Q4Z.js +0 -21
  67. package/dist/opfs-ahp-QL4REJJW.js +0 -318
@@ -0,0 +1,895 @@
1
+ const require_chunk = require('./chunk.cjs');
2
+ require('./logger2.cjs');
3
+ const require_execution_limits = require('./execution-limits.cjs');
4
+ let node_child_process = require("node:child_process");
5
+ let node_crypto = require("node:crypto");
6
+ node_crypto = require_chunk.__toESM(node_crypto);
7
+ let node_fs = require("node:fs");
8
+ let node_os = require("node:os");
9
+ let node_path = require("node:path");
10
+ let __vercel_sandbox = require("@vercel/sandbox");
11
+ let __inkeep_agents_core = require("@inkeep/agents-core");
12
+
13
+ //#region src/tools/sandbox-utils.ts
14
+ /**
15
+ * Shared utilities for sandbox executors
16
+ */
17
+ /**
18
+ * Create an execution wrapper that handles input/output for function tools
19
+ * This is used by both Native and Vercel sandbox executors
20
+ */
21
+ function createExecutionWrapper(executeCode, args) {
22
+ return `
23
+ // Function tool execution wrapper
24
+ const args = ${JSON.stringify(args, null, 2)};
25
+
26
+ // User's function code
27
+ const execute = ${executeCode}
28
+
29
+ // Execute the function and output the result
30
+ (async () => {
31
+ try {
32
+ const result = await execute(args);
33
+ // Output result as JSON on the last line
34
+ console.log(JSON.stringify({ success: true, result }));
35
+ } catch (error) {
36
+ console.error(JSON.stringify({
37
+ success: false,
38
+ error: error instanceof Error ? error.message : String(error)
39
+ }));
40
+ process.exit(1);
41
+ }
42
+ })();
43
+ `;
44
+ }
45
+ /**
46
+ * Parse execution result from stdout
47
+ * Returns the parsed result or the raw stdout if parsing fails
48
+ */
49
+ function parseExecutionResult(stdout, functionId, logger$3) {
50
+ try {
51
+ const outputLines = stdout.split("\n").filter((line) => line.trim());
52
+ const resultLine = outputLines[outputLines.length - 1];
53
+ return JSON.parse(resultLine);
54
+ } catch (parseError) {
55
+ if (logger$3) logger$3.warn({
56
+ functionId,
57
+ stdout,
58
+ parseError
59
+ }, "Failed to parse execution result");
60
+ return stdout;
61
+ }
62
+ }
63
+
64
+ //#endregion
65
+ //#region src/tools/NativeSandboxExecutor.ts
66
+ /**
67
+ * NativeSandboxExecutor - Function Tool Execution Engine
68
+ * ========================================================
69
+ *
70
+ * Executes user-defined function tools in isolated sandboxes using native Node.js processes.
71
+ * The main challenge here is that we can't just eval() user code - that's a security nightmare.
72
+ * Instead, we spin up separate Node.js processes with their own dependency trees.
73
+ *
74
+ * The tricky part is making this fast. Installing deps every time would be brutal
75
+ * (2-5s per execution), so we cache sandboxes based on their dependency fingerprint.
76
+ *
77
+ * How it works:
78
+ *
79
+ * 1. User calls a function tool
80
+ * 2. We hash the dependencies (e.g., "axios@1.6.0,lodash@4.17.21")
81
+ * 3. Check if we already have a sandbox with those deps installed
82
+ * 4. If yes: reuse it. If no: create new one, install deps, cache it
83
+ * 5. Write the user's function code to a temp file
84
+ * 6. Execute it in the sandboxed process with resource limits
85
+ * 7. Return the result
86
+ *
87
+ * Sandbox lifecycle:
88
+ * - Created when first needed for a dependency set
89
+ * - Reused up to 50 times or 5 minutes, whichever comes first
90
+ * - Automatically cleaned up when expired
91
+ * - Failed sandboxes are immediately destroyed
92
+ *
93
+ * Security stuff:
94
+ * - Each execution runs in its own process (not just a function call)
95
+ * - Output limited to 1MB to prevent memory bombs
96
+ * - Timeouts with graceful SIGTERM, then SIGKILL if needed
97
+ * - Runs as non-root when possible
98
+ * - Uses OS temp directory so it gets cleaned up automatically
99
+ *
100
+ * Performance:
101
+ * - Cold start: ~100-500ms (vs 2-5s without caching)
102
+ * - Hot path: ~50-100ms (just execution, no install)
103
+ * - Memory bounded by pool size limits
104
+ *
105
+ * Deployment notes:
106
+ * - Uses /tmp on Linux/macOS, %TEMP% on Windows
107
+ * - Works in Docker, Kubernetes, serverless (Vercel, Lambda)
108
+ * - No files left in project directory (no git pollution)
109
+ *
110
+ * The singleton pattern here is important - we need one shared pool
111
+ * across all tool executions, otherwise caching doesn't work.
112
+ */
113
+ const logger$2 = (0, __inkeep_agents_core.getLogger)("native-sandbox-executor");
114
+ /**
115
+ * Semaphore for limiting concurrent executions based on vCPU allocation
116
+ */
117
+ var ExecutionSemaphore = class {
118
+ permits;
119
+ waitQueue = [];
120
+ maxWaitTime;
121
+ constructor(permits, maxWaitTimeMs = require_execution_limits.FUNCTION_TOOL_SANDBOX_QUEUE_WAIT_TIMEOUT_MS) {
122
+ this.permits = Math.max(1, permits);
123
+ this.maxWaitTime = maxWaitTimeMs;
124
+ }
125
+ async acquire(fn) {
126
+ await new Promise((resolve, reject) => {
127
+ if (this.permits > 0) {
128
+ this.permits--;
129
+ resolve();
130
+ return;
131
+ }
132
+ const timeoutId = setTimeout(() => {
133
+ const index = this.waitQueue.findIndex((item) => item.resolve === resolve);
134
+ if (index !== -1) {
135
+ this.waitQueue.splice(index, 1);
136
+ reject(/* @__PURE__ */ new Error(`Function execution queue timeout after ${this.maxWaitTime}ms. Too many concurrent executions.`));
137
+ }
138
+ }, this.maxWaitTime);
139
+ this.waitQueue.push({
140
+ resolve: () => {
141
+ clearTimeout(timeoutId);
142
+ this.permits--;
143
+ resolve();
144
+ },
145
+ reject
146
+ });
147
+ });
148
+ try {
149
+ return await fn();
150
+ } finally {
151
+ this.permits++;
152
+ const next = this.waitQueue.shift();
153
+ if (next) next.resolve();
154
+ }
155
+ }
156
+ getAvailablePermits() {
157
+ return this.permits;
158
+ }
159
+ getQueueLength() {
160
+ return this.waitQueue.length;
161
+ }
162
+ };
163
+ var NativeSandboxExecutor = class NativeSandboxExecutor {
164
+ tempDir;
165
+ sandboxPool = {};
166
+ static instance = null;
167
+ executionSemaphores = /* @__PURE__ */ new Map();
168
+ constructor() {
169
+ this.tempDir = (0, node_path.join)((0, node_os.tmpdir)(), "inkeep-sandboxes");
170
+ this.ensureTempDir();
171
+ this.startPoolCleanup();
172
+ }
173
+ static getInstance() {
174
+ if (!NativeSandboxExecutor.instance) NativeSandboxExecutor.instance = new NativeSandboxExecutor();
175
+ return NativeSandboxExecutor.instance;
176
+ }
177
+ getSemaphore(vcpus) {
178
+ const effectiveVcpus = Math.max(1, vcpus || 1);
179
+ if (!this.executionSemaphores.has(effectiveVcpus)) {
180
+ logger$2.debug({ vcpus: effectiveVcpus }, "Creating new execution semaphore");
181
+ this.executionSemaphores.set(effectiveVcpus, new ExecutionSemaphore(effectiveVcpus));
182
+ }
183
+ const semaphore = this.executionSemaphores.get(effectiveVcpus);
184
+ if (!semaphore) throw new Error(`Failed to create semaphore for ${effectiveVcpus} vCPUs`);
185
+ return semaphore;
186
+ }
187
+ getExecutionStats() {
188
+ const stats = {};
189
+ for (const [vcpus, semaphore] of this.executionSemaphores.entries()) stats[`vcpu_${vcpus}`] = {
190
+ availablePermits: semaphore.getAvailablePermits(),
191
+ queueLength: semaphore.getQueueLength()
192
+ };
193
+ return stats;
194
+ }
195
+ ensureTempDir() {
196
+ try {
197
+ (0, node_fs.mkdirSync)(this.tempDir, { recursive: true });
198
+ } catch {}
199
+ }
200
+ generateDependencyHash(dependencies) {
201
+ const sortedDeps = Object.keys(dependencies).sort().map((key) => `${key}@${dependencies[key]}`).join(",");
202
+ return (0, node_crypto.createHash)("sha256").update(sortedDeps).digest("hex").substring(0, 16);
203
+ }
204
+ getCachedSandbox(dependencyHash) {
205
+ const poolKey = dependencyHash;
206
+ const sandbox = this.sandboxPool[poolKey];
207
+ if (sandbox && (0, node_fs.existsSync)(sandbox.sandboxDir)) {
208
+ const now = Date.now();
209
+ if (now - sandbox.lastUsed < require_execution_limits.FUNCTION_TOOL_SANDBOX_POOL_TTL_MS && sandbox.useCount < require_execution_limits.FUNCTION_TOOL_SANDBOX_MAX_USE_COUNT) {
210
+ sandbox.lastUsed = now;
211
+ sandbox.useCount++;
212
+ logger$2.debug({
213
+ poolKey,
214
+ useCount: sandbox.useCount,
215
+ sandboxDir: sandbox.sandboxDir,
216
+ lastUsed: new Date(sandbox.lastUsed)
217
+ }, "Reusing cached sandbox");
218
+ return sandbox.sandboxDir;
219
+ }
220
+ this.cleanupSandbox(sandbox.sandboxDir);
221
+ delete this.sandboxPool[poolKey];
222
+ }
223
+ return null;
224
+ }
225
+ addToPool(dependencyHash, sandboxDir, dependencies) {
226
+ const poolKey = dependencyHash;
227
+ if (this.sandboxPool[poolKey]) this.cleanupSandbox(this.sandboxPool[poolKey].sandboxDir);
228
+ this.sandboxPool[poolKey] = {
229
+ sandboxDir,
230
+ lastUsed: Date.now(),
231
+ useCount: 1,
232
+ dependencies
233
+ };
234
+ logger$2.debug({
235
+ poolKey,
236
+ sandboxDir
237
+ }, "Added sandbox to pool");
238
+ }
239
+ cleanupSandbox(sandboxDir) {
240
+ try {
241
+ (0, node_fs.rmSync)(sandboxDir, {
242
+ recursive: true,
243
+ force: true
244
+ });
245
+ logger$2.debug({ sandboxDir }, "Cleaned up sandbox");
246
+ } catch (error) {
247
+ logger$2.warn({
248
+ sandboxDir,
249
+ error
250
+ }, "Failed to clean up sandbox");
251
+ }
252
+ }
253
+ startPoolCleanup() {
254
+ setInterval(() => {
255
+ const now = Date.now();
256
+ const keysToDelete = [];
257
+ for (const [key, sandbox] of Object.entries(this.sandboxPool)) if (now - sandbox.lastUsed > require_execution_limits.FUNCTION_TOOL_SANDBOX_POOL_TTL_MS || sandbox.useCount >= require_execution_limits.FUNCTION_TOOL_SANDBOX_MAX_USE_COUNT) {
258
+ this.cleanupSandbox(sandbox.sandboxDir);
259
+ keysToDelete.push(key);
260
+ }
261
+ keysToDelete.forEach((key) => {
262
+ delete this.sandboxPool[key];
263
+ });
264
+ if (keysToDelete.length > 0) logger$2.debug({ cleanedCount: keysToDelete.length }, "Cleaned up expired sandboxes");
265
+ }, require_execution_limits.FUNCTION_TOOL_SANDBOX_CLEANUP_INTERVAL_MS);
266
+ }
267
+ detectModuleType(executeCode, configuredRuntime) {
268
+ const esmPatterns = [
269
+ /import\s+.*\s+from\s+['"]/g,
270
+ /import\s*\(/g,
271
+ /export\s+(default|const|let|var|function|class)/g,
272
+ /export\s*\{/g
273
+ ];
274
+ const cjsPatterns = [
275
+ /require\s*\(/g,
276
+ /module\.exports/g,
277
+ /exports\./g
278
+ ];
279
+ const hasEsmSyntax = esmPatterns.some((pattern) => pattern.test(executeCode));
280
+ const hasCjsSyntax = cjsPatterns.some((pattern) => pattern.test(executeCode));
281
+ if (configuredRuntime === "typescript") return hasCjsSyntax ? "cjs" : "esm";
282
+ if (hasEsmSyntax && hasCjsSyntax) {
283
+ logger$2.warn({ executeCode: `${executeCode.substring(0, 100)}...` }, "Both ESM and CommonJS syntax detected, defaulting to ESM");
284
+ return "esm";
285
+ }
286
+ if (hasEsmSyntax) return "esm";
287
+ if (hasCjsSyntax) return "cjs";
288
+ return "cjs";
289
+ }
290
+ async executeFunctionTool(toolId, args, config) {
291
+ const vcpus = config.sandboxConfig?.vcpus || 1;
292
+ const semaphore = this.getSemaphore(vcpus);
293
+ logger$2.debug({
294
+ toolId,
295
+ vcpus,
296
+ availablePermits: semaphore.getAvailablePermits(),
297
+ queueLength: semaphore.getQueueLength(),
298
+ sandboxConfig: config.sandboxConfig,
299
+ poolSize: Object.keys(this.sandboxPool).length
300
+ }, "Acquiring execution slot for function tool");
301
+ return semaphore.acquire(async () => {
302
+ return this.executeInSandbox_Internal(toolId, args, config);
303
+ });
304
+ }
305
+ async executeInSandbox_Internal(toolId, args, config) {
306
+ const dependencies = config.dependencies || {};
307
+ const dependencyHash = this.generateDependencyHash(dependencies);
308
+ logger$2.debug({
309
+ toolId,
310
+ dependencies,
311
+ dependencyHash,
312
+ sandboxConfig: config.sandboxConfig,
313
+ poolSize: Object.keys(this.sandboxPool).length
314
+ }, "Executing function tool");
315
+ let sandboxDir = this.getCachedSandbox(dependencyHash);
316
+ let isNewSandbox = false;
317
+ if (!sandboxDir) {
318
+ sandboxDir = (0, node_path.join)(this.tempDir, `sandbox-${dependencyHash}-${Date.now()}`);
319
+ (0, node_fs.mkdirSync)(sandboxDir, { recursive: true });
320
+ isNewSandbox = true;
321
+ logger$2.debug({
322
+ toolId,
323
+ dependencyHash,
324
+ sandboxDir,
325
+ dependencies
326
+ }, "Creating new sandbox");
327
+ const moduleType = this.detectModuleType(config.executeCode, config.sandboxConfig?.runtime);
328
+ const packageJson = {
329
+ name: `function-tool-${toolId}`,
330
+ version: "1.0.0",
331
+ ...moduleType === "esm" && { type: "module" },
332
+ dependencies,
333
+ scripts: { start: moduleType === "esm" ? "node index.mjs" : "node index.js" }
334
+ };
335
+ (0, node_fs.writeFileSync)((0, node_path.join)(sandboxDir, "package.json"), JSON.stringify(packageJson, null, 2), "utf8");
336
+ if (Object.keys(dependencies).length > 0) await this.installDependencies(sandboxDir);
337
+ this.addToPool(dependencyHash, sandboxDir, dependencies);
338
+ }
339
+ try {
340
+ const moduleType = this.detectModuleType(config.executeCode, config.sandboxConfig?.runtime);
341
+ const executionCode = createExecutionWrapper(config.executeCode, args);
342
+ (0, node_fs.writeFileSync)((0, node_path.join)(sandboxDir, `index.${moduleType === "esm" ? "mjs" : "js"}`), executionCode, "utf8");
343
+ return await this.executeInSandbox(sandboxDir, config.sandboxConfig?.timeout || require_execution_limits.FUNCTION_TOOL_EXECUTION_TIMEOUT_MS_DEFAULT, moduleType, config.sandboxConfig);
344
+ } catch (error) {
345
+ if (isNewSandbox) {
346
+ this.cleanupSandbox(sandboxDir);
347
+ const poolKey = dependencyHash;
348
+ delete this.sandboxPool[poolKey];
349
+ }
350
+ throw error;
351
+ }
352
+ }
353
+ async installDependencies(sandboxDir) {
354
+ return new Promise((resolve, reject) => {
355
+ const npm = (0, node_child_process.spawn)("npm", [
356
+ "install",
357
+ "--no-audit",
358
+ "--no-fund"
359
+ ], {
360
+ cwd: sandboxDir,
361
+ stdio: "pipe",
362
+ env: {
363
+ ...process.env,
364
+ npm_config_cache: (0, node_path.join)(sandboxDir, ".npm-cache"),
365
+ npm_config_logs_dir: (0, node_path.join)(sandboxDir, ".npm-logs"),
366
+ npm_config_tmp: (0, node_path.join)(sandboxDir, ".npm-tmp"),
367
+ HOME: sandboxDir,
368
+ npm_config_update_notifier: "false",
369
+ npm_config_progress: "false",
370
+ npm_config_loglevel: "error"
371
+ }
372
+ });
373
+ let stderr = "";
374
+ npm.stdout?.on("data", () => {});
375
+ npm.stderr?.on("data", (data) => {
376
+ stderr += data.toString();
377
+ });
378
+ npm.on("close", (code) => {
379
+ if (code === 0) {
380
+ logger$2.debug({ sandboxDir }, "Dependencies installed successfully");
381
+ resolve();
382
+ } else {
383
+ logger$2.error({
384
+ sandboxDir,
385
+ code,
386
+ stderr
387
+ }, "Failed to install dependencies");
388
+ reject(/* @__PURE__ */ new Error(`npm install failed with code ${code}: ${stderr}`));
389
+ }
390
+ });
391
+ npm.on("error", (err) => {
392
+ logger$2.error({
393
+ sandboxDir,
394
+ error: err
395
+ }, "Failed to spawn npm install");
396
+ reject(err);
397
+ });
398
+ });
399
+ }
400
+ async executeInSandbox(sandboxDir, timeout, moduleType, _sandboxConfig) {
401
+ return new Promise((resolve, reject) => {
402
+ const fileExtension = moduleType === "esm" ? "mjs" : "js";
403
+ const spawnOptions = {
404
+ cwd: sandboxDir,
405
+ stdio: "pipe",
406
+ uid: process.getuid ? process.getuid() : void 0,
407
+ gid: process.getgid ? process.getgid() : void 0
408
+ };
409
+ const node = (0, node_child_process.spawn)("node", [`index.${fileExtension}`], spawnOptions);
410
+ let stdout = "";
411
+ let stderr = "";
412
+ let outputSize = 0;
413
+ node.stdout?.on("data", (data) => {
414
+ const dataStr = data.toString();
415
+ outputSize += dataStr.length;
416
+ if (outputSize > require_execution_limits.FUNCTION_TOOL_SANDBOX_MAX_OUTPUT_SIZE_BYTES) {
417
+ node.kill("SIGTERM");
418
+ reject(/* @__PURE__ */ new Error(`Output size exceeded limit of ${require_execution_limits.FUNCTION_TOOL_SANDBOX_MAX_OUTPUT_SIZE_BYTES} bytes`));
419
+ return;
420
+ }
421
+ stdout += dataStr;
422
+ });
423
+ node.stderr?.on("data", (data) => {
424
+ const dataStr = data.toString();
425
+ outputSize += dataStr.length;
426
+ if (outputSize > require_execution_limits.FUNCTION_TOOL_SANDBOX_MAX_OUTPUT_SIZE_BYTES) {
427
+ node.kill("SIGTERM");
428
+ reject(/* @__PURE__ */ new Error(`Output size exceeded limit of ${require_execution_limits.FUNCTION_TOOL_SANDBOX_MAX_OUTPUT_SIZE_BYTES} bytes`));
429
+ return;
430
+ }
431
+ stderr += dataStr;
432
+ });
433
+ const timeoutId = setTimeout(() => {
434
+ logger$2.warn({
435
+ sandboxDir,
436
+ timeout
437
+ }, "Function execution timed out, killing process");
438
+ node.kill("SIGTERM");
439
+ const forceKillTimeout = Math.min(Math.max(timeout / 10, 2e3), 5e3);
440
+ setTimeout(() => {
441
+ try {
442
+ node.kill("SIGKILL");
443
+ } catch {}
444
+ }, forceKillTimeout);
445
+ reject(/* @__PURE__ */ new Error(`Function execution timed out after ${timeout}ms`));
446
+ }, timeout);
447
+ node.on("close", (code, signal) => {
448
+ clearTimeout(timeoutId);
449
+ if (code === 0) try {
450
+ const result = parseExecutionResult(stdout, "function", logger$2);
451
+ if (typeof result === "object" && result !== null && "success" in result) {
452
+ const parsed = result;
453
+ if (parsed.success) resolve(parsed.result);
454
+ else reject(new Error(parsed.error || "Function execution failed"));
455
+ } else resolve(result);
456
+ } catch (parseError) {
457
+ logger$2.error({
458
+ stdout,
459
+ stderr,
460
+ parseError
461
+ }, "Failed to parse function result");
462
+ reject(/* @__PURE__ */ new Error(`Invalid function result: ${stdout}`));
463
+ }
464
+ else {
465
+ const errorMsg = signal ? `Function execution killed by signal ${signal}: ${stderr}` : `Function execution failed with code ${code}: ${stderr}`;
466
+ logger$2.error({
467
+ code,
468
+ signal,
469
+ stderr
470
+ }, "Function execution failed");
471
+ reject(new Error(errorMsg));
472
+ }
473
+ });
474
+ node.on("error", (error) => {
475
+ clearTimeout(timeoutId);
476
+ logger$2.error({
477
+ sandboxDir,
478
+ error
479
+ }, "Failed to spawn node process");
480
+ reject(error);
481
+ });
482
+ });
483
+ }
484
+ };
485
+
486
+ //#endregion
487
+ //#region src/tools/VercelSandboxExecutor.ts
488
+ const logger$1 = (0, __inkeep_agents_core.getLogger)("VercelSandboxExecutor");
489
+ /**
490
+ * Vercel Sandbox Executor with pooling/reuse
491
+ * Executes function tools in isolated Vercel Sandbox MicroVMs
492
+ * Caches and reuses sandboxes based on dependencies to improve performance
493
+ */
494
+ var VercelSandboxExecutor = class VercelSandboxExecutor {
495
+ static instance;
496
+ config;
497
+ sandboxPool = /* @__PURE__ */ new Map();
498
+ cleanupInterval = null;
499
+ constructor(config) {
500
+ this.config = config;
501
+ logger$1.info({
502
+ teamId: config.teamId,
503
+ projectId: config.projectId,
504
+ runtime: config.runtime,
505
+ timeout: config.timeout,
506
+ vcpus: config.vcpus
507
+ }, "VercelSandboxExecutor initialized with pooling");
508
+ this.startPoolCleanup();
509
+ }
510
+ /**
511
+ * Get singleton instance of VercelSandboxExecutor
512
+ */
513
+ static getInstance(config) {
514
+ if (!VercelSandboxExecutor.instance) VercelSandboxExecutor.instance = new VercelSandboxExecutor(config);
515
+ return VercelSandboxExecutor.instance;
516
+ }
517
+ /**
518
+ * Generate a hash for dependencies to use as cache key
519
+ */
520
+ generateDependencyHash(dependencies) {
521
+ const sorted = Object.keys(dependencies).sort().map((key) => `${key}@${dependencies[key]}`).join(",");
522
+ return node_crypto.default.createHash("md5").update(sorted).digest("hex").substring(0, 8);
523
+ }
524
+ /**
525
+ * Get a cached sandbox if available and still valid
526
+ */
527
+ getCachedSandbox(dependencyHash) {
528
+ const cached = this.sandboxPool.get(dependencyHash);
529
+ if (!cached) return null;
530
+ const age = Date.now() - cached.createdAt;
531
+ if (age > require_execution_limits.FUNCTION_TOOL_SANDBOX_POOL_TTL_MS || cached.useCount >= require_execution_limits.FUNCTION_TOOL_SANDBOX_MAX_USE_COUNT) {
532
+ logger$1.debug({
533
+ dependencyHash,
534
+ age,
535
+ useCount: cached.useCount,
536
+ ttl: require_execution_limits.FUNCTION_TOOL_SANDBOX_POOL_TTL_MS,
537
+ maxUseCount: require_execution_limits.FUNCTION_TOOL_SANDBOX_MAX_USE_COUNT
538
+ }, "Sandbox expired, will create new one");
539
+ this.removeSandbox(dependencyHash);
540
+ return null;
541
+ }
542
+ logger$1.debug({
543
+ dependencyHash,
544
+ useCount: cached.useCount,
545
+ age
546
+ }, "Reusing cached sandbox");
547
+ return cached.sandbox;
548
+ }
549
+ /**
550
+ * Add sandbox to pool
551
+ */
552
+ addToPool(dependencyHash, sandbox, dependencies) {
553
+ this.sandboxPool.set(dependencyHash, {
554
+ sandbox,
555
+ createdAt: Date.now(),
556
+ useCount: 0,
557
+ dependencies
558
+ });
559
+ logger$1.debug({
560
+ dependencyHash,
561
+ poolSize: this.sandboxPool.size
562
+ }, "Sandbox added to pool");
563
+ }
564
+ /**
565
+ * Increment use count for a sandbox
566
+ */
567
+ incrementUseCount(dependencyHash) {
568
+ const cached = this.sandboxPool.get(dependencyHash);
569
+ if (cached) cached.useCount++;
570
+ }
571
+ /**
572
+ * Remove and clean up a sandbox
573
+ */
574
+ async removeSandbox(dependencyHash) {
575
+ const cached = this.sandboxPool.get(dependencyHash);
576
+ if (cached) {
577
+ try {
578
+ await cached.sandbox.stop();
579
+ logger$1.debug({ dependencyHash }, "Sandbox stopped");
580
+ } catch (error) {
581
+ logger$1.warn({
582
+ error,
583
+ dependencyHash
584
+ }, "Error stopping sandbox");
585
+ }
586
+ this.sandboxPool.delete(dependencyHash);
587
+ }
588
+ }
589
+ /**
590
+ * Start periodic cleanup of expired sandboxes
591
+ */
592
+ startPoolCleanup() {
593
+ this.cleanupInterval = setInterval(() => {
594
+ const now = Date.now();
595
+ const toRemove = [];
596
+ for (const [hash, cached] of this.sandboxPool.entries()) if (now - cached.createdAt > require_execution_limits.FUNCTION_TOOL_SANDBOX_POOL_TTL_MS || cached.useCount >= require_execution_limits.FUNCTION_TOOL_SANDBOX_MAX_USE_COUNT) toRemove.push(hash);
597
+ if (toRemove.length > 0) {
598
+ logger$1.info({
599
+ count: toRemove.length,
600
+ poolSize: this.sandboxPool.size
601
+ }, "Cleaning up expired sandboxes");
602
+ for (const hash of toRemove) this.removeSandbox(hash);
603
+ }
604
+ }, require_execution_limits.FUNCTION_TOOL_SANDBOX_CLEANUP_INTERVAL_MS);
605
+ }
606
+ /**
607
+ * Cleanup all sandboxes and stop cleanup interval
608
+ */
609
+ async cleanup() {
610
+ if (this.cleanupInterval) {
611
+ clearInterval(this.cleanupInterval);
612
+ this.cleanupInterval = null;
613
+ }
614
+ logger$1.info({ poolSize: this.sandboxPool.size }, "Cleaning up all sandboxes");
615
+ const promises = Array.from(this.sandboxPool.keys()).map((hash) => this.removeSandbox(hash));
616
+ await Promise.all(promises);
617
+ }
618
+ /**
619
+ * Extract environment variable names from code
620
+ * Matches patterns like process.env.VAR_NAME or process.env['VAR_NAME']
621
+ */
622
+ extractEnvVars(code) {
623
+ const envVars = /* @__PURE__ */ new Set();
624
+ const dotNotationRegex = /process\.env\.([A-Z_][A-Z0-9_]*)/g;
625
+ let match = dotNotationRegex.exec(code);
626
+ while (match !== null) {
627
+ envVars.add(match[1]);
628
+ match = dotNotationRegex.exec(code);
629
+ }
630
+ const bracketNotationRegex = /process\.env\[['"]([A-Z_][A-Z0-9_]*)['"]\]/g;
631
+ match = bracketNotationRegex.exec(code);
632
+ while (match !== null) {
633
+ envVars.add(match[1]);
634
+ match = bracketNotationRegex.exec(code);
635
+ }
636
+ return envVars;
637
+ }
638
+ /**
639
+ * Create .env file content from environment variables
640
+ * Note: Currently creates empty placeholders. Values will be populated in the future.
641
+ */
642
+ createEnvFileContent(envVarNames) {
643
+ const envLines = [];
644
+ for (const varName of envVarNames) {
645
+ envLines.push(`${varName}=""`);
646
+ logger$1.debug({ varName }, "Adding environment variable placeholder to sandbox");
647
+ }
648
+ return envLines.join("\n");
649
+ }
650
+ /**
651
+ * Execute a function tool in Vercel Sandbox with pooling
652
+ */
653
+ async executeFunctionTool(functionId, args, toolConfig) {
654
+ const startTime = Date.now();
655
+ const logs = [];
656
+ const dependencies = toolConfig.dependencies || {};
657
+ const dependencyHash = this.generateDependencyHash(dependencies);
658
+ try {
659
+ logger$1.info({
660
+ functionId,
661
+ functionName: toolConfig.name,
662
+ dependencyHash,
663
+ poolSize: this.sandboxPool.size
664
+ }, "Executing function in Vercel Sandbox");
665
+ let sandbox = this.getCachedSandbox(dependencyHash);
666
+ let isNewSandbox = false;
667
+ if (!sandbox) {
668
+ isNewSandbox = true;
669
+ sandbox = await __vercel_sandbox.Sandbox.create({
670
+ token: this.config.token,
671
+ teamId: this.config.teamId,
672
+ projectId: this.config.projectId,
673
+ timeout: this.config.timeout,
674
+ resources: { vcpus: this.config.vcpus || 1 },
675
+ runtime: this.config.runtime
676
+ });
677
+ logger$1.info({
678
+ functionId,
679
+ sandboxId: sandbox.sandboxId,
680
+ dependencyHash
681
+ }, `New sandbox created for function ${functionId}`);
682
+ this.addToPool(dependencyHash, sandbox, dependencies);
683
+ } else logger$1.info({
684
+ functionId,
685
+ sandboxId: sandbox.sandboxId,
686
+ dependencyHash
687
+ }, `Reusing cached sandbox for function ${functionId}`);
688
+ this.incrementUseCount(dependencyHash);
689
+ try {
690
+ if (isNewSandbox && toolConfig.dependencies && Object.keys(toolConfig.dependencies).length > 0) {
691
+ logger$1.debug({
692
+ functionId,
693
+ functionName: toolConfig.name,
694
+ dependencies: toolConfig.dependencies
695
+ }, "Installing dependencies in new sandbox");
696
+ const packageJson = { dependencies: toolConfig.dependencies };
697
+ const packageJsonContent = JSON.stringify(packageJson, null, 2);
698
+ await sandbox.writeFiles([{
699
+ path: "package.json",
700
+ content: Buffer.from(packageJsonContent, "utf-8")
701
+ }]);
702
+ const installCmd = await sandbox.runCommand({
703
+ cmd: "npm",
704
+ args: ["install", "--omit=dev"]
705
+ });
706
+ const installStdout = await installCmd.stdout();
707
+ const installStderr = await installCmd.stderr();
708
+ if (installStdout) logs.push(installStdout);
709
+ if (installStderr) logs.push(installStderr);
710
+ if (installCmd.exitCode !== 0) throw new Error(`Failed to install dependencies: ${installStderr}`);
711
+ logger$1.info({
712
+ functionId,
713
+ dependencyHash
714
+ }, "Dependencies installed successfully");
715
+ }
716
+ const executionCode = createExecutionWrapper(toolConfig.executeCode, args);
717
+ const envVars = this.extractEnvVars(toolConfig.executeCode);
718
+ const filesToWrite = [];
719
+ const filename = this.config.runtime === "typescript" ? "execute.ts" : "execute.js";
720
+ filesToWrite.push({
721
+ path: filename,
722
+ content: Buffer.from(executionCode, "utf-8")
723
+ });
724
+ if (envVars.size > 0) {
725
+ const envFileContent = this.createEnvFileContent(envVars);
726
+ if (envFileContent) {
727
+ filesToWrite.push({
728
+ path: ".env",
729
+ content: Buffer.from(envFileContent, "utf-8")
730
+ });
731
+ logger$1.info({
732
+ functionId,
733
+ envVarCount: envVars.size,
734
+ envVars: Array.from(envVars)
735
+ }, "Creating environment variable placeholders in sandbox");
736
+ }
737
+ }
738
+ await sandbox.writeFiles(filesToWrite);
739
+ logger$1.info({
740
+ functionId,
741
+ runtime: this.config.runtime === "typescript" ? "tsx" : "node",
742
+ hasEnvVars: envVars.size > 0
743
+ }, `Execution code written to file for runtime ${this.config.runtime}`);
744
+ const executeCmd = await (async () => {
745
+ if (envVars.size > 0) return sandbox.runCommand({
746
+ cmd: "npx",
747
+ args: this.config.runtime === "typescript" ? [
748
+ "--yes",
749
+ "dotenv-cli",
750
+ "--",
751
+ "npx",
752
+ "tsx",
753
+ filename
754
+ ] : [
755
+ "--yes",
756
+ "dotenv-cli",
757
+ "--",
758
+ "node",
759
+ filename
760
+ ]
761
+ });
762
+ const runtime = this.config.runtime === "typescript" ? "tsx" : "node";
763
+ return sandbox.runCommand({
764
+ cmd: runtime,
765
+ args: [filename]
766
+ });
767
+ })();
768
+ const executeStdout = await executeCmd.stdout();
769
+ const executeStderr = await executeCmd.stderr();
770
+ if (executeStdout) logs.push(executeStdout);
771
+ if (executeStderr) logs.push(executeStderr);
772
+ const executionTime = Date.now() - startTime;
773
+ if (executeCmd.exitCode !== 0) {
774
+ logger$1.error({
775
+ functionId,
776
+ exitCode: executeCmd.exitCode,
777
+ stderr: executeStderr
778
+ }, "Function execution failed");
779
+ return {
780
+ success: false,
781
+ error: executeStderr || "Function execution failed with non-zero exit code",
782
+ logs,
783
+ executionTime
784
+ };
785
+ }
786
+ const result = parseExecutionResult(executeStdout, functionId, logger$1);
787
+ logger$1.info({
788
+ functionId,
789
+ executionTime
790
+ }, "Function executed successfully in Vercel Sandbox");
791
+ return {
792
+ success: true,
793
+ result,
794
+ logs,
795
+ executionTime
796
+ };
797
+ } catch (innerError) {
798
+ await this.removeSandbox(dependencyHash);
799
+ throw innerError;
800
+ }
801
+ } catch (error) {
802
+ const executionTime = Date.now() - startTime;
803
+ const errorMessage = error instanceof Error ? error.message : String(error);
804
+ logger$1.error({
805
+ functionId,
806
+ error: errorMessage,
807
+ executionTime
808
+ }, "Vercel Sandbox execution error");
809
+ return {
810
+ success: false,
811
+ error: errorMessage,
812
+ logs,
813
+ executionTime
814
+ };
815
+ }
816
+ }
817
+ };
818
+
819
+ //#endregion
820
+ //#region src/tools/SandboxExecutorFactory.ts
821
+ const logger = (0, __inkeep_agents_core.getLogger)("SandboxExecutorFactory");
822
+ /**
823
+ * Factory for creating and managing sandbox executors
824
+ * Routes execution to the appropriate sandbox provider (native or Vercel)
825
+ */
826
+ var SandboxExecutorFactory = class SandboxExecutorFactory {
827
+ static instance;
828
+ nativeExecutor = null;
829
+ vercelExecutors = /* @__PURE__ */ new Map();
830
+ constructor() {
831
+ logger.info({}, "SandboxExecutorFactory initialized");
832
+ }
833
+ /**
834
+ * Get singleton instance of SandboxExecutorFactory
835
+ */
836
+ static getInstance() {
837
+ if (!SandboxExecutorFactory.instance) SandboxExecutorFactory.instance = new SandboxExecutorFactory();
838
+ return SandboxExecutorFactory.instance;
839
+ }
840
+ /**
841
+ * Execute a function tool using the appropriate sandbox provider
842
+ */
843
+ async executeFunctionTool(functionId, args, config) {
844
+ const sandboxConfig = config.sandboxConfig;
845
+ if (!sandboxConfig) throw new Error("Sandbox configuration is required for function tool execution");
846
+ if (sandboxConfig.provider === "native") return this.executeInNativeSandbox(functionId, args, config);
847
+ if (sandboxConfig.provider === "vercel") return this.executeInVercelSandbox(functionId, args, config);
848
+ throw new Error(`Unknown sandbox provider: ${sandboxConfig.provider}`);
849
+ }
850
+ /**
851
+ * Execute in native sandbox
852
+ */
853
+ async executeInNativeSandbox(functionId, args, config) {
854
+ if (!this.nativeExecutor) {
855
+ this.nativeExecutor = NativeSandboxExecutor.getInstance();
856
+ logger.info({}, "Native sandbox executor created");
857
+ }
858
+ return this.nativeExecutor.executeFunctionTool(functionId, args, config);
859
+ }
860
+ /**
861
+ * Execute in Vercel sandbox
862
+ */
863
+ async executeInVercelSandbox(functionId, args, config) {
864
+ const vercelConfig = config.sandboxConfig;
865
+ const configKey = `${vercelConfig.teamId}:${vercelConfig.projectId}`;
866
+ if (!this.vercelExecutors.has(configKey)) {
867
+ const executor$1 = VercelSandboxExecutor.getInstance(vercelConfig);
868
+ this.vercelExecutors.set(configKey, executor$1);
869
+ logger.info({
870
+ teamId: vercelConfig.teamId,
871
+ projectId: vercelConfig.projectId
872
+ }, "Vercel sandbox executor created");
873
+ }
874
+ const executor = this.vercelExecutors.get(configKey);
875
+ if (!executor) throw new Error(`Failed to get Vercel executor for config: ${configKey}`);
876
+ const result = await executor.executeFunctionTool(functionId, args, config);
877
+ if (!result.success) throw new Error(result.error || "Vercel sandbox execution failed");
878
+ return result.result;
879
+ }
880
+ /**
881
+ * Clean up all sandbox executors
882
+ */
883
+ async cleanup() {
884
+ logger.info({}, "Cleaning up sandbox executors");
885
+ this.nativeExecutor = null;
886
+ for (const [key, executor] of this.vercelExecutors.entries()) {
887
+ await executor.cleanup();
888
+ this.vercelExecutors.delete(key);
889
+ }
890
+ logger.info({}, "Sandbox executor cleanup completed");
891
+ }
892
+ };
893
+
894
+ //#endregion
895
+ exports.SandboxExecutorFactory = SandboxExecutorFactory;