@mastra/platform-workspace 1.3.0 → 1.4.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
@@ -1,6 +1,7 @@
1
1
  import { Buffer } from "buffer";
2
2
  import nodePath from "path";
3
3
  import { FileExistsError, FileNotFoundError, MastraFilesystem, MastraSandbox, ProcessHandle, SandboxNotReadyError, SandboxProcessManager, UnsupportedStdinCloseError, WorkspaceReadOnlyError } from "@mastra/core/workspace";
4
+ import { CommandExitError, Sandbox, TimeoutError } from "e2b";
4
5
  //#region src/client.ts
5
6
  const DEFAULT_PROXY_URL = "https://workspaces.mastra.ai";
6
7
  /**
@@ -13,12 +14,20 @@ function requireOption(value, name) {
13
14
  if (!value) throw new Error(`${name} is required`);
14
15
  return value;
15
16
  }
17
+ function resolveSandboxProvider(value) {
18
+ const provider = value?.trim() || "railway";
19
+ if (provider !== "railway" && provider !== "e2b") throw new Error("SANDBOX_PROVIDER must be either \"railway\" or \"e2b\"");
20
+ return provider;
21
+ }
16
22
  function resolvePlatformOptions(options) {
23
+ const configuredSandboxProvider = process.env.SANDBOX_PROVIDER?.trim();
17
24
  return {
18
25
  accessToken: requireOption(options.accessToken ?? process.env.MASTRA_PLATFORM_ACCESS_TOKEN, "accessToken"),
19
26
  projectId: requireOption(options.projectId ?? process.env.MASTRA_PROJECT_ID, "projectId"),
20
27
  actingUserId: options.actingUserId?.trim() || void 0,
21
28
  proxyUrl: (process.env.MASTRA_WORKSPACE_PROXY_URL ?? DEFAULT_PROXY_URL).replace(/\/$/, ""),
29
+ sandboxProvider: resolveSandboxProvider(configuredSandboxProvider),
30
+ useLegacyRoutes: !configuredSandboxProvider,
22
31
  sessionId: options.sessionId,
23
32
  threadId: options.threadId,
24
33
  fetch: options.fetch ?? fetch
@@ -65,6 +74,8 @@ var PlatformClient = class {
65
74
  projectId;
66
75
  actingUserId;
67
76
  proxyUrl;
77
+ sandboxProvider;
78
+ useLegacyRoutes;
68
79
  /** Advisory session correlation id — see {@link PlatformClientOptions.sessionId}. */
69
80
  sessionId;
70
81
  /** Advisory thread correlation id — see {@link PlatformClientOptions.threadId}. */
@@ -76,12 +87,15 @@ var PlatformClient = class {
76
87
  this.projectId = resolved.projectId;
77
88
  this.actingUserId = resolved.actingUserId;
78
89
  this.proxyUrl = resolved.proxyUrl;
90
+ this.sandboxProvider = resolved.sandboxProvider;
91
+ this.useLegacyRoutes = resolved.useLegacyRoutes;
79
92
  this.sessionId = resolved.sessionId;
80
93
  this.threadId = resolved.threadId;
81
94
  this.fetch = resolved.fetch;
82
95
  }
83
96
  async request(path, options = {}) {
84
- const url = new URL(`${this.proxyUrl}/v1/projects/${encodeURIComponent(this.projectId)}${path}`);
97
+ const providerPath = this.useLegacyRoutes ? "" : `/${this.sandboxProvider}`;
98
+ const url = new URL(`${this.proxyUrl}/v1${providerPath}/projects/${encodeURIComponent(this.projectId)}${path}`);
85
99
  for (const [key, value] of Object.entries(options.query ?? {})) if (value !== void 0) url.searchParams.set(key, String(value));
86
100
  const headers = new Headers(options.headers);
87
101
  headers.set("authorization", `Bearer ${this.accessToken}`);
@@ -502,6 +516,62 @@ function execViaLease(lease, options) {
502
516
  });
503
517
  }
504
518
  //#endregion
