@mastra/e2b 0.6.0-alpha.0 → 0.7.0-alpha.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/CHANGELOG.md CHANGED
@@ -1,5 +1,63 @@
1
1
  # @mastra/e2b
2
2
 
3
+ ## 0.7.0-alpha.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Added networking and bulk file upload support to `E2BSandbox`: ([#19577](https://github.com/mastra-ai/mastra/pull/19577))
8
+ - **Public port URLs**: `sandbox.networking.getPortUrl(port)` returns the public URL for a port, enabling preview URLs and deploys with the new `@mastra/deployer-sandbox` package.
9
+ - **Bulk file upload**: `sandbox.writeFiles(files)` uploads multiple files in one call.
10
+ - **Snapshot-stop**: `stop()` now pauses the sandbox immediately (snapshotting filesystem, memory, and running processes) instead of leaving it running until its timeout. The next `start()` with the same `id` resumes it, with background processes still running. `destroy()` still kills the sandbox permanently.
11
+ - **Detached lifecycle**: `getPortUrl()`, `stop()`, and `destroy()` now work from a fresh process by looking up the existing sandbox by its `id` metadata, without resuming a paused sandbox. Stopping or destroying never wakes (or bills) a paused sandbox first.
12
+
13
+ ```typescript
14
+ import { E2BSandbox } from '@mastra/e2b';
15
+
16
+ const sandbox = new E2BSandbox();
17
+ await sandbox.start();
18
+ const url = await sandbox.networking.getPortUrl(4111);
19
+ ```
20
+
21
+ - Added `clone()` support to the sandbox providers. `clone()` constructs an unstarted sibling sandbox that inherits the template's configuration (credentials, image, resources) with per-instance overrides for `id` and `env`, so one configured sandbox can act as a template for a fleet of sandbox clones (for example, one per project). ([#19616](https://github.com/mastra-ai/mastra/pull/19616))
22
+
23
+ ```ts
24
+ const template = new E2BSandbox({ apiKey, template: 'base' });
25
+
26
+ const projectSandbox = template.clone({
27
+ id: 'mc-project-42',
28
+ env: { GITHUB_TOKEN: token },
29
+ idleTimeoutMinutes: 30,
30
+ });
31
+ await projectSandbox.start();
32
+ ```
33
+
34
+ `idleTimeoutMinutes` is a best-effort hint that maps to each provider's native lifetime knob (Railway `idleTimeoutMinutes`, E2B/Modal/Vercel timeout in milliseconds, Daytona `autoStopInterval`, Blaxel TTL duration). Docker and Apple Container ignore it since they have no provider-side idle teardown.
35
+
36
+ ### Patch Changes
37
+
38
+ - Updated dependencies [[`ec857fc`](https://github.com/mastra-ai/mastra/commit/ec857fc79c264b53b38e16478c789b7177f2ad59), [`e1f2fae`](https://github.com/mastra-ai/mastra/commit/e1f2faebaf048c3d4c2e2c01d293767c195d5794), [`63aa799`](https://github.com/mastra-ai/mastra/commit/63aa799c6b44eacc7806cda6846b7c5bbee06b37), [`73db8db`](https://github.com/mastra-ai/mastra/commit/73db8db90d69ab6153c7942749f624db0d96952d), [`73db8db`](https://github.com/mastra-ai/mastra/commit/73db8db90d69ab6153c7942749f624db0d96952d), [`76b7181`](https://github.com/mastra-ai/mastra/commit/76b71810366e6d90b9d3973149d1c7ba3659ffb9), [`0c0e8d7`](https://github.com/mastra-ai/mastra/commit/0c0e8d7becd4d1445c656b78d5d845f606c1ff9d), [`9f7c67a`](https://github.com/mastra-ai/mastra/commit/9f7c67abeeb52c41c51a9b5edee60b62afe7cd8d), [`3b65e68`](https://github.com/mastra-ai/mastra/commit/3b65e68d7f1c771c7a70eea42d83fefdd28cad88), [`e3868e2`](https://github.com/mastra-ai/mastra/commit/e3868e22babfffd0133771669ca724501c2dd58e)]:
39
+ - @mastra/core@1.52.0-alpha.5
40
+
41
+ ## 0.6.0
42
+
43
+ ### Minor Changes
44
+
45
+ - Add `E2BCodeModeTransport` for running Code Mode in an `E2BSandbox`. ([#19372](https://github.com/mastra-ai/mastra/pull/19372))
46
+
47
+ The default `StdioCodeModeTransport` writes the runner/program to the host tmpdir and spawns `node <hostPath>`, which only works when the sandbox shares the host filesystem (e.g. `LocalSandbox`). E2B runs the program in a remote micro-VM, so those host paths don't exist there. `E2BCodeModeTransport` writes the program and runner into the sandbox via the E2B files API, strips TypeScript on the host with esbuild (no `--experimental-strip-types`, no Node-version dependency), auto-starts the sandbox when needed, surfaces captured stderr in timeout/no-result errors, and cleans up the sandbox directory afterwards.
48
+
49
+ ```ts
50
+ import { createCodeMode } from '@mastra/core/tools';
51
+ import { E2BSandbox, E2BCodeModeTransport } from '@mastra/e2b';
52
+
53
+ const { tool, instructions } = createCodeMode({ tools, sandbox: new E2BSandbox() }, new E2BCodeModeTransport());
54
+ ```
55
+
56
+ ### Patch Changes
57
+
58
+ - Updated dependencies [[`bd6d240`](https://github.com/mastra-ai/mastra/commit/bd6d2402db93dddaef0721667e7e8a030e7c6e16), [`0111486`](https://github.com/mastra-ai/mastra/commit/01114867612593eef5cfa2fda6a1194dfedda841), [`96a3749`](https://github.com/mastra-ai/mastra/commit/96a37492235f5b8076b3e3177d83ed5a5e44a640), [`fe1bda0`](https://github.com/mastra-ai/mastra/commit/fe1bda06f6af92a694a51712db747cda1e7185f0), [`25e7c12`](https://github.com/mastra-ai/mastra/commit/25e7c126a770069ae7fb7ecf1d2adb40e017b009), [`1ce5121`](https://github.com/mastra-ai/mastra/commit/1ce512155d122bb21f47d98383e82ffbf84b39e8), [`fb8aea3`](https://github.com/mastra-ai/mastra/commit/fb8aea384291e77311be3a64ee1717320d5c3c73), [`4adc391`](https://github.com/mastra-ai/mastra/commit/4adc3911075249c352bb4832d2471922826344de), [`a5c6337`](https://github.com/mastra-ai/mastra/commit/a5c6337d23c7686c81a32ce62f550f610543a240), [`3cfc47a`](https://github.com/mastra-ai/mastra/commit/3cfc47a6b89940aadd0f46fb01ae9624a73a865d), [`2bb7817`](https://github.com/mastra-ai/mastra/commit/2bb78176112fde628483de2830528f7eee911e56), [`51d9870`](https://github.com/mastra-ai/mastra/commit/51d987032c689c2855374d0f244f5d654da809d1), [`5cab274`](https://github.com/mastra-ai/mastra/commit/5cab2744250e22d12fefa7b32637dce224233cee), [`7fa27d3`](https://github.com/mastra-ai/mastra/commit/7fa27d3b6f5ed68cd34e454a4d3ad9c482a0cfbc), [`8b97958`](https://github.com/mastra-ai/mastra/commit/8b979589f9aa59ba67cac565949475f2ffeb4ac3), [`8410541`](https://github.com/mastra-ai/mastra/commit/84105412c60ecd3bb33a9838146f59c4b588228f), [`a58dcbb`](https://github.com/mastra-ai/mastra/commit/a58dcbb546d7e1d65ebdc1f39e55f0908fcd9391), [`aa38805`](https://github.com/mastra-ai/mastra/commit/aa38805b878b827403be785eb90688d7172f5a40), [`153bd3b`](https://github.com/mastra-ai/mastra/commit/153bd3b396bdfed6b74cf43de12db8fd2d83c04a), [`45a8e65`](https://github.com/mastra-ai/mastra/commit/45a8e65e1556d1362cb3f25187023c36de26661d), [`e955965`](https://github.com/mastra-ai/mastra/commit/e955965dce575a903e37cf054d28ea99aa48785e), [`2d22570`](https://github.com/mastra-ai/mastra/commit/2d22570c7dfdd02123d0ecc529efb05ccba2d9fc), [`07bb863`](https://github.com/mastra-ai/mastra/commit/07bb8631919c6f7cf377dccd45b096e0f17fbed0), [`c8ed116`](https://github.com/mastra-ai/mastra/commit/c8ed11699f62bcac70102ab4ec84d80d20541da6), [`01b338c`](https://github.com/mastra-ai/mastra/commit/01b338c56271f0219606710e3e8b26dee27ac6c2), [`a99eae8`](https://github.com/mastra-ai/mastra/commit/a99eae8908e500c1b2d12f9d277be616b98617a5), [`860ef7e`](https://github.com/mastra-ai/mastra/commit/860ef7e77d92b63469cbe5857aa1e626197e43e9), [`17e818c`](https://github.com/mastra-ai/mastra/commit/17e818c51a958ba90641b1a959dc38faf8c034e9), [`edce8d2`](https://github.com/mastra-ai/mastra/commit/edce8d2769f19e27a05737c627af2d765472a4f8), [`8a586ec`](https://github.com/mastra-ai/mastra/commit/8a586eca9a4914f31dff6140d0d45ac375b00669), [`4451dfe`](https://github.com/mastra-ai/mastra/commit/4451dfe857428e7abcc0261a507a2e186dae6d47), [`8b7361d`](https://github.com/mastra-ai/mastra/commit/8b7361d35de68b80d05d30a74e0c69e7218fd612), [`1d39058`](https://github.com/mastra-ai/mastra/commit/1d39058e548efd691799985d5c8af2737f1c3bd2), [`3927473`](https://github.com/mastra-ai/mastra/commit/392747323ddb10c643d12be7b9ae913159dfaeed), [`dce50dc`](https://github.com/mastra-ai/mastra/commit/dce50dc9a1c1fcd0f427bb5f6250ec74910cb04b), [`fd13f8e`](https://github.com/mastra-ai/mastra/commit/fd13f8e21990f9904c3eedba3a626bb4a929cdb8), [`634caff`](https://github.com/mastra-ai/mastra/commit/634caff29a9200ad058b67d53f96d9e5832fb8a2), [`f703f87`](https://github.com/mastra-ai/mastra/commit/f703f878de072d51fda557f9c50867d8252bef05), [`3e26c87`](https://github.com/mastra-ai/mastra/commit/3e26c87de0c5bc2583b795ce6ca5889b6b161acb), [`33f2b88`](https://github.com/mastra-ai/mastra/commit/33f2b88842c09a567f906fac4cb61cd5277ced59), [`177010f`](https://github.com/mastra-ai/mastra/commit/177010ff096d2e4b28d89803be5b1a4cad2a0d6b), [`0ad646f`](https://github.com/mastra-ai/mastra/commit/0ad646f71a530f2454664299e5e01bfd13fa12e5), [`b486abf`](https://github.com/mastra-ai/mastra/commit/b486abfa2a7528c6f527e4015c819ea9fa54aaad), [`54a51e0`](https://github.com/mastra-ai/mastra/commit/54a51e0a484fe1ebad3fb1f7ef5282a075709eb7), [`c43f3a9`](https://github.com/mastra-ai/mastra/commit/c43f3a9d1efde99b38789364ba4d0ba670f430e3), [`a5008f2`](https://github.com/mastra-ai/mastra/commit/a5008f22ae710ad9402ea9f2547d8c02f74d384b), [`e2d5f37`](https://github.com/mastra-ai/mastra/commit/e2d5f373bd289be534d5f8694d34465010533df6), [`4ce0163`](https://github.com/mastra-ai/mastra/commit/4ce0163dc86e675a86809685c8ce6c49f1aeb87e), [`4378341`](https://github.com/mastra-ai/mastra/commit/43783412df5ea3dd35f5b1f6e4851e79c346fc89)]:
59
+ - @mastra/core@1.51.0
60
+
3
61
  ## 0.6.0-alpha.0
4
62
 
5
63
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -570,11 +570,34 @@ function validateMountPath(mountPath) {
570
570
  }
571
571
  }
572
572
  var SAFE_MARKER_NAME = /^mount-[a-z0-9]+$/;
573
- var E2BSandbox = class extends workspace.MastraSandbox {
573
+ var E2BSandbox = class _E2BSandbox extends workspace.MastraSandbox {
574
574
  id;
575
575
  name = "E2BSandbox";
576
576
  provider = "e2b";
577
577
  status = "pending";
578
+ /**
579
+ * Networking capability: public HTTPS URLs for sandbox ports.
580
+ * E2B exposes every port via `getHost(port)` — no upfront declaration needed.
581
+ *
582
+ * When not attached in this process, the URL is resolved by looking up the
583
+ * existing sandbox by identity (without resuming it) and deriving the host
584
+ * (`{port}-{sandboxId}.{domain}`), so other processes can resolve
585
+ * deployments without waking a paused sandbox.
586
+ */
587
+ networking = {
588
+ getPortUrl: async (port) => {
589
+ try {
590
+ if (this._sandbox) {
591
+ return `https://${this._sandbox.getHost(port)}`;
592
+ }
593
+ const info = await this.lookupExistingSandboxInfo();
594
+ if (!info) return null;
595
+ return `https://${port}-${info.sandboxId}.${this.sandboxDomain}`;
596
+ } catch {
597
+ return null;
598
+ }
599
+ }
600
+ };
578
601
  _sandbox = null;
579
602
  _createdAt = null;
580
603
  _isRetrying = false;
@@ -585,6 +608,7 @@ var E2BSandbox = class extends workspace.MastraSandbox {
585
608
  network;
586
609
  connectionOpts;
587
610
  _instructionsOverride;
611
+ _constructorOptions;
588
612
  /** Resolved template ID after building (if needed) */
589
613
  _resolvedTemplateId;
590
614
  /** Promise for template preparation (started in constructor) */
@@ -608,11 +632,34 @@ var E2BSandbox = class extends workspace.MastraSandbox {
608
632
  ...options.accessToken && { accessToken: options.accessToken }
609
633
  };
610
634
  this._instructionsOverride = options.instructions;
635
+ this._constructorOptions = { ...options };
611
636
  this._templatePreparePromise = this.resolveTemplate().catch((err) => {
612
637
  this.logger.debug(`${LOG_PREFIX} Template preparation error (will retry on start):`, err);
613
638
  return "";
614
639
  });
615
640
  }
641
+ /**
642
+ * Construct a sibling `E2BSandbox` that inherits this sandbox's
643
+ * configuration (credentials, template, network, metadata, instructions)
644
+ * with per-instance overrides.
645
+ *
646
+ * Performs no I/O — the sandbox clone provisions (or reconnects to an
647
+ * existing E2B sandbox with the same logical `id`) on its own `start()`.
648
+ * Use it when one configured sandbox acts as the template for a fleet of
649
+ * independent sandboxes (e.g. one per project).
650
+ *
651
+ * `options.idleTimeoutMinutes` maps to the E2B sandbox `timeout` (ms);
652
+ * `options.sandboxId` is ignored because E2B reconnects by logical `id`.
653
+ */
654
+ clone(options = {}) {
655
+ const { id: _id, ...base } = this._constructorOptions;
656
+ return new _E2BSandbox({
657
+ ...base,
658
+ ...options.id !== void 0 && { id: options.id },
659
+ ...options.env !== void 0 && { env: options.env },
660
+ ...options.idleTimeoutMinutes !== void 0 && { timeout: options.idleTimeoutMinutes * 6e4 }
661
+ });
662
+ }
616
663
  /**
617
664
  * Get the underlying E2B Sandbox instance for direct access to E2B APIs.
618
665
  *
@@ -710,22 +757,32 @@ var E2BSandbox = class extends workspace.MastraSandbox {
710
757
  this._createdAt = /* @__PURE__ */ new Date();
711
758
  }
712
759
  /**
713
- * Stop the E2B sandbox.
714
- * Unmounts all filesystems and releases the sandbox reference.
760
+ * Stop the E2B sandbox by pausing it (snapshot-stop).
761
+ *
762
+ * Pausing freezes the whole VM — filesystem, memory, and running processes —
763
+ * and stops billing immediately. The next `start()` reconnects and resumes it,
764
+ * with background processes still running. Filesystem mounts are unmounted
765
+ * first (FUSE mounts don't survive pause) and reconciled again on start.
766
+ *
715
767
  * Status management is handled by the base class.
716
768
  */
717
769
  async stop() {
718
- try {
719
- const procs = await this.processes.list();
720
- await Promise.all(procs.map((p) => this.processes.kill(p.pid)));
721
- } catch {
722
- }
723
770
  for (const mountPath of [...this.mounts.entries.keys()]) {
724
771
  try {
725
772
  await this.unmount(mountPath);
726
773
  } catch {
727
774
  }
728
775
  }
776
+ if (this._sandbox) {
777
+ await this._sandbox.pause();
778
+ this.logger.debug(`${LOG_PREFIX} Paused sandbox ${this._sandbox.sandboxId} for: ${this.id}`);
779
+ } else {
780
+ const info = await this.lookupExistingSandboxInfo();
781
+ if (info?.state === "running") {
782
+ await e2b.Sandbox.pause(info.sandboxId, this.connectionOpts);
783
+ this.logger.debug(`${LOG_PREFIX} Paused detached sandbox ${info.sandboxId} for: ${this.id}`);
784
+ }
785
+ }
729
786
  this._sandbox = null;
730
787
  }
731
788
  /**
@@ -746,11 +803,14 @@ var E2BSandbox = class extends workspace.MastraSandbox {
746
803
  } catch {
747
804
  }
748
805
  }
749
- try {
750
- await this._sandbox.kill();
751
- } catch {
752
- }
806
+ await this._sandbox.kill();
753
807
  this._sandbox = null;
808
+ } else {
809
+ const info = await this.lookupExistingSandboxInfo();
810
+ if (info) {
811
+ await e2b.Sandbox.kill(info.sandboxId, this.connectionOpts);
812
+ this.logger.debug(`${LOG_PREFIX} Killed detached sandbox ${info.sandboxId} for: ${this.id}`);
813
+ }
754
814
  }
755
815
  this.mounts.clear();
756
816
  }
@@ -770,6 +830,21 @@ var E2BSandbox = class extends workspace.MastraSandbox {
770
830
  }
771
831
  };
772
832
  }
833
+ // ---------------------------------------------------------------------------
834
+ // File Upload
835
+ // ---------------------------------------------------------------------------
836
+ /**
837
+ * Bulk-write files into the sandbox filesystem via the SDK's native upload.
838
+ */
839
+ async writeFiles(files) {
840
+ await this.ensureRunning();
841
+ await this.e2b.files.write(
842
+ files.map((f) => ({
843
+ path: f.path,
844
+ data: typeof f.content === "string" ? f.content : new Blob([new Uint8Array(f.content)])
845
+ }))
846
+ );
847
+ }
773
848
  /**
774
849
  * Get instructions describing this E2B sandbox.
775
850
  * Used by agents to understand the execution environment.
@@ -1004,11 +1079,15 @@ var E2BSandbox = class extends workspace.MastraSandbox {
1004
1079
  generateId() {
1005
1080
  return `e2b-sandbox-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
1006
1081
  }
1082
+ /** Domain used to derive public sandbox hosts (self-hosted E2B or e2b.app). */
1083
+ get sandboxDomain() {
1084
+ return this.connectionOpts.domain ?? process.env.E2B_DOMAIN ?? "e2b.app";
1085
+ }
1007
1086
  /**
1008
- * Find an existing sandbox with matching mastra-sandbox-id metadata.
1009
- * Returns the connected sandbox if found, null otherwise.
1087
+ * Look up an existing sandbox with matching mastra-sandbox-id metadata
1088
+ * WITHOUT connecting or resuming it. Returns its list info or null.
1010
1089
  */
1011
- async findExistingSandbox() {
1090
+ async lookupExistingSandboxInfo() {
1012
1091
  try {
1013
1092
  const paginator = e2b.Sandbox.list({
1014
1093
  ...this.connectionOpts,
@@ -1024,13 +1103,28 @@ var E2BSandbox = class extends workspace.MastraSandbox {
1024
1103
  this.logger.debug(
1025
1104
  `${LOG_PREFIX} Found existing sandbox for ${this.id}: ${existingSandbox.sandboxId} (state: ${existingSandbox.state})`
1026
1105
  );
1027
- return await e2b.Sandbox.connect(existingSandbox.sandboxId, this.connectionOpts);
1106
+ return existingSandbox;
1028
1107
  }
1029
1108
  } catch (e) {
1030
1109
  this.logger.debug(`${LOG_PREFIX} Error querying for existing sandbox:`, e);
1031
1110
  }
1032
1111
  return null;
1033
1112
  }
1113
+ /**
1114
+ * Find an existing sandbox with matching mastra-sandbox-id metadata.
1115
+ * Returns the connected sandbox if found, null otherwise.
1116
+ * Connecting to a paused sandbox resumes it.
1117
+ */
1118
+ async findExistingSandbox() {
1119
+ const info = await this.lookupExistingSandboxInfo();
1120
+ if (!info) return null;
1121
+ try {
1122
+ return await e2b.Sandbox.connect(info.sandboxId, this.connectionOpts);
1123
+ } catch (e) {
1124
+ this.logger.debug(`${LOG_PREFIX} Error connecting to existing sandbox:`, e);
1125
+ return null;
1126
+ }
1127
+ }
1034
1128
  /**
1035
1129
  * Resolve the template specification to a template ID.
1036
1130
  *