@molecule/api-code-sandbox-e2b 1.2.4 → 1.2.6
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 +8 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/provider.d.ts +16 -0
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +87 -12
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@ AUTO-GENERATED — DO NOT EDIT THIS FILE.
|
|
|
3
3
|
Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
|
|
4
4
|
Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
|
|
5
5
|
To change this document, edit the module-level JSDoc in src/index.ts.
|
|
6
|
-
Generated: 2026-10-
|
|
6
|
+
Generated: 2026-10-04T09:45:27.540Z
|
|
7
7
|
-->
|
|
8
8
|
|
|
9
9
|
# @molecule/api-code-sandbox-e2b
|
|
@@ -603,5 +603,12 @@ default is `onTimeout: 'kill'`, so a sandbox nothing touched for its lifetime
|
|
|
603
603
|
would be destroyed with its files. This bond creates every sandbox with
|
|
604
604
|
`lifecycle: { onTimeout: { action: 'pause', keepMemory: true } }`; the memory
|
|
605
605
|
snapshot is what lets `resume()` truthfully report `processesPreserved: true`.
|
|
606
|
+
The one exception is the throwaway `verifyEgress()` probe, created with
|
|
607
|
+
`lifecycle: { onTimeout: 'kill' }`: a pause writes a full snapshot to the
|
|
608
|
+
host's disk, and nobody resumes a probe. Every pause and every
|
|
609
|
+
`commitTemplate()` is a new snapshot build on a self-hosted box's disk, so
|
|
610
|
+
a pause-on-timeout sandbox you do not need back is disk you pay for — keep a
|
|
611
|
+
sandbox you intend to keep warm alive with `keepAlive(ms)`, and destroy one
|
|
612
|
+
you are done with rather than letting it time out.
|
|
606
613
|
Extending the deadline is `keepAlive(ms)` — call it from a real activity
|
|
607
614
|
signal (an open editor's heartbeat), never as a side effect of polling.
|
package/dist/index.d.ts
CHANGED
|
@@ -144,6 +144,13 @@
|
|
|
144
144
|
* would be destroyed with its files. This bond creates every sandbox with
|
|
145
145
|
* `lifecycle: { onTimeout: { action: 'pause', keepMemory: true } }`; the memory
|
|
146
146
|
* snapshot is what lets `resume()` truthfully report `processesPreserved: true`.
|
|
147
|
+
* The one exception is the throwaway `verifyEgress()` probe, created with
|
|
148
|
+
* `lifecycle: { onTimeout: 'kill' }`: a pause writes a full snapshot to the
|
|
149
|
+
* host's disk, and nobody resumes a probe. Every pause and every
|
|
150
|
+
* `commitTemplate()` is a new snapshot build on a self-hosted box's disk, so
|
|
151
|
+
* a pause-on-timeout sandbox you do not need back is disk you pay for — keep a
|
|
152
|
+
* sandbox you intend to keep warm alive with `keepAlive(ms)`, and destroy one
|
|
153
|
+
* you are done with rather than letting it time out.
|
|
147
154
|
* Extending the deadline is `keepAlive(ms)` — call it from a real activity
|
|
148
155
|
* signal (an open editor's heartbeat), never as a side effect of polling.
|
|
149
156
|
*
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6JG;AAEH,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -144,6 +144,13 @@
|
|
|
144
144
|
* would be destroyed with its files. This bond creates every sandbox with
|
|
145
145
|
* `lifecycle: { onTimeout: { action: 'pause', keepMemory: true } }`; the memory
|
|
146
146
|
* snapshot is what lets `resume()` truthfully report `processesPreserved: true`.
|
|
147
|
+
* The one exception is the throwaway `verifyEgress()` probe, created with
|
|
148
|
+
* `lifecycle: { onTimeout: 'kill' }`: a pause writes a full snapshot to the
|
|
149
|
+
* host's disk, and nobody resumes a probe. Every pause and every
|
|
150
|
+
* `commitTemplate()` is a new snapshot build on a self-hosted box's disk, so
|
|
151
|
+
* a pause-on-timeout sandbox you do not need back is disk you pay for — keep a
|
|
152
|
+
* sandbox you intend to keep warm alive with `keepAlive(ms)`, and destroy one
|
|
153
|
+
* you are done with rather than letting it time out.
|
|
147
154
|
* Extending the deadline is `keepAlive(ms)` — call it from a real activity
|
|
148
155
|
* signal (an open editor's heartbeat), never as a side effect of polling.
|
|
149
156
|
*
|
package/dist/provider.d.ts
CHANGED
|
@@ -64,6 +64,22 @@ export declare class E2BSandboxProvider implements SandboxProvider {
|
|
|
64
64
|
* @returns A live sandbox handle.
|
|
65
65
|
*/
|
|
66
66
|
create(config: SandboxConfig): Promise<Sandbox>;
|
|
67
|
+
/**
|
|
68
|
+
* {@link create} with the sandbox's timeout action chosen by the caller.
|
|
69
|
+
*
|
|
70
|
+
* `'pause'` is E2B's `lifecycle: { onTimeout: { action: 'pause', keepMemory:
|
|
71
|
+
* true } }` — the data-safe default for anything that may hold a project
|
|
72
|
+
* (see {@link create}). `'kill'` is `lifecycle: { onTimeout: 'kill' }` — for a
|
|
73
|
+
* THROWAWAY sandbox this bond destroys itself (the egress probe): pausing one
|
|
74
|
+
* at its deadline would write a full memory + filesystem snapshot to the
|
|
75
|
+
* host's disk for a sandbox nobody will ever resume. E2B cannot change a
|
|
76
|
+
* sandbox's `onTimeout` after create, so the choice is made here, once.
|
|
77
|
+
*
|
|
78
|
+
* @param config - As for {@link create}.
|
|
79
|
+
* @param onTimeout - What E2B does at the sandbox's deadline.
|
|
80
|
+
* @returns A live sandbox handle.
|
|
81
|
+
*/
|
|
82
|
+
private createSandbox;
|
|
67
83
|
/**
|
|
68
84
|
* Resolve an existing sandbox by id.
|
|
69
85
|
*
|
package/dist/provider.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EACV,qBAAqB,EAErB,aAAa,EAKb,oBAAoB,EACpB,kBAAkB,EAClB,OAAO,EACP,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,eAAe,EAGf,UAAU,EACX,MAAM,4BAA4B,CAAA;AAEnC,OAAO,KAAK,EAGV,SAAS,EACT,oBAAoB,EAMrB,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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"}
|
package/dist/provider.js
CHANGED
|
@@ -128,6 +128,8 @@ function bareSnapshotName(snapshot) {
|
|
|
128
128
|
const withoutTag = full.includes(':') ? full.slice(0, full.lastIndexOf(':')) : full;
|
|
129
129
|
return withoutTag.includes('/') ? withoutTag.slice(withoutTag.lastIndexOf('/') + 1) : withoutTag;
|
|
130
130
|
}
|
|
131
|
+
/** How much of an incoming archive `importFiles` holds before spooling it into the sandbox. */
|
|
132
|
+
const IMPORT_PIECE_BYTES = 8 * 1024 * 1024;
|
|
131
133
|
/**
|
|
132
134
|
* Single-quote a value for POSIX `sh` so an arbitrary path is one argument.
|
|
133
135
|
*
|
|
@@ -551,6 +553,25 @@ class E2BSandbox {
|
|
|
551
553
|
async writeFile(path, content) {
|
|
552
554
|
await this.sbx.files.write(path, content);
|
|
553
555
|
}
|
|
556
|
+
/**
|
|
557
|
+
* Read a file's exact bytes through envd's file API (never command stdout,
|
|
558
|
+
* which envd truncates for large outputs).
|
|
559
|
+
*
|
|
560
|
+
* @param path - Absolute path inside the sandbox.
|
|
561
|
+
* @returns The file's bytes.
|
|
562
|
+
*/
|
|
563
|
+
async readFileBytes(path) {
|
|
564
|
+
return this.sbx.files.read(path, { format: 'bytes' });
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Write exact bytes to a file through envd's file API, replacing it.
|
|
568
|
+
*
|
|
569
|
+
* @param path - Absolute path inside the sandbox.
|
|
570
|
+
* @param data - The bytes to write.
|
|
571
|
+
*/
|
|
572
|
+
async writeFileBytes(path, data) {
|
|
573
|
+
await this.sbx.files.write(path, data);
|
|
574
|
+
}
|
|
554
575
|
/**
|
|
555
576
|
* List a directory. Throws when the path does not exist (an empty array
|
|
556
577
|
* means "exists and is empty", never "missing").
|
|
@@ -634,14 +655,47 @@ class E2BSandbox {
|
|
|
634
655
|
* @param archive - POSIX tar byte stream to extract there.
|
|
635
656
|
*/
|
|
636
657
|
async importFiles(path, archive) {
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
const
|
|
641
|
-
|
|
642
|
-
const
|
|
643
|
-
|
|
644
|
-
|
|
658
|
+
// Spooled into the sandbox in bounded pieces: the archive is never held
|
|
659
|
+
// whole in this process (an uploaded tarball can inflate to gigabytes, and
|
|
660
|
+
// this process is shared by every tenant).
|
|
661
|
+
const tag = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
662
|
+
const tarPath = `/tmp/mol-import-${tag}.tar`;
|
|
663
|
+
const piecePath = `${tarPath}.piece`;
|
|
664
|
+
let pending = [];
|
|
665
|
+
let pendingBytes = 0;
|
|
666
|
+
let pieces = 0;
|
|
667
|
+
const flush = async () => {
|
|
668
|
+
if (pendingBytes === 0)
|
|
669
|
+
return;
|
|
670
|
+
const piece = Buffer.concat(pending);
|
|
671
|
+
pending = [];
|
|
672
|
+
pendingBytes = 0;
|
|
673
|
+
await this.sbx.files.write(piecePath, new Blob([piece]));
|
|
674
|
+
const r = await this.sbx.commands.run(`cat ${piecePath} ${pieces === 0 ? '>' : '>>'} ${tarPath} && rm -f ${piecePath}`, { timeoutMs: 120_000 });
|
|
675
|
+
if (r.exitCode !== 0) {
|
|
676
|
+
throw new Error(`importFiles: spooling the archive failed (${r.exitCode}): ${r.stderr.slice(0, 300)}`);
|
|
677
|
+
}
|
|
678
|
+
pieces++;
|
|
679
|
+
};
|
|
680
|
+
try {
|
|
681
|
+
for await (const chunk of archive) {
|
|
682
|
+
pending.push(chunk);
|
|
683
|
+
pendingBytes += chunk.length;
|
|
684
|
+
if (pendingBytes >= IMPORT_PIECE_BYTES)
|
|
685
|
+
await flush();
|
|
686
|
+
}
|
|
687
|
+
await flush();
|
|
688
|
+
if (pieces === 0)
|
|
689
|
+
await this.sbx.commands.run(`: > ${tarPath}`, { timeoutMs: 30_000 });
|
|
690
|
+
const r = await this.sbx.commands.run(`mkdir -p ${shellQuote(path)} && tar xf ${tarPath} -C ${shellQuote(path)} --no-same-owner --no-same-permissions`, { timeoutMs: 300_000 });
|
|
691
|
+
if (r.exitCode !== 0) {
|
|
692
|
+
throw new Error(`importFiles: tar extract failed (${r.exitCode}): ${r.stderr.slice(0, 300)}`);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
finally {
|
|
696
|
+
await this.sbx.commands
|
|
697
|
+
.run(`rm -f ${tarPath} ${piecePath}`, { timeoutMs: 30_000 })
|
|
698
|
+
.catch(() => undefined);
|
|
645
699
|
}
|
|
646
700
|
}
|
|
647
701
|
/**
|
|
@@ -777,6 +831,24 @@ export class E2BSandboxProvider {
|
|
|
777
831
|
* @returns A live sandbox handle.
|
|
778
832
|
*/
|
|
779
833
|
async create(config) {
|
|
834
|
+
return this.createSandbox(config, 'pause');
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* {@link create} with the sandbox's timeout action chosen by the caller.
|
|
838
|
+
*
|
|
839
|
+
* `'pause'` is E2B's `lifecycle: { onTimeout: { action: 'pause', keepMemory:
|
|
840
|
+
* true } }` — the data-safe default for anything that may hold a project
|
|
841
|
+
* (see {@link create}). `'kill'` is `lifecycle: { onTimeout: 'kill' }` — for a
|
|
842
|
+
* THROWAWAY sandbox this bond destroys itself (the egress probe): pausing one
|
|
843
|
+
* at its deadline would write a full memory + filesystem snapshot to the
|
|
844
|
+
* host's disk for a sandbox nobody will ever resume. E2B cannot change a
|
|
845
|
+
* sandbox's `onTimeout` after create, so the choice is made here, once.
|
|
846
|
+
*
|
|
847
|
+
* @param config - As for {@link create}.
|
|
848
|
+
* @param onTimeout - What E2B does at the sandbox's deadline.
|
|
849
|
+
* @returns A live sandbox handle.
|
|
850
|
+
*/
|
|
851
|
+
async createSandbox(config, onTimeout) {
|
|
780
852
|
const client = await this.client();
|
|
781
853
|
const templateId = config.templateId ?? this.config.templateId;
|
|
782
854
|
if (config.volumeName && !config.volumeMountPath) {
|
|
@@ -789,7 +861,9 @@ export class E2BSandboxProvider {
|
|
|
789
861
|
timeoutMs: this.config.defaultTimeoutMs,
|
|
790
862
|
envs: config.env ?? {},
|
|
791
863
|
metadata: { projectId: config.projectId, ...(config.labels ?? {}) },
|
|
792
|
-
lifecycle:
|
|
864
|
+
lifecycle: onTimeout === 'kill'
|
|
865
|
+
? { onTimeout: 'kill' }
|
|
866
|
+
: { onTimeout: { action: 'pause', keepMemory: true } },
|
|
793
867
|
...(config.volumeName
|
|
794
868
|
? { volumeMounts: { [config.volumeMountPath]: config.volumeName } }
|
|
795
869
|
: {}),
|
|
@@ -1193,7 +1267,9 @@ export class E2BSandboxProvider {
|
|
|
1193
1267
|
async verifyEgress() {
|
|
1194
1268
|
let handle;
|
|
1195
1269
|
try {
|
|
1196
|
-
|
|
1270
|
+
// Kill, not pause, at its deadline: a leaked probe must not leave a
|
|
1271
|
+
// snapshot on the host's disk (see createSandbox).
|
|
1272
|
+
handle = await this.createSandbox({ projectId: `egress-probe-${Date.now()}`, env: {} }, 'kill');
|
|
1197
1273
|
}
|
|
1198
1274
|
catch (error) {
|
|
1199
1275
|
return {
|
|
@@ -1205,8 +1281,7 @@ export class E2BSandboxProvider {
|
|
|
1205
1281
|
const verdict = await this.probeEgress(handle);
|
|
1206
1282
|
// The probe sandbox is destroyed on EVERY path. A failed destroy is retried,
|
|
1207
1283
|
// and one that still fails is reported in the verdict's detail: the sandbox
|
|
1208
|
-
//
|
|
1209
|
-
// storage forever.
|
|
1284
|
+
// is killed at its own deadline, but until then it runs metered.
|
|
1210
1285
|
const leaked = await this.destroyProbe(handle.id);
|
|
1211
1286
|
return leaked ? { ...verdict, detail: `${verdict.detail} ${leaked}` } : verdict;
|
|
1212
1287
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@molecule/api-code-sandbox-e2b",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.6",
|
|
4
4
|
"description": "E2B (e2b.dev) code sandbox provider — Firecracker microVMs with golden templates, fork, and pause/resume",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"license": "Apache-2.0",
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@molecule/api-bond": "1.0.2",
|
|
33
|
-
"@molecule/api-code-sandbox": "1.
|
|
33
|
+
"@molecule/api-code-sandbox": "1.3.0",
|
|
34
34
|
"@types/node": "26.1.2",
|
|
35
35
|
"typescript": "6.0.3",
|
|
36
36
|
"vitest": "4.1.11"
|