@mastra/platform-workspace 1.5.0-alpha.4 → 1.5.0-alpha.6
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/README.md +13 -107
- package/dist/index.cjs +21 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +21 -6
- package/dist/index.js.map +1 -1
- package/dist/repo-template.d.ts.map +1 -1
- package/dist/sandbox.d.ts +3 -2
- package/dist/sandbox.d.ts.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @mastra/platform-workspace
|
|
2
2
|
|
|
3
|
-
Mastra Platform workspace provider.
|
|
3
|
+
Mastra Platform workspace provider. It gives agents environment-scoped sandbox execution and bucket-backed filesystem access through the Mastra Platform workspace proxy.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,23 +8,6 @@ Mastra Platform workspace provider. Gives agents environment-scoped sandbox exec
|
|
|
8
8
|
npm install @mastra/platform-workspace
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
## Configuration
|
|
12
|
-
|
|
13
|
-
All options can be passed to the constructor or read from environment variables:
|
|
14
|
-
|
|
15
|
-
| Option | Env var | Required |
|
|
16
|
-
| ----------------- | ------------------------------ | ---------------- |
|
|
17
|
-
| `accessToken` | `MASTRA_PLATFORM_ACCESS_TOKEN` | Yes |
|
|
18
|
-
| `projectId` | `MASTRA_PROJECT_ID` | Yes |
|
|
19
|
-
| `environmentId` | `MASTRA_ENVIRONMENT_ID` | Yes (sandbox) |
|
|
20
|
-
| `actingUserId` | — | No (sandbox) |
|
|
21
|
-
| `sandboxProvider` | `SANDBOX_PROVIDER` | No (sandbox) |
|
|
22
|
-
| `bucketName` | `MASTRA_PLATFORM_BUCKET_NAME` | Yes (filesystem) |
|
|
23
|
-
|
|
24
|
-
The sandbox provider resolves from the explicit `sandboxProvider` option, then `SANDBOX_PROVIDER`, then defaults to `e2b`. Set either option to `railway` to use Railway sandboxes. The proxy URL defaults to `https://workspaces.mastra.ai` and can be overridden with the `MASTRA_WORKSPACE_PROXY_URL` env var (useful for staging).
|
|
25
|
-
|
|
26
|
-
Requests to the proxy are authenticated with `Authorization: Bearer <accessToken>`. For sandbox requests authenticated with a project access token, set `actingUserId` to the stable opaque user subject from your authentication system. It is sent as `x-acting-user-id` for token partitioning and attribution; it is not an authorization claim.
|
|
27
|
-
|
|
28
11
|
## Usage
|
|
29
12
|
|
|
30
13
|
```typescript
|
|
@@ -33,11 +16,8 @@ import { Workspace } from '@mastra/core/workspace';
|
|
|
33
16
|
import { PlatformFilesystem, PlatformSandbox } from '@mastra/platform-workspace';
|
|
34
17
|
|
|
35
18
|
const workspace = new Workspace({
|
|
36
|
-
filesystem: new PlatformFilesystem({
|
|
37
|
-
// accessToken, projectId, bucketName all fall back to env
|
|
38
|
-
}),
|
|
19
|
+
filesystem: new PlatformFilesystem({}),
|
|
39
20
|
sandbox: new PlatformSandbox({
|
|
40
|
-
// accessToken, projectId, environmentId all fall back to env
|
|
41
21
|
idleTimeoutMinutes: 30,
|
|
42
22
|
networkIsolation: 'ISOLATED',
|
|
43
23
|
}),
|
|
@@ -50,98 +30,24 @@ const agent = new Agent({
|
|
|
50
30
|
});
|
|
51
31
|
```
|
|
52
32
|
|
|
53
|
-
##
|
|
54
|
-
|
|
55
|
-
`PlatformFilesystem` implements the Mastra filesystem interface against a workspace bucket. Object keys are percent-encoded per segment, so filenames with `?`, `#`, `%`, `&`, `+`, or spaces are preserved end-to-end.
|
|
56
|
-
|
|
57
|
-
```typescript
|
|
58
|
-
const fs = new PlatformFilesystem({ bucketName: 'reports' });
|
|
59
|
-
|
|
60
|
-
await fs.writeFile('/analyses/repo.md', markdown);
|
|
61
|
-
const content = await fs.readFile('/analyses/repo.md');
|
|
62
|
-
const entries = await fs.readdir('/analyses');
|
|
63
|
-
await fs.moveFile('/analyses/repo.md', '/analyses/repo-final.md');
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
Pass `readOnly: true` to mount the bucket read-only. Mutating calls will throw `WorkspaceReadOnlyError`.
|
|
67
|
-
|
|
68
|
-
## Sandbox
|
|
69
|
-
|
|
70
|
-
`PlatformSandbox` executes commands inside a provider-backed sandbox tied to a Platform environment. Sessions boot from the configured provider template or checkpoint.
|
|
71
|
-
|
|
72
|
-
```typescript
|
|
73
|
-
const sandbox = new PlatformSandbox({ environmentId: 'env_abc' });
|
|
74
|
-
|
|
75
|
-
const result = await sandbox.executeCommand('python', ['analyze.py'], {
|
|
76
|
-
timeout: 30_000,
|
|
77
|
-
env: { INPUT: 'repo' },
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
console.log(result.stdout);
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
Pass an existing `sandboxId` to reattach to a live sandbox instead of creating a new one.
|
|
33
|
+
## Documentation
|
|
84
34
|
|
|
85
|
-
|
|
35
|
+
Both providers authenticate through the workspace proxy with `MASTRA_PLATFORM_ACCESS_TOKEN` and `MASTRA_PROJECT_ID`. `PlatformSandbox` also requires `MASTRA_ENVIRONMENT_ID`; `PlatformFilesystem` requires `MASTRA_PLATFORM_BUCKET_NAME`. Constructor values override environment variables, and `MASTRA_WORKSPACE_PROXY_URL` can point requests at a non-production proxy.
|
|
86
36
|
|
|
87
|
-
|
|
37
|
+
`PlatformFilesystem` implements the Mastra filesystem interface against a Platform bucket. It supports reading, writing, listing, moving, and deleting files, preserves reserved characters in object names, and can be mounted with `readOnly: true` to reject mutations.
|
|
88
38
|
|
|
89
|
-
|
|
90
|
-
import { PlatformSandbox, Template } from '@mastra/platform-workspace';
|
|
91
|
-
|
|
92
|
-
const commitSha = process.env.REPOSITORY_COMMIT_SHA!;
|
|
93
|
-
const template = Template()
|
|
94
|
-
.cpuCount(4)
|
|
95
|
-
.memoryMB(8_192)
|
|
96
|
-
.setWorkdir('/workspace/repo')
|
|
97
|
-
.setEnvs({ BUILD_CONFIG_MARKER: 'template-v1' })
|
|
98
|
-
.aptInstall(['git', 'jq'])
|
|
99
|
-
.runCmd('git clone https://github.com/mastra-ai/mastra.git /workspace/repo')
|
|
100
|
-
.runCmd(`git checkout ${commitSha}`)
|
|
101
|
-
.runCmd('pnpm install --frozen-lockfile');
|
|
102
|
-
|
|
103
|
-
const sandbox = new PlatformSandbox({
|
|
104
|
-
environmentId: 'env_abc',
|
|
105
|
-
sandboxProvider: 'e2b',
|
|
106
|
-
template,
|
|
107
|
-
});
|
|
108
|
-
await sandbox.start();
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
Platform serializes the builder and stores build state under a server-derived content hash within the selected environment and provider. Passing the same definition to another sandbox reuses that build. Call `await template.build(options)` to start or reuse the provider build without provisioning a sandbox; it returns `ready`, `pending`, or `failed`. For E2B templates, `cpuCount()` and `memoryMB()` set the resources inherited by sandboxes created from the exact build or a resource-matched stale build. They default to 2 CPUs and 1,024 MB. Effective resource values participate in the template identity, so changing either value creates a distinct template while explicit defaults reuse the omitted-default build. If a pending build falls back to the provider base, that sandbox may use provider-default resources; check `templatePending` to detect this case. Railway currently ignores these two methods because its sandbox template API doesn't expose matching resource settings.
|
|
112
|
-
|
|
113
|
-
By default, operation arguments are serialized and sent to Platform. Use `setEnvs(values, { ephemeral: true })` for short-lived build credentials: these values are sent separately, excluded from content identity and persistence, unavailable at runtime, and take precedence over serialized values with the same key. Supply them on every build or fresh provision that may need to build. Railway's provider cache includes transient build variables, so rotating a value may trigger another provider build even though the Platform template ID stays stable.
|
|
114
|
-
|
|
115
|
-
## Errors
|
|
39
|
+
`PlatformSandbox` starts or reconnects to an environment-scoped provider sandbox and implements command execution, lifecycle, and networking operations. The provider defaults to E2B and can be changed to Railway through `sandboxProvider` or `SANDBOX_PROVIDER`. Pass an existing `sandboxId` to reattach to a live sandbox, and `actingUserId` to partition and attribute project-token requests to a stable application user.
|
|
116
40
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
```typescript
|
|
120
|
-
import { PlatformApiError } from '@mastra/platform-workspace';
|
|
121
|
-
|
|
122
|
-
try {
|
|
123
|
-
await fs.readFile('/missing.txt');
|
|
124
|
-
} catch (err) {
|
|
125
|
-
if (err instanceof PlatformApiError) {
|
|
126
|
-
if (err.code === 'not_found') {
|
|
127
|
-
// handle missing file
|
|
128
|
-
} else if (err.code === 'authentication_error') {
|
|
129
|
-
// refresh token
|
|
130
|
-
}
|
|
131
|
-
console.error(err.status, err.code, err.proxyMessage, err.body);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
```
|
|
41
|
+
The exported `Template()` builder creates reusable sandbox images from commands, packages, environment values, repository checkouts, CPU, memory, and working-directory settings. Platform derives a content identity from the serialized template so matching definitions can reuse previous builds. Ephemeral environment values are excluded from that identity and are not persisted into the runtime image.
|
|
135
42
|
|
|
136
|
-
|
|
43
|
+
Proxy failures throw `PlatformApiError`, which includes the HTTP status, parsed machine-readable error code, proxy message, and raw response body. Use these fields to distinguish missing resources, authentication failures, and provider errors.
|
|
137
44
|
|
|
138
|
-
|
|
45
|
+
- [Mastra Platform workspaces](https://mastra.ai/docs/mastra-platform/workspaces)
|
|
139
46
|
|
|
140
|
-
|
|
47
|
+
## Changelog
|
|
141
48
|
|
|
142
|
-
|
|
49
|
+
See the [package changelog](https://github.com/mastra-ai/mastra/blob/main/workspaces/platform-workspace/CHANGELOG.md) for version history and release notes.
|
|
143
50
|
|
|
144
|
-
|
|
145
|
-
- `SandboxExecTransportError` — both the initial WebSocket attempt and the built-in retry closed without an `exit` frame against a live sandbox. Carries `{ opened, closeCode, closeReason, wsEndpoint }` diagnostics plus `sandboxId`, `command`, and `attempts` so upstream logs / alerts can distinguish "the Railway data plane is broken" from "your command failed".
|
|
51
|
+
## Support
|
|
146
52
|
|
|
147
|
-
|
|
53
|
+
We have an [open community Discord](https://discord.gg/mastra-ai). Come and say hello and let us know if you have any questions or need any help getting things running.
|
package/dist/index.cjs
CHANGED
|
@@ -1047,9 +1047,9 @@ var SandboxDestroyedError = class extends Error {
|
|
|
1047
1047
|
* sandbox.
|
|
1048
1048
|
*/
|
|
1049
1049
|
function buildCommand(command, args) {
|
|
1050
|
-
return args?.length ? `${command} ${args.map(shellQuote).join(" ")}` : command;
|
|
1050
|
+
return args?.length ? `${command} ${args.map(shellQuote$1).join(" ")}` : command;
|
|
1051
1051
|
}
|
|
1052
|
-
function shellQuote(arg) {
|
|
1052
|
+
function shellQuote$1(arg) {
|
|
1053
1053
|
if (/^[a-zA-Z0-9._\-/=:@]+$/.test(arg)) return arg;
|
|
1054
1054
|
return `'${arg.replace(/'/g, `'\\''`)}'`;
|
|
1055
1055
|
}
|
|
@@ -1519,14 +1519,15 @@ var PlatformSandbox = class PlatformSandbox extends _mastra_core_workspace.Mastr
|
|
|
1519
1519
|
* sandbox they can't safely retry.
|
|
1520
1520
|
*
|
|
1521
1521
|
* Railway requires a caller-supplied recovery `id` before it can have a
|
|
1522
|
-
* checkpoint to delete. E2B
|
|
1523
|
-
*
|
|
1522
|
+
* checkpoint to delete. On E2B the sandbox itself is the persistent thing
|
|
1523
|
+
* (idle sandboxes pause and resume in place), so destroy only kills the
|
|
1524
|
+
* VM; any snapshot a caller captured on purpose is the caller's to manage.
|
|
1524
1525
|
*/
|
|
1525
1526
|
async destroy() {
|
|
1526
1527
|
if (!this._sandboxId) return;
|
|
1527
1528
|
const destroyedSandboxId = this._sandboxId;
|
|
1528
1529
|
this._captureInFlight = null;
|
|
1529
|
-
if (this._hasRecoveryKey
|
|
1530
|
+
if (this._hasRecoveryKey && this._client.sandboxProvider !== "e2b") try {
|
|
1530
1531
|
await this._request(`/sandbox/${encodeURIComponent(destroyedSandboxId)}/checkpoint`, {
|
|
1531
1532
|
method: "DELETE",
|
|
1532
1533
|
headers: { "content-type": "application/json" },
|
|
@@ -1989,6 +1990,16 @@ function setupMarkerContent(setupCommand) {
|
|
|
1989
1990
|
function setupMarkerCommand(content) {
|
|
1990
1991
|
return `mkdir -p "$(dirname "${SETUP_MARKER_PATH}")" && printf '%s' '${content}' > "${SETUP_MARKER_PATH}"`;
|
|
1991
1992
|
}
|
|
1993
|
+
function repoCloneCommand({ cloneUrl, destination, branch, tokenEnv }) {
|
|
1994
|
+
return `git ${tokenEnv ? `${gitAuthFlag$1(tokenEnv)} ` : ""}clone --depth=1 --single-branch ${branch ? `--branch ${shellQuote(branch)} ` : ""}${shellQuote(cloneUrl)} ${shellQuote(destination)}`;
|
|
1995
|
+
}
|
|
1996
|
+
/** Per-invocation auth header; `-c` config never reaches `.git/config`. */
|
|
1997
|
+
function gitAuthFlag$1(tokenEnv) {
|
|
1998
|
+
return `-c http.extraheader="AUTHORIZATION: basic $(printf 'x-access-token:%s' "$${tokenEnv}" | base64 -w0)"`;
|
|
1999
|
+
}
|
|
2000
|
+
function shellQuote(value) {
|
|
2001
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
2002
|
+
}
|
|
1992
2003
|
//#endregion
|
|
1993
2004
|
//#region src/repo-template.ts
|
|
1994
2005
|
const execFileAsync = (0, util.promisify)(child_process.execFile);
|
|
@@ -2070,7 +2081,11 @@ function createRepoTemplate(options) {
|
|
|
2070
2081
|
if (Object.keys(buildEnv).length > 0) template = template.setEnvs(buildEnv, { ephemeral: true });
|
|
2071
2082
|
template = withResources(template, options);
|
|
2072
2083
|
if (workingDirectory) template = template.runCmd(`mkdir -p "${workingDirectory}"`).setWorkdir(workingDirectory);
|
|
2073
|
-
template = template.runCmd(
|
|
2084
|
+
template = template.runCmd(repoCloneCommand({
|
|
2085
|
+
cloneUrl,
|
|
2086
|
+
destination: repoDir,
|
|
2087
|
+
...token ? { tokenEnv: BUILD_TOKEN_ENV } : {}
|
|
2088
|
+
})).runCmd(`git -C "${repoDir}" ${auth}fetch origin ${sha}`).runCmd(`git -C "${repoDir}" checkout ${sha}`);
|
|
2074
2089
|
for (const command of setupCommands) template = template.runCmd(`cd "${repoDir}" && ${command}`);
|
|
2075
2090
|
template = template.runCmd(setupMarkerCommand(setupMarkerContent(setupCommands)));
|
|
2076
2091
|
return template.withFamily(family);
|