@mastra/platform-workspace 0.2.1 → 0.2.2-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/dist/index.js CHANGED
@@ -1,632 +1,677 @@
1
- import { Buffer } from 'buffer';
2
- import nodePath from 'path';
3
- import { MastraFilesystem, FileNotFoundError, WorkspaceReadOnlyError, FileExistsError, MastraSandbox, SandboxNotReadyError, SandboxProcessManager, ProcessHandle } from '@mastra/core/workspace';
4
-
5
- // src/client.ts
6
- var DEFAULT_PROXY_URL = "https://workspaces.mastra.ai";
7
- var DEFAULT_REQUEST_TIMEOUT_MS = 6e4;
1
+ import { Buffer } from "buffer";
2
+ import nodePath from "path";
3
+ import { FileExistsError, FileNotFoundError, MastraFilesystem, MastraSandbox, ProcessHandle, SandboxNotReadyError, SandboxProcessManager, WorkspaceReadOnlyError } from "@mastra/core/workspace";
4
+ //#region src/client.ts
5
+ const DEFAULT_PROXY_URL = "https://workspaces.mastra.ai";
6
+ /**
7
+ * Default per-request timeout for calls to the workspace proxy. Applied only
8
+ * when the caller doesn't already pass an `AbortSignal`. Long-running routes
9
+ * (e.g. `POST /sandbox/:id/exec`) pass their own longer signal.
10
+ */
11
+ const DEFAULT_REQUEST_TIMEOUT_MS = 6e4;
8
12
  function requireOption(value, name) {
9
- if (!value) throw new Error(`${name} is required`);
10
- return value;
13
+ if (!value) throw new Error(`${name} is required`);
14
+ return value;
11
15
  }
12
16
  function resolvePlatformOptions(options) {
13
- return {
14
- accessToken: requireOption(
15
- options.accessToken ?? process.env.MASTRA_PLATFORM_SECRET_KEY ?? // Deprecated alias — prefer MASTRA_PLATFORM_SECRET_KEY.
16
- process.env.MASTRA_PLATFORM_ACCESS_TOKEN,
17
- "accessToken"
18
- ),
19
- projectId: requireOption(options.projectId ?? process.env.MASTRA_PROJECT_ID, "projectId"),
20
- proxyUrl: (process.env.MASTRA_WORKSPACE_PROXY_URL ?? DEFAULT_PROXY_URL).replace(/\/$/, ""),
21
- fetch: options.fetch ?? fetch
22
- };
17
+ return {
18
+ accessToken: requireOption(options.accessToken ?? process.env.MASTRA_PLATFORM_SECRET_KEY ?? process.env.MASTRA_PLATFORM_ACCESS_TOKEN, "accessToken"),
19
+ projectId: requireOption(options.projectId ?? process.env.MASTRA_PROJECT_ID, "projectId"),
20
+ proxyUrl: (process.env.MASTRA_WORKSPACE_PROXY_URL ?? DEFAULT_PROXY_URL).replace(/\/$/, ""),
21
+ fetch: options.fetch ?? fetch
22
+ };
23
23
  }
24
24
  function parseProxyError(body) {
25
- if (!body) return void 0;
26
- let parsed;
27
- try {
28
- parsed = JSON.parse(body);
29
- } catch {
30
- return void 0;
31
- }
32
- if (typeof parsed !== "object" || parsed === null) return void 0;
33
- const err = parsed.error;
34
- if (typeof err !== "object" || err === null) return void 0;
35
- const { message, type } = err;
36
- if (typeof message !== "string" || typeof type !== "string") return void 0;
37
- return { message, type };
25
+ if (!body) return void 0;
26
+ let parsed;
27
+ try {
28
+ parsed = JSON.parse(body);
29
+ } catch {
30
+ return;
31
+ }
32
+ if (typeof parsed !== "object" || parsed === null) return void 0;
33
+ const err = parsed.error;
34
+ if (typeof err !== "object" || err === null) return void 0;
35
+ const { message, type } = err;
36
+ if (typeof message !== "string" || typeof type !== "string") return void 0;
37
+ return {
38
+ message,
39
+ type
40
+ };
38
41
  }
39
42
  var PlatformApiError = class extends Error {
40
- status;
41
- body;
42
- /** Machine-readable proxy error kind (e.g. `not_found`), when the response body matches `{ error: { message, type } }`. */
43
- code;
44
- /** Human-readable proxy error message, when the response body matches `{ error: { message, type } }`. */
45
- proxyMessage;
46
- constructor(status, body) {
47
- const parsed = parseProxyError(body);
48
- const summary = parsed ? `${parsed.type}: ${parsed.message}` : body;
49
- super(`Platform proxy request failed with ${status}${summary ? `: ${summary}` : ""}`);
50
- this.name = "PlatformApiError";
51
- this.status = status;
52
- this.body = body;
53
- this.code = parsed?.type;
54
- this.proxyMessage = parsed?.message;
55
- }
43
+ status;
44
+ body;
45
+ /** Machine-readable proxy error kind (e.g. `not_found`), when the response body matches `{ error: { message, type } }`. */
46
+ code;
47
+ /** Human-readable proxy error message, when the response body matches `{ error: { message, type } }`. */
48
+ proxyMessage;
49
+ constructor(status, body) {
50
+ const parsed = parseProxyError(body);
51
+ const summary = parsed ? `${parsed.type}: ${parsed.message}` : body;
52
+ super(`Platform proxy request failed with ${status}${summary ? `: ${summary}` : ""}`);
53
+ this.name = "PlatformApiError";
54
+ this.status = status;
55
+ this.body = body;
56
+ this.code = parsed?.type;
57
+ this.proxyMessage = parsed?.message;
58
+ }
56
59
  };
57
60
  var PlatformClient = class {
58
- accessToken;
59
- projectId;
60
- proxyUrl;
61
- fetch;
62
- constructor(options) {
63
- const resolved = resolvePlatformOptions(options);
64
- this.accessToken = resolved.accessToken;
65
- this.projectId = resolved.projectId;
66
- this.proxyUrl = resolved.proxyUrl;
67
- this.fetch = resolved.fetch;
68
- }
69
- async request(path, options = {}) {
70
- const url = new URL(`${this.proxyUrl}/v1/projects/${encodeURIComponent(this.projectId)}${path}`);
71
- for (const [key, value] of Object.entries(options.query ?? {})) {
72
- if (value !== void 0) url.searchParams.set(key, String(value));
73
- }
74
- const headers = new Headers(options.headers);
75
- headers.set("authorization", `Bearer ${this.accessToken}`);
76
- const { query: _query, ...fetchOptions } = options;
77
- const signal = fetchOptions.signal ?? AbortSignal.timeout(DEFAULT_REQUEST_TIMEOUT_MS);
78
- const response = await this.fetch(url, { ...fetchOptions, headers, signal });
79
- if (!response.ok) {
80
- throw new PlatformApiError(response.status, await response.text());
81
- }
82
- return response;
83
- }
61
+ accessToken;
62
+ projectId;
63
+ proxyUrl;
64
+ fetch;
65
+ constructor(options) {
66
+ const resolved = resolvePlatformOptions(options);
67
+ this.accessToken = resolved.accessToken;
68
+ this.projectId = resolved.projectId;
69
+ this.proxyUrl = resolved.proxyUrl;
70
+ this.fetch = resolved.fetch;
71
+ }
72
+ async request(path, options = {}) {
73
+ const url = new URL(`${this.proxyUrl}/v1/projects/${encodeURIComponent(this.projectId)}${path}`);
74
+ for (const [key, value] of Object.entries(options.query ?? {})) if (value !== void 0) url.searchParams.set(key, String(value));
75
+ const headers = new Headers(options.headers);
76
+ headers.set("authorization", `Bearer ${this.accessToken}`);
77
+ const { query: _query, ...fetchOptions } = options;
78
+ const signal = fetchOptions.signal ?? AbortSignal.timeout(DEFAULT_REQUEST_TIMEOUT_MS);
79
+ const response = await this.fetch(url, {
80
+ ...fetchOptions,
81
+ headers,
82
+ signal
83
+ });
84
+ if (!response.ok) throw new PlatformApiError(response.status, await response.text());
85
+ return response;
86
+ }
84
87
  };