519
+ //#region src/e2b-exec.ts
520
+ const E2B_ENVD_VERSION = "0.4.0";
521
+ const execViaE2BLease = async (lease, options) => {
522
+ const stdoutChunks = [];
523
+ const stderrChunks = [];
524
+ const onStdout = (data) => {
525
+ stdoutChunks.push(data);
526
+ options.onStdout?.(data);
527
+ };
528
+ const onStderr = (data) => {
529
+ stderrChunks.push(data);
530
+ options.onStderr?.(data);
531
+ };
532
+ try {
533
+ const result = await new Sandbox({
534
+ sandboxId: lease.sandboxId,
535
+ envdVersion: E2B_ENVD_VERSION,
536
+ envdAccessToken: lease.jwt,
537
+ sandboxUrl: lease.wsEndpoint,
538
+ validateApiKey: false
539
+ }).commands.run(options.command, {
540
+ cwd: options.cwd,
541
+ envs: options.env,
542
+ timeoutMs: options.timeoutMs,
543
+ onStdout,
544
+ onStderr
545
+ });
546
+ return {
547
+ exitCode: result.exitCode,
548
+ stdout: result.stdout,
549
+ stderr: result.stderr,
550
+ truncated: false,
551
+ timedOut: false,
552
+ opened: true
553
+ };
554
+ } catch (error) {
555
+ if (error instanceof CommandExitError) return {
556
+ exitCode: error.exitCode,
557
+ stdout: error.stdout,
558
+ stderr: error.stderr,
559
+ truncated: false,
560
+ timedOut: false,
561
+ opened: true
562
+ };
563
+ return {
564
+ exitCode: null,
565
+ stdout: stdoutChunks.join(""),
566
+ stderr: stderrChunks.join(""),
567
+ truncated: false,
568
+ timedOut: error instanceof TimeoutError,
569
+ closeReason: error instanceof Error ? error.message : String(error),
570
+ opened: true
571
+ };
572
+ }
573
+ };
574
+ //#endregion
505
575
  //#region src/private-net-exec.ts
506
576
  /**
507
577
  * Thrown by {@link execViaPrivateNetwork} when the sidecar returns a non-2xx
@@ -843,6 +913,7 @@ var PlatformSandbox = class PlatformSandbox extends MastraSandbox {
843
913
  _instructionsOverride;
844
914
  _createdAt = null;
845
915
  _webSocketFactory;
916
+ _e2bExecRunner;
846
917
  _privateNetFetch;
847
918
  /**
848
919
  * Registry that maps `sandboxId → instanceUrl` for the private-network
@@ -940,6 +1011,7 @@ var PlatformSandbox = class PlatformSandbox extends MastraSandbox {
940
1011
  this._timeout = options.timeout;
941
1012
  this._instructionsOverride = options.instructions;
942
1013
  this._webSocketFactory = options.webSocketFactory;
1014
+ this._e2bExecRunner = options.e2bExecRunner ?? execViaE2BLease;
943
1015
  this._privateNetFetch = options.privateNetFetch;
944
1016
  this._addressRegistry = options.addressRegistry;
945
1017
  }
@@ -960,6 +1032,7 @@ var PlatformSandbox = class PlatformSandbox extends MastraSandbox {
960
1032
  */
