@mastra/e2b 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -568,11 +568,34 @@ function validateMountPath(mountPath) {
568
568
  }
569
569
  }
570
570
  var SAFE_MARKER_NAME = /^mount-[a-z0-9]+$/;
571
- var E2BSandbox = class extends MastraSandbox {
571
+ var E2BSandbox = class _E2BSandbox extends MastraSandbox {
572
572
  id;
573
573
  name = "E2BSandbox";
574
574
  provider = "e2b";
575
575
  status = "pending";
576
+ /**
577
+ * Networking capability: public HTTPS URLs for sandbox ports.
578
+ * E2B exposes every port via `getHost(port)` — no upfront declaration needed.
579
+ *
580
+ * When not attached in this process, the URL is resolved by looking up the
581
+ * existing sandbox by identity (without resuming it) and deriving the host
582
+ * (`{port}-{sandboxId}.{domain}`), so other processes can resolve
583
+ * deployments without waking a paused sandbox.
584
+ */
585
+ networking = {
586
+ getPortUrl: async (port) => {
587
+ try {
588
+ if (this._sandbox) {
589
+ return `https://${this._sandbox.getHost(port)}`;
590
+ }
591
+ const info = await this.lookupExistingSandboxInfo();
592
+ if (!info) return null;
593
+ return `https://${port}-${info.sandboxId}.${this.sandboxDomain}`;
594
+ } catch {
595
+ return null;
596
+ }
597
+ }
598
+ };
576
599
  _sandbox = null;
577
600
  _createdAt = null;
578
601
  _isRetrying = false;
@@ -583,6 +606,7 @@ var E2BSandbox = class extends MastraSandbox {
583
606
  network;
584
607
  connectionOpts;
585
608
  _instructionsOverride;
609
+ _constructorOptions;
586
610
  /** Resolved template ID after building (if needed) */
587
611
  _resolvedTemplateId;
588
612
  /** Promise for template preparation (started in constructor) */
@@ -606,11 +630,34 @@ var E2BSandbox = class extends MastraSandbox {
606
630
  ...options.accessToken && { accessToken: options.accessToken }
607
631
  };
608
632
  this._instructionsOverride = options.instructions;
633
+ this._constructorOptions = { ...options };
609
634
  this._templatePreparePromise = this.resolveTemplate().catch((err) => {
610
635
  this.logger.debug(`${LOG_PREFIX} Template preparation error (will retry on start):`, err);
611
636
  return "";
612
637
  });
613
638
  }
639
+ /**
640
+ * Construct a sibling `E2BSandbox` that inherits this sandbox's
641
+ * configuration (credentials, template, network, metadata, instructions)
642
+ * with per-instance overrides.
643
+ *
644
+ * Performs no I/O — the sandbox clone provisions (or reconnects to an
645
+ * existing E2B sandbox with the same logical `id`) on its own `start()`.
646
+ * Use it when one configured sandbox acts as the template for a fleet of
647
+ * independent sandboxes (e.g. one per project).
648
+ *
649
+ * `options.idleTimeoutMinutes` maps to the E2B sandbox `timeout` (ms);
650
+ * `options.sandboxId` is ignored because E2B reconnects by logical `id`.
651
+ */
652
+ clone(options = {}) {
653
+ const { id: _id, ...base } = this._constructorOptions;
654
+ return new _E2BSandbox({
655
+ ...base,
656
+ ...options.id !== void 0 && { id: options.id },
657
+ ...options.env !== void 0 && { env: options.env },
658
+ ...options.idleTimeoutMinutes !== void 0 && { timeout: options.idleTimeoutMinutes * 6e4 }
659
+ });
660
+ }
614
661
  /**
615
662
  * Get the underlying E2B Sandbox instance for direct access to E2B APIs.
616
663
  *
@@ -708,22 +755,32 @@ var E2BSandbox = class extends MastraSandbox {
708
755
  this._createdAt = /* @__PURE__ */ new Date();
709
756
  }
710
757
  /**
711
- * Stop the E2B sandbox.
712
- * Unmounts all filesystems and releases the sandbox reference.
758
+ * Stop the E2B sandbox by pausing it (snapshot-stop).
759
+ *
760
+ * Pausing freezes the whole VM — filesystem, memory, and running processes —
761
+ * and stops billing immediately. The next `start()` reconnects and resumes it,
762
+ * with background processes still running. Filesystem mounts are unmounted
763
+ * first (FUSE mounts don't survive pause) and reconciled again on start.
764
+ *
713
765
  * Status management is handled by the base class.
714
766
  */
715
767
  async stop() {
716
- try {
717
- const procs = await this.processes.list();
718
- await Promise.all(procs.map((p) => this.processes.kill(p.pid)));
719
- } catch {
720
- }
721
768
  for (const mountPath of [...this.mounts.entries.keys()]) {
722
769
  try {
723
770
  await this.unmount(mountPath);
724
771
  } catch {
725
772
  }
726
773
  }
774
+ if (this._sandbox) {
775
+ await this._sandbox.pause();
776
+ this.logger.debug(`${LOG_PREFIX} Paused sandbox ${this._sandbox.sandboxId} for: ${this.id}`);
777
+ } else {
778
+ const info = await this.lookupExistingSandboxInfo();
779
+ if (info?.state === "running") {
780
+ await Sandbox.pause(info.sandboxId, this.connectionOpts);
781
+ this.logger.debug(`${LOG_PREFIX} Paused detached sandbox ${info.sandboxId} for: ${this.id}`);
782
+ }
783
+ }
727
784
  this._sandbox = null;
728
785
  }
729
786
  /**
@@ -744,11 +801,14 @@ var E2BSandbox = class extends MastraSandbox {
744
801
  } catch {
745
802
  }
746
803
  }
747
- try {
748
- await this._sandbox.kill();
749
- } catch {
750
- }
804
+ await this._sandbox.kill();
751
805
  this._sandbox = null;
806
+ } else {
807
+ const info = await this.lookupExistingSandboxInfo();
808
+ if (info) {
809
+ await Sandbox.kill(info.sandboxId, this.connectionOpts);
810
+ this.logger.debug(`${LOG_PREFIX} Killed detached sandbox ${info.sandboxId} for: ${this.id}`);
811
+ }
752
812
  }
753
813
  this.mounts.clear();
754
814
  }
@@ -768,6 +828,21 @@ var E2BSandbox = class extends MastraSandbox {
768
828
  }
769
829
  };
770
830
  }
831
+ // ---------------------------------------------------------------------------
832
+ // File Upload
833
+ // ---------------------------------------------------------------------------
834
+ /**
835
+ * Bulk-write files into the sandbox filesystem via the SDK's native upload.
836
+ */
837
+ async writeFiles(files) {
838
+ await this.ensureRunning();
839
+ await this.e2b.files.write(
840
+ files.map((f) => ({
841
+ path: f.path,
842
+ data: typeof f.content === "string" ? f.content : new Blob([new Uint8Array(f.content)])
843
+ }))
844
+ );
845
+ }
771
846
  /**
772
847
  * Get instructions describing this E2B sandbox.
773
848
  * Used by agents to understand the execution environment.
@@ -1002,11 +1077,15 @@ var E2BSandbox = class extends MastraSandbox {
1002
1077
  generateId() {
1003
1078
  return `e2b-sandbox-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
1004
1079
  }
1080
+ /** Domain used to derive public sandbox hosts (self-hosted E2B or e2b.app). */
1081
+ get sandboxDomain() {
1082
+ return this.connectionOpts.domain ?? process.env.E2B_DOMAIN ?? "e2b.app";
1083
+ }
1005
1084
  /**
1006
- * Find an existing sandbox with matching mastra-sandbox-id metadata.
1007
- * Returns the connected sandbox if found, null otherwise.
1085
+ * Look up an existing sandbox with matching mastra-sandbox-id metadata
1086
+ * WITHOUT connecting or resuming it. Returns its list info or null.
1008
1087
  */
1009
- async findExistingSandbox() {
1088
+ async lookupExistingSandboxInfo() {
1010
1089
  try {
1011
1090
  const paginator = Sandbox.list({
1012
1091
  ...this.connectionOpts,
@@ -1022,13 +1101,28 @@ var E2BSandbox = class extends MastraSandbox {
1022
1101
  this.logger.debug(
1023
1102
  `${LOG_PREFIX} Found existing sandbox for ${this.id}: ${existingSandbox.sandboxId} (state: ${existingSandbox.state})`
1024
1103
  );
1025
- return await Sandbox.connect(existingSandbox.sandboxId, this.connectionOpts);
1104
+ return existingSandbox;
1026
1105
  }
1027
1106
  } catch (e) {
1028
1107
  this.logger.debug(`${LOG_PREFIX} Error querying for existing sandbox:`, e);
1029
1108
  }
1030
1109
  return null;
1031
1110
  }
1111
+ /**
1112
+ * Find an existing sandbox with matching mastra-sandbox-id metadata.
1113
+ * Returns the connected sandbox if found, null otherwise.
1114
+ * Connecting to a paused sandbox resumes it.
1115
+ */
1116
+ async findExistingSandbox() {
1117
+ const info = await this.lookupExistingSandboxInfo();
1118
+ if (!info) return null;
1119
+ try {
1120
+ return await Sandbox.connect(info.sandboxId, this.connectionOpts);
1121
+ } catch (e) {
1122
+ this.logger.debug(`${LOG_PREFIX} Error connecting to existing sandbox:`, e);
1123
+ return null;
1124
+ }
1125
+ }
1032
1126
  /**
1033
1127
  * Resolve the template specification to a template ID.
1034
1128
  *