88
+ //#endregion
89
+ //#region src/filesystem.ts
85
90
  function normalizePath(input) {
86
- if (!input || input === ".") return "/";
87
- let normalized = input.startsWith("/") ? input : `/${input}`;
88
- normalized = nodePath.posix.normalize(normalized);
89
- return normalized === "." ? "/" : normalized;
91
+ if (!input || input === ".") return "/";
92
+ let normalized = input.startsWith("/") ? input : `/${input}`;
93
+ normalized = nodePath.posix.normalize(normalized);
94
+ return normalized === "." ? "/" : normalized;
90
95
  }
91
96
  function keyFromPath(path) {
92
- const normalized = normalizePath(path);
93
- return normalized === "/" ? "" : normalized.slice(1);
97
+ const normalized = normalizePath(path);
98
+ return normalized === "/" ? "" : normalized.slice(1);
94
99
  }
100
+ /**
101
+ * Encode each `/`-delimited segment of an object key with `encodeURIComponent`
102
+ * so reserved URL characters (`?`, `#`, `%`, `&`, `+`, spaces, etc.) are
103
+ * treated as part of the key instead of URL syntax. Kept segment-aware so
104
+ * `/` continues to act as a path separator on the wire.
105
+ */
95
106
  function encodeKeyPath(key) {
96
- return key.split("/").map(encodeURIComponent).join("/");
107
+ return key.split("/").map(encodeURIComponent).join("/");
97
108
  }
98
109
  function nameFromPath(path) {
99
- const normalized = normalizePath(path);
100
- if (normalized === "/") return "";
101
- return normalized.slice(normalized.lastIndexOf("/") + 1);
110
+ const normalized = normalizePath(path);
111
+ if (normalized === "/") return "";
112
+ return normalized.slice(normalized.lastIndexOf("/") + 1);
102
113
  }
103
114
  function contentToBody(content) {
104
- if (typeof content === "string") return content;
105
- return Buffer.from(content);
115
+ if (typeof content === "string") return content;
116
+ return Buffer.from(content);
106
117
  }
107
118
  function headerDate(headers, name) {
108
- const value = headers.get(name);
109
- return value ? new Date(value) : /* @__PURE__ */ new Date(0);
119
+ const value = headers.get(name);
120
+ return value ? new Date(value) : /* @__PURE__ */ new Date(0);
110
121
  }
111
122
  function headerSize(headers) {
112
- const value = headers.get("content-length");
113
- return value ? Number(value) : 0;
123
+ const value = headers.get("content-length");
124
+ return value ? Number(value) : 0;
114
125
  }
115
126
  function isNotFound(error) {
116
- return typeof error === "object" && error !== null && "status" in error && error.status === 404;
127
+ return typeof error === "object" && error !== null && "status" in error && error.status === 404;
117
128
  }
