@inkeep/agents-run-api 0.39.4 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_virtual/_raw_/home/runner/work/agents/agents/agents-run-api/templates/v1/phase1/system-prompt.js +5 -0
- package/dist/_virtual/_raw_/home/runner/work/agents/agents/agents-run-api/templates/v1/phase1/thinking-preparation.js +5 -0
- package/dist/_virtual/_raw_/home/runner/work/agents/agents/agents-run-api/templates/v1/phase1/tool.js +5 -0
- package/dist/_virtual/_raw_/home/runner/work/agents/agents/agents-run-api/templates/v1/phase2/data-component.js +5 -0
- package/dist/_virtual/_raw_/home/runner/work/agents/agents/agents-run-api/templates/v1/phase2/data-components.js +5 -0
- package/dist/_virtual/_raw_/home/runner/work/agents/agents/agents-run-api/templates/v1/phase2/system-prompt.js +5 -0
- package/dist/_virtual/_raw_/home/runner/work/agents/agents/agents-run-api/templates/v1/shared/artifact-retrieval-guidance.js +5 -0
- package/dist/_virtual/_raw_/home/runner/work/agents/agents/agents-run-api/templates/v1/shared/artifact.js +5 -0
- package/dist/a2a/client.d.ts +184 -0
- package/dist/a2a/client.js +510 -0
- package/dist/a2a/handlers.d.ts +7 -0
- package/dist/a2a/handlers.js +560 -0
- package/dist/a2a/transfer.d.ts +22 -0
- package/dist/a2a/transfer.js +46 -0
- package/dist/a2a/types.d.ts +79 -0
- package/dist/a2a/types.js +22 -0
- package/dist/agents/Agent.d.ts +266 -0
- package/dist/agents/Agent.js +1927 -0
- package/dist/agents/ModelFactory.d.ts +63 -0
- package/dist/agents/ModelFactory.js +194 -0
- package/dist/agents/SystemPromptBuilder.d.ts +21 -0
- package/dist/agents/SystemPromptBuilder.js +48 -0
- package/dist/agents/ToolSessionManager.d.ts +63 -0
- package/dist/agents/ToolSessionManager.js +146 -0
- package/dist/agents/generateTaskHandler.d.ts +49 -0
- package/dist/agents/generateTaskHandler.js +521 -0
- package/dist/agents/relationTools.d.ts +57 -0
- package/dist/agents/relationTools.js +262 -0
- package/dist/agents/types.d.ts +28 -0
- package/dist/agents/types.js +1 -0
- package/dist/agents/versions/v1/Phase1Config.d.ts +27 -0
- package/dist/agents/versions/v1/Phase1Config.js +424 -0
- package/dist/agents/versions/v1/Phase2Config.d.ts +31 -0
- package/dist/agents/versions/v1/Phase2Config.js +330 -0
- package/dist/constants/execution-limits/defaults.d.ts +51 -0
- package/dist/constants/execution-limits/defaults.js +52 -0
- package/dist/constants/execution-limits/index.d.ts +6 -0
- package/dist/constants/execution-limits/index.js +21 -0
- package/dist/create-app.d.ts +9 -0
- package/dist/create-app.js +195 -0
- package/dist/data/agent.d.ts +7 -0
- package/dist/data/agent.js +72 -0
- package/dist/data/agents.d.ts +34 -0
- package/dist/data/agents.js +139 -0
- package/dist/data/conversations.d.ts +128 -0
- package/dist/data/conversations.js +522 -0
- package/dist/data/db/dbClient.d.ts +6 -0
- package/dist/data/db/dbClient.js +17 -0
- package/dist/env.d.ts +57 -0
- package/dist/env.js +1 -2
- package/dist/handlers/executionHandler.d.ts +39 -0
- package/dist/handlers/executionHandler.js +456 -0
- package/dist/index.d.ts +8 -29
- package/dist/index.js +5 -11235
- package/dist/instrumentation.d.ts +1 -2
- package/dist/instrumentation.js +66 -3
- package/dist/{logger2.js → logger.d.ts} +1 -2
- package/dist/logger.js +1 -1
- package/dist/middleware/api-key-auth.d.ts +26 -0
- package/dist/middleware/api-key-auth.js +240 -0
- package/dist/middleware/index.d.ts +2 -0
- package/dist/middleware/index.js +3 -0
- package/dist/openapi.d.ts +4 -0
- package/dist/openapi.js +54 -0
- package/dist/routes/agents.d.ts +12 -0
- package/dist/routes/agents.js +147 -0
- package/dist/routes/chat.d.ts +13 -0
- package/dist/routes/chat.js +293 -0
- package/dist/routes/chatDataStream.d.ts +13 -0
- package/dist/routes/chatDataStream.js +352 -0
- package/dist/routes/mcp.d.ts +13 -0
- package/dist/routes/mcp.js +495 -0
- package/dist/services/AgentSession.d.ts +356 -0
- package/dist/services/AgentSession.js +1208 -0
- package/dist/services/ArtifactParser.d.ts +105 -0
- package/dist/services/ArtifactParser.js +338 -0
- package/dist/services/ArtifactService.d.ts +123 -0
- package/dist/services/ArtifactService.js +612 -0
- package/dist/services/BaseCompressor.d.ts +183 -0
- package/dist/services/BaseCompressor.js +504 -0
- package/dist/services/ConversationCompressor.d.ts +32 -0
- package/dist/services/ConversationCompressor.js +91 -0
- package/dist/services/IncrementalStreamParser.d.ts +98 -0
- package/dist/services/IncrementalStreamParser.js +327 -0
- package/dist/services/MidGenerationCompressor.d.ts +63 -0
- package/dist/services/MidGenerationCompressor.js +104 -0
- package/dist/services/PendingToolApprovalManager.d.ts +62 -0
- package/dist/services/PendingToolApprovalManager.js +133 -0
- package/dist/services/ResponseFormatter.d.ts +39 -0
- package/dist/services/ResponseFormatter.js +152 -0
- package/dist/tools/NativeSandboxExecutor.d.ts +38 -0
- package/dist/tools/NativeSandboxExecutor.js +432 -0
- package/dist/tools/SandboxExecutorFactory.d.ts +36 -0
- package/dist/tools/SandboxExecutorFactory.js +80 -0
- package/dist/tools/VercelSandboxExecutor.d.ts +71 -0
- package/dist/tools/VercelSandboxExecutor.js +340 -0
- package/dist/tools/distill-conversation-history-tool.d.ts +62 -0
- package/dist/tools/distill-conversation-history-tool.js +206 -0
- package/dist/tools/distill-conversation-tool.d.ts +41 -0
- package/dist/tools/distill-conversation-tool.js +141 -0
- package/dist/tools/sandbox-utils.d.ts +18 -0
- package/dist/tools/sandbox-utils.js +53 -0
- package/dist/types/chat.d.ts +27 -0
- package/dist/types/chat.js +1 -0
- package/dist/types/execution-context.d.ts +46 -0
- package/dist/types/execution-context.js +27 -0
- package/dist/types/xml.d.ts +5 -0
- package/dist/utils/SchemaProcessor.d.ts +52 -0
- package/dist/utils/SchemaProcessor.js +182 -0
- package/dist/utils/agent-operations.d.ts +62 -0
- package/dist/utils/agent-operations.js +53 -0
- package/dist/utils/artifact-component-schema.d.ts +42 -0
- package/dist/utils/artifact-component-schema.js +186 -0
- package/dist/utils/cleanup.d.ts +21 -0
- package/dist/utils/cleanup.js +59 -0
- package/dist/utils/data-component-schema.d.ts +2 -0
- package/dist/utils/data-component-schema.js +3 -0
- package/dist/utils/default-status-schemas.d.ts +20 -0
- package/dist/utils/default-status-schemas.js +24 -0
- package/dist/utils/json-postprocessor.d.ts +13 -0
- package/dist/{json-postprocessor.cjs → utils/json-postprocessor.js} +1 -2
- package/dist/utils/model-context-utils.d.ts +39 -0
- package/dist/utils/model-context-utils.js +181 -0
- package/dist/utils/model-resolver.d.ts +6 -0
- package/dist/utils/model-resolver.js +34 -0
- package/dist/utils/schema-validation.d.ts +44 -0
- package/dist/utils/schema-validation.js +97 -0
- package/dist/utils/stream-helpers.d.ts +197 -0
- package/dist/utils/stream-helpers.js +518 -0
- package/dist/utils/stream-registry.d.ts +22 -0
- package/dist/utils/stream-registry.js +34 -0
- package/dist/utils/token-estimator.d.ts +69 -0
- package/dist/utils/token-estimator.js +53 -0
- package/dist/utils/tracer.d.ts +7 -0
- package/dist/utils/tracer.js +7 -0
- package/package.json +5 -20
- package/dist/SandboxExecutorFactory.cjs +0 -895
- package/dist/SandboxExecutorFactory.js +0 -893
- package/dist/SandboxExecutorFactory.js.map +0 -1
- package/dist/chunk-VBDAOXYI.cjs +0 -927
- package/dist/chunk-VBDAOXYI.js +0 -832
- package/dist/chunk-VBDAOXYI.js.map +0 -1
- package/dist/chunk.cjs +0 -34
- package/dist/conversations.cjs +0 -7
- package/dist/conversations.js +0 -7
- package/dist/conversations2.cjs +0 -209
- package/dist/conversations2.js +0 -180
- package/dist/conversations2.js.map +0 -1
- package/dist/dbClient.cjs +0 -9676
- package/dist/dbClient.js +0 -9670
- package/dist/dbClient.js.map +0 -1
- package/dist/dbClient2.cjs +0 -5
- package/dist/dbClient2.js +0 -5
- package/dist/env.cjs +0 -59
- package/dist/env.js.map +0 -1
- package/dist/execution-limits.cjs +0 -260
- package/dist/execution-limits.js +0 -63
- package/dist/execution-limits.js.map +0 -1
- package/dist/index.cjs +0 -11260
- package/dist/index.d.cts +0 -36
- package/dist/index.d.cts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/instrumentation.cjs +0 -12
- package/dist/instrumentation.d.cts +0 -18
- package/dist/instrumentation.d.cts.map +0 -1
- package/dist/instrumentation.d.ts.map +0 -1
- package/dist/instrumentation2.cjs +0 -116
- package/dist/instrumentation2.js +0 -69
- package/dist/instrumentation2.js.map +0 -1
- package/dist/json-postprocessor.js +0 -20
- package/dist/json-postprocessor.js.map +0 -1
- package/dist/logger.cjs +0 -5
- package/dist/logger2.cjs +0 -1
- package/dist/nodefs.cjs +0 -29
- package/dist/nodefs.js +0 -27
- package/dist/nodefs.js.map +0 -1
- package/dist/opfs-ahp.cjs +0 -367
- package/dist/opfs-ahp.js +0 -368
- package/dist/opfs-ahp.js.map +0 -1
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
import { getLogger } from "../logger.js";
|
|
2
|
+
import { FUNCTION_TOOL_EXECUTION_TIMEOUT_MS_DEFAULT, FUNCTION_TOOL_SANDBOX_CLEANUP_INTERVAL_MS, FUNCTION_TOOL_SANDBOX_MAX_OUTPUT_SIZE_BYTES, FUNCTION_TOOL_SANDBOX_MAX_USE_COUNT, FUNCTION_TOOL_SANDBOX_POOL_TTL_MS, FUNCTION_TOOL_SANDBOX_QUEUE_WAIT_TIMEOUT_MS } from "../constants/execution-limits/index.js";
|
|
3
|
+
import { createExecutionWrapper, parseExecutionResult } from "./sandbox-utils.js";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
|
|
10
|
+
//#region src/tools/NativeSandboxExecutor.ts
|
|
11
|
+
/**
|
|
12
|
+
* NativeSandboxExecutor - Function Tool Execution Engine
|
|
13
|
+
* ========================================================
|
|
14
|
+
*
|
|
15
|
+
* Executes user-defined function tools in isolated sandboxes using native Node.js processes.
|
|
16
|
+
* The main challenge here is that we can't just eval() user code - that's a security nightmare.
|
|
17
|
+
* Instead, we spin up separate Node.js processes with their own dependency trees.
|
|
18
|
+
*
|
|
19
|
+
* The tricky part is making this fast. Installing deps every time would be brutal
|
|
20
|
+
* (2-5s per execution), so we cache sandboxes based on their dependency fingerprint.
|
|
21
|
+
*
|
|
22
|
+
* How it works:
|
|
23
|
+
*
|
|
24
|
+
* 1. User calls a function tool
|
|
25
|
+
* 2. We hash the dependencies (e.g., "axios@1.6.0,lodash@4.17.21")
|
|
26
|
+
* 3. Check if we already have a sandbox with those deps installed
|
|
27
|
+
* 4. If yes: reuse it. If no: create new one, install deps, cache it
|
|
28
|
+
* 5. Write the user's function code to a temp file
|
|
29
|
+
* 6. Execute it in the sandboxed process with resource limits
|
|
30
|
+
* 7. Return the result
|
|
31
|
+
*
|
|
32
|
+
* Sandbox lifecycle:
|
|
33
|
+
* - Created when first needed for a dependency set
|
|
34
|
+
* - Reused up to 50 times or 5 minutes, whichever comes first
|
|
35
|
+
* - Automatically cleaned up when expired
|
|
36
|
+
* - Failed sandboxes are immediately destroyed
|
|
37
|
+
*
|
|
38
|
+
* Security stuff:
|
|
39
|
+
* - Each execution runs in its own process (not just a function call)
|
|
40
|
+
* - Output limited to 1MB to prevent memory bombs
|
|
41
|
+
* - Timeouts with graceful SIGTERM, then SIGKILL if needed
|
|
42
|
+
* - Runs as non-root when possible
|
|
43
|
+
* - Uses OS temp directory so it gets cleaned up automatically
|
|
44
|
+
*
|
|
45
|
+
* Performance:
|
|
46
|
+
* - Cold start: ~100-500ms (vs 2-5s without caching)
|
|
47
|
+
* - Hot path: ~50-100ms (just execution, no install)
|
|
48
|
+
* - Memory bounded by pool size limits
|
|
49
|
+
*
|
|
50
|
+
* Deployment notes:
|
|
51
|
+
* - Uses /tmp on Linux/macOS, %TEMP% on Windows
|
|
52
|
+
* - Works in Docker, Kubernetes, serverless (Vercel, Lambda)
|
|
53
|
+
* - No files left in project directory (no git pollution)
|
|
54
|
+
*
|
|
55
|
+
* The singleton pattern here is important - we need one shared pool
|
|
56
|
+
* across all tool executions, otherwise caching doesn't work.
|
|
57
|
+
*/
|
|
58
|
+
const logger = getLogger("native-sandbox-executor");
|
|
59
|
+
/**
|
|
60
|
+
* Semaphore for limiting concurrent executions based on vCPU allocation
|
|
61
|
+
*/
|
|
62
|
+
var ExecutionSemaphore = class {
|
|
63
|
+
permits;
|
|
64
|
+
waitQueue = [];
|
|
65
|
+
maxWaitTime;
|
|
66
|
+
constructor(permits, maxWaitTimeMs = FUNCTION_TOOL_SANDBOX_QUEUE_WAIT_TIMEOUT_MS) {
|
|
67
|
+
this.permits = Math.max(1, permits);
|
|
68
|
+
this.maxWaitTime = maxWaitTimeMs;
|
|
69
|
+
}
|
|
70
|
+
async acquire(fn) {
|
|
71
|
+
await new Promise((resolve, reject) => {
|
|
72
|
+
if (this.permits > 0) {
|
|
73
|
+
this.permits--;
|
|
74
|
+
resolve();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const timeoutId = setTimeout(() => {
|
|
78
|
+
const index = this.waitQueue.findIndex((item) => item.resolve === resolve);
|
|
79
|
+
if (index !== -1) {
|
|
80
|
+
this.waitQueue.splice(index, 1);
|
|
81
|
+
reject(/* @__PURE__ */ new Error(`Function execution queue timeout after ${this.maxWaitTime}ms. Too many concurrent executions.`));
|
|
82
|
+
}
|
|
83
|
+
}, this.maxWaitTime);
|
|
84
|
+
this.waitQueue.push({
|
|
85
|
+
resolve: () => {
|
|
86
|
+
clearTimeout(timeoutId);
|
|
87
|
+
this.permits--;
|
|
88
|
+
resolve();
|
|
89
|
+
},
|
|
90
|
+
reject
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
try {
|
|
94
|
+
return await fn();
|
|
95
|
+
} finally {
|
|
96
|
+
this.permits++;
|
|
97
|
+
const next = this.waitQueue.shift();
|
|
98
|
+
if (next) next.resolve();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
getAvailablePermits() {
|
|
102
|
+
return this.permits;
|
|
103
|
+
}
|
|
104
|
+
getQueueLength() {
|
|
105
|
+
return this.waitQueue.length;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
var NativeSandboxExecutor = class NativeSandboxExecutor {
|
|
109
|
+
tempDir;
|
|
110
|
+
sandboxPool = {};
|
|
111
|
+
static instance = null;
|
|
112
|
+
executionSemaphores = /* @__PURE__ */ new Map();
|
|
113
|
+
constructor() {
|
|
114
|
+
this.tempDir = join(tmpdir(), "inkeep-sandboxes");
|
|
115
|
+
this.ensureTempDir();
|
|
116
|
+
this.startPoolCleanup();
|
|
117
|
+
}
|
|
118
|
+
static getInstance() {
|
|
119
|
+
if (!NativeSandboxExecutor.instance) NativeSandboxExecutor.instance = new NativeSandboxExecutor();
|
|
120
|
+
return NativeSandboxExecutor.instance;
|
|
121
|
+
}
|
|
122
|
+
getSemaphore(vcpus) {
|
|
123
|
+
const effectiveVcpus = Math.max(1, vcpus || 1);
|
|
124
|
+
if (!this.executionSemaphores.has(effectiveVcpus)) {
|
|
125
|
+
logger.debug({ vcpus: effectiveVcpus }, "Creating new execution semaphore");
|
|
126
|
+
this.executionSemaphores.set(effectiveVcpus, new ExecutionSemaphore(effectiveVcpus));
|
|
127
|
+
}
|
|
128
|
+
const semaphore = this.executionSemaphores.get(effectiveVcpus);
|
|
129
|
+
if (!semaphore) throw new Error(`Failed to create semaphore for ${effectiveVcpus} vCPUs`);
|
|
130
|
+
return semaphore;
|
|
131
|
+
}
|
|
132
|
+
getExecutionStats() {
|
|
133
|
+
const stats = {};
|
|
134
|
+
for (const [vcpus, semaphore] of this.executionSemaphores.entries()) stats[`vcpu_${vcpus}`] = {
|
|
135
|
+
availablePermits: semaphore.getAvailablePermits(),
|
|
136
|
+
queueLength: semaphore.getQueueLength()
|
|
137
|
+
};
|
|
138
|
+
return stats;
|
|
139
|
+
}
|
|
140
|
+
ensureTempDir() {
|
|
141
|
+
try {
|
|
142
|
+
mkdirSync(this.tempDir, { recursive: true });
|
|
143
|
+
} catch {}
|
|
144
|
+
}
|
|
145
|
+
generateDependencyHash(dependencies) {
|
|
146
|
+
const sortedDeps = Object.keys(dependencies).sort().map((key) => `${key}@${dependencies[key]}`).join(",");
|
|
147
|
+
return createHash("sha256").update(sortedDeps).digest("hex").substring(0, 16);
|
|
148
|
+
}
|
|
149
|
+
getCachedSandbox(dependencyHash) {
|
|
150
|
+
const poolKey = dependencyHash;
|
|
151
|
+
const sandbox = this.sandboxPool[poolKey];
|
|
152
|
+
if (sandbox && existsSync(sandbox.sandboxDir)) {
|
|
153
|
+
const now = Date.now();
|
|
154
|
+
if (now - sandbox.lastUsed < FUNCTION_TOOL_SANDBOX_POOL_TTL_MS && sandbox.useCount < FUNCTION_TOOL_SANDBOX_MAX_USE_COUNT) {
|
|
155
|
+
sandbox.lastUsed = now;
|
|
156
|
+
sandbox.useCount++;
|
|
157
|
+
logger.debug({
|
|
158
|
+
poolKey,
|
|
159
|
+
useCount: sandbox.useCount,
|
|
160
|
+
sandboxDir: sandbox.sandboxDir,
|
|
161
|
+
lastUsed: new Date(sandbox.lastUsed)
|
|
162
|
+
}, "Reusing cached sandbox");
|
|
163
|
+
return sandbox.sandboxDir;
|
|
164
|
+
}
|
|
165
|
+
this.cleanupSandbox(sandbox.sandboxDir);
|
|
166
|
+
delete this.sandboxPool[poolKey];
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
addToPool(dependencyHash, sandboxDir, dependencies) {
|
|
171
|
+
const poolKey = dependencyHash;
|
|
172
|
+
if (this.sandboxPool[poolKey]) this.cleanupSandbox(this.sandboxPool[poolKey].sandboxDir);
|
|
173
|
+
this.sandboxPool[poolKey] = {
|
|
174
|
+
sandboxDir,
|
|
175
|
+
lastUsed: Date.now(),
|
|
176
|
+
useCount: 1,
|
|
177
|
+
dependencies
|
|
178
|
+
};
|
|
179
|
+
logger.debug({
|
|
180
|
+
poolKey,
|
|
181
|
+
sandboxDir
|
|
182
|
+
}, "Added sandbox to pool");
|
|
183
|
+
}
|
|
184
|
+
cleanupSandbox(sandboxDir) {
|
|
185
|
+
try {
|
|
186
|
+
rmSync(sandboxDir, {
|
|
187
|
+
recursive: true,
|
|
188
|
+
force: true
|
|
189
|
+
});
|
|
190
|
+
logger.debug({ sandboxDir }, "Cleaned up sandbox");
|
|
191
|
+
} catch (error) {
|
|
192
|
+
logger.warn({
|
|
193
|
+
sandboxDir,
|
|
194
|
+
error
|
|
195
|
+
}, "Failed to clean up sandbox");
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
startPoolCleanup() {
|
|
199
|
+
setInterval(() => {
|
|
200
|
+
const now = Date.now();
|
|
201
|
+
const keysToDelete = [];
|
|
202
|
+
for (const [key, sandbox] of Object.entries(this.sandboxPool)) if (now - sandbox.lastUsed > FUNCTION_TOOL_SANDBOX_POOL_TTL_MS || sandbox.useCount >= FUNCTION_TOOL_SANDBOX_MAX_USE_COUNT) {
|
|
203
|
+
this.cleanupSandbox(sandbox.sandboxDir);
|
|
204
|
+
keysToDelete.push(key);
|
|
205
|
+
}
|
|
206
|
+
keysToDelete.forEach((key) => {
|
|
207
|
+
delete this.sandboxPool[key];
|
|
208
|
+
});
|
|
209
|
+
if (keysToDelete.length > 0) logger.debug({ cleanedCount: keysToDelete.length }, "Cleaned up expired sandboxes");
|
|
210
|
+
}, FUNCTION_TOOL_SANDBOX_CLEANUP_INTERVAL_MS);
|
|
211
|
+
}
|
|
212
|
+
detectModuleType(executeCode, configuredRuntime) {
|
|
213
|
+
const esmPatterns = [
|
|
214
|
+
/import\s+.*\s+from\s+['"]/g,
|
|
215
|
+
/import\s*\(/g,
|
|
216
|
+
/export\s+(default|const|let|var|function|class)/g,
|
|
217
|
+
/export\s*\{/g
|
|
218
|
+
];
|
|
219
|
+
const cjsPatterns = [
|
|
220
|
+
/require\s*\(/g,
|
|
221
|
+
/module\.exports/g,
|
|
222
|
+
/exports\./g
|
|
223
|
+
];
|
|
224
|
+
const hasEsmSyntax = esmPatterns.some((pattern) => pattern.test(executeCode));
|
|
225
|
+
const hasCjsSyntax = cjsPatterns.some((pattern) => pattern.test(executeCode));
|
|
226
|
+
if (configuredRuntime === "typescript") return hasCjsSyntax ? "cjs" : "esm";
|
|
227
|
+
if (hasEsmSyntax && hasCjsSyntax) {
|
|
228
|
+
logger.warn({ executeCode: `${executeCode.substring(0, 100)}...` }, "Both ESM and CommonJS syntax detected, defaulting to ESM");
|
|
229
|
+
return "esm";
|
|
230
|
+
}
|
|
231
|
+
if (hasEsmSyntax) return "esm";
|
|
232
|
+
if (hasCjsSyntax) return "cjs";
|
|
233
|
+
return "cjs";
|
|
234
|
+
}
|
|
235
|
+
async executeFunctionTool(toolId, args, config) {
|
|
236
|
+
const vcpus = config.sandboxConfig?.vcpus || 1;
|
|
237
|
+
const semaphore = this.getSemaphore(vcpus);
|
|
238
|
+
logger.debug({
|
|
239
|
+
toolId,
|
|
240
|
+
vcpus,
|
|
241
|
+
availablePermits: semaphore.getAvailablePermits(),
|
|
242
|
+
queueLength: semaphore.getQueueLength(),
|
|
243
|
+
sandboxConfig: config.sandboxConfig,
|
|
244
|
+
poolSize: Object.keys(this.sandboxPool).length
|
|
245
|
+
}, "Acquiring execution slot for function tool");
|
|
246
|
+
return semaphore.acquire(async () => {
|
|
247
|
+
return this.executeInSandbox_Internal(toolId, args, config);
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
async executeInSandbox_Internal(toolId, args, config) {
|
|
251
|
+
const dependencies = config.dependencies || {};
|
|
252
|
+
const dependencyHash = this.generateDependencyHash(dependencies);
|
|
253
|
+
logger.debug({
|
|
254
|
+
toolId,
|
|
255
|
+
dependencies,
|
|
256
|
+
dependencyHash,
|
|
257
|
+
sandboxConfig: config.sandboxConfig,
|
|
258
|
+
poolSize: Object.keys(this.sandboxPool).length
|
|
259
|
+
}, "Executing function tool");
|
|
260
|
+
let sandboxDir = this.getCachedSandbox(dependencyHash);
|
|
261
|
+
let isNewSandbox = false;
|
|
262
|
+
if (!sandboxDir) {
|
|
263
|
+
sandboxDir = join(this.tempDir, `sandbox-${dependencyHash}-${Date.now()}`);
|
|
264
|
+
mkdirSync(sandboxDir, { recursive: true });
|
|
265
|
+
isNewSandbox = true;
|
|
266
|
+
logger.debug({
|
|
267
|
+
toolId,
|
|
268
|
+
dependencyHash,
|
|
269
|
+
sandboxDir,
|
|
270
|
+
dependencies
|
|
271
|
+
}, "Creating new sandbox");
|
|
272
|
+
const moduleType = this.detectModuleType(config.executeCode, config.sandboxConfig?.runtime);
|
|
273
|
+
const packageJson = {
|
|
274
|
+
name: `function-tool-${toolId}`,
|
|
275
|
+
version: "1.0.0",
|
|
276
|
+
...moduleType === "esm" && { type: "module" },
|
|
277
|
+
dependencies,
|
|
278
|
+
scripts: { start: moduleType === "esm" ? "node index.mjs" : "node index.js" }
|
|
279
|
+
};
|
|
280
|
+
writeFileSync(join(sandboxDir, "package.json"), JSON.stringify(packageJson, null, 2), "utf8");
|
|
281
|
+
if (Object.keys(dependencies).length > 0) await this.installDependencies(sandboxDir);
|
|
282
|
+
this.addToPool(dependencyHash, sandboxDir, dependencies);
|
|
283
|
+
}
|
|
284
|
+
try {
|
|
285
|
+
const moduleType = this.detectModuleType(config.executeCode, config.sandboxConfig?.runtime);
|
|
286
|
+
const executionCode = createExecutionWrapper(config.executeCode, args);
|
|
287
|
+
writeFileSync(join(sandboxDir, `index.${moduleType === "esm" ? "mjs" : "js"}`), executionCode, "utf8");
|
|
288
|
+
return await this.executeInSandbox(sandboxDir, config.sandboxConfig?.timeout || FUNCTION_TOOL_EXECUTION_TIMEOUT_MS_DEFAULT, moduleType, config.sandboxConfig);
|
|
289
|
+
} catch (error) {
|
|
290
|
+
if (isNewSandbox) {
|
|
291
|
+
this.cleanupSandbox(sandboxDir);
|
|
292
|
+
const poolKey = dependencyHash;
|
|
293
|
+
delete this.sandboxPool[poolKey];
|
|
294
|
+
}
|
|
295
|
+
throw error;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
async installDependencies(sandboxDir) {
|
|
299
|
+
return new Promise((resolve, reject) => {
|
|
300
|
+
const npm = spawn("npm", [
|
|
301
|
+
"install",
|
|
302
|
+
"--no-audit",
|
|
303
|
+
"--no-fund"
|
|
304
|
+
], {
|
|
305
|
+
cwd: sandboxDir,
|
|
306
|
+
stdio: "pipe",
|
|
307
|
+
env: {
|
|
308
|
+
...process.env,
|
|
309
|
+
npm_config_cache: join(sandboxDir, ".npm-cache"),
|
|
310
|
+
npm_config_logs_dir: join(sandboxDir, ".npm-logs"),
|
|
311
|
+
npm_config_tmp: join(sandboxDir, ".npm-tmp"),
|
|
312
|
+
HOME: sandboxDir,
|
|
313
|
+
npm_config_update_notifier: "false",
|
|
314
|
+
npm_config_progress: "false",
|
|
315
|
+
npm_config_loglevel: "error"
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
let stderr = "";
|
|
319
|
+
npm.stdout?.on("data", () => {});
|
|
320
|
+
npm.stderr?.on("data", (data) => {
|
|
321
|
+
stderr += data.toString();
|
|
322
|
+
});
|
|
323
|
+
npm.on("close", (code) => {
|
|
324
|
+
if (code === 0) {
|
|
325
|
+
logger.debug({ sandboxDir }, "Dependencies installed successfully");
|
|
326
|
+
resolve();
|
|
327
|
+
} else {
|
|
328
|
+
logger.error({
|
|
329
|
+
sandboxDir,
|
|
330
|
+
code,
|
|
331
|
+
stderr
|
|
332
|
+
}, "Failed to install dependencies");
|
|
333
|
+
reject(/* @__PURE__ */ new Error(`npm install failed with code ${code}: ${stderr}`));
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
npm.on("error", (err) => {
|
|
337
|
+
logger.error({
|
|
338
|
+
sandboxDir,
|
|
339
|
+
error: err
|
|
340
|
+
}, "Failed to spawn npm install");
|
|
341
|
+
reject(err);
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
async executeInSandbox(sandboxDir, timeout, moduleType, _sandboxConfig) {
|
|
346
|
+
return new Promise((resolve, reject) => {
|
|
347
|
+
const fileExtension = moduleType === "esm" ? "mjs" : "js";
|
|
348
|
+
const spawnOptions = {
|
|
349
|
+
cwd: sandboxDir,
|
|
350
|
+
stdio: "pipe",
|
|
351
|
+
uid: process.getuid ? process.getuid() : void 0,
|
|
352
|
+
gid: process.getgid ? process.getgid() : void 0
|
|
353
|
+
};
|
|
354
|
+
const node = spawn("node", [`index.${fileExtension}`], spawnOptions);
|
|
355
|
+
let stdout = "";
|
|
356
|
+
let stderr = "";
|
|
357
|
+
let outputSize = 0;
|
|
358
|
+
node.stdout?.on("data", (data) => {
|
|
359
|
+
const dataStr = data.toString();
|
|
360
|
+
outputSize += dataStr.length;
|
|
361
|
+
if (outputSize > FUNCTION_TOOL_SANDBOX_MAX_OUTPUT_SIZE_BYTES) {
|
|
362
|
+
node.kill("SIGTERM");
|
|
363
|
+
reject(/* @__PURE__ */ new Error(`Output size exceeded limit of ${FUNCTION_TOOL_SANDBOX_MAX_OUTPUT_SIZE_BYTES} bytes`));
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
stdout += dataStr;
|
|
367
|
+
});
|
|
368
|
+
node.stderr?.on("data", (data) => {
|
|
369
|
+
const dataStr = data.toString();
|
|
370
|
+
outputSize += dataStr.length;
|
|
371
|
+
if (outputSize > FUNCTION_TOOL_SANDBOX_MAX_OUTPUT_SIZE_BYTES) {
|
|
372
|
+
node.kill("SIGTERM");
|
|
373
|
+
reject(/* @__PURE__ */ new Error(`Output size exceeded limit of ${FUNCTION_TOOL_SANDBOX_MAX_OUTPUT_SIZE_BYTES} bytes`));
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
stderr += dataStr;
|
|
377
|
+
});
|
|
378
|
+
const timeoutId = setTimeout(() => {
|
|
379
|
+
logger.warn({
|
|
380
|
+
sandboxDir,
|
|
381
|
+
timeout
|
|
382
|
+
}, "Function execution timed out, killing process");
|
|
383
|
+
node.kill("SIGTERM");
|
|
384
|
+
const forceKillTimeout = Math.min(Math.max(timeout / 10, 2e3), 5e3);
|
|
385
|
+
setTimeout(() => {
|
|
386
|
+
try {
|
|
387
|
+
node.kill("SIGKILL");
|
|
388
|
+
} catch {}
|
|
389
|
+
}, forceKillTimeout);
|
|
390
|
+
reject(/* @__PURE__ */ new Error(`Function execution timed out after ${timeout}ms`));
|
|
391
|
+
}, timeout);
|
|
392
|
+
node.on("close", (code, signal) => {
|
|
393
|
+
clearTimeout(timeoutId);
|
|
394
|
+
if (code === 0) try {
|
|
395
|
+
const result = parseExecutionResult(stdout, "function", logger);
|
|
396
|
+
if (typeof result === "object" && result !== null && "success" in result) {
|
|
397
|
+
const parsed = result;
|
|
398
|
+
if (parsed.success) resolve(parsed.result);
|
|
399
|
+
else reject(new Error(parsed.error || "Function execution failed"));
|
|
400
|
+
} else resolve(result);
|
|
401
|
+
} catch (parseError) {
|
|
402
|
+
logger.error({
|
|
403
|
+
stdout,
|
|
404
|
+
stderr,
|
|
405
|
+
parseError
|
|
406
|
+
}, "Failed to parse function result");
|
|
407
|
+
reject(/* @__PURE__ */ new Error(`Invalid function result: ${stdout}`));
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
const errorMsg = signal ? `Function execution killed by signal ${signal}: ${stderr}` : `Function execution failed with code ${code}: ${stderr}`;
|
|
411
|
+
logger.error({
|
|
412
|
+
code,
|
|
413
|
+
signal,
|
|
414
|
+
stderr
|
|
415
|
+
}, "Function execution failed");
|
|
416
|
+
reject(new Error(errorMsg));
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
node.on("error", (error) => {
|
|
420
|
+
clearTimeout(timeoutId);
|
|
421
|
+
logger.error({
|
|
422
|
+
sandboxDir,
|
|
423
|
+
error
|
|
424
|
+
}, "Failed to spawn node process");
|
|
425
|
+
reject(error);
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
//#endregion
|
|
432
|
+
export { NativeSandboxExecutor };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { FunctionToolConfig } from "./NativeSandboxExecutor.js";
|
|
2
|
+
|
|
3
|
+
//#region src/tools/SandboxExecutorFactory.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Factory for creating and managing sandbox executors
|
|
7
|
+
* Routes execution to the appropriate sandbox provider (native or Vercel)
|
|
8
|
+
*/
|
|
9
|
+
declare class SandboxExecutorFactory {
|
|
10
|
+
private static instance;
|
|
11
|
+
private nativeExecutor;
|
|
12
|
+
private vercelExecutors;
|
|
13
|
+
private constructor();
|
|
14
|
+
/**
|
|
15
|
+
* Get singleton instance of SandboxExecutorFactory
|
|
16
|
+
*/
|
|
17
|
+
static getInstance(): SandboxExecutorFactory;
|
|
18
|
+
/**
|
|
19
|
+
* Execute a function tool using the appropriate sandbox provider
|
|
20
|
+
*/
|
|
21
|
+
executeFunctionTool(functionId: string, args: Record<string, unknown>, config: FunctionToolConfig): Promise<unknown>;
|
|
22
|
+
/**
|
|
23
|
+
* Execute in native sandbox
|
|
24
|
+
*/
|
|
25
|
+
private executeInNativeSandbox;
|
|
26
|
+
/**
|
|
27
|
+
* Execute in Vercel sandbox
|
|
28
|
+
*/
|
|
29
|
+
private executeInVercelSandbox;
|
|
30
|
+
/**
|
|
31
|
+
* Clean up all sandbox executors
|
|
32
|
+
*/
|
|
33
|
+
cleanup(): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { SandboxExecutorFactory };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { getLogger } from "../logger.js";
|
|
2
|
+
import { NativeSandboxExecutor } from "./NativeSandboxExecutor.js";
|
|
3
|
+
import { VercelSandboxExecutor } from "./VercelSandboxExecutor.js";
|
|
4
|
+
|
|
5
|
+
//#region src/tools/SandboxExecutorFactory.ts
|
|
6
|
+
const logger = getLogger("SandboxExecutorFactory");
|
|
7
|
+
/**
|
|
8
|
+
* Factory for creating and managing sandbox executors
|
|
9
|
+
* Routes execution to the appropriate sandbox provider (native or Vercel)
|
|
10
|
+
*/
|
|
11
|
+
var SandboxExecutorFactory = class SandboxExecutorFactory {
|
|
12
|
+
static instance;
|
|
13
|
+
nativeExecutor = null;
|
|
14
|
+
vercelExecutors = /* @__PURE__ */ new Map();
|
|
15
|
+
constructor() {
|
|
16
|
+
logger.info({}, "SandboxExecutorFactory initialized");
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Get singleton instance of SandboxExecutorFactory
|
|
20
|
+
*/
|
|
21
|
+
static getInstance() {
|
|
22
|
+
if (!SandboxExecutorFactory.instance) SandboxExecutorFactory.instance = new SandboxExecutorFactory();
|
|
23
|
+
return SandboxExecutorFactory.instance;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Execute a function tool using the appropriate sandbox provider
|
|
27
|
+
*/
|
|
28
|
+
async executeFunctionTool(functionId, args, config) {
|
|
29
|
+
const sandboxConfig = config.sandboxConfig;
|
|
30
|
+
if (!sandboxConfig) throw new Error("Sandbox configuration is required for function tool execution");
|
|
31
|
+
if (sandboxConfig.provider === "native") return this.executeInNativeSandbox(functionId, args, config);
|
|
32
|
+
if (sandboxConfig.provider === "vercel") return this.executeInVercelSandbox(functionId, args, config);
|
|
33
|
+
throw new Error(`Unknown sandbox provider: ${sandboxConfig.provider}`);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Execute in native sandbox
|
|
37
|
+
*/
|
|
38
|
+
async executeInNativeSandbox(functionId, args, config) {
|
|
39
|
+
if (!this.nativeExecutor) {
|
|
40
|
+
this.nativeExecutor = NativeSandboxExecutor.getInstance();
|
|
41
|
+
logger.info({}, "Native sandbox executor created");
|
|
42
|
+
}
|
|
43
|
+
return this.nativeExecutor.executeFunctionTool(functionId, args, config);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Execute in Vercel sandbox
|
|
47
|
+
*/
|
|
48
|
+
async executeInVercelSandbox(functionId, args, config) {
|
|
49
|
+
const vercelConfig = config.sandboxConfig;
|
|
50
|
+
const configKey = `${vercelConfig.teamId}:${vercelConfig.projectId}`;
|
|
51
|
+
if (!this.vercelExecutors.has(configKey)) {
|
|
52
|
+
const executor$1 = VercelSandboxExecutor.getInstance(vercelConfig);
|
|
53
|
+
this.vercelExecutors.set(configKey, executor$1);
|
|
54
|
+
logger.info({
|
|
55
|
+
teamId: vercelConfig.teamId,
|
|
56
|
+
projectId: vercelConfig.projectId
|
|
57
|
+
}, "Vercel sandbox executor created");
|
|
58
|
+
}
|
|
59
|
+
const executor = this.vercelExecutors.get(configKey);
|
|
60
|
+
if (!executor) throw new Error(`Failed to get Vercel executor for config: ${configKey}`);
|
|
61
|
+
const result = await executor.executeFunctionTool(functionId, args, config);
|
|
62
|
+
if (!result.success) throw new Error(result.error || "Vercel sandbox execution failed");
|
|
63
|
+
return result.result;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Clean up all sandbox executors
|
|
67
|
+
*/
|
|
68
|
+
async cleanup() {
|
|
69
|
+
logger.info({}, "Cleaning up sandbox executors");
|
|
70
|
+
this.nativeExecutor = null;
|
|
71
|
+
for (const [key, executor] of this.vercelExecutors.entries()) {
|
|
72
|
+
await executor.cleanup();
|
|
73
|
+
this.vercelExecutors.delete(key);
|
|
74
|
+
}
|
|
75
|
+
logger.info({}, "Sandbox executor cleanup completed");
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
//#endregion
|
|
80
|
+
export { SandboxExecutorFactory };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { VercelSandboxConfig } from "../types/execution-context.js";
|
|
2
|
+
import { FunctionToolConfig } from "./NativeSandboxExecutor.js";
|
|
3
|
+
|
|
4
|
+
//#region src/tools/VercelSandboxExecutor.d.ts
|
|
5
|
+
interface ExecutionResult {
|
|
6
|
+
success: boolean;
|
|
7
|
+
result?: unknown;
|
|
8
|
+
error?: string;
|
|
9
|
+
logs?: string[];
|
|
10
|
+
executionTime?: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Vercel Sandbox Executor with pooling/reuse
|
|
14
|
+
* Executes function tools in isolated Vercel Sandbox MicroVMs
|
|
15
|
+
* Caches and reuses sandboxes based on dependencies to improve performance
|
|
16
|
+
*/
|
|
17
|
+
declare class VercelSandboxExecutor {
|
|
18
|
+
private static instance;
|
|
19
|
+
private config;
|
|
20
|
+
private sandboxPool;
|
|
21
|
+
private cleanupInterval;
|
|
22
|
+
private constructor();
|
|
23
|
+
/**
|
|
24
|
+
* Get singleton instance of VercelSandboxExecutor
|
|
25
|
+
*/
|
|
26
|
+
static getInstance(config: VercelSandboxConfig): VercelSandboxExecutor;
|
|
27
|
+
/**
|
|
28
|
+
* Generate a hash for dependencies to use as cache key
|
|
29
|
+
*/
|
|
30
|
+
private generateDependencyHash;
|
|
31
|
+
/**
|
|
32
|
+
* Get a cached sandbox if available and still valid
|
|
33
|
+
*/
|
|
34
|
+
private getCachedSandbox;
|
|
35
|
+
/**
|
|
36
|
+
* Add sandbox to pool
|
|
37
|
+
*/
|
|
38
|
+
private addToPool;
|
|
39
|
+
/**
|
|
40
|
+
* Increment use count for a sandbox
|
|
41
|
+
*/
|
|
42
|
+
private incrementUseCount;
|
|
43
|
+
/**
|
|
44
|
+
* Remove and clean up a sandbox
|
|
45
|
+
*/
|
|
46
|
+
private removeSandbox;
|
|
47
|
+
/**
|
|
48
|
+
* Start periodic cleanup of expired sandboxes
|
|
49
|
+
*/
|
|
50
|
+
private startPoolCleanup;
|
|
51
|
+
/**
|
|
52
|
+
* Cleanup all sandboxes and stop cleanup interval
|
|
53
|
+
*/
|
|
54
|
+
cleanup(): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Extract environment variable names from code
|
|
57
|
+
* Matches patterns like process.env.VAR_NAME or process.env['VAR_NAME']
|
|
58
|
+
*/
|
|
59
|
+
private extractEnvVars;
|
|
60
|
+
/**
|
|
61
|
+
* Create .env file content from environment variables
|
|
62
|
+
* Note: Currently creates empty placeholders. Values will be populated in the future.
|
|
63
|
+
*/
|
|
64
|
+
private createEnvFileContent;
|
|
65
|
+
/**
|
|
66
|
+
* Execute a function tool in Vercel Sandbox with pooling
|
|
67
|
+
*/
|
|
68
|
+
executeFunctionTool(functionId: string, args: Record<string, unknown>, toolConfig: FunctionToolConfig): Promise<ExecutionResult>;
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
export { ExecutionResult, VercelSandboxExecutor };
|