@molecule/api-code-sandbox-e2b 1.0.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/LICENSE +115 -0
- package/README.md +281 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/provider.d.ts +92 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +465 -0
- package/dist/types.d.ts +103 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +12 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work.
|
|
38
|
+
|
|
39
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
40
|
+
form, that is based on (or derived from) the Work and for which the
|
|
41
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
42
|
+
represent, as a whole, an original work of authorship.
|
|
43
|
+
|
|
44
|
+
"Contribution" shall mean any work of authorship, including the
|
|
45
|
+
original version of the Work and any modifications or additions
|
|
46
|
+
to that Work, that is intentionally submitted to the Licensor for
|
|
47
|
+
inclusion in the Work by the copyright owner or by an individual or
|
|
48
|
+
Legal Entity authorized to submit on behalf of the copyright owner.
|
|
49
|
+
|
|
50
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
51
|
+
on behalf of whom a Contribution has been received by the Licensor and
|
|
52
|
+
subsequently incorporated within the Work.
|
|
53
|
+
|
|
54
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
55
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
56
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
57
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
58
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
59
|
+
Work and such Derivative Works in Source or Object form.
|
|
60
|
+
|
|
61
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
62
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
63
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
64
|
+
patent license to make, have made, use, offer to sell, sell, import,
|
|
65
|
+
and otherwise transfer the Work.
|
|
66
|
+
|
|
67
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
68
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
69
|
+
modifications, and in Source or Object form, provided that You
|
|
70
|
+
meet the following conditions:
|
|
71
|
+
|
|
72
|
+
(a) You must give any other recipients of the Work or
|
|
73
|
+
Derivative Works a copy of this License; and
|
|
74
|
+
|
|
75
|
+
(b) You must cause any modified files to carry prominent notices
|
|
76
|
+
stating that You changed the files; and
|
|
77
|
+
|
|
78
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
79
|
+
that You distribute, all copyright, patent, trademark, and
|
|
80
|
+
attribution notices from the Source form of the Work,
|
|
81
|
+
excluding those notices that do not pertain to any part of
|
|
82
|
+
the Derivative Works; and
|
|
83
|
+
|
|
84
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
85
|
+
distribution, then any Derivative Works that You distribute must
|
|
86
|
+
include a readable copy of the attribution notices contained
|
|
87
|
+
within such NOTICE file.
|
|
88
|
+
|
|
89
|
+
5. Submission of Contributions.
|
|
90
|
+
|
|
91
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
92
|
+
names, trademarks, service marks, or product names of the Licensor.
|
|
93
|
+
|
|
94
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
95
|
+
agreed to in writing, Licensor provides the Work on an "AS IS" BASIS,
|
|
96
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND.
|
|
97
|
+
|
|
98
|
+
8. Limitation of Liability. In no event and under no legal theory shall
|
|
99
|
+
any Contributor be liable to You for damages.
|
|
100
|
+
|
|
101
|
+
9. Accepting Warranty or Additional Liability.
|
|
102
|
+
|
|
103
|
+
Copyright 2026 Molecule Dev, Inc.
|
|
104
|
+
|
|
105
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
106
|
+
you may not use this file except in compliance with the License.
|
|
107
|
+
You may obtain a copy of the License at
|
|
108
|
+
|
|
109
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
110
|
+
|
|
111
|
+
Unless required by applicable law or agreed to in writing, software
|
|
112
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
113
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
114
|
+
See the License for the specific language governing permissions and
|
|
115
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
AUTO-GENERATED — DO NOT EDIT THIS FILE.
|
|
3
|
+
Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
|
|
4
|
+
Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
|
|
5
|
+
To change this document, edit the module-level JSDoc in src/index.ts.
|
|
6
|
+
Generated: 2026-08-10T23:56:44.341Z
|
|
7
|
+
-->
|
|
8
|
+
|
|
9
|
+
# @molecule/api-code-sandbox-e2b
|
|
10
|
+
|
|
11
|
+
> **Auto-generated, AI-first package reference** for the [molecule.dev](https://molecule.dev) ecosystem.
|
|
12
|
+
> It is written to be read by coding agents as much as by people, and is generated from this
|
|
13
|
+
> package's source — edit `src/index.ts` JSDoc, not this file.
|
|
14
|
+
|
|
15
|
+
E2B (e2b.dev) code sandbox provider.
|
|
16
|
+
|
|
17
|
+
E2B runs isolated Firecracker microVMs purpose-built for agent/dev workloads:
|
|
18
|
+
a sandbox spawns from a golden **template** (a Dockerfile-built image with the
|
|
19
|
+
whole dependency set baked in) in ~1s, exposes every internal port at
|
|
20
|
+
`https://<port>-<id>.e2b.app`, pauses/resumes with filesystem + memory state
|
|
21
|
+
preserved in ~1s, and governs outbound traffic by a DNS network policy. This
|
|
22
|
+
bond maps that platform onto the `@molecule/api-code-sandbox` contract through
|
|
23
|
+
the official `e2b` SDK.
|
|
24
|
+
|
|
25
|
+
The design that makes it fast: a single golden SUPERSET template carries the
|
|
26
|
+
entire `@molecule` fleet node_modules + postgres + warmed Vite deps, so a boot
|
|
27
|
+
only copies the ONE selected app's source in and starts the dev servers — no
|
|
28
|
+
per-boot `npm install`. The 133 flagship template sources are NOT baked into
|
|
29
|
+
the image; they are copied from the control plane at boot, so templates and
|
|
30
|
+
`mlcl` stay private.
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { bond } from '@molecule/api-bond'
|
|
36
|
+
import { provider } from '@molecule/api-code-sandbox-e2b'
|
|
37
|
+
|
|
38
|
+
bond('codeSandbox', provider)
|
|
39
|
+
// Requires E2B_API_KEY (and E2B_TEMPLATE_ID for the golden superset template).
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { createProvider } from '@molecule/api-code-sandbox-e2b'
|
|
44
|
+
|
|
45
|
+
const provider = createProvider({
|
|
46
|
+
templateId: 'molecule-superset',
|
|
47
|
+
defaultPreviewPort: 5173,
|
|
48
|
+
defaultNetworkRules: [
|
|
49
|
+
{ domain: 'registry.npmjs.org', action: 'allow' },
|
|
50
|
+
{ domain: '*.npmjs.org', action: 'allow' },
|
|
51
|
+
{ domain: 'github.com', action: 'allow' },
|
|
52
|
+
{ domain: '*', action: 'deny' },
|
|
53
|
+
],
|
|
54
|
+
})
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Type
|
|
58
|
+
|
|
59
|
+
`provider`
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm install @molecule/api-code-sandbox-e2b e2b @molecule/api-bond @molecule/api-code-sandbox @molecule/api-i18n
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## API
|
|
68
|
+
|
|
69
|
+
### Interfaces
|
|
70
|
+
|
|
71
|
+
#### `E2BCommandResultLike`
|
|
72
|
+
|
|
73
|
+
Result of an E2B command run (subset of the SDK's `CommandResult`).
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
interface E2BCommandResultLike {
|
|
77
|
+
stdout: string
|
|
78
|
+
stderr: string
|
|
79
|
+
exitCode: number
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
#### `E2BCommandsLike`
|
|
84
|
+
|
|
85
|
+
Subset of the SDK's `Commands` the bond uses.
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
interface E2BCommandsLike {
|
|
89
|
+
run(
|
|
90
|
+
cmd: string,
|
|
91
|
+
opts?: { cwd?: string; timeoutMs?: number; envs?: Record<string, string> },
|
|
92
|
+
): Promise<E2BCommandResultLike>
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
#### `E2BConfig`
|
|
97
|
+
|
|
98
|
+
Bond configuration. All fields have safe defaults; see {@link createProvider}.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
interface E2BConfig {
|
|
102
|
+
/**
|
|
103
|
+
* E2B API key. Falls back to `E2B_API_KEY` in the environment. Required — the
|
|
104
|
+
* provider throws on first use if neither is set.
|
|
105
|
+
*/
|
|
106
|
+
apiKey?: string
|
|
107
|
+
/**
|
|
108
|
+
* The golden template id every sandbox boots from. Falls back to
|
|
109
|
+
* `E2B_TEMPLATE_ID`, then E2B's `base` template. This is the caller's OWN
|
|
110
|
+
* identifier for the superset template (fleet node_modules + postgres +
|
|
111
|
+
* warmed vite deps), built out of band by the template pipeline.
|
|
112
|
+
*/
|
|
113
|
+
templateId?: string
|
|
114
|
+
/**
|
|
115
|
+
* Default port the preview URL points at (the app's Vite dev server).
|
|
116
|
+
* `getPreviewUrl()` uses this when no port is given.
|
|
117
|
+
*/
|
|
118
|
+
defaultPreviewPort?: number
|
|
119
|
+
/**
|
|
120
|
+
* Default sandbox lifetime before E2B auto-pauses it, in milliseconds. The
|
|
121
|
+
* control plane extends this per-heartbeat; this is only the initial ceiling.
|
|
122
|
+
*/
|
|
123
|
+
defaultTimeoutMs?: number
|
|
124
|
+
/**
|
|
125
|
+
* Egress allow-list (domains / CIDRs) applied to every sandbox at create
|
|
126
|
+
* time; everything else is denied (`denyOut: [ALL_TRAFFIC]`). Wildcards like
|
|
127
|
+
* `*.npmjs.org` are supported. Empty/omitted means the bond does NOT
|
|
128
|
+
* constrain egress — prod must supply this or `verifyEgress` observes `open`
|
|
129
|
+
* and the control plane refuses to boot (Rule 18).
|
|
130
|
+
*
|
|
131
|
+
* Verified against a live E2B sandbox: with `denyOut: [ALL_TRAFFIC]`, a
|
|
132
|
+
* non-allowlisted host AND a raw destination IP are both blocked — a stronger
|
|
133
|
+
* boundary than a DNS-only policy.
|
|
134
|
+
*/
|
|
135
|
+
defaultAllowOut?: string[]
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
#### `E2BFilesystemLike`
|
|
140
|
+
|
|
141
|
+
Subset of the SDK's `Filesystem` the bond uses.
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
interface E2BFilesystemLike {
|
|
145
|
+
read(path: string): Promise<string>
|
|
146
|
+
write(path: string, data: string): Promise<unknown>
|
|
147
|
+
list(path: string): Promise<Array<{ name: string; type?: string; size?: number }>>
|
|
148
|
+
remove(path: string): Promise<void>
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
#### `E2BSandboxClientLike`
|
|
153
|
+
|
|
154
|
+
Subset of the SDK's `Sandbox` static surface the bond uses.
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
interface E2BSandboxClientLike {
|
|
158
|
+
create(templateId: string, opts?: Record<string, unknown>): Promise<E2BSandboxLike>
|
|
159
|
+
connect(sandboxId: string, opts?: Record<string, unknown>): Promise<E2BSandboxLike>
|
|
160
|
+
list(
|
|
161
|
+
opts?: Record<string, unknown>,
|
|
162
|
+
): Promise<Array<{ sandboxId: string }> | { sandboxes?: Array<{ sandboxId: string }> }>
|
|
163
|
+
kill?(sandboxId: string, opts?: Record<string, unknown>): Promise<boolean>
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
#### `E2BSandboxLike`
|
|
168
|
+
|
|
169
|
+
Subset of the SDK's `Sandbox` instance the bond uses.
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
interface E2BSandboxLike {
|
|
173
|
+
sandboxId: string
|
|
174
|
+
commands: E2BCommandsLike
|
|
175
|
+
files: E2BFilesystemLike
|
|
176
|
+
getHost(port: number): string
|
|
177
|
+
setTimeout(ms: number): Promise<void>
|
|
178
|
+
kill(): Promise<void>
|
|
179
|
+
pause?(): Promise<string>
|
|
180
|
+
betaPause?(): Promise<string>
|
|
181
|
+
isRunning(): Promise<boolean>
|
|
182
|
+
updateNetwork?(opts: { allowOut?: string[]; denyOut?: string[] }): Promise<void>
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Classes
|
|
187
|
+
|
|
188
|
+
#### `E2BSandboxProvider`
|
|
189
|
+
|
|
190
|
+
E2B implementation of {@link SandboxProvider}.
|
|
191
|
+
|
|
192
|
+
Only the required surface (`create`/`get`/`list`/`destroy`) plus the boot-path
|
|
193
|
+
optionals are wired here; `verifyEgress` and `commitTemplate`/`getTemplate`
|
|
194
|
+
land in follow-up steps. Leaving `verifyEgress` UNimplemented is deliberate:
|
|
195
|
+
the control plane treats "unsupported" as `inconclusive` and refuses to boot
|
|
196
|
+
in prod, which is the correct safe default until egress observation is proven
|
|
197
|
+
(Rule 18 — never trade cost for security).
|
|
198
|
+
|
|
199
|
+
### Functions
|
|
200
|
+
|
|
201
|
+
#### `createProvider(config, clientOverride)`
|
|
202
|
+
|
|
203
|
+
Create an E2B provider with the given configuration.
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
function createProvider(
|
|
207
|
+
config?: E2BConfig,
|
|
208
|
+
clientOverride?: E2BSandboxClientLike,
|
|
209
|
+
): E2BSandboxProvider
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
- `config` — Bond configuration; API key falls back to `E2B_API_KEY`.
|
|
213
|
+
- `clientOverride` — Inject a fake client (tests).
|
|
214
|
+
|
|
215
|
+
**Returns:** A configured provider ready to `bond('codeSandbox', provider)`.
|
|
216
|
+
|
|
217
|
+
### Constants
|
|
218
|
+
|
|
219
|
+
#### `provider`
|
|
220
|
+
|
|
221
|
+
Default provider instance, configured from the environment.
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
const provider: SandboxProvider
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Core Interface
|
|
228
|
+
|
|
229
|
+
Implements `@molecule/api-code-sandbox` interface.
|
|
230
|
+
|
|
231
|
+
## Bond Wiring
|
|
232
|
+
|
|
233
|
+
Setup function to register this provider with the core interface:
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
import { setProvider } from '@molecule/api-code-sandbox'
|
|
237
|
+
import { provider } from '@molecule/api-code-sandbox-e2b'
|
|
238
|
+
|
|
239
|
+
export function setupCodeSandboxE2b(): void {
|
|
240
|
+
setProvider(provider)
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Injection Notes
|
|
245
|
+
|
|
246
|
+
### Requirements
|
|
247
|
+
|
|
248
|
+
Peer dependencies:
|
|
249
|
+
|
|
250
|
+
- `@molecule/api-bond` ^1.0.1
|
|
251
|
+
- `@molecule/api-code-sandbox` ^1.0.1
|
|
252
|
+
- `@molecule/api-i18n` ^1.0.1
|
|
253
|
+
|
|
254
|
+
### Environment Variables
|
|
255
|
+
|
|
256
|
+
- `E2B_API_KEY` _(required)_ — E2B API key
|
|
257
|
+
- Setup: Create an API key in the E2B dashboard.
|
|
258
|
+
- Get it here: [https://e2b.dev/dashboard](https://e2b.dev/dashboard)
|
|
259
|
+
- Example: `e2b_...`
|
|
260
|
+
- `E2B_TEMPLATE_ID` _(optional)_ — E2B golden template id
|
|
261
|
+
- Setup: The superset template id every sandbox boots from; defaults to E2B base.
|
|
262
|
+
- Example: `molecule-superset`
|
|
263
|
+
|
|
264
|
+
### Runtime Dependencies
|
|
265
|
+
|
|
266
|
+
- `e2b`
|
|
267
|
+
- `@molecule/api-bond`
|
|
268
|
+
- `@molecule/api-code-sandbox`
|
|
269
|
+
- `@molecule/api-i18n`
|
|
270
|
+
|
|
271
|
+
**`verifyEgress` is intentionally not implemented yet.** The control plane
|
|
272
|
+
treats an absent `verifyEgress` as an `inconclusive` verdict and refuses to
|
|
273
|
+
boot sandboxes in production — the correct safe default until egress
|
|
274
|
+
observation is proven against E2B's `updateNetwork` policy (Rule 18: never
|
|
275
|
+
trade cost for security). Do not stub it with a fabricated `filtered`.
|
|
276
|
+
|
|
277
|
+
**E2B pauses, it does not stop.** `sleep()`/`stop()` both map to E2B pause
|
|
278
|
+
(FS + memory snapshot); `wake()`/`start()` reconnect by id. `hibernate()`/
|
|
279
|
+
`resume()` report `processesPreserved: true` because the memory snapshot
|
|
280
|
+
restores the process tree — unlike a Docker stop, a resumed E2B sandbox's
|
|
281
|
+
dev servers are still running.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2B (e2b.dev) code sandbox provider.
|
|
3
|
+
*
|
|
4
|
+
* E2B runs isolated Firecracker microVMs purpose-built for agent/dev workloads:
|
|
5
|
+
* a sandbox spawns from a golden **template** (a Dockerfile-built image with the
|
|
6
|
+
* whole dependency set baked in) in ~1s, exposes every internal port at
|
|
7
|
+
* `https://<port>-<id>.e2b.app`, pauses/resumes with filesystem + memory state
|
|
8
|
+
* preserved in ~1s, and governs outbound traffic by a DNS network policy. This
|
|
9
|
+
* bond maps that platform onto the `@molecule/api-code-sandbox` contract through
|
|
10
|
+
* the official `e2b` SDK.
|
|
11
|
+
*
|
|
12
|
+
* The design that makes it fast: a single golden SUPERSET template carries the
|
|
13
|
+
* entire `@molecule` fleet node_modules + postgres + warmed Vite deps, so a boot
|
|
14
|
+
* only copies the ONE selected app's source in and starts the dev servers — no
|
|
15
|
+
* per-boot `npm install`. The 133 flagship template sources are NOT baked into
|
|
16
|
+
* the image; they are copied from the control plane at boot, so templates and
|
|
17
|
+
* `mlcl` stay private.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* import { bond } from '@molecule/api-bond'
|
|
22
|
+
* import { provider } from '@molecule/api-code-sandbox-e2b'
|
|
23
|
+
*
|
|
24
|
+
* bond('codeSandbox', provider)
|
|
25
|
+
* // Requires E2B_API_KEY (and E2B_TEMPLATE_ID for the golden superset template).
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* import { createProvider } from '@molecule/api-code-sandbox-e2b'
|
|
31
|
+
*
|
|
32
|
+
* const provider = createProvider({
|
|
33
|
+
* templateId: 'molecule-superset',
|
|
34
|
+
* defaultPreviewPort: 5173,
|
|
35
|
+
* defaultNetworkRules: [
|
|
36
|
+
* { domain: 'registry.npmjs.org', action: 'allow' },
|
|
37
|
+
* { domain: '*.npmjs.org', action: 'allow' },
|
|
38
|
+
* { domain: 'github.com', action: 'allow' },
|
|
39
|
+
* { domain: '*', action: 'deny' },
|
|
40
|
+
* ],
|
|
41
|
+
* })
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @remarks
|
|
45
|
+
* **`verifyEgress` is intentionally not implemented yet.** The control plane
|
|
46
|
+
* treats an absent `verifyEgress` as an `inconclusive` verdict and refuses to
|
|
47
|
+
* boot sandboxes in production — the correct safe default until egress
|
|
48
|
+
* observation is proven against E2B's `updateNetwork` policy (Rule 18: never
|
|
49
|
+
* trade cost for security). Do not stub it with a fabricated `filtered`.
|
|
50
|
+
*
|
|
51
|
+
* **E2B pauses, it does not stop.** `sleep()`/`stop()` both map to E2B pause
|
|
52
|
+
* (FS + memory snapshot); `wake()`/`start()` reconnect by id. `hibernate()`/
|
|
53
|
+
* `resume()` report `processesPreserved: true` because the memory snapshot
|
|
54
|
+
* restores the process tree — unlike a Docker stop, a resumed E2B sandbox's
|
|
55
|
+
* dev servers are still running.
|
|
56
|
+
*
|
|
57
|
+
* @module
|
|
58
|
+
*/
|
|
59
|
+
export * from './provider.js';
|
|
60
|
+
export * from './types.js';
|
|
61
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AAEH,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2B (e2b.dev) code sandbox provider.
|
|
3
|
+
*
|
|
4
|
+
* E2B runs isolated Firecracker microVMs purpose-built for agent/dev workloads:
|
|
5
|
+
* a sandbox spawns from a golden **template** (a Dockerfile-built image with the
|
|
6
|
+
* whole dependency set baked in) in ~1s, exposes every internal port at
|
|
7
|
+
* `https://<port>-<id>.e2b.app`, pauses/resumes with filesystem + memory state
|
|
8
|
+
* preserved in ~1s, and governs outbound traffic by a DNS network policy. This
|
|
9
|
+
* bond maps that platform onto the `@molecule/api-code-sandbox` contract through
|
|
10
|
+
* the official `e2b` SDK.
|
|
11
|
+
*
|
|
12
|
+
* The design that makes it fast: a single golden SUPERSET template carries the
|
|
13
|
+
* entire `@molecule` fleet node_modules + postgres + warmed Vite deps, so a boot
|
|
14
|
+
* only copies the ONE selected app's source in and starts the dev servers — no
|
|
15
|
+
* per-boot `npm install`. The 133 flagship template sources are NOT baked into
|
|
16
|
+
* the image; they are copied from the control plane at boot, so templates and
|
|
17
|
+
* `mlcl` stay private.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* import { bond } from '@molecule/api-bond'
|
|
22
|
+
* import { provider } from '@molecule/api-code-sandbox-e2b'
|
|
23
|
+
*
|
|
24
|
+
* bond('codeSandbox', provider)
|
|
25
|
+
* // Requires E2B_API_KEY (and E2B_TEMPLATE_ID for the golden superset template).
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* import { createProvider } from '@molecule/api-code-sandbox-e2b'
|
|
31
|
+
*
|
|
32
|
+
* const provider = createProvider({
|
|
33
|
+
* templateId: 'molecule-superset',
|
|
34
|
+
* defaultPreviewPort: 5173,
|
|
35
|
+
* defaultNetworkRules: [
|
|
36
|
+
* { domain: 'registry.npmjs.org', action: 'allow' },
|
|
37
|
+
* { domain: '*.npmjs.org', action: 'allow' },
|
|
38
|
+
* { domain: 'github.com', action: 'allow' },
|
|
39
|
+
* { domain: '*', action: 'deny' },
|
|
40
|
+
* ],
|
|
41
|
+
* })
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @remarks
|
|
45
|
+
* **`verifyEgress` is intentionally not implemented yet.** The control plane
|
|
46
|
+
* treats an absent `verifyEgress` as an `inconclusive` verdict and refuses to
|
|
47
|
+
* boot sandboxes in production — the correct safe default until egress
|
|
48
|
+
* observation is proven against E2B's `updateNetwork` policy (Rule 18: never
|
|
49
|
+
* trade cost for security). Do not stub it with a fabricated `filtered`.
|
|
50
|
+
*
|
|
51
|
+
* **E2B pauses, it does not stop.** `sleep()`/`stop()` both map to E2B pause
|
|
52
|
+
* (FS + memory snapshot); `wake()`/`start()` reconnect by id. `hibernate()`/
|
|
53
|
+
* `resume()` report `processesPreserved: true` because the memory snapshot
|
|
54
|
+
* restores the process tree — unlike a Docker stop, a resumed E2B sandbox's
|
|
55
|
+
* dev servers are still running.
|
|
56
|
+
*
|
|
57
|
+
* @module
|
|
58
|
+
*/
|
|
59
|
+
export * from './provider.js';
|
|
60
|
+
export * from './types.js';
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2B code-sandbox provider implementation.
|
|
3
|
+
*
|
|
4
|
+
* Maps the `@molecule/api-code-sandbox` contract onto E2B's Firecracker microVM
|
|
5
|
+
* platform via the official `e2b` SDK. The SDK is reached through the structural
|
|
6
|
+
* {@link E2BSandboxClientLike} shim so the provider is unit-testable with a fake
|
|
7
|
+
* client; {@link defaultClient} adapts the real `Sandbox` class.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
import type { EgressVerdict, Sandbox, SandboxConfig, SandboxProvider } from '@molecule/api-code-sandbox';
|
|
12
|
+
import type { E2BConfig, E2BSandboxClientLike } from './types.js';
|
|
13
|
+
/**
|
|
14
|
+
* E2B implementation of {@link SandboxProvider}.
|
|
15
|
+
*
|
|
16
|
+
* Only the required surface (`create`/`get`/`list`/`destroy`) plus the boot-path
|
|
17
|
+
* optionals are wired here; `verifyEgress` and `commitTemplate`/`getTemplate`
|
|
18
|
+
* land in follow-up steps. Leaving `verifyEgress` UNimplemented is deliberate:
|
|
19
|
+
* the control plane treats "unsupported" as `inconclusive` and refuses to boot
|
|
20
|
+
* in prod, which is the correct safe default until egress observation is proven
|
|
21
|
+
* (Rule 18 — never trade cost for security).
|
|
22
|
+
*/
|
|
23
|
+
export declare class E2BSandboxProvider implements SandboxProvider {
|
|
24
|
+
readonly name = "e2b";
|
|
25
|
+
private readonly config;
|
|
26
|
+
private clientPromise;
|
|
27
|
+
private readonly clientOverride?;
|
|
28
|
+
/**
|
|
29
|
+
* Construct the provider from config, resolving the API key and template id.
|
|
30
|
+
*
|
|
31
|
+
* @param config - Bond configuration; API key falls back to `E2B_API_KEY`.
|
|
32
|
+
* @param clientOverride - Inject a fake client (tests).
|
|
33
|
+
*/
|
|
34
|
+
constructor(config?: E2BConfig, clientOverride?: E2BSandboxClientLike);
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the SDK client, memoized. Throws when no API key is configured.
|
|
37
|
+
*
|
|
38
|
+
* @returns The E2B client (injected override or real SDK adapter).
|
|
39
|
+
*/
|
|
40
|
+
private client;
|
|
41
|
+
/**
|
|
42
|
+
* Create a new sandbox from the golden template and apply the egress policy.
|
|
43
|
+
*
|
|
44
|
+
* @param config - Project id, env, optional per-boot templateId + labels.
|
|
45
|
+
* @returns A live sandbox handle.
|
|
46
|
+
*/
|
|
47
|
+
create(config: SandboxConfig): Promise<Sandbox>;
|
|
48
|
+
/**
|
|
49
|
+
* Resolve an existing sandbox by id.
|
|
50
|
+
*
|
|
51
|
+
* @param id - The sandbox id.
|
|
52
|
+
* @returns A live handle, or `null` if no sandbox has that id.
|
|
53
|
+
*/
|
|
54
|
+
get(id: string): Promise<Sandbox | null>;
|
|
55
|
+
/**
|
|
56
|
+
* List the caller's live sandboxes.
|
|
57
|
+
*
|
|
58
|
+
* @param _userId - Unused; E2B scopes by API key, not per-user labels here.
|
|
59
|
+
* @returns Live handles for every running sandbox.
|
|
60
|
+
*/
|
|
61
|
+
list(_userId: string): Promise<Sandbox[]>;
|
|
62
|
+
/**
|
|
63
|
+
* Destroy a sandbox, freeing its resources.
|
|
64
|
+
*
|
|
65
|
+
* @param id - The sandbox id.
|
|
66
|
+
*/
|
|
67
|
+
destroy(id: string): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* PROVE egress is deny-by-default by OBSERVING it on a throwaway sandbox.
|
|
70
|
+
*
|
|
71
|
+
* Creates a sandbox, applies `{ allowOut: [npm], denyOut: [ALL_TRAFFIC] }`,
|
|
72
|
+
* then curls an allow-listed host, a non-allow-listed host, AND a raw IP from
|
|
73
|
+
* inside. `filtered` requires BOTH the non-allow-listed host and the raw IP to
|
|
74
|
+
* be blocked while the allow-listed host is reachable — never an attestation.
|
|
75
|
+
* Any failure to run the probe is `inconclusive`, never `filtered` ("could not
|
|
76
|
+
* look" must not read as "safe"). Verified live: E2B blocks raw IPs too.
|
|
77
|
+
*
|
|
78
|
+
* @returns The observed egress verdict.
|
|
79
|
+
*/
|
|
80
|
+
verifyEgress(): Promise<EgressVerdict>;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Create an E2B provider with the given configuration.
|
|
84
|
+
*
|
|
85
|
+
* @param config - Bond configuration; API key falls back to `E2B_API_KEY`.
|
|
86
|
+
* @param clientOverride - Inject a fake client (tests).
|
|
87
|
+
* @returns A configured provider ready to `bond('codeSandbox', provider)`.
|
|
88
|
+
*/
|
|
89
|
+
export declare function createProvider(config?: E2BConfig, clientOverride?: E2BSandboxClientLike): E2BSandboxProvider;
|
|
90
|
+
/** Default provider instance, configured from the environment. */
|
|
91
|
+
export declare const provider: SandboxProvider;
|
|
92
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAEV,aAAa,EAKb,OAAO,EACP,aAAa,EACb,eAAe,EAChB,MAAM,4BAA4B,CAAA;AAEnC,OAAO,KAAK,EAAE,SAAS,EAAE,oBAAoB,EAAkB,MAAM,YAAY,CAAA;AAoRjF;;;;;;;;;GASG;AACH,qBAAa,kBAAmB,YAAW,eAAe;IACxD,QAAQ,CAAC,IAAI,SAAQ;IAErB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0D;IACjF,OAAO,CAAC,aAAa,CAA6C;IAClE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAsB;IAEtD;;;;;OAKG;gBACS,MAAM,GAAE,SAAc,EAAE,cAAc,CAAC,EAAE,oBAAoB;IAYzE;;;;OAIG;YACW,MAAM;IASpB;;;;;OAKG;IACG,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC;IAmBrD;;;;;OAKG;IACG,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAgB9C;;;;;OAKG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAY/C;;;;OAIG;IACG,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcxC;;;;;;;;;;;OAWG;IACG,YAAY,IAAI,OAAO,CAAC,aAAa,CAAC;CAyD7C;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,MAAM,GAAE,SAAc,EACtB,cAAc,CAAC,EAAE,oBAAoB,GACpC,kBAAkB,CAEpB;AAED,kEAAkE;AAClE,eAAO,MAAM,QAAQ,EAAE,eAAkC,CAAA"}
|
package/dist/provider.js
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2B code-sandbox provider implementation.
|
|
3
|
+
*
|
|
4
|
+
* Maps the `@molecule/api-code-sandbox` contract onto E2B's Firecracker microVM
|
|
5
|
+
* platform via the official `e2b` SDK. The SDK is reached through the structural
|
|
6
|
+
* {@link E2BSandboxClientLike} shim so the provider is unit-testable with a fake
|
|
7
|
+
* client; {@link defaultClient} adapts the real `Sandbox` class.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
/** Default Vite dev-server port the preview URL points at. */
|
|
12
|
+
const DEFAULT_PREVIEW_PORT = 5173;
|
|
13
|
+
/** Default sandbox lifetime before E2B auto-pauses (control plane extends per heartbeat). */
|
|
14
|
+
const DEFAULT_TIMEOUT_MS = 60 * 60 * 1000;
|
|
15
|
+
/**
|
|
16
|
+
* E2B's "all destinations" selector (`0.0.0.0/0`, the SDK's `ALL_TRAFFIC`).
|
|
17
|
+
* Put in `denyOut` alongside an `allowOut` list to make egress deny-by-default —
|
|
18
|
+
* E2B REQUIRES this pairing (a bare `allowOut` is a 400) and it blocks raw IPs
|
|
19
|
+
* too, not just DNS names. Inlined as the literal to avoid importing the SDK at
|
|
20
|
+
* module load (the provider imports `e2b` lazily).
|
|
21
|
+
*/
|
|
22
|
+
const ALL_TRAFFIC = '0.0.0.0/0';
|
|
23
|
+
/** Hosts the egress probe treats as the canonical allow/deny witnesses. */
|
|
24
|
+
const EGRESS_PROBE_ALLOW = 'registry.npmjs.org';
|
|
25
|
+
const EGRESS_PROBE_DENY = 'example.com';
|
|
26
|
+
/**
|
|
27
|
+
* Adapt the real `e2b` SDK `Sandbox` class to {@link E2BSandboxClientLike}.
|
|
28
|
+
* Imported lazily so the SDK is only required when the bond is actually used.
|
|
29
|
+
*/
|
|
30
|
+
async function defaultClient(apiKey) {
|
|
31
|
+
const { Sandbox } = await import('e2b');
|
|
32
|
+
const S = Sandbox;
|
|
33
|
+
return {
|
|
34
|
+
create: (templateId, opts) => S.create(templateId, { apiKey, ...opts }),
|
|
35
|
+
connect: (id, opts) => S.connect(id, { apiKey, ...opts }),
|
|
36
|
+
kill: (id, opts) => S.kill(id, { apiKey, ...opts }),
|
|
37
|
+
async list(opts) {
|
|
38
|
+
// Sandbox.list returns a paginator; normalize to a flat array of running
|
|
39
|
+
// sandboxes. Support the async-iterator, nextItems(), and array shapes so
|
|
40
|
+
// a minor SDK change does not silently return nothing.
|
|
41
|
+
const result = S.list({ apiKey, ...opts });
|
|
42
|
+
const out = [];
|
|
43
|
+
const push = (items) => {
|
|
44
|
+
for (const it of items)
|
|
45
|
+
if (it?.sandboxId)
|
|
46
|
+
out.push({ sandboxId: it.sandboxId });
|
|
47
|
+
};
|
|
48
|
+
const r = await Promise.resolve(result).catch(() => result);
|
|
49
|
+
if (Array.isArray(r)) {
|
|
50
|
+
push(r);
|
|
51
|
+
}
|
|
52
|
+
else if (r && typeof r.nextItems === 'function') {
|
|
53
|
+
const pager = r;
|
|
54
|
+
do {
|
|
55
|
+
push(await pager.nextItems());
|
|
56
|
+
} while (pager.hasNext);
|
|
57
|
+
}
|
|
58
|
+
else if (r &&
|
|
59
|
+
typeof r[Symbol.asyncIterator] === 'function') {
|
|
60
|
+
for await (const it of r)
|
|
61
|
+
push([it]);
|
|
62
|
+
}
|
|
63
|
+
else if (r && Array.isArray(r.sandboxes)) {
|
|
64
|
+
push(r.sandboxes);
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Build a deny-by-default network update from an allow-list.
|
|
72
|
+
*
|
|
73
|
+
* @param allowOut - Domains/CIDRs permitted; everything else is denied.
|
|
74
|
+
* @returns The SDK `updateNetwork` payload (allowOut + denyOut ALL_TRAFFIC).
|
|
75
|
+
*/
|
|
76
|
+
function denyByDefault(allowOut) {
|
|
77
|
+
return { allowOut, denyOut: [ALL_TRAFFIC] };
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* A live E2B sandbox mapped onto the core `Sandbox` handle.
|
|
81
|
+
*
|
|
82
|
+
* E2B pauses/resumes rather than start/stop; a resume yields a NEW underlying
|
|
83
|
+
* SDK instance, so the handle re-resolves it by id on wake and swaps
|
|
84
|
+
* {@link E2BSandbox.sbx}. `getHost` is a pure string builder
|
|
85
|
+
* (`<port>-<id>.e2b.app`) so `previewUrl` is known the instant the id is.
|
|
86
|
+
*/
|
|
87
|
+
class E2BSandbox {
|
|
88
|
+
id;
|
|
89
|
+
status = 'running';
|
|
90
|
+
previewUrl;
|
|
91
|
+
sbx;
|
|
92
|
+
client;
|
|
93
|
+
previewPort;
|
|
94
|
+
timeoutMs;
|
|
95
|
+
/**
|
|
96
|
+
* Wrap a live E2B SDK sandbox as a core `Sandbox` handle.
|
|
97
|
+
*
|
|
98
|
+
* @param sbx - The live E2B SDK sandbox instance.
|
|
99
|
+
* @param client - The client used to reconnect the instance on resume.
|
|
100
|
+
* @param opts - Preview port and auto-pause timeout for this sandbox.
|
|
101
|
+
*/
|
|
102
|
+
constructor(sbx, client, opts) {
|
|
103
|
+
this.sbx = sbx;
|
|
104
|
+
this.id = sbx.sandboxId;
|
|
105
|
+
this.client = client;
|
|
106
|
+
this.previewPort = opts.previewPort;
|
|
107
|
+
this.timeoutMs = opts.timeoutMs;
|
|
108
|
+
this.previewUrl = this.getPreviewUrl();
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The public HTTPS URL for a port inside the sandbox.
|
|
112
|
+
*
|
|
113
|
+
* @param port - Sandbox port; defaults to the configured preview port.
|
|
114
|
+
* @returns `https://<port>-<id>.e2b.app`.
|
|
115
|
+
*/
|
|
116
|
+
getPreviewUrl(port) {
|
|
117
|
+
return `https://${this.sbx.getHost(port ?? this.previewPort)}`;
|
|
118
|
+
}
|
|
119
|
+
/** Resume the sandbox (E2B has no separate cold start). */
|
|
120
|
+
async start() {
|
|
121
|
+
await this.wake();
|
|
122
|
+
}
|
|
123
|
+
/** Pause the sandbox (E2B has no stop distinct from pause). */
|
|
124
|
+
async stop() {
|
|
125
|
+
// E2B has no stop-without-losing-state distinct from pause; treat as pause.
|
|
126
|
+
await this.sleep();
|
|
127
|
+
}
|
|
128
|
+
/** Pause the sandbox, discarding the hibernation outcome. */
|
|
129
|
+
async sleep() {
|
|
130
|
+
await this.hibernate();
|
|
131
|
+
}
|
|
132
|
+
/** Resume the sandbox, discarding the hibernation outcome. */
|
|
133
|
+
async wake() {
|
|
134
|
+
await this.resume();
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Pause the sandbox (E2B FS + memory snapshot).
|
|
138
|
+
*
|
|
139
|
+
* @returns The outcome; `processesPreserved` is true because the memory
|
|
140
|
+
* snapshot restores the process tree on resume.
|
|
141
|
+
*/
|
|
142
|
+
async hibernate() {
|
|
143
|
+
const pause = this.sbx.betaPause ?? this.sbx.pause;
|
|
144
|
+
if (!pause) {
|
|
145
|
+
// No pause available — nothing we can honestly suspend; report it.
|
|
146
|
+
return { processesPreserved: true, mechanism: 'noop', detail: 'pause not supported by SDK' };
|
|
147
|
+
}
|
|
148
|
+
await pause.call(this.sbx);
|
|
149
|
+
this.status = 'sleeping';
|
|
150
|
+
// E2B pause snapshots FS + memory, so the process tree survives resume.
|
|
151
|
+
return { processesPreserved: true, mechanism: 'pause' };
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Resume the sandbox by reconnecting to a fresh live instance for its id.
|
|
155
|
+
*
|
|
156
|
+
* @returns The outcome; `processesPreserved` is true (memory snapshot).
|
|
157
|
+
*/
|
|
158
|
+
async resume() {
|
|
159
|
+
// Reconnect resolves a fresh live instance for the same id (a no-op if it is
|
|
160
|
+
// already running); swap it in so subsequent calls hit the live sandbox.
|
|
161
|
+
this.sbx = await this.client.connect(this.id, { timeoutMs: this.timeoutMs });
|
|
162
|
+
this.status = 'running';
|
|
163
|
+
this.previewUrl = this.getPreviewUrl();
|
|
164
|
+
return { processesPreserved: true, mechanism: 'resume' };
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Run a command to completion in the sandbox.
|
|
168
|
+
*
|
|
169
|
+
* @param command - The shell command to run.
|
|
170
|
+
* @param opts - Working directory, timeout (ms), and env vars.
|
|
171
|
+
* @returns stdout, stderr and the exit code.
|
|
172
|
+
*/
|
|
173
|
+
async exec(command, opts) {
|
|
174
|
+
const r = await this.sbx.commands.run(command, {
|
|
175
|
+
cwd: opts?.cwd,
|
|
176
|
+
timeoutMs: opts?.timeout,
|
|
177
|
+
envs: opts?.env,
|
|
178
|
+
});
|
|
179
|
+
return { stdout: r.stdout, stderr: r.stderr, exitCode: r.exitCode };
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Read a file's contents as text.
|
|
183
|
+
*
|
|
184
|
+
* @param path - Absolute path inside the sandbox.
|
|
185
|
+
* @returns The file contents.
|
|
186
|
+
*/
|
|
187
|
+
async readFile(path) {
|
|
188
|
+
return this.sbx.files.read(path);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Write text to a file, creating parent directories as needed.
|
|
192
|
+
*
|
|
193
|
+
* @param path - Absolute path inside the sandbox.
|
|
194
|
+
* @param content - The text to write.
|
|
195
|
+
*/
|
|
196
|
+
async writeFile(path, content) {
|
|
197
|
+
await this.sbx.files.write(path, content);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* List a directory. Throws when the path does not exist (an empty array
|
|
201
|
+
* means "exists and is empty", never "missing").
|
|
202
|
+
*
|
|
203
|
+
* @param path - Absolute directory path inside the sandbox.
|
|
204
|
+
* @returns The directory entries.
|
|
205
|
+
*/
|
|
206
|
+
async readDir(path) {
|
|
207
|
+
// E2B throws when the path is missing (contract: empty array ONLY means
|
|
208
|
+
// "exists and empty"), so we do not catch here.
|
|
209
|
+
const entries = await this.sbx.files.list(path);
|
|
210
|
+
return entries.map((e) => ({
|
|
211
|
+
name: e.name,
|
|
212
|
+
type: e.type === 'dir' || e.type === 'directory' ? 'directory' : 'file',
|
|
213
|
+
...(typeof e.size === 'number' ? { size: e.size } : {}),
|
|
214
|
+
}));
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Delete a file.
|
|
218
|
+
*
|
|
219
|
+
* @param path - Absolute path inside the sandbox.
|
|
220
|
+
*/
|
|
221
|
+
async deleteFile(path) {
|
|
222
|
+
await this.sbx.files.remove(path);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Subscribe to filesystem changes. Not wired for E2B (the control plane polls
|
|
226
|
+
* the tree); returns a no-op unsubscribe so register-and-forget callers are safe.
|
|
227
|
+
*
|
|
228
|
+
* @param _cb - Ignored change callback.
|
|
229
|
+
* @returns A no-op unsubscribe function.
|
|
230
|
+
*/
|
|
231
|
+
onFileChange(_cb) {
|
|
232
|
+
// E2B exposes filesystem watch via files.watchDir; the control plane polls
|
|
233
|
+
// the tree rather than subscribing, so this bond does not wire it yet.
|
|
234
|
+
// Returning a no-op unsubscribe keeps callers that register-and-forget safe.
|
|
235
|
+
return () => { };
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Extend the sandbox's auto-pause deadline (heartbeat).
|
|
239
|
+
*
|
|
240
|
+
* @param ms - New lifetime in milliseconds from now.
|
|
241
|
+
*/
|
|
242
|
+
async keepAlive(ms) {
|
|
243
|
+
await this.sbx.setTimeout(ms);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Apply a deny-by-default egress allow-list to this sandbox.
|
|
247
|
+
*
|
|
248
|
+
* @param allowOut - Domains/CIDRs to permit; everything else denied. A no-op
|
|
249
|
+
* when empty or when the SDK build lacks `updateNetwork`.
|
|
250
|
+
*/
|
|
251
|
+
async applyNetwork(allowOut) {
|
|
252
|
+
if (!this.sbx.updateNetwork || allowOut.length === 0)
|
|
253
|
+
return;
|
|
254
|
+
await this.sbx.updateNetwork(denyByDefault(allowOut));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* E2B implementation of {@link SandboxProvider}.
|
|
259
|
+
*
|
|
260
|
+
* Only the required surface (`create`/`get`/`list`/`destroy`) plus the boot-path
|
|
261
|
+
* optionals are wired here; `verifyEgress` and `commitTemplate`/`getTemplate`
|
|
262
|
+
* land in follow-up steps. Leaving `verifyEgress` UNimplemented is deliberate:
|
|
263
|
+
* the control plane treats "unsupported" as `inconclusive` and refuses to boot
|
|
264
|
+
* in prod, which is the correct safe default until egress observation is proven
|
|
265
|
+
* (Rule 18 — never trade cost for security).
|
|
266
|
+
*/
|
|
267
|
+
export class E2BSandboxProvider {
|
|
268
|
+
name = 'e2b';
|
|
269
|
+
config;
|
|
270
|
+
clientPromise = null;
|
|
271
|
+
clientOverride;
|
|
272
|
+
/**
|
|
273
|
+
* Construct the provider from config, resolving the API key and template id.
|
|
274
|
+
*
|
|
275
|
+
* @param config - Bond configuration; API key falls back to `E2B_API_KEY`.
|
|
276
|
+
* @param clientOverride - Inject a fake client (tests).
|
|
277
|
+
*/
|
|
278
|
+
constructor(config = {}, clientOverride) {
|
|
279
|
+
const apiKey = config.apiKey ?? process.env.E2B_API_KEY ?? '';
|
|
280
|
+
this.config = {
|
|
281
|
+
apiKey,
|
|
282
|
+
templateId: config.templateId ?? process.env.E2B_TEMPLATE_ID ?? 'base',
|
|
283
|
+
defaultPreviewPort: config.defaultPreviewPort ?? DEFAULT_PREVIEW_PORT,
|
|
284
|
+
defaultTimeoutMs: config.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
285
|
+
defaultAllowOut: config.defaultAllowOut ?? [],
|
|
286
|
+
};
|
|
287
|
+
this.clientOverride = clientOverride;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Resolve the SDK client, memoized. Throws when no API key is configured.
|
|
291
|
+
*
|
|
292
|
+
* @returns The E2B client (injected override or real SDK adapter).
|
|
293
|
+
*/
|
|
294
|
+
async client() {
|
|
295
|
+
if (this.clientOverride)
|
|
296
|
+
return this.clientOverride;
|
|
297
|
+
if (!this.config.apiKey) {
|
|
298
|
+
throw new Error('E2B provider requires an API key (config.apiKey or E2B_API_KEY)');
|
|
299
|
+
}
|
|
300
|
+
if (!this.clientPromise)
|
|
301
|
+
this.clientPromise = defaultClient(this.config.apiKey);
|
|
302
|
+
return this.clientPromise;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Create a new sandbox from the golden template and apply the egress policy.
|
|
306
|
+
*
|
|
307
|
+
* @param config - Project id, env, optional per-boot templateId + labels.
|
|
308
|
+
* @returns A live sandbox handle.
|
|
309
|
+
*/
|
|
310
|
+
async create(config) {
|
|
311
|
+
const client = await this.client();
|
|
312
|
+
const templateId = config.templateId ?? this.config.templateId;
|
|
313
|
+
const sbx = await client.create(templateId, {
|
|
314
|
+
timeoutMs: this.config.defaultTimeoutMs,
|
|
315
|
+
envs: config.env ?? {},
|
|
316
|
+
metadata: { projectId: config.projectId, ...(config.labels ?? {}) },
|
|
317
|
+
});
|
|
318
|
+
const handle = new E2BSandbox(sbx, client, {
|
|
319
|
+
previewPort: this.config.defaultPreviewPort,
|
|
320
|
+
timeoutMs: this.config.defaultTimeoutMs,
|
|
321
|
+
});
|
|
322
|
+
// Apply the egress allow-list immediately, before any user code runs.
|
|
323
|
+
if (this.config.defaultAllowOut.length > 0) {
|
|
324
|
+
await handle.applyNetwork(this.config.defaultAllowOut);
|
|
325
|
+
}
|
|
326
|
+
return handle;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Resolve an existing sandbox by id.
|
|
330
|
+
*
|
|
331
|
+
* @param id - The sandbox id.
|
|
332
|
+
* @returns A live handle, or `null` if no sandbox has that id.
|
|
333
|
+
*/
|
|
334
|
+
async get(id) {
|
|
335
|
+
const client = await this.client();
|
|
336
|
+
try {
|
|
337
|
+
const sbx = await client.connect(id, { timeoutMs: this.config.defaultTimeoutMs });
|
|
338
|
+
return new E2BSandbox(sbx, client, {
|
|
339
|
+
previewPort: this.config.defaultPreviewPort,
|
|
340
|
+
timeoutMs: this.config.defaultTimeoutMs,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
catch (_error) {
|
|
344
|
+
// connect throws SandboxNotFoundError for a genuinely absent id → null.
|
|
345
|
+
// A transient error also lands here; get() is used as an existence probe,
|
|
346
|
+
// and the callers re-resolve, so returning null is the safe answer.
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* List the caller's live sandboxes.
|
|
352
|
+
*
|
|
353
|
+
* @param _userId - Unused; E2B scopes by API key, not per-user labels here.
|
|
354
|
+
* @returns Live handles for every running sandbox.
|
|
355
|
+
*/
|
|
356
|
+
async list(_userId) {
|
|
357
|
+
const client = await this.client();
|
|
358
|
+
const running = await client.list({});
|
|
359
|
+
const items = Array.isArray(running) ? running : (running.sandboxes ?? []);
|
|
360
|
+
const handles = [];
|
|
361
|
+
for (const it of items) {
|
|
362
|
+
const h = await this.get(it.sandboxId);
|
|
363
|
+
if (h)
|
|
364
|
+
handles.push(h);
|
|
365
|
+
}
|
|
366
|
+
return handles;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Destroy a sandbox, freeing its resources.
|
|
370
|
+
*
|
|
371
|
+
* @param id - The sandbox id.
|
|
372
|
+
*/
|
|
373
|
+
async destroy(id) {
|
|
374
|
+
const client = await this.client();
|
|
375
|
+
if (client.kill) {
|
|
376
|
+
await client.kill(id, {});
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const sbx = await client.connect(id, {}).catch((error) => {
|
|
380
|
+
// Already gone / unreachable — nothing to kill; destroy is idempotent.
|
|
381
|
+
void error;
|
|
382
|
+
return null;
|
|
383
|
+
});
|
|
384
|
+
if (sbx)
|
|
385
|
+
await sbx.kill();
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* PROVE egress is deny-by-default by OBSERVING it on a throwaway sandbox.
|
|
389
|
+
*
|
|
390
|
+
* Creates a sandbox, applies `{ allowOut: [npm], denyOut: [ALL_TRAFFIC] }`,
|
|
391
|
+
* then curls an allow-listed host, a non-allow-listed host, AND a raw IP from
|
|
392
|
+
* inside. `filtered` requires BOTH the non-allow-listed host and the raw IP to
|
|
393
|
+
* be blocked while the allow-listed host is reachable — never an attestation.
|
|
394
|
+
* Any failure to run the probe is `inconclusive`, never `filtered` ("could not
|
|
395
|
+
* look" must not read as "safe"). Verified live: E2B blocks raw IPs too.
|
|
396
|
+
*
|
|
397
|
+
* @returns The observed egress verdict.
|
|
398
|
+
*/
|
|
399
|
+
async verifyEgress() {
|
|
400
|
+
let handle = null;
|
|
401
|
+
try {
|
|
402
|
+
handle = await this.create({ projectId: `egress-probe-${Date.now()}`, env: {} });
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
return {
|
|
406
|
+
state: 'inconclusive',
|
|
407
|
+
detail: `Could not create a probe sandbox: ${error instanceof Error ? error.message : String(error)}`,
|
|
408
|
+
remediation: 'Check E2B_API_KEY and account capacity.',
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
try {
|
|
412
|
+
// Force a KNOWN deny-default policy for the probe regardless of config.
|
|
413
|
+
await handle.applyNetwork([
|
|
414
|
+
EGRESS_PROBE_ALLOW,
|
|
415
|
+
`*.${EGRESS_PROBE_ALLOW.split('.').slice(-2).join('.')}`,
|
|
416
|
+
]);
|
|
417
|
+
const code = async (host) => (await handle.exec(`curl -s -o /dev/null -m 8 -w '%{http_code}' https://${host}/ || echo 000`)).stdout.trim();
|
|
418
|
+
const [allowed, deniedHost, deniedIp] = await Promise.all([
|
|
419
|
+
code(EGRESS_PROBE_ALLOW),
|
|
420
|
+
code(EGRESS_PROBE_DENY),
|
|
421
|
+
code('1.1.1.1'),
|
|
422
|
+
]);
|
|
423
|
+
const blocked = (c) => c === '' || c.startsWith('000');
|
|
424
|
+
if (blocked(deniedHost) && blocked(deniedIp) && !blocked(allowed)) {
|
|
425
|
+
return {
|
|
426
|
+
state: 'filtered',
|
|
427
|
+
detail: `deny-by-default verified: ${EGRESS_PROBE_ALLOW}=${allowed} reachable; ${EGRESS_PROBE_DENY}=${deniedHost} and raw IP=${deniedIp} blocked.`,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
return {
|
|
431
|
+
state: 'open',
|
|
432
|
+
detail: `Non-allow-listed egress was reachable (host=${deniedHost}, rawIP=${deniedIp}, allowed=${allowed}).`,
|
|
433
|
+
remediation: 'Ensure updateNetwork applies allowOut + denyOut:[0.0.0.0/0]; check the E2B account supports network policy.',
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
catch (error) {
|
|
437
|
+
return {
|
|
438
|
+
state: 'inconclusive',
|
|
439
|
+
detail: `Egress probe could not run: ${error instanceof Error ? error.message : String(error)}`,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
finally {
|
|
443
|
+
// Best-effort cleanup of the throwaway probe sandbox; a failed destroy
|
|
444
|
+
// must not mask the verdict (E2B auto-pauses idle sandboxes regardless).
|
|
445
|
+
if (handle) {
|
|
446
|
+
await this.destroy(handle.id).catch((_error) => {
|
|
447
|
+
// intentional noop — probe teardown is best-effort; the sandbox
|
|
448
|
+
// auto-pauses and the verdict is what matters.
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Create an E2B provider with the given configuration.
|
|
456
|
+
*
|
|
457
|
+
* @param config - Bond configuration; API key falls back to `E2B_API_KEY`.
|
|
458
|
+
* @param clientOverride - Inject a fake client (tests).
|
|
459
|
+
* @returns A configured provider ready to `bond('codeSandbox', provider)`.
|
|
460
|
+
*/
|
|
461
|
+
export function createProvider(config = {}, clientOverride) {
|
|
462
|
+
return new E2BSandboxProvider(config, clientOverride);
|
|
463
|
+
}
|
|
464
|
+
/** Default provider instance, configured from the environment. */
|
|
465
|
+
export const provider = createProvider();
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration + narrow structural shims for the E2B code-sandbox bond.
|
|
3
|
+
*
|
|
4
|
+
* The bond talks to E2B through the official `e2b` SDK; these types capture only
|
|
5
|
+
* the slice of that SDK surface the provider touches, plus the bond's own
|
|
6
|
+
* configuration. Keeping them structural (rather than importing the SDK's own
|
|
7
|
+
* types) keeps the provider testable with a fake client and documents exactly
|
|
8
|
+
* which SDK methods this bond depends on.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
/** Bond configuration. All fields have safe defaults; see {@link createProvider}. */
|
|
13
|
+
export interface E2BConfig {
|
|
14
|
+
/**
|
|
15
|
+
* E2B API key. Falls back to `E2B_API_KEY` in the environment. Required — the
|
|
16
|
+
* provider throws on first use if neither is set.
|
|
17
|
+
*/
|
|
18
|
+
apiKey?: string;
|
|
19
|
+
/**
|
|
20
|
+
* The golden template id every sandbox boots from. Falls back to
|
|
21
|
+
* `E2B_TEMPLATE_ID`, then E2B's `base` template. This is the caller's OWN
|
|
22
|
+
* identifier for the superset template (fleet node_modules + postgres +
|
|
23
|
+
* warmed vite deps), built out of band by the template pipeline.
|
|
24
|
+
*/
|
|
25
|
+
templateId?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Default port the preview URL points at (the app's Vite dev server).
|
|
28
|
+
* `getPreviewUrl()` uses this when no port is given.
|
|
29
|
+
*/
|
|
30
|
+
defaultPreviewPort?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Default sandbox lifetime before E2B auto-pauses it, in milliseconds. The
|
|
33
|
+
* control plane extends this per-heartbeat; this is only the initial ceiling.
|
|
34
|
+
*/
|
|
35
|
+
defaultTimeoutMs?: number;
|
|
36
|
+
/**
|
|
37
|
+
* Egress allow-list (domains / CIDRs) applied to every sandbox at create
|
|
38
|
+
* time; everything else is denied (`denyOut: [ALL_TRAFFIC]`). Wildcards like
|
|
39
|
+
* `*.npmjs.org` are supported. Empty/omitted means the bond does NOT
|
|
40
|
+
* constrain egress — prod must supply this or `verifyEgress` observes `open`
|
|
41
|
+
* and the control plane refuses to boot (Rule 18).
|
|
42
|
+
*
|
|
43
|
+
* Verified against a live E2B sandbox: with `denyOut: [ALL_TRAFFIC]`, a
|
|
44
|
+
* non-allowlisted host AND a raw destination IP are both blocked — a stronger
|
|
45
|
+
* boundary than a DNS-only policy.
|
|
46
|
+
*/
|
|
47
|
+
defaultAllowOut?: string[];
|
|
48
|
+
}
|
|
49
|
+
/** Result of an E2B command run (subset of the SDK's `CommandResult`). */
|
|
50
|
+
export interface E2BCommandResultLike {
|
|
51
|
+
stdout: string;
|
|
52
|
+
stderr: string;
|
|
53
|
+
exitCode: number;
|
|
54
|
+
}
|
|
55
|
+
/** Subset of the SDK's `Filesystem` the bond uses. */
|
|
56
|
+
export interface E2BFilesystemLike {
|
|
57
|
+
read(path: string): Promise<string>;
|
|
58
|
+
write(path: string, data: string): Promise<unknown>;
|
|
59
|
+
list(path: string): Promise<Array<{
|
|
60
|
+
name: string;
|
|
61
|
+
type?: string;
|
|
62
|
+
size?: number;
|
|
63
|
+
}>>;
|
|
64
|
+
remove(path: string): Promise<void>;
|
|
65
|
+
}
|
|
66
|
+
/** Subset of the SDK's `Commands` the bond uses. */
|
|
67
|
+
export interface E2BCommandsLike {
|
|
68
|
+
run(cmd: string, opts?: {
|
|
69
|
+
cwd?: string;
|
|
70
|
+
timeoutMs?: number;
|
|
71
|
+
envs?: Record<string, string>;
|
|
72
|
+
}): Promise<E2BCommandResultLike>;
|
|
73
|
+
}
|
|
74
|
+
/** Subset of the SDK's `Sandbox` instance the bond uses. */
|
|
75
|
+
export interface E2BSandboxLike {
|
|
76
|
+
sandboxId: string;
|
|
77
|
+
commands: E2BCommandsLike;
|
|
78
|
+
files: E2BFilesystemLike;
|
|
79
|
+
getHost(port: number): string;
|
|
80
|
+
setTimeout(ms: number): Promise<void>;
|
|
81
|
+
kill(): Promise<void>;
|
|
82
|
+
pause?(): Promise<string>;
|
|
83
|
+
betaPause?(): Promise<string>;
|
|
84
|
+
isRunning(): Promise<boolean>;
|
|
85
|
+
updateNetwork?(opts: {
|
|
86
|
+
allowOut?: string[];
|
|
87
|
+
denyOut?: string[];
|
|
88
|
+
}): Promise<void>;
|
|
89
|
+
}
|
|
90
|
+
/** Subset of the SDK's `Sandbox` static surface the bond uses. */
|
|
91
|
+
export interface E2BSandboxClientLike {
|
|
92
|
+
create(templateId: string, opts?: Record<string, unknown>): Promise<E2BSandboxLike>;
|
|
93
|
+
connect(sandboxId: string, opts?: Record<string, unknown>): Promise<E2BSandboxLike>;
|
|
94
|
+
list(opts?: Record<string, unknown>): Promise<Array<{
|
|
95
|
+
sandboxId: string;
|
|
96
|
+
}> | {
|
|
97
|
+
sandboxes?: Array<{
|
|
98
|
+
sandboxId: string;
|
|
99
|
+
}>;
|
|
100
|
+
}>;
|
|
101
|
+
kill?(sandboxId: string, opts?: Record<string, unknown>): Promise<boolean>;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,qFAAqF;AACrF,MAAM,WAAW,SAAS;IACxB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;;;;;;;;OAUG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;CAC3B;AAED,0EAA0E;AAC1E,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,sDAAsD;AACtD,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACnC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACnD,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAA;IAClF,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACpC;AAED,oDAAoD;AACpD,MAAM,WAAW,eAAe;IAC9B,GAAG,CACD,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GACzE,OAAO,CAAC,oBAAoB,CAAC,CAAA;CACjC;AAED,4DAA4D;AAC5D,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,eAAe,CAAA;IACzB,KAAK,EAAE,iBAAiB,CAAA;IACxB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;IAC7B,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACrC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACrB,KAAK,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;IACzB,SAAS,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;IAC7B,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;IAC7B,aAAa,CAAC,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACjF;AAED,kEAAkE;AAClE,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;IACnF,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;IACnF,IAAI,CACF,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAAC,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG;QAAE,SAAS,CAAC,EAAE,KAAK,CAAC;YAAE,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,CAAA;IACvF,IAAI,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CAC3E"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration + narrow structural shims for the E2B code-sandbox bond.
|
|
3
|
+
*
|
|
4
|
+
* The bond talks to E2B through the official `e2b` SDK; these types capture only
|
|
5
|
+
* the slice of that SDK surface the provider touches, plus the bond's own
|
|
6
|
+
* configuration. Keeping them structural (rather than importing the SDK's own
|
|
7
|
+
* types) keeps the provider testable with a fake client and documents exactly
|
|
8
|
+
* which SDK methods this bond depends on.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@molecule/api-code-sandbox-e2b",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "E2B (e2b.dev) code sandbox provider — Firecracker microVMs with golden templates, fork, and pause/resume",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"test": "vitest run",
|
|
11
|
+
"test:watch": "vitest"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"keywords": [
|
|
24
|
+
"molecule",
|
|
25
|
+
"sandbox",
|
|
26
|
+
"e2b",
|
|
27
|
+
"firecracker",
|
|
28
|
+
"code-execution"
|
|
29
|
+
],
|
|
30
|
+
"license": "Apache-2.0",
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@molecule/api-bond": "1.0.1",
|
|
33
|
+
"@molecule/api-code-sandbox": "1.0.3",
|
|
34
|
+
"@types/node": "26.1.2",
|
|
35
|
+
"typescript": "6.0.3",
|
|
36
|
+
"vitest": "4.1.10"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"@molecule/api-bond": "^1.0.1",
|
|
40
|
+
"@molecule/api-code-sandbox": "^1.0.1",
|
|
41
|
+
"@molecule/api-i18n": "^1.0.1"
|
|
42
|
+
},
|
|
43
|
+
"peerDependenciesMeta": {
|
|
44
|
+
"@molecule/api-code-sandbox": {
|
|
45
|
+
"optional": true
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "https://github.com/molecule-dev/molecule.git",
|
|
51
|
+
"directory": "packages/api/bonds/code-sandbox/e2b"
|
|
52
|
+
},
|
|
53
|
+
"homepage": "https://github.com/molecule-dev/molecule/tree/main/packages/api/bonds/code-sandbox/e2b",
|
|
54
|
+
"bugs": "https://github.com/molecule-dev/molecule/issues",
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"access": "public"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"e2b": "2.38.3"
|
|
60
|
+
}
|
|
61
|
+
}
|