zeitlich 0.2.40 → 0.2.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -1
- package/dist/{activities-CvUrG3YG.d.cts → activities-Coafq5zr.d.cts} +2 -2
- package/dist/{activities-CULxRzJ1.d.ts → activities-CrN-ghLo.d.ts} +2 -2
- package/dist/adapters/sandbox/daytona/index.cjs +4 -23
- package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/index.d.cts +18 -86
- package/dist/adapters/sandbox/daytona/index.d.ts +18 -86
- package/dist/adapters/sandbox/daytona/index.js +4 -23
- package/dist/adapters/sandbox/daytona/index.js.map +1 -1
- package/dist/adapters/sandbox/daytona/workflow.cjs +1 -7
- package/dist/adapters/sandbox/daytona/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/workflow.d.cts +9 -2
- package/dist/adapters/sandbox/daytona/workflow.d.ts +9 -2
- package/dist/adapters/sandbox/daytona/workflow.js +1 -7
- package/dist/adapters/sandbox/daytona/workflow.js.map +1 -1
- package/dist/adapters/sandbox/e2b/index.cjs +21 -3
- package/dist/adapters/sandbox/e2b/index.cjs.map +1 -1
- package/dist/adapters/sandbox/e2b/index.d.cts +48 -7
- package/dist/adapters/sandbox/e2b/index.d.ts +48 -7
- package/dist/adapters/sandbox/e2b/index.js +22 -5
- package/dist/adapters/sandbox/e2b/index.js.map +1 -1
- package/dist/adapters/sandbox/e2b/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/e2b/workflow.d.cts +4 -2
- package/dist/adapters/sandbox/e2b/workflow.d.ts +4 -2
- package/dist/adapters/sandbox/e2b/workflow.js.map +1 -1
- package/dist/adapters/sandbox/inmemory/index.cjs +11 -0
- package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
- package/dist/adapters/sandbox/inmemory/index.d.cts +11 -3
- package/dist/adapters/sandbox/inmemory/index.d.ts +11 -3
- package/dist/adapters/sandbox/inmemory/index.js +11 -1
- package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.d.cts +4 -2
- package/dist/adapters/sandbox/inmemory/workflow.d.ts +4 -2
- package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -1
- package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
- package/dist/adapters/thread/anthropic/index.d.cts +6 -6
- package/dist/adapters/thread/anthropic/index.d.ts +6 -6
- package/dist/adapters/thread/anthropic/index.js.map +1 -1
- package/dist/adapters/thread/anthropic/workflow.d.cts +6 -6
- package/dist/adapters/thread/anthropic/workflow.d.ts +6 -6
- package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
- package/dist/adapters/thread/google-genai/index.d.cts +6 -6
- package/dist/adapters/thread/google-genai/index.d.ts +6 -6
- package/dist/adapters/thread/google-genai/index.js.map +1 -1
- package/dist/adapters/thread/google-genai/workflow.d.cts +6 -6
- package/dist/adapters/thread/google-genai/workflow.d.ts +6 -6
- package/dist/adapters/thread/langchain/index.cjs.map +1 -1
- package/dist/adapters/thread/langchain/index.d.cts +6 -6
- package/dist/adapters/thread/langchain/index.d.ts +6 -6
- package/dist/adapters/thread/langchain/index.js.map +1 -1
- package/dist/adapters/thread/langchain/workflow.d.cts +6 -6
- package/dist/adapters/thread/langchain/workflow.d.ts +6 -6
- package/dist/index.cjs +316 -119
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +93 -17
- package/dist/index.d.ts +93 -17
- package/dist/index.js +317 -120
- package/dist/index.js.map +1 -1
- package/dist/{proxy-5EbwzaY4.d.cts → proxy-Bf7uI-Hw.d.cts} +1 -1
- package/dist/{proxy-wZufFfBh.d.ts → proxy-COqA95FW.d.ts} +1 -1
- package/dist/{thread-manager-BqBAIsED.d.ts → thread-manager-BhkOyQ1I.d.ts} +2 -2
- package/dist/{thread-manager-BNiIt5r8.d.ts → thread-manager-Bi1XlbpJ.d.ts} +2 -2
- package/dist/{thread-manager-DF8WuCRs.d.cts → thread-manager-BsLO3Fgc.d.cts} +2 -2
- package/dist/{thread-manager-BoN5DOvG.d.cts → thread-manager-wRVVBFgj.d.cts} +2 -2
- package/dist/{types-C7OoY7h8.d.ts → types-BkX4HLzi.d.ts} +1 -1
- package/dist/{types-CuISs0Ub.d.cts → types-C66-BVBr.d.cts} +1 -1
- package/dist/types-CJ7tCdl6.d.cts +266 -0
- package/dist/types-CJ7tCdl6.d.ts +266 -0
- package/dist/{types-DeQH84C_.d.ts → types-CdALEF3z.d.cts} +342 -23
- package/dist/{types-Cn2r3ol3.d.cts → types-ChAy_jSP.d.ts} +342 -23
- package/dist/types-CjY93AWZ.d.cts +84 -0
- package/dist/types-gVa5XCWD.d.ts +84 -0
- package/dist/{workflow-DhplIN65.d.cts → workflow-BwT5EybR.d.ts} +7 -6
- package/dist/{workflow-C2MZZj5K.d.ts → workflow-DMmiaw6w.d.cts} +7 -6
- package/dist/workflow.cjs +138 -77
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +4 -4
- package/dist/workflow.d.ts +4 -4
- package/dist/workflow.js +139 -78
- package/dist/workflow.js.map +1 -1
- package/package.json +17 -33
- package/src/adapters/sandbox/daytona/index.ts +25 -48
- package/src/adapters/sandbox/daytona/proxy.ts +7 -8
- package/src/adapters/sandbox/e2b/README.md +81 -0
- package/src/adapters/sandbox/e2b/index.ts +53 -11
- package/src/adapters/sandbox/e2b/keep-alive.test.ts +115 -0
- package/src/adapters/sandbox/e2b/proxy.ts +3 -2
- package/src/adapters/sandbox/e2b/types.ts +34 -2
- package/src/adapters/sandbox/inmemory/index.ts +21 -1
- package/src/adapters/sandbox/inmemory/proxy.ts +7 -3
- package/src/index.ts +1 -1
- package/src/lib/activity.ts +5 -0
- package/src/lib/sandbox/capability-types.test.ts +859 -0
- package/src/lib/sandbox/index.ts +1 -0
- package/src/lib/sandbox/manager.ts +187 -31
- package/src/lib/sandbox/types.ts +189 -46
- package/src/lib/session/index.ts +1 -0
- package/src/lib/session/session.integration.test.ts +58 -0
- package/src/lib/session/session.ts +109 -50
- package/src/lib/session/types.ts +189 -8
- package/src/lib/subagent/handler.ts +66 -43
- package/src/lib/subagent/subagent.integration.test.ts +2 -0
- package/src/lib/subagent/types.ts +492 -16
- package/src/lib/subagent/workflow.ts +11 -1
- package/src/lib/tool-router/auto-append-sandbox.integration.test.ts +158 -0
- package/src/lib/tool-router/index.ts +1 -1
- package/src/lib/tool-router/with-sandbox.ts +45 -2
- package/src/lib/virtual-fs/filesystem.ts +41 -16
- package/src/lib/virtual-fs/types.ts +19 -0
- package/src/lib/virtual-fs/virtual-fs.test.ts +204 -1
- package/src/tools/read-file/handler.test.ts +83 -0
- package/src/workflow.ts +3 -0
- package/tsup.config.ts +0 -4
- package/dist/adapters/sandbox/bedrock/index.cjs +0 -457
- package/dist/adapters/sandbox/bedrock/index.cjs.map +0 -1
- package/dist/adapters/sandbox/bedrock/index.d.cts +0 -25
- package/dist/adapters/sandbox/bedrock/index.d.ts +0 -25
- package/dist/adapters/sandbox/bedrock/index.js +0 -454
- package/dist/adapters/sandbox/bedrock/index.js.map +0 -1
- package/dist/adapters/sandbox/bedrock/workflow.cjs +0 -36
- package/dist/adapters/sandbox/bedrock/workflow.cjs.map +0 -1
- package/dist/adapters/sandbox/bedrock/workflow.d.cts +0 -29
- package/dist/adapters/sandbox/bedrock/workflow.d.ts +0 -29
- package/dist/adapters/sandbox/bedrock/workflow.js +0 -34
- package/dist/adapters/sandbox/bedrock/workflow.js.map +0 -1
- package/dist/types-DAsQ21Rt.d.ts +0 -74
- package/dist/types-lm8tMNJQ.d.cts +0 -74
- package/dist/types-yx0LzPGn.d.cts +0 -173
- package/dist/types-yx0LzPGn.d.ts +0 -173
- package/src/adapters/sandbox/bedrock/filesystem.ts +0 -340
- package/src/adapters/sandbox/bedrock/index.ts +0 -274
- package/src/adapters/sandbox/bedrock/proxy.ts +0 -59
- package/src/adapters/sandbox/bedrock/types.ts +0 -24
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zeitlich",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.42",
|
|
4
4
|
"description": "[EXPERIMENTAL] An opinionated AI agent implementation for Temporal",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -156,26 +156,6 @@
|
|
|
156
156
|
"types": "./dist/adapters/sandbox/e2b/workflow.d.ts",
|
|
157
157
|
"default": "./dist/adapters/sandbox/e2b/workflow.js"
|
|
158
158
|
}
|
|
159
|
-
},
|
|
160
|
-
"./adapters/sandbox/bedrock": {
|
|
161
|
-
"import": {
|
|
162
|
-
"types": "./dist/adapters/sandbox/bedrock/index.d.ts",
|
|
163
|
-
"default": "./dist/adapters/sandbox/bedrock/index.js"
|
|
164
|
-
},
|
|
165
|
-
"require": {
|
|
166
|
-
"types": "./dist/adapters/sandbox/bedrock/index.d.ts",
|
|
167
|
-
"default": "./dist/adapters/sandbox/bedrock/index.js"
|
|
168
|
-
}
|
|
169
|
-
},
|
|
170
|
-
"./adapters/sandbox/bedrock/workflow": {
|
|
171
|
-
"import": {
|
|
172
|
-
"types": "./dist/adapters/sandbox/bedrock/workflow.d.ts",
|
|
173
|
-
"default": "./dist/adapters/sandbox/bedrock/workflow.js"
|
|
174
|
-
},
|
|
175
|
-
"require": {
|
|
176
|
-
"types": "./dist/adapters/sandbox/bedrock/workflow.d.ts",
|
|
177
|
-
"default": "./dist/adapters/sandbox/bedrock/workflow.js"
|
|
178
|
-
}
|
|
179
159
|
}
|
|
180
160
|
},
|
|
181
161
|
"files": [
|
|
@@ -220,15 +200,16 @@
|
|
|
220
200
|
"node": ">=18"
|
|
221
201
|
},
|
|
222
202
|
"devDependencies": {
|
|
223
|
-
"@anthropic-ai/sdk": "^0.
|
|
224
|
-
"@
|
|
225
|
-
"@daytonaio/sdk": "^0.158.1",
|
|
203
|
+
"@anthropic-ai/sdk": "^0.93.0",
|
|
204
|
+
"@daytonaio/sdk": "^0.171.0",
|
|
226
205
|
"@e2b/code-interpreter": "^2.3.3",
|
|
227
206
|
"@eslint/js": "^10.0.1",
|
|
228
207
|
"@google/genai": "^1.44.0",
|
|
229
208
|
"@langchain/core": "^1.1.30",
|
|
230
|
-
"@temporalio/
|
|
231
|
-
"@temporalio/
|
|
209
|
+
"@temporalio/common": "^1.17.0",
|
|
210
|
+
"@temporalio/envconfig": "^1.17.0",
|
|
211
|
+
"@temporalio/worker": "^1.17.0",
|
|
212
|
+
"@temporalio/workflow": "^1.17.0",
|
|
232
213
|
"@types/node": "^25.3.3",
|
|
233
214
|
"eslint": "^10.0.2",
|
|
234
215
|
"husky": "^9.1.7",
|
|
@@ -242,18 +223,18 @@
|
|
|
242
223
|
},
|
|
243
224
|
"peerDependencies": {
|
|
244
225
|
"@anthropic-ai/sdk": ">=0.50.0",
|
|
245
|
-
"@aws-sdk/client-bedrock-agentcore": "^3.900.0",
|
|
246
226
|
"@daytonaio/sdk": ">=0.153.0",
|
|
247
227
|
"@e2b/code-interpreter": "^2.3.3",
|
|
248
228
|
"@google/genai": "^1.43.0",
|
|
249
229
|
"@langchain/core": ">=1.0.0",
|
|
230
|
+
"@temporalio/common": ">=1.16.0 <2.0.0",
|
|
231
|
+
"@temporalio/envconfig": ">=1.16.0 <2.0.0",
|
|
232
|
+
"@temporalio/worker": ">=1.16.0 <2.0.0",
|
|
233
|
+
"@temporalio/workflow": ">=1.16.0 <2.0.0",
|
|
250
234
|
"ioredis": ">=5.0.0",
|
|
251
235
|
"just-bash": ">=2.0.0"
|
|
252
236
|
},
|
|
253
237
|
"peerDependenciesMeta": {
|
|
254
|
-
"@aws-sdk/client-bedrock-agentcore": {
|
|
255
|
-
"optional": true
|
|
256
|
-
},
|
|
257
238
|
"@daytonaio/sdk": {
|
|
258
239
|
"optional": true
|
|
259
240
|
},
|
|
@@ -266,6 +247,12 @@
|
|
|
266
247
|
"@langchain/core": {
|
|
267
248
|
"optional": true
|
|
268
249
|
},
|
|
250
|
+
"@temporalio/envconfig": {
|
|
251
|
+
"optional": true
|
|
252
|
+
},
|
|
253
|
+
"@temporalio/worker": {
|
|
254
|
+
"optional": true
|
|
255
|
+
},
|
|
269
256
|
"just-bash": {
|
|
270
257
|
"optional": true
|
|
271
258
|
}
|
|
@@ -276,9 +263,6 @@
|
|
|
276
263
|
},
|
|
277
264
|
"homepage": "https://github.com/bead-ai/zeitlich#readme",
|
|
278
265
|
"dependencies": {
|
|
279
|
-
"@temporalio/common": "^1.15.0",
|
|
280
|
-
"@temporalio/plugin": "^1.15.0",
|
|
281
|
-
"@temporalio/workflow": "^1.15.0",
|
|
282
266
|
"zod": "^4.3.6"
|
|
283
267
|
}
|
|
284
268
|
}
|
|
@@ -2,16 +2,13 @@ import { Daytona, type Sandbox as DaytonaSdkSandbox } from "@daytonaio/sdk";
|
|
|
2
2
|
import type {
|
|
3
3
|
Sandbox,
|
|
4
4
|
SandboxCapabilities,
|
|
5
|
+
SandboxCapability,
|
|
5
6
|
SandboxCreateResult,
|
|
6
7
|
SandboxProvider,
|
|
7
|
-
SandboxSnapshot,
|
|
8
8
|
ExecOptions,
|
|
9
9
|
ExecResult,
|
|
10
10
|
} from "../../../lib/sandbox/types";
|
|
11
|
-
import {
|
|
12
|
-
SandboxNotFoundError,
|
|
13
|
-
SandboxNotSupportedError,
|
|
14
|
-
} from "../../../lib/sandbox/types";
|
|
11
|
+
import { SandboxNotFoundError } from "../../../lib/sandbox/types";
|
|
15
12
|
import { DaytonaSandboxFileSystem } from "./filesystem";
|
|
16
13
|
import type {
|
|
17
14
|
DaytonaSandbox,
|
|
@@ -64,16 +61,35 @@ class DaytonaSandboxImpl implements Sandbox {
|
|
|
64
61
|
// DaytonaSandboxProvider
|
|
65
62
|
// ============================================================================
|
|
66
63
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Single source of truth for the Daytona adapter's capability set. Daytona
|
|
66
|
+
* implements only base lifecycle (`create` / `get` / `destroy`); both the
|
|
67
|
+
* type-level `TCaps` (`never`) and the runtime `supportedCapabilities`
|
|
68
|
+
* set fall out of this empty array, so the two surfaces cannot drift.
|
|
69
|
+
*/
|
|
70
|
+
const DAYTONA_CAPS = [] as const satisfies readonly SandboxCapability[];
|
|
71
|
+
type DaytonaCaps = (typeof DAYTONA_CAPS)[number]; // → never
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Daytona implements only base sandbox lifecycle (`create` / `get` /
|
|
75
|
+
* `destroy`). Snapshot, restore, fork, pause, and resume are not supported
|
|
76
|
+
* — the type-level capability set is `never`, so calling any of those
|
|
77
|
+
* methods on a Daytona provider, manager, or `SandboxOps` proxy is a
|
|
78
|
+
* compile-time TypeScript error.
|
|
79
|
+
*/
|
|
80
|
+
export class DaytonaSandboxProvider
|
|
81
|
+
implements
|
|
82
|
+
SandboxProvider<DaytonaSandboxCreateOptions, DaytonaSandbox, DaytonaCaps>
|
|
83
|
+
{
|
|
71
84
|
readonly id = "daytona";
|
|
72
85
|
readonly capabilities: SandboxCapabilities = {
|
|
73
86
|
filesystem: true,
|
|
74
87
|
execution: true,
|
|
75
88
|
persistence: false,
|
|
76
89
|
};
|
|
90
|
+
readonly supportedCapabilities: ReadonlySet<DaytonaCaps> = new Set(
|
|
91
|
+
DAYTONA_CAPS
|
|
92
|
+
);
|
|
77
93
|
|
|
78
94
|
private client: Daytona;
|
|
79
95
|
private readonly defaultWorkspaceBase: string;
|
|
@@ -140,45 +156,6 @@ export class DaytonaSandboxProvider implements SandboxProvider<
|
|
|
140
156
|
// Already gone
|
|
141
157
|
}
|
|
142
158
|
}
|
|
143
|
-
|
|
144
|
-
async pause(_sandboxId: string, _ttlSeconds?: number): Promise<void> {
|
|
145
|
-
throw new SandboxNotSupportedError("pause");
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
async resume(_sandboxId: string): Promise<void> {
|
|
149
|
-
// Daytona sandboxes don't support pause, so resume is a no-op
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
async fork(
|
|
153
|
-
_sandboxId: string,
|
|
154
|
-
_options?: DaytonaSandboxCreateOptions
|
|
155
|
-
): Promise<Sandbox> {
|
|
156
|
-
throw new Error("Not implemented");
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
async snapshot(
|
|
160
|
-
_sandboxId: string,
|
|
161
|
-
_options?: DaytonaSandboxCreateOptions
|
|
162
|
-
): Promise<SandboxSnapshot> {
|
|
163
|
-
throw new SandboxNotSupportedError(
|
|
164
|
-
"snapshot (use Daytona's native snapshot API directly)"
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
async restore(
|
|
169
|
-
_snapshot: SandboxSnapshot,
|
|
170
|
-
_options?: DaytonaSandboxCreateOptions
|
|
171
|
-
): Promise<never> {
|
|
172
|
-
throw new SandboxNotSupportedError(
|
|
173
|
-
"restore (use Daytona's native snapshot API directly)"
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
async deleteSnapshot(_snapshot: SandboxSnapshot): Promise<void> {
|
|
178
|
-
throw new SandboxNotSupportedError(
|
|
179
|
-
"deleteSnapshot (use Daytona's native snapshot API directly)"
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
159
|
}
|
|
183
160
|
|
|
184
161
|
// Re-exports
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
* By default the scope is derived from `workflowInfo().workflowType`,
|
|
11
11
|
* so activities are automatically namespaced per workflow.
|
|
12
12
|
*
|
|
13
|
+
* Daytona only exposes base sandbox lifecycle (`create`/`destroy`) — the
|
|
14
|
+
* returned proxy is typed with `TCaps = never`, so calling
|
|
15
|
+
* `pauseSandbox` / `snapshotSandbox` / `forkSandbox` / etc. on it is a
|
|
16
|
+
* TypeScript error rather than a runtime throw.
|
|
17
|
+
*
|
|
13
18
|
* @example
|
|
14
19
|
* ```typescript
|
|
15
20
|
* import { proxyDaytonaSandboxOps } from 'zeitlich/adapters/sandbox/daytona/workflow';
|
|
@@ -26,7 +31,7 @@ const ADAPTER_PREFIX = "daytona";
|
|
|
26
31
|
export function proxyDaytonaSandboxOps(
|
|
27
32
|
scope?: string,
|
|
28
33
|
options?: Parameters<typeof proxyActivities>[0]
|
|
29
|
-
): SandboxOps {
|
|
34
|
+
): SandboxOps<DaytonaSandboxCreateOptions, unknown, never> {
|
|
30
35
|
const resolvedScope = scope ?? workflowInfo().workflowType;
|
|
31
36
|
|
|
32
37
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -49,11 +54,5 @@ export function proxyDaytonaSandboxOps(
|
|
|
49
54
|
return {
|
|
50
55
|
createSandbox: acts[p("createSandbox")],
|
|
51
56
|
destroySandbox: acts[p("destroySandbox")],
|
|
52
|
-
|
|
53
|
-
resumeSandbox: acts[p("resumeSandbox")],
|
|
54
|
-
snapshotSandbox: acts[p("snapshotSandbox")],
|
|
55
|
-
restoreSandbox: acts[p("restoreSandbox")],
|
|
56
|
-
deleteSandboxSnapshot: acts[p("deleteSandboxSnapshot")],
|
|
57
|
-
forkSandbox: acts[p("forkSandbox")],
|
|
58
|
-
} as SandboxOps<DaytonaSandboxCreateOptions>;
|
|
57
|
+
} as SandboxOps<DaytonaSandboxCreateOptions, unknown, never>;
|
|
59
58
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# E2B Sandbox Adapter
|
|
2
|
+
|
|
3
|
+
Adapter that exposes [E2B](https://e2b.dev/) cloud sandboxes through the
|
|
4
|
+
standard `SandboxProvider` interface used by the rest of Zeitlich.
|
|
5
|
+
|
|
6
|
+
## Configuration
|
|
7
|
+
|
|
8
|
+
`E2bSandboxProvider` accepts an `E2bSandboxConfig` (provider-level defaults).
|
|
9
|
+
Per-create overrides may be passed to `create()` via `E2bSandboxCreateOptions`.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { E2bSandboxProvider } from "zeitlich/adapters/sandbox/e2b";
|
|
13
|
+
|
|
14
|
+
const provider = new E2bSandboxProvider({
|
|
15
|
+
template: "my-template",
|
|
16
|
+
timeoutMs: 15 * 60 * 1000, // kill-on-abandon safety net
|
|
17
|
+
keepAliveMs: 15 * 60 * 1000, // refreshed on every tool call
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Keep-alive pattern
|
|
22
|
+
|
|
23
|
+
E2B's `timeoutMs` on `Sandbox.create` is a **sandbox lifetime**, not an idle
|
|
24
|
+
timeout: when it elapses, E2B kills the sandbox regardless of activity. Long
|
|
25
|
+
agent loops (LLM thinking + many tool calls) can outlive that window, and the
|
|
26
|
+
next tool call hits `Sandbox.connect(sandboxId)` and surfaces a
|
|
27
|
+
`SandboxNotFoundError` mid-run.
|
|
28
|
+
|
|
29
|
+
`keepAliveMs` solves this without giving up the kill-on-abandon safety net.
|
|
30
|
+
When set, every call to `provider.get(sandboxId)` passes
|
|
31
|
+
`{ timeoutMs: keepAliveMs }` to `Sandbox.connect()`. Per the E2B SDK's
|
|
32
|
+
`SandboxConnectOpts.timeoutMs` JSDoc:
|
|
33
|
+
|
|
34
|
+
> For running sandboxes, the timeout will update only if the new timeout is
|
|
35
|
+
> longer than the existing one.
|
|
36
|
+
|
|
37
|
+
So `connect()` with a `timeoutMs` is **monotonic**: it never shrinks the
|
|
38
|
+
lifetime of a running sandbox. Pick `keepAliveMs` as the **full per-call
|
|
39
|
+
refresh window** you want — passing a value smaller than the time remaining
|
|
40
|
+
is a no-op rather than a shrink, but you should still pick the value with
|
|
41
|
+
"every tool call should give me at least this much headroom" in mind, not
|
|
42
|
+
"floor to add".
|
|
43
|
+
|
|
44
|
+
`provider.get()` is invoked exactly once per tool call by `withSandbox`, so:
|
|
45
|
+
|
|
46
|
+
- An active session's tool calls each refresh the lifetime to at least
|
|
47
|
+
`keepAliveMs`. The sandbox cannot be killed mid-run as long as tools are
|
|
48
|
+
still firing — conceptually this is the sandbox equivalent of a Temporal
|
|
49
|
+
activity heartbeat.
|
|
50
|
+
- An abandoned sandbox still dies `keepAliveMs` after the last tool call. The
|
|
51
|
+
existing kill-on-timeout safety net is preserved.
|
|
52
|
+
- Consumers can drop `timeoutMs` back down to short, safe values (e.g.
|
|
53
|
+
15 minutes) without tuning against worst-case run length.
|
|
54
|
+
|
|
55
|
+
### Recommended usage
|
|
56
|
+
|
|
57
|
+
- Set `timeoutMs` to a value that bounds how long a sandbox can sit unused
|
|
58
|
+
after the consumer abnormally terminates (worker crash, workflow terminated,
|
|
59
|
+
`workflowRunTimeout`). This is your **abandon safety net**.
|
|
60
|
+
- Set `keepAliveMs` to your **per-call refresh window** — typically the same
|
|
61
|
+
value as `timeoutMs`, or shorter if you want sandboxes to be reaped sooner
|
|
62
|
+
after the last tool call.
|
|
63
|
+
|
|
64
|
+
### Provider-level only
|
|
65
|
+
|
|
66
|
+
`keepAliveMs` is a provider-construction-time config. There is intentionally
|
|
67
|
+
no per-create override: every sandbox managed by the provider refreshes by
|
|
68
|
+
the same amount on each `get()`. If a real use case for per-sandbox refresh
|
|
69
|
+
windows ever shows up we can add it without breaking changes.
|
|
70
|
+
|
|
71
|
+
### When connect-with-options is not enough
|
|
72
|
+
|
|
73
|
+
If you ever need to **shrink** a sandbox's remaining lifetime (e.g. force an
|
|
74
|
+
early reap), `connect()` won't do it because of the monotonic-extend rule
|
|
75
|
+
above. Use `Sandbox.setTimeout(timeoutMs)` or the static
|
|
76
|
+
`SandboxApi.setTimeout(sandboxId, timeoutMs)` instead — those can extend or
|
|
77
|
+
reduce.
|
|
78
|
+
|
|
79
|
+
If E2B ever changes the semantics of `Sandbox.connect(sandboxId, { timeoutMs })`
|
|
80
|
+
so it stops extending a running sandbox's lifetime at all, `setTimeout` is
|
|
81
|
+
also a drop-in replacement for the call site in `provider.get()`.
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
NotFoundError as E2bNotFoundError,
|
|
3
|
+
Sandbox as E2bSdkSandbox,
|
|
4
|
+
SandboxNotFoundError as E2bSandboxNotFoundError,
|
|
5
|
+
} from "@e2b/code-interpreter";
|
|
2
6
|
import type {
|
|
3
7
|
Sandbox,
|
|
4
8
|
SandboxCapabilities,
|
|
9
|
+
SandboxCapability,
|
|
5
10
|
SandboxCreateResult,
|
|
6
11
|
SandboxProvider,
|
|
7
12
|
SandboxSnapshot,
|
|
@@ -19,6 +24,20 @@ import type {
|
|
|
19
24
|
E2bSandboxCreateOptions,
|
|
20
25
|
} from "./types";
|
|
21
26
|
|
|
27
|
+
/**
|
|
28
|
+
* True iff `err` is the E2B SDK's "this sandbox doesn't exist (anymore)"
|
|
29
|
+
* signal. We narrow to `SandboxNotFoundError` (the canonical class) and to
|
|
30
|
+
* its deprecated parent `NotFoundError` as a defensive fallback — older
|
|
31
|
+
* SDK paths still throw the parent for sandbox-not-found cases. Any other
|
|
32
|
+
* error (auth failure, network blip, 5xx, validation) is propagated
|
|
33
|
+
* unchanged so callers can react to it specifically.
|
|
34
|
+
*/
|
|
35
|
+
function isE2bSandboxNotFound(err: unknown): boolean {
|
|
36
|
+
return (
|
|
37
|
+
err instanceof E2bSandboxNotFoundError || err instanceof E2bNotFoundError
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
22
41
|
// ============================================================================
|
|
23
42
|
// E2bSandbox
|
|
24
43
|
// ============================================================================
|
|
@@ -62,20 +81,35 @@ class E2bSandboxImpl implements Sandbox {
|
|
|
62
81
|
// E2bSandboxProvider
|
|
63
82
|
// ============================================================================
|
|
64
83
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
84
|
+
/**
|
|
85
|
+
* Single source of truth for the E2B adapter's capability set. Both the
|
|
86
|
+
* runtime `supportedCapabilities` set and the type-level `TCaps` flow
|
|
87
|
+
* out of this array, so the two surfaces cannot drift.
|
|
88
|
+
*/
|
|
89
|
+
export const E2B_CAPS = [
|
|
90
|
+
"pause",
|
|
91
|
+
"resume",
|
|
92
|
+
"snapshot",
|
|
93
|
+
"restore",
|
|
94
|
+
"fork",
|
|
95
|
+
] as const satisfies readonly SandboxCapability[];
|
|
96
|
+
export type E2bCaps = (typeof E2B_CAPS)[number];
|
|
97
|
+
|
|
98
|
+
export class E2bSandboxProvider
|
|
99
|
+
implements SandboxProvider<E2bSandboxCreateOptions, E2bSandbox, E2bCaps>
|
|
100
|
+
{
|
|
69
101
|
readonly id = "e2b";
|
|
70
102
|
readonly capabilities: SandboxCapabilities = {
|
|
71
103
|
filesystem: true,
|
|
72
104
|
execution: true,
|
|
73
105
|
persistence: true,
|
|
74
106
|
};
|
|
107
|
+
readonly supportedCapabilities: ReadonlySet<E2bCaps> = new Set(E2B_CAPS);
|
|
75
108
|
|
|
76
109
|
private readonly defaultTemplate?: string;
|
|
77
110
|
private readonly defaultWorkspaceBase: string;
|
|
78
111
|
private readonly defaultTimeoutMs?: number;
|
|
112
|
+
private readonly defaultKeepAliveMs?: number;
|
|
79
113
|
private readonly defaultAllowInternetAccess?: boolean;
|
|
80
114
|
private readonly defaultNetwork?: E2bSandboxConfig["network"];
|
|
81
115
|
private readonly defaultMetadata?: E2bSandboxConfig["metadata"];
|
|
@@ -85,6 +119,7 @@ export class E2bSandboxProvider implements SandboxProvider<
|
|
|
85
119
|
this.defaultTemplate = config?.template;
|
|
86
120
|
this.defaultWorkspaceBase = config?.workspaceBase ?? "/home/user";
|
|
87
121
|
this.defaultTimeoutMs = config?.timeoutMs;
|
|
122
|
+
this.defaultKeepAliveMs = config?.keepAliveMs;
|
|
88
123
|
this.defaultAllowInternetAccess = config?.allowInternetAccess;
|
|
89
124
|
this.defaultNetwork = config?.network;
|
|
90
125
|
this.defaultMetadata = config?.metadata;
|
|
@@ -120,15 +155,22 @@ export class E2bSandboxProvider implements SandboxProvider<
|
|
|
120
155
|
}
|
|
121
156
|
|
|
122
157
|
async get(sandboxId: string): Promise<E2bSandbox> {
|
|
158
|
+
const keepAliveMs = this.defaultKeepAliveMs;
|
|
123
159
|
try {
|
|
124
|
-
const sdkSandbox =
|
|
160
|
+
const sdkSandbox =
|
|
161
|
+
keepAliveMs !== undefined
|
|
162
|
+
? await E2bSdkSandbox.connect(sandboxId, { timeoutMs: keepAliveMs })
|
|
163
|
+
: await E2bSdkSandbox.connect(sandboxId);
|
|
125
164
|
return new E2bSandboxImpl(
|
|
126
165
|
sandboxId,
|
|
127
166
|
sdkSandbox,
|
|
128
167
|
this.defaultWorkspaceBase
|
|
129
168
|
);
|
|
130
|
-
} catch {
|
|
131
|
-
|
|
169
|
+
} catch (err) {
|
|
170
|
+
if (isE2bSandboxNotFound(err)) {
|
|
171
|
+
throw new SandboxNotFoundError(sandboxId);
|
|
172
|
+
}
|
|
173
|
+
throw err;
|
|
132
174
|
}
|
|
133
175
|
}
|
|
134
176
|
|
|
@@ -137,7 +179,7 @@ export class E2bSandboxProvider implements SandboxProvider<
|
|
|
137
179
|
const sdkSandbox = await E2bSdkSandbox.connect(sandboxId);
|
|
138
180
|
await sdkSandbox.kill();
|
|
139
181
|
} catch {
|
|
140
|
-
// Already gone or not found
|
|
182
|
+
// Already gone or not found — destroy is idempotent.
|
|
141
183
|
}
|
|
142
184
|
}
|
|
143
185
|
|
|
@@ -166,7 +208,7 @@ export class E2bSandboxProvider implements SandboxProvider<
|
|
|
166
208
|
async restore(
|
|
167
209
|
snapshot: SandboxSnapshot,
|
|
168
210
|
options?: E2bSandboxCreateOptions
|
|
169
|
-
): Promise<
|
|
211
|
+
): Promise<E2bSandbox> {
|
|
170
212
|
const data = snapshot.data as { snapshotId?: string } | null;
|
|
171
213
|
if (!data?.snapshotId) {
|
|
172
214
|
throw new SandboxNotSupportedError(
|
|
@@ -195,7 +237,7 @@ export class E2bSandboxProvider implements SandboxProvider<
|
|
|
195
237
|
async fork(
|
|
196
238
|
sandboxId: string,
|
|
197
239
|
options?: E2bSandboxCreateOptions
|
|
198
|
-
): Promise<
|
|
240
|
+
): Promise<E2bSandbox> {
|
|
199
241
|
const { snapshotId } = await E2bSdkSandbox.createSnapshot(sandboxId);
|
|
200
242
|
const sdkOpts = this.buildSdkCreateOpts(options);
|
|
201
243
|
const sdkSandbox = await E2bSdkSandbox.create(snapshotId, sdkOpts);
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { describe, expect, it, vi, beforeEach } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
Sandbox as E2bSdkSandbox,
|
|
4
|
+
SandboxNotFoundError as E2bSandboxNotFoundError,
|
|
5
|
+
} from "@e2b/code-interpreter";
|
|
6
|
+
import { E2bSandboxProvider } from "./index";
|
|
7
|
+
import { SandboxNotFoundError } from "../../../lib/sandbox/types";
|
|
8
|
+
|
|
9
|
+
vi.mock("@e2b/code-interpreter", () => {
|
|
10
|
+
class FakeSdkSandbox {
|
|
11
|
+
static create = vi.fn();
|
|
12
|
+
static connect = vi.fn();
|
|
13
|
+
static createSnapshot = vi.fn();
|
|
14
|
+
static deleteSnapshot = vi.fn();
|
|
15
|
+
sandboxId: string;
|
|
16
|
+
constructor(sandboxId: string) {
|
|
17
|
+
this.sandboxId = sandboxId;
|
|
18
|
+
}
|
|
19
|
+
commands = { run: vi.fn() };
|
|
20
|
+
files = {};
|
|
21
|
+
async kill() {}
|
|
22
|
+
async pause() {}
|
|
23
|
+
}
|
|
24
|
+
// Mirror the real SDK error class hierarchy: SandboxNotFoundError extends
|
|
25
|
+
// (deprecated) NotFoundError extends SandboxError extends Error.
|
|
26
|
+
class FakeSandboxError extends Error {}
|
|
27
|
+
class FakeNotFoundError extends FakeSandboxError {}
|
|
28
|
+
class FakeSandboxNotFoundError extends FakeNotFoundError {}
|
|
29
|
+
return {
|
|
30
|
+
Sandbox: FakeSdkSandbox,
|
|
31
|
+
SandboxError: FakeSandboxError,
|
|
32
|
+
NotFoundError: FakeNotFoundError,
|
|
33
|
+
SandboxNotFoundError: FakeSandboxNotFoundError,
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const sdk = E2bSdkSandbox as unknown as {
|
|
38
|
+
create: ReturnType<typeof vi.fn>;
|
|
39
|
+
connect: ReturnType<typeof vi.fn>;
|
|
40
|
+
createSnapshot: ReturnType<typeof vi.fn>;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
function makeFakeSdkSandbox(id = "sbx-1") {
|
|
44
|
+
return {
|
|
45
|
+
sandboxId: id,
|
|
46
|
+
commands: { run: vi.fn() },
|
|
47
|
+
files: {},
|
|
48
|
+
kill: vi.fn(),
|
|
49
|
+
pause: vi.fn(),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe("E2bSandboxProvider keep-alive", () => {
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
sdk.create.mockReset();
|
|
56
|
+
sdk.connect.mockReset();
|
|
57
|
+
sdk.createSnapshot.mockReset();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("forwards timeoutMs to connect() when keepAliveMs is configured at the provider level", async () => {
|
|
61
|
+
const fake = makeFakeSdkSandbox();
|
|
62
|
+
sdk.connect.mockResolvedValue(fake);
|
|
63
|
+
|
|
64
|
+
const provider = new E2bSandboxProvider({ keepAliveMs: 15 * 60 * 1000 });
|
|
65
|
+
const sandbox = await provider.get("sbx-1");
|
|
66
|
+
|
|
67
|
+
expect(sandbox.id).toBe("sbx-1");
|
|
68
|
+
expect(sdk.connect).toHaveBeenCalledTimes(1);
|
|
69
|
+
expect(sdk.connect).toHaveBeenCalledWith("sbx-1", {
|
|
70
|
+
timeoutMs: 15 * 60 * 1000,
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("omits timeoutMs from connect() when keepAliveMs is not configured", async () => {
|
|
75
|
+
const fake = makeFakeSdkSandbox();
|
|
76
|
+
sdk.connect.mockResolvedValue(fake);
|
|
77
|
+
|
|
78
|
+
const provider = new E2bSandboxProvider();
|
|
79
|
+
await provider.get("sbx-1");
|
|
80
|
+
|
|
81
|
+
expect(sdk.connect).toHaveBeenCalledTimes(1);
|
|
82
|
+
expect(sdk.connect).toHaveBeenCalledWith("sbx-1");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("uses provider-level keepAliveMs for every sandbox managed by the provider", async () => {
|
|
86
|
+
const fake = makeFakeSdkSandbox("sbx-default");
|
|
87
|
+
sdk.connect.mockResolvedValue(fake);
|
|
88
|
+
|
|
89
|
+
const provider = new E2bSandboxProvider({ keepAliveMs: 60_000 });
|
|
90
|
+
await provider.get("sbx-default");
|
|
91
|
+
|
|
92
|
+
expect(sdk.connect).toHaveBeenCalledWith("sbx-default", {
|
|
93
|
+
timeoutMs: 60_000,
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("translates the SDK's SandboxNotFoundError into our SandboxNotFoundError", async () => {
|
|
98
|
+
sdk.connect.mockRejectedValue(
|
|
99
|
+
new E2bSandboxNotFoundError("sandbox missing-sbx not found")
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const provider = new E2bSandboxProvider({ keepAliveMs: 60_000 });
|
|
103
|
+
await expect(provider.get("missing-sbx")).rejects.toBeInstanceOf(
|
|
104
|
+
SandboxNotFoundError
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("propagates non-not-found connect() errors unchanged (auth, network, 5xx)", async () => {
|
|
109
|
+
const transient = new Error("ECONNRESET: socket hang up");
|
|
110
|
+
sdk.connect.mockRejectedValue(transient);
|
|
111
|
+
|
|
112
|
+
const provider = new E2bSandboxProvider({ keepAliveMs: 60_000 });
|
|
113
|
+
await expect(provider.get("sbx-1")).rejects.toBe(transient);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { proxyActivities, workflowInfo } from "@temporalio/workflow";
|
|
21
21
|
import type { SandboxOps } from "../../../lib/sandbox/types";
|
|
22
|
+
import type { E2bCaps } from "./index";
|
|
22
23
|
import type { E2bSandboxCreateOptions } from "./types";
|
|
23
24
|
|
|
24
25
|
const ADAPTER_PREFIX = "e2b";
|
|
@@ -26,7 +27,7 @@ const ADAPTER_PREFIX = "e2b";
|
|
|
26
27
|
export function proxyE2bSandboxOps(
|
|
27
28
|
scope?: string,
|
|
28
29
|
options?: Parameters<typeof proxyActivities>[0]
|
|
29
|
-
): SandboxOps {
|
|
30
|
+
): SandboxOps<E2bSandboxCreateOptions, unknown, E2bCaps> {
|
|
30
31
|
const resolvedScope = scope ?? workflowInfo().workflowType;
|
|
31
32
|
|
|
32
33
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -55,5 +56,5 @@ export function proxyE2bSandboxOps(
|
|
|
55
56
|
restoreSandbox: acts[p("restoreSandbox")],
|
|
56
57
|
deleteSandboxSnapshot: acts[p("deleteSandboxSnapshot")],
|
|
57
58
|
forkSandbox: acts[p("forkSandbox")],
|
|
58
|
-
} as SandboxOps<E2bSandboxCreateOptions>;
|
|
59
|
+
} as SandboxOps<E2bSandboxCreateOptions, unknown, E2bCaps>;
|
|
59
60
|
}
|
|
@@ -19,8 +19,35 @@ export interface E2bSandboxConfig {
|
|
|
19
19
|
template?: string;
|
|
20
20
|
/** Default working directory inside the sandbox */
|
|
21
21
|
workspaceBase?: string;
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Sandbox lifetime in milliseconds. Despite the name, this is **not** an
|
|
24
|
+
* idle timeout: E2B kills the sandbox once this many milliseconds elapse
|
|
25
|
+
* from creation regardless of activity. Pair with {@link keepAliveMs} to
|
|
26
|
+
* refresh the lifetime on every `provider.get()` call so that this value
|
|
27
|
+
* acts as a kill-on-abandon safety net rather than a hard cap on run
|
|
28
|
+
* length.
|
|
29
|
+
*/
|
|
23
30
|
timeoutMs?: number;
|
|
31
|
+
/**
|
|
32
|
+
* If set, every call to `provider.get(sandboxId)` passes
|
|
33
|
+
* `{ timeoutMs: keepAliveMs }` to `Sandbox.connect()`, refreshing the
|
|
34
|
+
* sandbox lifetime on each tool invocation. The provider-level
|
|
35
|
+
* `timeoutMs` then acts as a kill-on-abandon safety net rather than a
|
|
36
|
+
* hard cap on run length.
|
|
37
|
+
*
|
|
38
|
+
* E2B's `Sandbox.connect()` is monotonic for running sandboxes: per the
|
|
39
|
+
* SDK's `SandboxConnectOpts.timeoutMs` doc, "the timeout will update
|
|
40
|
+
* only if the new timeout is longer than the existing one". Pick
|
|
41
|
+
* `keepAliveMs` as the full per-call refresh window you want; passing a
|
|
42
|
+
* value smaller than the time remaining is a no-op rather than a
|
|
43
|
+
* shrink. (If you ever need to shrink, use `Sandbox.setTimeout` /
|
|
44
|
+
* `SandboxApi.setTimeout`, which can extend or reduce.)
|
|
45
|
+
*
|
|
46
|
+
* Per-sandbox overrides are intentionally not exposed — this is a
|
|
47
|
+
* provider-level config only. Every sandbox managed by the provider
|
|
48
|
+
* refreshes by the same amount on each `get()`.
|
|
49
|
+
*/
|
|
50
|
+
keepAliveMs?: number;
|
|
24
51
|
/** Default outbound internet access policy */
|
|
25
52
|
allowInternetAccess?: boolean;
|
|
26
53
|
/** Default outbound network allow/deny rules */
|
|
@@ -34,6 +61,11 @@ export interface E2bSandboxConfig {
|
|
|
34
61
|
export interface E2bSandboxCreateOptions extends SandboxCreateOptions {
|
|
35
62
|
/** Sandbox template name or ID — overrides the provider default */
|
|
36
63
|
template?: string;
|
|
37
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* Sandbox lifetime in milliseconds — overrides the provider default. See
|
|
66
|
+
* {@link E2bSandboxConfig.timeoutMs} for the full semantics; pair with
|
|
67
|
+
* the provider-level `keepAliveMs` to refresh on every `provider.get()`
|
|
68
|
+
* call.
|
|
69
|
+
*/
|
|
38
70
|
timeoutMs?: number;
|
|
39
71
|
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import type {
|
|
9
9
|
Sandbox,
|
|
10
10
|
SandboxCapabilities,
|
|
11
|
+
SandboxCapability,
|
|
11
12
|
SandboxCreateOptions,
|
|
12
13
|
SandboxCreateResult,
|
|
13
14
|
SandboxFileSystem,
|
|
@@ -133,13 +134,32 @@ class InMemorySandboxImpl implements Sandbox {
|
|
|
133
134
|
// InMemorySandboxProvider
|
|
134
135
|
// ============================================================================
|
|
135
136
|
|
|
136
|
-
|
|
137
|
+
/**
|
|
138
|
+
* Single source of truth for the in-memory adapter's capability set. The
|
|
139
|
+
* runtime `supportedCapabilities` set and the type-level `TCaps` are both
|
|
140
|
+
* derived from this array, so the two surfaces cannot drift.
|
|
141
|
+
*/
|
|
142
|
+
export const IN_MEMORY_CAPS = [
|
|
143
|
+
"pause",
|
|
144
|
+
"resume",
|
|
145
|
+
"snapshot",
|
|
146
|
+
"restore",
|
|
147
|
+
"fork",
|
|
148
|
+
] as const satisfies readonly SandboxCapability[];
|
|
149
|
+
export type InMemoryCaps = (typeof IN_MEMORY_CAPS)[number];
|
|
150
|
+
|
|
151
|
+
export class InMemorySandboxProvider
|
|
152
|
+
implements SandboxProvider<SandboxCreateOptions, Sandbox, InMemoryCaps>
|
|
153
|
+
{
|
|
137
154
|
readonly id = "inMemory";
|
|
138
155
|
readonly capabilities: SandboxCapabilities = {
|
|
139
156
|
filesystem: true,
|
|
140
157
|
execution: true,
|
|
141
158
|
persistence: true,
|
|
142
159
|
};
|
|
160
|
+
readonly supportedCapabilities: ReadonlySet<InMemoryCaps> = new Set(
|
|
161
|
+
IN_MEMORY_CAPS
|
|
162
|
+
);
|
|
143
163
|
|
|
144
164
|
private sandboxes = new Map<string, InMemorySandboxImpl>();
|
|
145
165
|
|