118
129
  var PlatformFilesystem = class extends MastraFilesystem {
119
- id;
120
- name = "PlatformFilesystem";
121
- provider = "platform";
122
- readOnly;
123
- displayName;
124
- icon;
125
- description;
126
- status = "pending";
127
- _client;
128
- _bucketName;
129
- _instructionsOverride;
130
- constructor(options = {}) {
131
- super({ ...options, name: "PlatformFilesystem" });
132
- this.id = options.id ?? this.generateId();
133
- this._bucketName = options.bucketName ?? process.env.MASTRA_PLATFORM_BUCKET_NAME ?? "";
134
- if (!this._bucketName) throw new Error("bucketName is required");
135
- this.readOnly = options.readOnly;
136
- this.displayName = options.displayName;
137
- this.icon = options.icon ?? "cloud";
138
- this.description = options.description;
139
- this._instructionsOverride = options.instructions;
140
- this._client = new PlatformClient(options);
141
- }
142
- generateId() {
143
- return `platform-fs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
144
- }
145
- async readFile(path, options) {
146
- await this.ensureReady();
147
- let response;
148
- try {
149
- response = await this._client.request(
150
- `/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`
151
- );
152
- } catch (error) {
153
- if (isNotFound(error)) throw new FileNotFoundError(path);
154
- throw error;
155
- }
156
- const buffer = Buffer.from(await response.arrayBuffer());
157
- return options?.encoding ? buffer.toString(options.encoding) : buffer;
158
- }
159
- async writeFile(path, content, options) {
160
- await this.ensureReady();
161
- if (this.readOnly) throw new WorkspaceReadOnlyError("writeFile");
162
- const headers = {};
163
- if (options?.mimeType) headers["content-type"] = options.mimeType;
164
- if (options?.overwrite === false) headers["if-none-match"] = "*";
165
- try {
166
- await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`, {
167
- method: "PUT",
168
- headers,
169
- body: contentToBody(content)
170
- });
171
- } catch (error) {
172
- if (typeof error === "object" && error !== null && "status" in error && error.status === 412) {
173
- throw new FileExistsError(path);
174
- }
175
- throw error;
176
- }
177
- }
178
- /**
179
- * Append bytes to a file.
180
- *
181
- * **Not atomic.** Object storage behind the workspace proxy has no native
182
- * append or compare-and-swap primitive, so this implementation is a
183
- * read-modify-write: it reads the current contents, concatenates the new
184
- * bytes, and PUTs the whole object back. Concurrent `appendFile` calls to
185
- * the same path can overwrite each other's writes ("last write wins").
186
- * Use `writeFile` with distinct keys for concurrent writers.
187
- */
188
- async appendFile(path, content) {
189
- const existing = await this.exists(path) ? await this.readFile(path) : Buffer.alloc(0);
190
- await this.writeFile(
191
- path,
192
- Buffer.concat([Buffer.isBuffer(existing) ? existing : Buffer.from(existing), Buffer.from(content)])
193
- );
194
- }
195
- async deleteFile(path, options) {
196
- await this.ensureReady();
197
- if (this.readOnly) throw new WorkspaceReadOnlyError("deleteFile");
198
- try {
199
- await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`, {
200
- method: "DELETE",
201
- query: { recursive: options?.recursive }
202
- });
203
- } catch (error) {
204
- if (isNotFound(error) && options?.force) return;
205
- if (isNotFound(error)) throw new FileNotFoundError(path);
206
- throw error;
207
- }
208
- }
209
- async copyFile(src, dest, options) {
210
- await this.ensureReady();
211
- if (this.readOnly) throw new WorkspaceReadOnlyError("copyFile");
212
- if (options?.overwrite === false) {
213
- throw new Error("PlatformFilesystem.copyFile does not support overwrite: false \u2014 the proxy always overwrites.");
214
- }
215
- await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(src))}`, {
216
- method: "POST",
217
- query: { op: "copy" },
218
- headers: { "content-type": "application/json" },
219
- body: JSON.stringify({ destination: keyFromPath(dest) })
220
- });
221
- }
222
- async moveFile(src, dest, options) {
223
- await this.ensureReady();
224
- if (this.readOnly) throw new WorkspaceReadOnlyError("moveFile");
225
- if (options?.overwrite === false) {
226
- throw new Error("PlatformFilesystem.moveFile does not support overwrite: false \u2014 the proxy always overwrites.");
227
- }
228
- await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(src))}`, {
229
- method: "POST",
230
- query: { op: "rename" },
231
- headers: { "content-type": "application/json" },
232
- body: JSON.stringify({ destination: keyFromPath(dest) })
233
- });
234
- }
235
- async mkdir(path, _options) {
236
- await this.ensureReady();
237
- if (this.readOnly) throw new WorkspaceReadOnlyError("mkdir");
238
- await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`, {
239
- method: "POST",
240
- query: { op: "mkdir" }
241
- });
242
- }
243
- async rmdir(path, options) {
244
- await this.deleteFile(path.endsWith("/") ? path : `${path}/`, { recursive: true, force: options?.force });
245
- }
246
- async readdir(path, options) {
247
- await this.ensureReady();
248
- const prefix = keyFromPath(path);
249
- const response = await this._client.request(
250
- `/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(prefix)}`,
251
- {
252
- query: {
253
- delimiter: options?.recursive ? void 0 : "/",
254
- prefix: prefix ? `${prefix.replace(/\/$/, "")}/` : void 0
255
- }
256
- }
257
- );
258
- const json = await response.json();
259
- return [
260
- ...(json.commonPrefixes ?? []).map((prefix2) => ({
261
- name: nameFromPath(prefix2.replace(/\/$/, "")),
262
- type: "directory"
263
- })),
264
- ...(json.contents ?? []).filter((object) => object.key && !object.key.endsWith("/")).map((object) => ({
265
- name: nameFromPath(object.key),
266
- type: "file",
267
- size: object.size
268
- }))
269
- ].filter(
270
- (entry) => !options?.extension || entry.type === "directory" || matchesExtension(entry.name, options.extension)
271
- );
272
- }
273
- async exists(path) {
274
- try {
275
- await this.stat(path);
276
- return true;
277
- } catch (error) {
278
- if (isNotFound(error) || error instanceof FileNotFoundError) return false;
279
- throw error;
280
- }
281
- }
282
- async stat(path) {
283
- await this.ensureReady();
284
- const normalized = normalizePath(path);
285
- if (normalized === "/") {
286
- return { name: "", path: "/", type: "directory", size: 0, createdAt: /* @__PURE__ */ new Date(0), modifiedAt: /* @__PURE__ */ new Date(0) };
287
- }
288
- let response;
289
- try {
290
- response = await this._client.request(
291
- `/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`,
292
- {
293
- method: "HEAD"
294
- }
295
- );
296
- } catch (error) {
297
- if (isNotFound(error)) throw new FileNotFoundError(path);
298
- throw error;
299
- }
300
- return {
301
- name: nameFromPath(path),
302
- path: normalized,
303
- type: normalized.endsWith("/") ? "directory" : "file",
304
- size: headerSize(response.headers),
305
- createdAt: headerDate(response.headers, "last-modified"),
306
- modifiedAt: headerDate(response.headers, "last-modified"),
307
- mimeType: response.headers.get("content-type") ?? void 0
308
- };
309
- }
310
- realpath(path) {
311
- return Promise.resolve(normalizePath(path));
312
- }
313
- getInstructions(opts) {
314
- const defaultInstructions = `Platform filesystem backed by Mastra Platform bucket ${this._bucketName}. Use absolute workspace paths.`;
315
- if (typeof this._instructionsOverride === "function") {
316
- return this._instructionsOverride({ defaultInstructions, requestContext: opts?.requestContext });
317
- }
318
- if (typeof this._instructionsOverride === "string") return this._instructionsOverride;
319
- return defaultInstructions;
320
- }
321
- getInfo() {
322
- return {
323
- id: this.id,
324
- name: this.name,
325
- provider: this.provider,
326
- status: this.status,
327
- readOnly: this.readOnly,
328
- icon: this.icon,
329
- metadata: {
330
- bucketName: this._bucketName,
331
- ...this.displayName && { displayName: this.displayName },
332
- ...this.description && { description: this.description }
333
- }
334
- };
335
- }
130
+ id;
131
+ name = "PlatformFilesystem";
132
+ provider = "platform";
133
+ readOnly;
134
+ displayName;
135
+ icon;
136
+ description;
137
+ status = "pending";
138
+ _client;
139
+ _bucketName;
140
+ _instructionsOverride;
141
+ constructor(options = {}) {
142
+ super({
143
+ ...options,
144
+ name: "PlatformFilesystem"
145
+ });
146
+ this.id = options.id ?? this.generateId();
147
+ this._bucketName = options.bucketName ?? process.env.MASTRA_PLATFORM_BUCKET_NAME ?? "";
148
+ if (!this._bucketName) throw new Error("bucketName is required");
149
+ this.readOnly = options.readOnly;
150
+ this.displayName = options.displayName;
151
+ this.icon = options.icon ?? "cloud";
152
+ this.description = options.description;
153
+ this._instructionsOverride = options.instructions;
154
+ this._client = new PlatformClient(options);
155
+ }
156
+ generateId() {
157
+ return `platform-fs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
158
+ }
159
+ async readFile(path, options) {
160
+ await this.ensureReady();
161
+ let response;
162
+ try {
163
+ response = await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`);
164
+ } catch (error) {
165
+ if (isNotFound(error)) throw new FileNotFoundError(path);
166
+ throw error;
167
+ }
168
+ const buffer = Buffer.from(await response.arrayBuffer());
169
+ return options?.encoding ? buffer.toString(options.encoding) : buffer;
170
+ }
171
+ async writeFile(path, content, options) {
172
+ await this.ensureReady();
173
+ if (this.readOnly) throw new WorkspaceReadOnlyError("writeFile");
174
+ const headers = {};
175
+ if (options?.mimeType) headers["content-type"] = options.mimeType;
176
+ if (options?.overwrite === false) headers["if-none-match"] = "*";
177
+ try {
178
+ await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`, {
179
+ method: "PUT",
180
+ headers,
181
+ body: contentToBody(content)
182
+ });
183
+ } catch (error) {
184
+ if (typeof error === "object" && error !== null && "status" in error && error.status === 412) throw new FileExistsError(path);
185
+ throw error;
186
+ }
187
+ }
188
+ /**
189
+ * Append bytes to a file.
190
+ *
191
+ * **Not atomic.** Object storage behind the workspace proxy has no native
192
+ * append or compare-and-swap primitive, so this implementation is a
193
+ * read-modify-write: it reads the current contents, concatenates the new
194
+ * bytes, and PUTs the whole object back. Concurrent `appendFile` calls to
195
+ * the same path can overwrite each other's writes ("last write wins").
196
+ * Use `writeFile` with distinct keys for concurrent writers.
197
+ */
198
+ async appendFile(path, content) {
199
+ const existing = await this.exists(path) ? await this.readFile(path) : Buffer.alloc(0);
200
+ await this.writeFile(path, Buffer.concat([Buffer.isBuffer(existing) ? existing : Buffer.from(existing), Buffer.from(content)]));
201
+ }
202
+ async deleteFile(path, options) {
203
+ await this.ensureReady();
204
+ if (this.readOnly) throw new WorkspaceReadOnlyError("deleteFile");
205
+ try {
206
+ await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`, {
207
+ method: "DELETE",
208
+ query: { recursive: options?.recursive }
209
+ });
210
+ } catch (error) {
211
+ if (isNotFound(error) && options?.force) return;
212
+ if (isNotFound(error)) throw new FileNotFoundError(path);
213
+ throw error;
214
+ }
215
+ }
216
+ async copyFile(src, dest, options) {
217
+ await this.ensureReady();
218
+ if (this.readOnly) throw new WorkspaceReadOnlyError("copyFile");
219
+ if (options?.overwrite === false) throw new Error("PlatformFilesystem.copyFile does not support overwrite: false — the proxy always overwrites.");
220
+ await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(src))}`, {
221
+ method: "POST",
222
+ query: { op: "copy" },
223
+ headers: { "content-type": "application/json" },
224
+ body: JSON.stringify({ destination: keyFromPath(dest) })
225
+ });
226
+ }
227
+ async moveFile(src, dest, options) {
228
+ await this.ensureReady();
229
+ if (this.readOnly) throw new WorkspaceReadOnlyError("moveFile");
230
+ if (options?.overwrite === false) throw new Error("PlatformFilesystem.moveFile does not support overwrite: false — the proxy always overwrites.");
231
+ await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(src))}`, {
232
+ method: "POST",
233
+ query: { op: "rename" },
234
+ headers: { "content-type": "application/json" },
235
+ body: JSON.stringify({ destination: keyFromPath(dest) })
236
+ });
237
+ }
238
+ async mkdir(path, _options) {
239
+ await this.ensureReady();
240
+ if (this.readOnly) throw new WorkspaceReadOnlyError("mkdir");
241
+ await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`, {
242
+ method: "POST",
243
+ query: { op: "mkdir" }
244
+ });
245
+ }
246
+ async rmdir(path, options) {
247
+ await this.deleteFile(path.endsWith("/") ? path : `${path}/`, {
248
+ recursive: true,
249
+ force: options?.force
250
+ });
251
+ }
252
+ async readdir(path, options) {
253
+ await this.ensureReady();
254
+ const prefix = keyFromPath(path);
255
+ const json = await (await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(prefix)}`, { query: {
256
+ delimiter: options?.recursive ? void 0 : "/",
257
+ prefix: prefix ? `${prefix.replace(/\/$/, "")}/` : void 0
258
+ } })).json();
259
+ return [...(json.commonPrefixes ?? []).map((prefix) => ({
260
+ name: nameFromPath(prefix.replace(/\/$/, "")),
261
+ type: "directory"
262
+ })), ...(json.contents ?? []).filter((object) => object.key && !object.key.endsWith("/")).map((object) => ({
263
+ name: nameFromPath(object.key),
264
+ type: "file",
265
+ size: object.size
266
+ }))].filter((entry) => !options?.extension || entry.type === "directory" || matchesExtension(entry.name, options.extension));
267
+ }
268
+ async exists(path) {
269
+ try {
270
+ await this.stat(path);
271
+ return true;
272
+ } catch (error) {
273
+ if (isNotFound(error) || error instanceof FileNotFoundError) return false;
274
+ throw error;
275
+ }
276
+ }
277
+ async stat(path) {
278
+ await this.ensureReady();
279
+ const normalized = normalizePath(path);
280
+ if (normalized === "/") return {
281
+ name: "",
282
+ path: "/",
283
+ type: "directory",
284
+ size: 0,
285
+ createdAt: /* @__PURE__ */ new Date(0),
286
+ modifiedAt: /* @__PURE__ */ new Date(0)
287
+ };
288
+ let response;
289
+ try {
290
+ response = await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`, { method: "HEAD" });
291
+ } catch (error) {
292
+ if (isNotFound(error)) throw new FileNotFoundError(path);
293
+ throw error;
294
+ }
295
+ return {
296
+ name: nameFromPath(path),
297
+ path: normalized,
298
+ type: normalized.endsWith("/") ? "directory" : "file",
299
+ size: headerSize(response.headers),
300
+ createdAt: headerDate(response.headers, "last-modified"),
301
+ modifiedAt: headerDate(response.headers, "last-modified"),
302
+ mimeType: response.headers.get("content-type") ?? void 0
303
+ };
304
+ }
305
+ realpath(path) {
306
+ return Promise.resolve(normalizePath(path));
307
+ }
308
+ getInstructions(opts) {
309
+ const defaultInstructions = `Platform filesystem backed by Mastra Platform bucket ${this._bucketName}. Use absolute workspace paths.`;
310
+ if (typeof this._instructionsOverride === "function") return this._instructionsOverride({
311
+ defaultInstructions,
312
+ requestContext: opts?.requestContext
313
+ });
314
+ if (typeof this._instructionsOverride === "string") return this._instructionsOverride;
315
+ return defaultInstructions;
316
+ }
317
+ getInfo() {
318
+ return {
319
+ id: this.id,
320
+ name: this.name,
321
+ provider: this.provider,
322
+ status: this.status,
323
+ readOnly: this.readOnly,
324
+ icon: this.icon,
325
+ metadata: {
326
+ bucketName: this._bucketName,
327
+ ...this.displayName && { displayName: this.displayName },
328
+ ...this.description && { description: this.description }
329
+ }
330
+ };
331
+ }
336
332
  };
337
333
  function matchesExtension(name, extension) {
338
- const extensions = Array.isArray(extension) ? extension : [extension];
339
- return extensions.some((ext) => name.endsWith(ext));
334
+ return (Array.isArray(extension) ? extension : [extension]).some((ext) => name.endsWith(ext));
340
335
  }
336
+ //#endregion
337
+ //#region src/sandbox.ts
338
+ /** Max attempts for `POST /sandbox` when the proxy returns transient 5xx errors. */
339
+ const CREATE_MAX_ATTEMPTS = 3;
340
+ /** Base delay between create retries; multiplied by the attempt number. */
341
+ const CREATE_RETRY_BASE_DELAY_MS = 2e3;
342
+ /**
343
+ * Compose a shell command line from a `command` string and optional `args`.
344
+ *
345
+ * IMPORTANT: `command` is treated as a **shell string** and passed to the
346
+ * remote shell verbatim so callers can use pipes, redirects, and chaining
347
+ * (`ls -la | grep foo`). This matches the contract of {@link MastraSandbox}
348
+ * and the local sandbox implementation. `args` are always shell-quoted so
349
+ * they cannot inject syntax.
350
+ *
351
+ * Callers MUST NOT pass untrusted input as `command`. Untrusted values must
352
+ * be passed via `args`, where they are safely quoted. Passing untrusted
353
+ * input as `command` allows arbitrary shell syntax execution on the remote
354
+ * sandbox.
355
+ */
341
356
  function buildCommand(command, args) {
342
- return args?.length ? `${command} ${args.map(shellQuote).join(" ")}` : command;
357
+ return args?.length ? `${command} ${args.map(shellQuote).join(" ")}` : command;
343
358
  }
344
359
  function shellQuote(arg) {
345
- if (/^[a-zA-Z0-9._\-/=:@]+$/.test(arg)) return arg;
346
- return `'${arg.replace(/'/g, `'\\''`)}'`;
360
+ if (/^[a-zA-Z0-9._\-/=:@]+$/.test(arg)) return arg;
361
+ return `'${arg.replace(/'/g, `'\\''`)}'`;
347
362
  }
348
363
  var PlatformProcessHandle = class extends ProcessHandle {
349
- pid;
350
- resultPromise;
351
- exitCodeValue;
352
- constructor(pid, resultPromise, options) {
353
- super(options);
354
- this.pid = pid;
355
- this.resultPromise = resultPromise.then((result) => {
356
- this.exitCodeValue = result.exitCode;
357
- if (result.stdout) this.emitStdout(result.stdout);
358
- if (result.stderr) this.emitStderr(result.stderr);
359
- return result;
360
- });
361
- }
362
- get exitCode() {
363
- return this.exitCodeValue;
364
- }
365
- async wait() {
366
- return this.resultPromise;
367
- }
368
- async kill() {
369
- throw new Error("Platform sandbox command execution does not support killing individual processes");
370
- }
371
- async sendStdin() {
372
- throw new Error("Platform sandbox command execution does not support stdin");
373
- }
364
+ pid;
365
+ resultPromise;
366
+ exitCodeValue;
367
+ constructor(pid, resultPromise, options) {
368
+ super(options);
369
+ this.pid = pid;
370
+ this.resultPromise = resultPromise.then((result) => {
371
+ this.exitCodeValue = result.exitCode;
372
+ if (result.stdout) this.emitStdout(result.stdout);
373
+ if (result.stderr) this.emitStderr(result.stderr);
374
+ return result;
375
+ });
376
+ }
377
+ get exitCode() {
378
+ return this.exitCodeValue;
379
+ }
380
+ async wait() {
381
+ return this.resultPromise;
382
+ }
383
+ async kill() {
384
+ throw new Error("Platform sandbox command execution does not support killing individual processes");
385
+ }
386
+ async sendStdin() {
387
+ throw new Error("Platform sandbox command execution does not support stdin");
388
+ }
374
389
  };
375
390
  var PlatformProcessManager = class extends SandboxProcessManager {
376
- spawnCounter = 0;
377
- /**
378
- * Spawn a process on the remote sandbox.
379
- *
380
- * `command` is interpreted as a shell string by the remote shell, matching
381
- * the {@link MastraSandbox} contract. See {@link PlatformSandbox.executeCommand}
382
- * for the untrusted-input caveat: never pass untrusted values as `command`.
383
- */
384
- async spawn(command, options = {}) {
385
- const pid = `platform-proc-${Date.now().toString(36)}-${(this.spawnCounter++).toString(36)}`;
386
- const resultPromise = this.sandbox.executeCommand(command, void 0, options);
387
- const handle = new PlatformProcessHandle(pid, resultPromise, options);
388
- this._tracked.set(handle.pid, handle);
389
- return handle;
390
- }
391
- async list() {
392
- return Array.from(this._tracked.values()).map((handle) => ({
393
- pid: handle.pid,
394
- command: handle.command,
395
- running: handle.exitCode === void 0,
396
- ...handle.exitCode !== void 0 && { exitCode: handle.exitCode }
397
- }));
398
- }
391
+ spawnCounter = 0;
392
+ /**
393
+ * Spawn a process on the remote sandbox.
394
+ *
395
+ * `command` is interpreted as a shell string by the remote shell, matching
396
+ * the {@link MastraSandbox} contract. See {@link PlatformSandbox.executeCommand}
397
+ * for the untrusted-input caveat: never pass untrusted values as `command`.
398
+ */
399
+ async spawn(command, options = {}) {
400
+ const handle = new PlatformProcessHandle(`platform-proc-${Date.now().toString(36)}-${(this.spawnCounter++).toString(36)}`, this.sandbox.executeCommand(command, void 0, options), options);
401
+ this._tracked.set(handle.pid, handle);
402
+ return handle;
403
+ }
404
+ async list() {
405
+ return Array.from(this._tracked.values()).map((handle) => ({
406
+ pid: handle.pid,
407
+ command: handle.command,
408
+ running: handle.exitCode === void 0,
409
+ ...handle.exitCode !== void 0 && { exitCode: handle.exitCode }
410
+ }));
411
+ }
399
412
  };
400
- var PlatformSandbox = class _PlatformSandbox extends MastraSandbox {
401
- id;
402
- name = "PlatformSandbox";
403
- provider = "platform";
404
- status = "pending";
405
- _client;
406
- _environmentId;
407
- _sandboxId;
408
- _idleTimeoutMinutes;
409
- _networkIsolation;
410
- _env;
411
- _timeout;
412
- _instructionsOverride;
413
- _createdAt = null;
414
- constructor(options = {}) {
415
- super({ ...options, name: "PlatformSandbox", processes: new PlatformProcessManager() });
416
- this.id = options.id ?? this.generateId();
417
- this._client = new PlatformClient(options);
418
- this._environmentId = options.environmentId ?? process.env.MASTRA_ENVIRONMENT_ID ?? "";
419
- if (!this._environmentId && !options.sandboxId) throw new Error("environmentId is required");
420
- this._sandboxId = options.sandboxId;
421
- this._idleTimeoutMinutes = options.idleTimeoutMinutes;
422
- this._networkIsolation = options.networkIsolation;
423
- this._env = options.env ?? {};
424
- this._timeout = options.timeout;
425
- this._instructionsOverride = options.instructions;
426
- }
427
- generateId() {
428
- return `platform-sandbox-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
429
- }
430
- /**
431
- * Construct a sibling {@link PlatformSandbox} that inherits this sandbox's
432
- * credentials and defaults (access token, project, environment, network
433
- * isolation, timeout, instructions, env, idle timeout) with per-instance
434
- * overrides from `options`.
435
- *
436
- * Performs no I/O and does not require this sandbox to be started — the
437
- * returned sandbox is not started and provisions (or reattaches, when
438
- * `sandboxId` is set) on its own `start()`. Use it when one configured
439
- * sandbox acts as the template for a fleet of independent sandboxes
440
- * (e.g. one per project).
441
- */
442
- clone(options = {}) {
443
- return new _PlatformSandbox({
444
- ...options.id !== void 0 && { id: options.id },
445
- accessToken: this._client.accessToken,
446
- projectId: this._client.projectId,
447
- fetch: this._client.fetch,
448
- environmentId: this._environmentId,
449
- ...options.sandboxId !== void 0 && { sandboxId: options.sandboxId },
450
- idleTimeoutMinutes: options.idleTimeoutMinutes ?? this._idleTimeoutMinutes,
451
- ...this._networkIsolation !== void 0 && { networkIsolation: this._networkIsolation },
452
- env: options.env ?? this._env,
453
- ...this._timeout !== void 0 && { timeout: this._timeout },
454
- ...this._instructionsOverride !== void 0 && { instructions: this._instructionsOverride }
455
- });
456
- }
457
- async start() {
458
- if (this._sandboxId) {
459
- try {
460
- const response2 = await this._client.request(`/sandbox/${encodeURIComponent(this._sandboxId)}`);
461
- const json2 = await response2.json();
462
- this._createdAt = json2.createdAt ? new Date(json2.createdAt) : /* @__PURE__ */ new Date();
463
- return;
464
- } catch (error) {
465
- if (!(error instanceof PlatformApiError) || error.status !== 404) throw error;
466
- this._sandboxId = void 0;
467
- }
468
- }
469
- if (!this._environmentId) throw new Error("environmentId is required");
470
- const response = await this._client.request("/sandbox", {
471
- method: "POST",
472
- headers: { "content-type": "application/json" },
473
- body: JSON.stringify({
474
- // Sent so the platform can associate the provisioned resource with a
475
- // caller-stable identifier (used for opt-in checkpoint recovery). The
476
- // platform treats it as an advisory key: unknown values fall through
477
- // to a fresh sandbox, matching pre-existing behavior.
478
- id: this.id,
479
- environmentId: this._environmentId,
480
- idleTimeoutMinutes: this._idleTimeoutMinutes,
481
- networkIsolation: this._networkIsolation,
482
- env: this._env
483
- })
484
- });
485
- const json = await response.json();
486
- this._sandboxId = json.id;
487
- this._createdAt = json.createdAt ? new Date(json.createdAt) : /* @__PURE__ */ new Date();
488
- }
489
- async stop() {
490
- await this.destroy();
491
- }
492
- async destroy() {
493
- if (!this._sandboxId) return;
494
- await this._client.request(`/sandbox/${encodeURIComponent(this._sandboxId)}`, { method: "DELETE" });
495
- this._sandboxId = void 0;
496
- this._createdAt = null;
497
- }
498
- /**
499
- * Execute a command on the remote sandbox.
500
- *
501
- * `command` is a **shell string**: it is concatenated verbatim into the
502
- * command line sent to the remote shell, which lets callers use pipes,
503
- * redirects, and chaining (`ls -la | grep foo`). This matches the contract
504
- * of {@link MastraSandbox} and the local sandbox implementation.
505
- *
506
- * `args`, when provided, are always shell-quoted so they cannot inject
507
- * additional shell syntax.
508
- *
509
- * Security: callers MUST NOT pass untrusted input as `command`. If any part
510
- * of the invocation is derived from an untrusted source, pass it through
511
- * `args` (which is safely quoted) or shell-quote it yourself before
512
- * inclusion. Untrusted `command` values allow arbitrary shell syntax
513
- * execution on the remote sandbox.
514
- */
515
- async executeCommand(command, args, options) {
516
- await this.ensureRunning();
517
- if (!this._sandboxId) throw new SandboxNotReadyError(this.id);
518
- const started = Date.now();
519
- const fullCommand = buildCommand(command, args);
520
- const effectiveTimeout = options?.timeout ?? this._timeout;
521
- const timeoutSec = effectiveTimeout != null ? Math.ceil(effectiveTimeout / 1e3) : void 0;
522
- const clientSignal = effectiveTimeout != null && effectiveTimeout > 0 ? AbortSignal.timeout(effectiveTimeout + 3e4) : void 0;
523
- const response = await this._client.request(`/sandbox/${encodeURIComponent(this._sandboxId)}/exec`, {
524
- method: "POST",
525
- headers: { "content-type": "application/json" },
526
- body: JSON.stringify({
527
- command: fullCommand,
528
- timeoutSec,
529
- cwd: options?.cwd,
530
- env: options?.env
531
- }),
532
- signal: clientSignal
533
- });
534
- const json = await response.json();
535
- const exitCode = json.exitCode ?? (json.timedOut ? 124 : 1);
536
- return {
537
- success: exitCode === 0,
538
- exitCode,
539
- stdout: json.stdout,
540
- stderr: json.stderr,
541
- executionTimeMs: Date.now() - started,
542
- timedOut: json.timedOut,
543
- command: fullCommand
544
- };
545
- }
546
- async getInfo() {
547
- if (!this._sandboxId) {
548
- return {
549
- id: this.id,
550
- name: this.name,
551
- provider: this.provider,
552
- status: this.status,
553
- createdAt: this._createdAt ?? /* @__PURE__ */ new Date()
554
- };
555
- }
556
- const response = await this._client.request(`/sandbox/${encodeURIComponent(this._sandboxId)}`);
557
- const json = await response.json();
558
- return {
559
- id: json.id,
560
- name: this.name,
561
- provider: this.provider,
562
- status: this.status,
563
- createdAt: json.createdAt ? new Date(json.createdAt) : this._createdAt ?? /* @__PURE__ */ new Date(),
564
- metadata: {
565
- providerResourceId: json.providerResourceId ?? void 0,
566
- platformStatus: json.status
567
- }
568
- };
569
- }
570
- getInstructions(opts) {
571
- const defaultInstructions = `Platform sandbox${this._sandboxId ? ` ${this._sandboxId}` : ""}. Execute commands with the sandbox command APIs.`;
572
- if (typeof this._instructionsOverride === "function") {
573
- return this._instructionsOverride({ defaultInstructions, requestContext: opts?.requestContext });
574
- }
575
- if (typeof this._instructionsOverride === "string") return this._instructionsOverride;
576
- return defaultInstructions;
577
- }
413
+ var PlatformSandbox = class PlatformSandbox extends MastraSandbox {
414
+ id;
415
+ name = "PlatformSandbox";
416
+ provider = "platform";
417
+ status = "pending";
418
+ _client;
419
+ _environmentId;
420
+ _sandboxId;
421
+ _idleTimeoutMinutes;
422
+ _networkIsolation;
423
+ _env;
424
+ _timeout;
425
+ _instructionsOverride;
426
+ _createdAt = null;
427
+ constructor(options = {}) {
428
+ super({
429
+ ...options,
430
+ name: "PlatformSandbox",
431
+ processes: new PlatformProcessManager()
432
+ });
433
+ this.id = options.id ?? this.generateId();
434
+ this._client = new PlatformClient(options);
435
+ this._environmentId = options.environmentId ?? process.env.MASTRA_ENVIRONMENT_ID ?? "";
436
+ if (!this._environmentId && !options.sandboxId) throw new Error("environmentId is required");
437
+ this._sandboxId = options.sandboxId;
438
+ this._idleTimeoutMinutes = options.idleTimeoutMinutes;
439
+ this._networkIsolation = options.networkIsolation;
440
+ this._env = options.env ?? {};
441
+ this._timeout = options.timeout;
442
+ this._instructionsOverride = options.instructions;
443
+ }
444
+ generateId() {
445
+ return `platform-sandbox-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
446
+ }
447
+ /**
448
+ * Construct a sibling {@link PlatformSandbox} that inherits this sandbox's
449
+ * credentials and defaults (access token, project, environment, network
450
+ * isolation, timeout, instructions, env, idle timeout) with per-instance
451
+ * overrides from `options`.
452
+ *
453
+ * Performs no I/O and does not require this sandbox to be started — the
454
+ * returned sandbox is not started and provisions (or reattaches, when
455
+ * `sandboxId` is set) on its own `start()`. Use it when one configured
456
+ * sandbox acts as the template for a fleet of independent sandboxes
457
+ * (e.g. one per project).
458
+ */
459
+ clone(options = {}) {
460
+ return new PlatformSandbox({
461
+ ...options.id !== void 0 && { id: options.id },
462
+ accessToken: this._client.accessToken,
463
+ projectId: this._client.projectId,
464
+ fetch: this._client.fetch,
465
+ environmentId: this._environmentId,
466
+ ...options.sandboxId !== void 0 && { sandboxId: options.sandboxId },
467
+ idleTimeoutMinutes: options.idleTimeoutMinutes ?? this._idleTimeoutMinutes,
468
+ ...this._networkIsolation !== void 0 && { networkIsolation: this._networkIsolation },
469
+ env: options.env ?? this._env,
470
+ ...this._timeout !== void 0 && { timeout: this._timeout },
471
+ ...this._instructionsOverride !== void 0 && { instructions: this._instructionsOverride }
472
+ });
473
+ }
474
+ async start() {
475
+ if (this._sandboxId) try {
476
+ const json = await (await this._client.request(`/sandbox/${encodeURIComponent(this._sandboxId)}`)).json();
477
+ if (!json.destroyedAt) {
478
+ this._createdAt = json.createdAt ? new Date(json.createdAt) : /* @__PURE__ */ new Date();
479
+ return;
480
+ }
481
+ this._sandboxId = void 0;
482
+ } catch (error) {
483
+ if (!(error instanceof PlatformApiError) || error.status !== 404) throw error;
484
+ this._sandboxId = void 0;
485
+ }
486
+ if (!this._environmentId) throw new Error("environmentId is required");
487
+ const body = JSON.stringify({
488
+ id: this.id,
489
+ environmentId: this._environmentId,
490
+ idleTimeoutMinutes: this._idleTimeoutMinutes,
491
+ networkIsolation: this._networkIsolation,
492
+ env: this._env
493
+ });
494
+ let response;
495
+ for (let attempt = 1;; attempt++) try {
496
+ response = await this._client.request("/sandbox", {
497
+ method: "POST",
498
+ headers: { "content-type": "application/json" },
499
+ body
500
+ });
501
+ break;
502
+ } catch (error) {
503
+ if (!(error instanceof PlatformApiError && error.status >= 500) || attempt >= CREATE_MAX_ATTEMPTS) throw error;
504
+ await new Promise((resolve) => setTimeout(resolve, CREATE_RETRY_BASE_DELAY_MS * attempt));
505
+ }
506
+ const json = await response.json();
507
+ this._sandboxId = json.id;
508
+ this._createdAt = json.createdAt ? new Date(json.createdAt) : /* @__PURE__ */ new Date();
509
+ }
510
+ async stop() {
511
+ await this.destroy();
512
+ }
513
+ async destroy() {
514
+ if (!this._sandboxId) return;
515
+ await this._client.request(`/sandbox/${encodeURIComponent(this._sandboxId)}`, { method: "DELETE" });
516
+ this._sandboxId = void 0;
517
+ this._createdAt = null;
518
+ }
519
+ /**
520
+ * Execute a command on the remote sandbox.
521
+ *
522
+ * `command` is a **shell string**: it is concatenated verbatim into the
523
+ * command line sent to the remote shell, which lets callers use pipes,
524
+ * redirects, and chaining (`ls -la | grep foo`). This matches the contract
525
+ * of {@link MastraSandbox} and the local sandbox implementation.
526
+ *
527
+ * `args`, when provided, are always shell-quoted so they cannot inject
528
+ * additional shell syntax.
529
+ *
530
+ * Security: callers MUST NOT pass untrusted input as `command`. If any part
531
+ * of the invocation is derived from an untrusted source, pass it through
532
+ * `args` (which is safely quoted) or shell-quote it yourself before
533
+ * inclusion. Untrusted `command` values allow arbitrary shell syntax
534
+ * execution on the remote sandbox.
535
+ */
536
+ async executeCommand(command, args, options) {
537
+ await this.ensureRunning();
538
+ if (!this._sandboxId) throw new SandboxNotReadyError(this.id);
539
+ const started = Date.now();
540
+ const fullCommand = buildCommand(command, args);
541
+ const effectiveTimeout = options?.timeout ?? this._timeout;
542
+ const timeoutSec = effectiveTimeout != null ? Math.ceil(effectiveTimeout / 1e3) : void 0;
543
+ const clientSignal = effectiveTimeout != null && effectiveTimeout > 0 ? AbortSignal.timeout(effectiveTimeout + 3e4) : void 0;
544
+ const json = await (await this._client.request(`/sandbox/${encodeURIComponent(this._sandboxId)}/exec`, {
545
+ method: "POST",
546
+ headers: { "content-type": "application/json" },
547
+ body: JSON.stringify({
548
+ command: fullCommand,
549
+ timeoutSec,
550
+ cwd: options?.cwd,
551
+ env: options?.env
552
+ }),
553
+ signal: clientSignal
554
+ })).json();
555
+ const exitCode = json.exitCode ?? (json.timedOut ? 124 : 1);
556
+ return {
557
+ success: exitCode === 0,
558
+ exitCode,
559
+ stdout: json.stdout,
560
+ stderr: json.stderr,
561
+ executionTimeMs: Date.now() - started,
562
+ timedOut: json.timedOut,
563
+ command: fullCommand
564
+ };
565
+ }
566
+ async getInfo() {
567
+ if (!this._sandboxId) return {
568
+ id: this.id,
569
+ name: this.name,
570
+ provider: this.provider,
571
+ status: this.status,
572
+ createdAt: this._createdAt ?? /* @__PURE__ */ new Date()
573
+ };
574
+ const json = await (await this._client.request(`/sandbox/${encodeURIComponent(this._sandboxId)}`)).json();
575
+ return {
576
+ id: json.id,
577
+ name: this.name,
578
+ provider: this.provider,
579
+ status: this.status,
580
+ createdAt: json.createdAt ? new Date(json.createdAt) : this._createdAt ?? /* @__PURE__ */ new Date(),
581
+ metadata: {
582
+ sandboxId: json.id,
583
+ providerResourceId: json.providerResourceId ?? void 0,
584
+ platformStatus: json.status
585
+ }
586
+ };
587
+ }
588
+ getInstructions(opts) {
589
+ const defaultInstructions = `Platform sandbox${this._sandboxId ? ` ${this._sandboxId}` : ""}. Execute commands with the sandbox command APIs.`;
590
+ if (typeof this._instructionsOverride === "function") return this._instructionsOverride({
591
+ defaultInstructions,
592
+ requestContext: opts?.requestContext
593
+ });
594
+ if (typeof this._instructionsOverride === "string") return this._instructionsOverride;
595
+ return defaultInstructions;
596
+ }
578
597
  };
579
-
580
- // src/provider.ts
581
- var platformSandboxProvider = {
582
- id: "platform",
583
- name: "Mastra Platform Sandbox",
584
- description: "Environment-scoped sandbox execution through Mastra Platform workspace proxy",
585
- configSchema: {
586
- type: "object",
587
- properties: {
588
- accessToken: {
589
- type: "string",
590
- description: "Mastra Platform secret key (falls back to MASTRA_PLATFORM_SECRET_KEY)"
591
- },
592
- projectId: { type: "string", description: "Platform project ID (falls back to MASTRA_PROJECT_ID)" },
593
- environmentId: { type: "string", description: "Platform environment ID (falls back to MASTRA_ENVIRONMENT_ID)" },
594
- sandboxId: { type: "string", description: "Reattach to an existing Platform sandbox by ID" },
595
- idleTimeoutMinutes: { type: "number", description: "Minutes before the sandbox can be destroyed while idle" },
596
- networkIsolation: {
597
- type: "string",
598
- description: "Network isolation mode",
599
- enum: ["ISOLATED", "PRIVATE"],
600
- default: "ISOLATED"
601
- },
602
- env: { type: "object", description: "Environment variables", additionalProperties: { type: "string" } },
603
- timeout: { type: "number", description: "Default command timeout in ms" }
604
- }
605
- },
606
- createSandbox: (config) => new PlatformSandbox(config)
598
+ //#endregion
599
+ //#region src/provider.ts
600
+ const platformSandboxProvider = {
601
+ id: "platform",
602
+ name: "Mastra Platform Sandbox",
603
+ description: "Environment-scoped sandbox execution through Mastra Platform workspace proxy",
604
+ configSchema: {
605
+ type: "object",
606
+ properties: {
607
+ accessToken: {
608
+ type: "string",
609
+ description: "Mastra Platform secret key (falls back to MASTRA_PLATFORM_SECRET_KEY)"
610
+ },
611
+ projectId: {
612
+ type: "string",
613
+ description: "Platform project ID (falls back to MASTRA_PROJECT_ID)"
614
+ },
615
+ environmentId: {
616
+ type: "string",
617
+ description: "Platform environment ID (falls back to MASTRA_ENVIRONMENT_ID)"
618
+ },
619
+ sandboxId: {
620
+ type: "string",
621
+ description: "Reattach to an existing Platform sandbox by ID"
622
+ },
623
+ idleTimeoutMinutes: {
624
+ type: "number",
625
+ description: "Minutes before the sandbox can be destroyed while idle"
626
+ },
627
+ networkIsolation: {
628
+ type: "string",
629
+ description: "Network isolation mode",
630
+ enum: ["ISOLATED", "PRIVATE"],
631
+ default: "ISOLATED"
632
+ },
633
+ env: {
634
+ type: "object",
635
+ description: "Environment variables",
636
+ additionalProperties: { type: "string" }
637
+ },
638
+ timeout: {
639
+ type: "number",
640
+ description: "Default command timeout in ms"
641
+ }
642
+ }
643
+ },
644
+ createSandbox: (config) => new PlatformSandbox(config)
607
645
  };
608
- var platformFilesystemProvider = {
609
- id: "platform",
610
- name: "Mastra Platform Filesystem",
611
- description: "Bucket-backed filesystem access through Mastra Platform workspace proxy",
612
- configSchema: {
613
- type: "object",
614
- properties: {
615
- accessToken: {
616
- type: "string",
617
- description: "Mastra Platform secret key (falls back to MASTRA_PLATFORM_SECRET_KEY)"
618
- },
619
- projectId: { type: "string", description: "Platform project ID (falls back to MASTRA_PROJECT_ID)" },
620
- bucketName: {
621
- type: "string",
622
- description: "Platform workspace bucket name (falls back to MASTRA_PLATFORM_BUCKET_NAME)"
623
- },
624
- readOnly: { type: "boolean", description: "Mount as read-only", default: false }
625
- }
626
- },
627
- createFilesystem: (config) => new PlatformFilesystem(config)
646
+ const platformFilesystemProvider = {
647
+ id: "platform",
648
+ name: "Mastra Platform Filesystem",
649
+ description: "Bucket-backed filesystem access through Mastra Platform workspace proxy",
650
+ configSchema: {
651
+ type: "object",
652
+ properties: {
653
+ accessToken: {
654
+ type: "string",
655
+ description: "Mastra Platform secret key (falls back to MASTRA_PLATFORM_SECRET_KEY)"
656
+ },
657
+ projectId: {
658
+ type: "string",
659
+ description: "Platform project ID (falls back to MASTRA_PROJECT_ID)"
660
+ },
661
+ bucketName: {
662
+ type: "string",
663
+ description: "Platform workspace bucket name (falls back to MASTRA_PLATFORM_BUCKET_NAME)"
664
+ },
665
+ readOnly: {
666
+ type: "boolean",
667
+ description: "Mount as read-only",
668
+ default: false
669
+ }
670
+ }
671
+ },
672
+ createFilesystem: (config) => new PlatformFilesystem(config)
628
673
  };
629
-
674
+ //#endregion
630
675
  export { PlatformApiError, PlatformClient, PlatformFilesystem, PlatformSandbox, platformFilesystemProvider, platformSandboxProvider };
631
- //# sourceMappingURL=index.js.map
676
+
632
677
  //# sourceMappingURL=index.js.map