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