961
1033
  clone(options = {}) {
962
1034
  const id = options.id ?? options.checkpointName;
1035
+ const seedCheckpointName = options.seedCheckpointName ?? (this._client.sandboxProvider === "e2b" ? options.checkpointName : void 0) ?? this._seedCheckpointName;
963
1036
  return new PlatformSandbox({
964
1037
  ...id !== void 0 && { id },
965
1038
  accessToken: this._client.accessToken,
@@ -970,13 +1043,14 @@ var PlatformSandbox = class PlatformSandbox extends MastraSandbox {
970
1043
  fetch: this._client.fetch,
971
1044
  environmentId: this._environmentId,
972
1045
  ...options.sandboxId !== void 0 && { sandboxId: options.sandboxId },
973
- ...(options.seedCheckpointName ?? this._seedCheckpointName) !== void 0 && { seedCheckpointName: options.seedCheckpointName ?? this._seedCheckpointName },
1046
+ ...seedCheckpointName !== void 0 && { seedCheckpointName },
974
1047
  idleTimeoutMinutes: options.idleTimeoutMinutes ?? this._idleTimeoutMinutes,
975
1048
  ...this._networkIsolation !== void 0 && { networkIsolation: this._networkIsolation },
976
1049
  env: options.env ?? this._env,
977
1050
  ...this._timeout !== void 0 && { timeout: this._timeout },
978
1051
  ...this._instructionsOverride !== void 0 && { instructions: this._instructionsOverride },
979
1052
  ...this._webSocketFactory !== void 0 && { webSocketFactory: this._webSocketFactory },
1053
+ e2bExecRunner: this._e2bExecRunner,
980
1054
  ...this._privateNetFetch !== void 0 && { privateNetFetch: this._privateNetFetch },
981
1055
  ...this._addressRegistry !== void 0 && { addressRegistry: this._addressRegistry }
982
1056
  });
@@ -1177,7 +1251,7 @@ var PlatformSandbox = class PlatformSandbox extends MastraSandbox {
1177
1251
  * it. Any in-flight capture is awaited first so the preserved checkpoint
1178
1252
  * reflects the latest disk state we asked for.
1179
1253
  *
1180
- * Corresponds to `DELETE /v1/projects/:pid/sandbox/:sandboxId` on
1254
+ * Corresponds to `DELETE /v1/:provider/projects/:pid/sandbox/:sandboxId` on
1181
1255
  * workspace-proxy, which by contract does not touch the checkpoint. Use
1182
1256
  * {@link destroy} when you want the checkpoint released too.
1183
1257
  */
@@ -1198,15 +1272,15 @@ var PlatformSandbox = class PlatformSandbox extends MastraSandbox {
1198
1272
  * transient proxy error must not leave the caller with a half-torn-down
1199
1273
  * sandbox they can't safely retry.
1200
1274
  *
1201
- * Requires the caller to have constructed with a recovery `id` (there is
1202
- * no checkpoint to delete otherwise); callers without one skip the
1203
- * checkpoint DELETE and behave identically to {@link stop}.
1275
+ * Railway requires a caller-supplied recovery `id` before it can have a
1276
+ * checkpoint to delete. E2B also permits capture with the automatic id, so
1277
+ * destroy releases that named snapshot even when no recovery id was supplied.
1204
1278
  */
1205
1279
  async destroy() {
1206
1280
  if (!this._sandboxId) return;
1207
1281
  const destroyedSandboxId = this._sandboxId;
1208
1282
  this._captureInFlight = null;
1209
- if (this._hasRecoveryKey) try {
1283
+ if (this._hasRecoveryKey || this._client.sandboxProvider === "e2b") try {
1210
1284
  await this._client.request(`/sandbox/${encodeURIComponent(destroyedSandboxId)}/checkpoint`, {
1211
1285
  method: "DELETE",
1212
1286
  headers: { "content-type": "application/json" },
@@ -1289,7 +1363,7 @@ var PlatformSandbox = class PlatformSandbox extends MastraSandbox {
1289
1363
  * to a skip as described above.
1290
1364
  */
1291
1365
  async captureCheckpoint() {
1292
- if (!this._hasRecoveryKey) {
1366
+ if (!this._hasRecoveryKey && this._client.sandboxProvider !== "e2b") {
1293
1367
  this.logger.debug(`captureCheckpoint skipped: no recovery key configured for sandbox ${this._sandboxId ?? "(unstarted)"}`);
1294
1368
  return {
1295
1369
  status: "skipped",
@@ -1472,13 +1546,14 @@ var PlatformSandbox = class PlatformSandbox extends MastraSandbox {
1472
1546
  }
1473
1547
  lastLease = lease;
1474
1548
  attemptsMade = attempt + 1;
1475
- const result = await execViaLease(lease, {
1549
+ const execOptions = {
1476
1550
  command: fullCommand,
1477
1551
  ...options?.cwd !== void 0 && { cwd: options.cwd },
1478
1552
  ...filteredEnv !== void 0 && { env: filteredEnv },
1479
1553
  ...effectiveTimeout != null && effectiveTimeout > 0 && { timeoutMs: effectiveTimeout },
1480
1554
  ...this._webSocketFactory && { webSocketFactory: this._webSocketFactory }
1481
- });
1555
+ };
1556
+ const result = lease.provider === "e2b" ? await this._e2bExecRunner(lease, execOptions) : await execViaLease(lease, execOptions);
1482
1557
  lastResult = result;
1483
1558
  if (result.exitCode !== null || result.timedOut) return result;
1484
1559
  }
@@ -1561,6 +1636,9 @@ var PlatformSandbox = class PlatformSandbox extends MastraSandbox {
1561
1636
  const json = await (await this._client.request(`/sandbox/${encodeURIComponent(sandboxId)}/exec-lease`, { method: "POST" })).json();
1562
1637
  const expiresAtMs = json.expiresAt ? Date.parse(json.expiresAt) : null;
1563
1638
  const lease = {
1639
+ provider: json.provider,
1640
+ sandboxId: json.sandboxId,
1641
+ providerResourceId: json.providerResourceId,
1564
1642
  jwt: json.jwt,
1565
1643
  wsEndpoint: json.wsEndpoint,
1566
1644
  subprotocol: json.subprotocol,