withruntime 0.9.0 → 0.11.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/BLAXEL.md +4 -4
- package/DAYTONA.md +52 -40
- package/E2B.md +65 -62
- package/README.md +15 -7
- package/README.src.md +15 -7
- package/VERCEL.md +34 -22
- package/compatibility-lock.json +16 -16
- package/dist/blaxel/errors.js +1 -1
- package/dist/blaxel/lookup.d.ts +3 -2
- package/dist/blaxel/lookup.js +3 -2
- package/dist/blaxel/pagination.d.ts +2 -2
- package/dist/blaxel/pagination.js +45 -17
- package/dist/blaxel/preview.d.ts +2 -1
- package/dist/blaxel/preview.js +15 -13
- package/dist/blaxel/process.js +56 -10
- package/dist/blaxel/sandbox.d.ts +2 -0
- package/dist/blaxel/sandbox.js +2 -0
- package/dist/blaxel/snapshot.js +3 -5
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +262 -56
- package/dist/client.d.ts +41 -3
- package/dist/client.js +67 -11
- package/dist/cloudflare/identity.d.ts +7 -0
- package/dist/cloudflare/identity.js +13 -0
- package/dist/cloudflare/index.d.ts +9 -0
- package/dist/cloudflare/index.js +47 -9
- package/dist/cloudflare/worker.d.ts +4 -0
- package/dist/cloudflare/worker.js +166 -16
- package/dist/codesandbox/guest-service.js +57 -20
- package/dist/codesandbox/index.js +42 -8
- package/dist/compat/core.d.ts +9 -8
- package/dist/compat/core.js +60 -21
- package/dist/compat/files.js +22 -4
- package/dist/compat/lease.d.ts +36 -0
- package/dist/compat/lease.js +87 -0
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +107 -38
- package/dist/daytona/computer-use.d.ts +162 -0
- package/dist/daytona/computer-use.js +324 -0
- package/dist/daytona/context.d.ts +10 -0
- package/dist/daytona/context.js +24 -0
- package/dist/daytona/daytona.d.ts +20 -0
- package/dist/daytona/daytona.js +134 -20
- package/dist/daytona/filesystem.d.ts +17 -4
- package/dist/daytona/filesystem.js +129 -38
- package/dist/daytona/index.d.ts +3 -27
- package/dist/daytona/index.js +2 -7
- package/dist/daytona/process.d.ts +63 -6
- package/dist/daytona/process.js +224 -23
- package/dist/daytona/sandbox.d.ts +20 -6
- package/dist/daytona/sandbox.js +55 -32
- package/dist/e2b/client.d.ts +5 -0
- package/dist/e2b/client.js +6 -2
- package/dist/e2b/commands.d.ts +43 -12
- package/dist/e2b/commands.js +217 -64
- package/dist/e2b/errors.d.ts +7 -0
- package/dist/e2b/errors.js +20 -0
- package/dist/e2b/filesystem.d.ts +2 -1
- package/dist/e2b/filesystem.js +43 -6
- package/dist/e2b/index.d.ts +3 -2
- package/dist/e2b/index.js +2 -1
- package/dist/e2b/pty.d.ts +24 -0
- package/dist/e2b/pty.js +83 -0
- package/dist/e2b/sandbox.d.ts +17 -6
- package/dist/e2b/sandbox.js +239 -43
- package/dist/e2b/users.d.ts +30 -0
- package/dist/e2b/users.js +185 -0
- package/dist/file-source.d.ts +16 -0
- package/dist/file-source.js +162 -0
- package/dist/freestyle/index.d.ts +9 -1
- package/dist/freestyle/index.js +38 -5
- package/dist/index.d.ts +3 -1
- package/dist/login.js +16 -10
- package/dist/mcp.d.ts +2 -1
- package/dist/mcp.js +6 -1
- package/dist/modal/index.js +2 -1
- package/dist/network-cli.js +139 -43
- package/dist/openai-agents/index.js +9 -45
- package/dist/page.d.ts +1 -1
- package/dist/page.js +2 -2
- package/dist/products/browser.d.ts +60 -0
- package/dist/products/browser.js +37 -0
- package/dist/products/desktop.d.ts +2 -2
- package/dist/products/desktop.js +2 -2
- package/dist/products/images.d.ts +3 -1
- package/dist/products/images.js +14 -2
- package/dist/products/index.d.ts +14 -0
- package/dist/products/index.js +12 -0
- package/dist/products/network-products.d.ts +7 -0
- package/dist/products/network-products.js +3 -0
- package/dist/products/observability.d.ts +44 -0
- package/dist/products/observability.js +6 -0
- package/dist/products/previews.d.ts +8 -2
- package/dist/products/previews.js +11 -3
- package/dist/products/private-network.d.ts +22 -0
- package/dist/products/private-network.js +10 -0
- package/dist/products/volumes.d.ts +42 -0
- package/dist/products/volumes.js +36 -0
- package/dist/products/watch.js +61 -8
- package/dist/proxy.js +3 -1
- package/dist/runloop/index.d.ts +13 -27
- package/dist/runloop/index.js +149 -52
- package/dist/sandbox.d.ts +59 -8
- package/dist/sandbox.js +479 -68
- package/dist/snapshots.d.ts +9 -4
- package/dist/sprites/index.js +69 -33
- package/dist/tar.d.ts +3 -1
- package/dist/tar.js +140 -14
- package/dist/transport.d.ts +4 -7
- package/dist/transport.js +205 -122
- package/dist/types.d.ts +5 -2
- package/dist/vercel/command.d.ts +1 -1
- package/dist/vercel/command.js +3 -2
- package/dist/vercel/errors.d.ts +1 -1
- package/dist/vercel/errors.js +7 -1
- package/dist/vercel/index.d.ts +1 -7
- package/dist/vercel/index.js +1 -3
- package/dist/vercel/sandbox-user.d.ts +70 -0
- package/dist/vercel/sandbox-user.js +206 -0
- package/dist/vercel/sandbox.d.ts +55 -20
- package/dist/vercel/sandbox.js +353 -99
- package/dist/vercel/snapshot.d.ts +3 -2
- package/dist/vercel/snapshot.js +9 -5
- package/package.json +2 -1
package/README.src.md
CHANGED
|
@@ -14,7 +14,7 @@ The client uses `RUNTIME_API_KEY` when it is set, and otherwise the connection
|
|
|
14
14
|
this machine saved when the CLI connected it (one browser approval, no key to
|
|
15
15
|
copy). On a server or a CI runner, set `RUNTIME_API_KEY` from your secret
|
|
16
16
|
manager: `npx withruntime keys create` prints a new key once after an account
|
|
17
|
-
owner approves it in the browser, or create one at
|
|
17
|
+
owner, admin or developer approves it in the browser, or create one at
|
|
18
18
|
https://withruntime.com/account/keys. Never put a key in browser code, a URL or
|
|
19
19
|
a command-line argument.
|
|
20
20
|
|
|
@@ -22,7 +22,8 @@ a command-line argument.
|
|
|
22
22
|
import { Sandbox } from "withruntime";
|
|
23
23
|
|
|
24
24
|
await using sbx = await Sandbox.create();
|
|
25
|
-
|
|
25
|
+
await sbx.files.write("/workspace/invoice.py", "print(sum([125, 250, 375]))\n");
|
|
26
|
+
const result = await sbx.exec("python3 /workspace/invoice.py");
|
|
26
27
|
console.log(result.exitCode, result.stdout);
|
|
27
28
|
```
|
|
28
29
|
|
|
@@ -32,6 +33,8 @@ Node 22 call `await sbx.stop()`). With no arguments you get the free
|
|
|
32
33
|
trial while it lasts ({{trial-hours}} free hours, up to {{trial-sandboxes}} sandboxes running at once), 2 vCPU, 4 GiB of
|
|
33
34
|
memory and a 4 GiB disk. The current default image includes NumPy, pandas and
|
|
34
35
|
matplotlib; see the [sandbox environment](https://withruntime.com/docs/sandbox-environment).
|
|
36
|
+
Omitting `funding` can use prepaid credit after the trial is exhausted. Use
|
|
37
|
+
`Sandbox.create({ funding: "trial" })` for free use only; it never falls back to paid.
|
|
35
38
|
|
|
36
39
|
The sandbox object does the rest:
|
|
37
40
|
|
|
@@ -59,7 +62,9 @@ no compute; `idlePauseSeconds` sets {{idle-pause-min}} to {{idle-pause-max}}, or
|
|
|
59
62
|
`Sandbox.getOrCreate(name)` returns the sandbox with that name, woken if it is
|
|
60
63
|
paused, or creates it.
|
|
61
64
|
|
|
62
|
-
|
|
65
|
+
`runtime.sandbox`, `snapshot`, `image`, `volume`, `job`, `domain`, `port` and
|
|
66
|
+
`address` name the products as the CLI does. Their existing plural names
|
|
67
|
+
remain aliases of the same product clients. The client has `sandboxes`, `images`, `volumes`, `snapshots`, `jobs`, `secrets`,
|
|
63
68
|
`limits`, `feedback` and `support`; `webhooks`, `events`, `otel` and `audit`
|
|
64
69
|
to watch the account; `domains`, `ports`, `addresses`, `tunnel` and `network`
|
|
65
70
|
to connect sandboxes to your own world (paid accounts); and `mcp`, `sso`,
|
|
@@ -67,6 +72,8 @@ to connect sandboxes to your own world (paid accounts); and `mcp`, `sso`,
|
|
|
67
72
|
the key is read-only and what its agent may still spend today. Every write carries an idempotency key, made for you, so the SDK's
|
|
68
73
|
own retries (timeouts, 429, 503) never do anything twice. Errors are typed and
|
|
69
74
|
carry a `code`, a `hint` and a `requestId`.
|
|
75
|
+
`runtime.usageRequests("7d")` reads the account's API call counts and error rates;
|
|
76
|
+
counts are exact decimal strings, and an empty window has a null `errorPercent`.
|
|
70
77
|
|
|
71
78
|
## Behind a proxy
|
|
72
79
|
|
|
@@ -89,7 +96,7 @@ Runtime improves fast because agents tell us what they run into: reports go stra
|
|
|
89
96
|
|
|
90
97
|
## Code written for E2B
|
|
91
98
|
|
|
92
|
-
`withruntime/e2b`
|
|
99
|
+
`withruntime/e2b` translates supported E2B sandbox calls to Runtime. Change the
|
|
93
100
|
import and set `RUNTIME_API_KEY`. The
|
|
94
101
|
[switch guide](https://withruntime.com/docs/migrate) covers the other providers
|
|
95
102
|
and how to work out what you save:
|
|
@@ -123,7 +130,7 @@ package list every mapping and gap.
|
|
|
123
130
|
|
|
124
131
|
## Code written for Blaxel
|
|
125
132
|
|
|
126
|
-
`withruntime/blaxel`
|
|
133
|
+
`withruntime/blaxel` translates supported Blaxel sandbox calls. Change the
|
|
127
134
|
import:
|
|
128
135
|
|
|
129
136
|
```ts no-run
|
|
@@ -134,7 +141,7 @@ Sandboxes get Blaxel's default of 4096 MB, with one vCPU for every 2048 MB.
|
|
|
134
141
|
Standby becomes Runtime's pause: after 60 seconds without a call a sandbox
|
|
135
142
|
pauses with its memory and processes, and the next call wakes it. Envs,
|
|
136
143
|
processes by name, files, previews, snapshots, forks and the code interpreter
|
|
137
|
-
|
|
144
|
+
are supported by the adapter, along with sessions: `sandbox.sessions` makes a
|
|
138
145
|
Runtime sandbox session and `SandboxInstance.fromSession` drives the sandbox
|
|
139
146
|
with its token. A Blaxel key is never sent anywhere. Drives, codegen, schedules and Blaxel's agent, model and MCP hosting throw
|
|
140
147
|
`NotSupportedError` before anything happens, naming what to use instead.
|
|
@@ -154,7 +161,8 @@ https://withruntime.com/docs/frameworks.
|
|
|
154
161
|
## CLI
|
|
155
162
|
|
|
156
163
|
```bash no-run
|
|
157
|
-
|
|
164
|
+
printf '%s\n' 'print(sum([125, 250, 375]))' > invoice.py
|
|
165
|
+
npx withruntime sandbox run -- python3 - < invoice.py # connects on first use
|
|
158
166
|
npx withruntime sandbox create
|
|
159
167
|
npx withruntime help
|
|
160
168
|
```
|
package/VERCEL.md
CHANGED
|
@@ -16,9 +16,9 @@ from withruntime.vercel import sandbox # was: from vercel import sandbox
|
|
|
16
16
|
from withruntime.vercel.sandbox import sync as sandbox # was: from vercel.sandbox import sync as sandbox
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
The mapping was written against `@vercel/sandbox` 3.5.
|
|
19
|
+
The mapping was written against `@vercel/sandbox` 3.5.1 and `vercel-sandbox`
|
|
20
20
|
0.7.0 on PyPI (the `vercel.sandbox` module of `vercel` 0.11.4), checked
|
|
21
|
-
|
|
21
|
+
30 September 2026.
|
|
22
22
|
|
|
23
23
|
## Keys
|
|
24
24
|
|
|
@@ -34,7 +34,10 @@ are ignored.
|
|
|
34
34
|
- **Machine:** Vercel's default, 2 vCPUs with 2048 MiB each.
|
|
35
35
|
`resources: { vcpus }` sets another, with 2048 MiB per vCPU.
|
|
36
36
|
- **Timeout:** Vercel's default of 5 minutes. Runtime's leases run 60 seconds to
|
|
37
|
-
an hour
|
|
37
|
+
an hour. A longer `timeout` (Python `execution_time_limit`) starts with an
|
|
38
|
+
hour's lease, and the sandbox object renews it toward the timeout while it
|
|
39
|
+
lives, never past it; once the program ends, the sandbox pauses within an
|
|
40
|
+
hour. `extendTimeout` moves the end later, as often as needed.
|
|
38
41
|
- **Persistence:** persistent by default, as in Vercel. `stop()` and the end of
|
|
39
42
|
the timeout pause the sandbox, keeping its files and also its memory and
|
|
40
43
|
processes. `Sandbox.get({ name })`, or the next command or file call, wakes
|
|
@@ -63,6 +66,8 @@ are ignored.
|
|
|
63
66
|
| `source: { type: "tarball", url }`, `{ type: "snapshot" }` | Downloaded and unpacked into the working directory; a Runtime snapshot, with its own shape. |
|
|
64
67
|
| `networkPolicy`, `updateNetworkPolicy` | Runtime network rules: `allow-all`, `deny-all`, or a list of domains and subnets to allow and deny. |
|
|
65
68
|
| `Sandbox.get({ name })`, `getOrCreate`, `list({ tags })` | The newest live sandbox with that name (or a Runtime id), woken; create when missing; `sandboxes.list` with Vercel's paginator. |
|
|
69
|
+
| `list({ namePrefix, since, until, sortBy, sortOrder, cursor })` | Applied here over every sandbox the key sees; the cursor continues the same order. Python's `query_sandboxes` does the same. |
|
|
70
|
+
| `createUser`, `asUser`, `createGroup`, `addUserToGroup` | `useradd`, `groupadd` and `usermod` with `sudo`, as Vercel does; a user's commands run under `sudo -u` in its home, its files given to it. |
|
|
66
71
|
| `Sandbox.fork({ sourceSandbox, name })` | A Runtime fork, which copies memory and processes too. |
|
|
67
72
|
| `runCommand(cmd, args)`, `runCommand({ cmd, args, cwd, env, sudo })` | `exec` of the argv without a shell. `sudo` runs it under `sudo --preserve-env`. A non-zero exit is a result. Past `timeoutMs` it exits 137. |
|
|
68
73
|
| `stdout`, `stderr` writers, `detached: true`, `logs()`, `wait()` | Output streams to the writers; a detached command is a Runtime process, its logs followed from the start. |
|
|
@@ -72,8 +77,8 @@ are ignored.
|
|
|
72
77
|
| Paths | Relative paths resolve from `/vercel/sandbox`, which is `/workspace`. A command that names `/vercel/sandbox` finds a link to it. |
|
|
73
78
|
| `domain(port)`, `update({ ports })` | The public preview of that port; ports added or removed. |
|
|
74
79
|
| `stop()`, `delete()`, `extendTimeout(ms)` | `pause` (persistent) or `stop`; `stop`; `extend`. |
|
|
75
|
-
| `update({ timeout, networkPolicy, snapshotExpiration })` |
|
|
76
|
-
| `snapshot()`, `Snapshot.get`, `Snapshot.list`, `snapshot.delete()` | A
|
|
80
|
+
| `update({ timeout, networkPolicy, snapshotExpiration })` | A new timeout (earlier only while the lease has not reached it), network rules, retention. |
|
|
81
|
+
| `snapshot()`, `Snapshot.get`, `Snapshot.list`, `snapshot.delete()` | A verified disk-only capture, then the source stops; restored snapshots start fresh processes; `snapshots.get`, `list`, `delete`. |
|
|
77
82
|
| Error classes | `APIError` with a `response` whose status is Runtime's and `json` of `{ error: { code, message } }`; also `code`, `hint` and `requestId`. |
|
|
78
83
|
| Python: `create_sandbox`, `run_process`, `create_process`, `box.fs` | The same mapping; operations await or work as context managers, which stop and by default destroy the sandbox; readers iterate by line. |
|
|
79
84
|
|
|
@@ -82,21 +87,30 @@ are ignored.
|
|
|
82
87
|
Each of these throws `NotSupportedError` before anything happens. Its
|
|
83
88
|
`feature` names the gap and its `alternative` says what to use.
|
|
84
89
|
|
|
85
|
-
| Vercel Sandbox | Use instead
|
|
86
|
-
| ---------------------------------------------------------- |
|
|
87
|
-
| An `image` with no Runtime image of that name | Build it: `npx withruntime image build --dockerfile Dockerfile --name <image>`.
|
|
88
|
-
| `
|
|
89
|
-
|
|
|
90
|
-
|
|
|
91
|
-
|
|
|
92
|
-
| `
|
|
93
|
-
|
|
|
94
|
-
|
|
|
95
|
-
| `
|
|
96
|
-
|
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
99
|
-
|
|
90
|
+
| Vercel Sandbox | Use instead |
|
|
91
|
+
| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
92
|
+
| An `image` with no Runtime image of that name | Build it: `npx withruntime image build --dockerfile Dockerfile --name <image>`. |
|
|
93
|
+
| Drives (`mounts`), `networkId` | Runtime volumes through `withruntime: { create: { volumes } }`; network rules. |
|
|
94
|
+
| A region outside the US, `failoverRegions` | Runtime runs in one US region. |
|
|
95
|
+
| Network rules that transform or forward requests | Allow the domain and store the credential as a Runtime secret for it: the sandbox sees a placeholder. |
|
|
96
|
+
| A `timeout` earlier than the current lease's end | Stop the sandbox when you are done with it. |
|
|
97
|
+
| Sessions (`currentSession`, `listSessions`) | A Runtime sandbox is its own session. |
|
|
98
|
+
| `openInteractive` | `sandbox.withruntime.terminal(...)` or `npx withruntime sandbox shell <id>`. |
|
|
99
|
+
| `update` of tags, resources, persistence or region | Create a new sandbox with them. |
|
|
100
|
+
| Overrides on `Sandbox.fork` | Fork, then change what Runtime can change. |
|
|
101
|
+
| `delete({ deleteOrphanSnapshots: true })`, `Snapshot.tree` | Delete snapshots one by one; `Snapshot.list`. |
|
|
102
|
+
| `Drive`, `defineSandboxProxy` | Runtime volumes; `sandbox.withruntime.previews`. |
|
|
103
|
+
| `keepLastSnapshots` | Manage saved snapshots with `Snapshot.list` and `snapshot.delete`. |
|
|
104
|
+
|
|
105
|
+
Explicit snapshots require the released native disk-capture capability; a server
|
|
106
|
+
that does not confirm disk-only capture is refused. If capture succeeds but the
|
|
107
|
+
source cannot finish stopping, the error retains `snapshotId` and
|
|
108
|
+
`sourceSandboxId` so the saved filesystem remains recoverable. JavaScript exposes
|
|
109
|
+
them in `error.json.error.details` for API errors and directly on other errors;
|
|
110
|
+
Python exposes them in `error.data` for API errors and directly on other errors.
|
|
111
|
+
`snapshot_source_stop_timeout` means the source is still stopping after its wait;
|
|
112
|
+
a failed stop request keeps its original error code. These contracts remain
|
|
113
|
+
partial; controlled package comparisons do not prove hosted restoration.
|
|
100
114
|
|
|
101
115
|
Some differences are not refusals:
|
|
102
116
|
|
|
@@ -106,8 +120,6 @@ Some differences are not refusals:
|
|
|
106
120
|
`pwd` prints `/workspace`.
|
|
107
121
|
- Runtime's stock image is Ubuntu 24.04 with Python 3.12, Node.js 24 and Bun;
|
|
108
122
|
Vercel's `universal` has Python 3.14.
|
|
109
|
-
- `keepLastSnapshots` is accepted: a paused Runtime sandbox keeps exactly its
|
|
110
|
-
latest state.
|
|
111
123
|
- A git source is cloned into the working directory itself.
|
|
112
124
|
|
|
113
125
|
## Tests
|
package/compatibility-lock.json
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
{
|
|
10
10
|
"registry": "npm",
|
|
11
11
|
"name": "e2b",
|
|
12
|
-
"version": "2.
|
|
12
|
+
"version": "2.52.0",
|
|
13
13
|
"artifacts": {
|
|
14
|
-
"2.
|
|
14
|
+
"2.52.0": "sha512-TmUZlAvQq8BbguodpA+BBdawR/x5uILMQtHcLkdaLUqevsDjDdh46IYYU8ZRYKsQ4OhWr4xhsFIPSg1kgf+O0w=="
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
{
|
|
@@ -25,19 +25,19 @@
|
|
|
25
25
|
{
|
|
26
26
|
"registry": "pypi",
|
|
27
27
|
"name": "e2b",
|
|
28
|
-
"version": "2.
|
|
28
|
+
"version": "2.52.0",
|
|
29
29
|
"artifacts": {
|
|
30
|
-
"e2b-2.
|
|
31
|
-
"e2b-2.
|
|
30
|
+
"e2b-2.52.0-py3-none-any.whl": "sha256-6f4b5084b09986db43e99c7405bae6b14dc56984a69bbb1dc0687f1c71515cfb",
|
|
31
|
+
"e2b-2.52.0.tar.gz": "sha256-58c0042609513799341d4c1837e2581a4a5e73d3d62c9f1c57495c5e23eedee2"
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"registry": "pypi",
|
|
36
36
|
"name": "e2b-code-interpreter",
|
|
37
|
-
"version": "2.10.
|
|
37
|
+
"version": "2.10.1",
|
|
38
38
|
"artifacts": {
|
|
39
|
-
"e2b_code_interpreter-2.10.
|
|
40
|
-
"e2b_code_interpreter-2.10.
|
|
39
|
+
"e2b_code_interpreter-2.10.1-py3-none-any.whl": "sha256-f4c9732699ee4b1c289acedac8bff66fccecc6ab374bf0b4ef88d5ad063e333d",
|
|
40
|
+
"e2b_code_interpreter-2.10.1.tar.gz": "sha256-94e504667788c6cbb175e5deaf1f68e8df474b670343fe1d72e2496b49d5a666"
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
]
|
|
@@ -128,18 +128,18 @@
|
|
|
128
128
|
{
|
|
129
129
|
"registry": "npm",
|
|
130
130
|
"name": "@blaxel/core",
|
|
131
|
-
"version": "0.3.
|
|
131
|
+
"version": "0.3.25",
|
|
132
132
|
"artifacts": {
|
|
133
|
-
"0.3.
|
|
133
|
+
"0.3.25": "sha512-K6aGUyIpgrPnkpwMOI/U/gbZwveOMu1Am04pkawZQjkp7NUa/roG0HC6NJPBp3AVVJY3CQtkGSWpAJ2DV4ZKpw=="
|
|
134
134
|
}
|
|
135
135
|
},
|
|
136
136
|
{
|
|
137
137
|
"registry": "pypi",
|
|
138
138
|
"name": "blaxel",
|
|
139
|
-
"version": "0.4.
|
|
139
|
+
"version": "0.4.13",
|
|
140
140
|
"artifacts": {
|
|
141
|
-
"blaxel-0.4.
|
|
142
|
-
"blaxel-0.4.
|
|
141
|
+
"blaxel-0.4.13-py3-none-any.whl": "sha256-b8817718ca63a2b31a5026f98c7191d5ed8f2f4c1aafc2d4aa57491a560a2146",
|
|
142
|
+
"blaxel-0.4.13.tar.gz": "sha256-a036256792e85238724b8bce38fb7c0b4f661fe4c3ac576258a8be1736ac8483"
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
]
|
|
@@ -225,10 +225,10 @@
|
|
|
225
225
|
{
|
|
226
226
|
"registry": "pypi",
|
|
227
227
|
"name": "prime-sandboxes",
|
|
228
|
-
"version": "0.4.
|
|
228
|
+
"version": "0.4.1",
|
|
229
229
|
"artifacts": {
|
|
230
|
-
"prime_sandboxes-0.4.
|
|
231
|
-
"prime_sandboxes-0.4.
|
|
230
|
+
"prime_sandboxes-0.4.1-py3-none-any.whl": "sha256-1a1bb96afacc66d690815be82c0b7af180ea141421b7d180b4a0adbada4f38f0",
|
|
231
|
+
"prime_sandboxes-0.4.1.tar.gz": "sha256-53f7577dc9070e3b8e30db360f9e3cce3bcc35f8765b26f24f64dbc3c7763b06"
|
|
232
232
|
}
|
|
233
233
|
}
|
|
234
234
|
]
|
package/dist/blaxel/errors.js
CHANGED
|
@@ -95,7 +95,7 @@ const BLAXEL_HINTS = {
|
|
|
95
95
|
sandbox_paused: "Call sandbox.unarchive(), then try again.",
|
|
96
96
|
not_running: "The sandbox is not running: call sandbox.unarchive() if it was archived, or make a new one with SandboxInstance.create if it was deleted.",
|
|
97
97
|
trial_busy: "The trial's sandboxes are all in use: delete one you no longer need (sandbox.delete()) or archive it (sandbox.archive()), then try again. Moving to paid credit is the account owner's decision.",
|
|
98
|
-
public_preview_not_allowed: "On the trial, share the port privately: sandbox.previews.create({ metadata: { name }, spec: { port, public: false } }) and a token from preview.tokens.create(expiresAt). A public preview needs a paid sandbox, which is the account owner's decision.",
|
|
98
|
+
public_preview_not_allowed: "On the trial, share the port privately: sandbox.previews.create({ metadata: { name }, spec: { port, public: false } }) and a token from preview.tokens.create(expiresAt) on a server supporting absolute preview deadlines. A public preview needs a paid sandbox, which is the account owner's decision.",
|
|
99
99
|
busy: AGAIN,
|
|
100
100
|
guest_busy: AGAIN,
|
|
101
101
|
rate_limited: AGAIN,
|
package/dist/blaxel/lookup.d.ts
CHANGED
|
@@ -32,6 +32,7 @@ export declare function findSandbox(client: Runtime, name: string): Promise<Runt
|
|
|
32
32
|
export declare function findByExternalId(client: Runtime, externalId: string): Promise<RuntimeSandbox>;
|
|
33
33
|
/** Runs a create or fork that names a sandbox. When the name is still held
|
|
34
34
|
* by a sandbox that is stopping (deleted a moment ago, by this client or
|
|
35
|
-
* another), waits for it to stop, up to 30 seconds, and runs it once more
|
|
36
|
-
*
|
|
35
|
+
* another), waits for it to stop, up to 30 seconds, and runs it once more.
|
|
36
|
+
* This reconciles Runtime's stopping-name conflicts. Blaxel 0.3.25 delegates
|
|
37
|
+
* createIfNotExists reconciliation to its control plane without client polling. */
|
|
37
38
|
export declare function whenNameFree<T>(client: Runtime, work: () => Promise<T>): Promise<T>;
|
package/dist/blaxel/lookup.js
CHANGED
|
@@ -54,8 +54,9 @@ export async function findByExternalId(client, externalId) {
|
|
|
54
54
|
}
|
|
55
55
|
/** Runs a create or fork that names a sandbox. When the name is still held
|
|
56
56
|
* by a sandbox that is stopping (deleted a moment ago, by this client or
|
|
57
|
-
* another), waits for it to stop, up to 30 seconds, and runs it once more
|
|
58
|
-
*
|
|
57
|
+
* another), waits for it to stop, up to 30 seconds, and runs it once more.
|
|
58
|
+
* This reconciles Runtime's stopping-name conflicts. Blaxel 0.3.25 delegates
|
|
59
|
+
* createIfNotExists reconciliation to its control plane without client polling. */
|
|
59
60
|
export async function whenNameFree(client, work) {
|
|
60
61
|
try {
|
|
61
62
|
return await work();
|
|
@@ -28,7 +28,7 @@ export type PaginatedList<T, TQuery extends CursorPaginationQuery = CursorPagina
|
|
|
28
28
|
/** Blaxel's own helper: the items of a list answer in any of its shapes. */
|
|
29
29
|
export declare function unwrapListData<T>(response: ListResponse<T>): T[];
|
|
30
30
|
/** Blaxel's own helper, for a list fetched by `fetchPage`. */
|
|
31
|
-
export declare function createPaginatedList<TRaw, TItem, TQuery extends CursorPaginationQuery>({ response, fetchPage, mapItem, query, }: {
|
|
31
|
+
export declare function createPaginatedList<TRaw, TItem, TQuery extends CursorPaginationQuery>({ response, fetchPage, mapItem, query, seenCursors, }: {
|
|
32
32
|
response: ListResponse<TRaw>;
|
|
33
33
|
fetchPage: (query?: TQuery) => Promise<ListResponse<TRaw>>;
|
|
34
34
|
mapItem: (item: TRaw) => TItem | Promise<TItem>;
|
|
@@ -36,4 +36,4 @@ export declare function createPaginatedList<TRaw, TItem, TQuery extends CursorPa
|
|
|
36
36
|
seenCursors?: Set<string>;
|
|
37
37
|
}): Promise<PaginatedList<TItem, TQuery>>;
|
|
38
38
|
/** A Runtime page, each item mapped, as Blaxel's paginated list. */
|
|
39
|
-
export declare function paginate<TRaw, TItem>(page: Page<TRaw>, map: (item: TRaw) => TItem): PaginatedList<TItem>;
|
|
39
|
+
export declare function paginate<TRaw, TItem>(page: Page<TRaw>, map: (item: TRaw) => TItem, seenCursors?: Set<string>): PaginatedList<TItem>;
|
|
@@ -8,30 +8,58 @@ export function unwrapListData(response) {
|
|
|
8
8
|
return response.data ?? [];
|
|
9
9
|
}
|
|
10
10
|
/** Blaxel's own helper, for a list fetched by `fetchPage`. */
|
|
11
|
-
export async function createPaginatedList({ response, fetchPage, mapItem, query, }) {
|
|
12
|
-
const meta = !response || Array.isArray(response)
|
|
11
|
+
export async function createPaginatedList({ response, fetchPage, mapItem, query, seenCursors, }) {
|
|
12
|
+
const meta = !response || Array.isArray(response)
|
|
13
|
+
? { hasMore: false }
|
|
14
|
+
: (response.meta ?? { hasMore: false });
|
|
13
15
|
const data = await Promise.all(unwrapListData(response).map(mapItem));
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
const cursors = new Set(seenCursors);
|
|
17
|
+
if (query?.cursor)
|
|
18
|
+
cursors.add(query.cursor);
|
|
19
|
+
return build(data, meta.nextCursor || undefined, async (cursor) => {
|
|
20
|
+
if (cursors.has(cursor))
|
|
21
|
+
throw new Error("Pagination returned a repeated cursor");
|
|
22
|
+
const nextQuery = { ...(query ?? {}), cursor };
|
|
23
|
+
const nextSeenCursors = new Set(cursors);
|
|
24
|
+
nextSeenCursors.add(cursor);
|
|
25
|
+
return createPaginatedList({
|
|
26
|
+
response: await fetchPage(nextQuery),
|
|
27
|
+
fetchPage,
|
|
28
|
+
mapItem,
|
|
29
|
+
query: nextQuery,
|
|
30
|
+
seenCursors: nextSeenCursors,
|
|
31
|
+
});
|
|
32
|
+
}, meta);
|
|
20
33
|
}
|
|
21
34
|
/** A Runtime page, each item mapped, as Blaxel's paginated list. */
|
|
22
|
-
export function paginate(page, map) {
|
|
23
|
-
return build(page.data.map(map), page.nextCursor ?? undefined, async () => {
|
|
24
|
-
|
|
25
|
-
|
|
35
|
+
export function paginate(page, map, seenCursors = new Set()) {
|
|
36
|
+
return build(page.data.map(map), page.nextCursor ?? undefined, async (cursor) => {
|
|
37
|
+
if (cursor && seenCursors.has(cursor))
|
|
38
|
+
throw new Error("Pagination returned a repeated cursor");
|
|
39
|
+
const nextSeenCursors = new Set(seenCursors);
|
|
40
|
+
if (cursor)
|
|
41
|
+
nextSeenCursors.add(cursor);
|
|
42
|
+
const next = await guard(() => page.next(cursor));
|
|
43
|
+
return next ? paginate(next, map, nextSeenCursors) : null;
|
|
26
44
|
});
|
|
27
45
|
}
|
|
28
|
-
function build(data, nextCursor, more
|
|
46
|
+
function build(data, nextCursor, more, meta = {
|
|
47
|
+
hasMore: nextCursor !== undefined,
|
|
48
|
+
...(nextCursor ? { nextCursor } : {}),
|
|
49
|
+
}) {
|
|
29
50
|
const list = {
|
|
30
51
|
data,
|
|
31
|
-
meta
|
|
32
|
-
hasMore
|
|
33
|
-
|
|
34
|
-
|
|
52
|
+
meta,
|
|
53
|
+
get hasMore() {
|
|
54
|
+
return Boolean(list.nextCursor);
|
|
55
|
+
},
|
|
56
|
+
get nextCursor() {
|
|
57
|
+
return meta.nextCursor || undefined;
|
|
58
|
+
},
|
|
59
|
+
nextPage: async () => {
|
|
60
|
+
const cursor = list.nextCursor;
|
|
61
|
+
return cursor ? more(cursor) : null;
|
|
62
|
+
},
|
|
35
63
|
async autoPagingEach(onItem) {
|
|
36
64
|
for await (const item of list)
|
|
37
65
|
if ((await onItem(item)) === false)
|
package/dist/blaxel/preview.d.ts
CHANGED
|
@@ -67,7 +67,8 @@ export declare class SandboxPreviewTokens {
|
|
|
67
67
|
constructor(preview: SandboxPreview, ctx: PreviewContext);
|
|
68
68
|
get previewName(): string;
|
|
69
69
|
get resourceName(): string;
|
|
70
|
-
/** A token
|
|
70
|
+
/** A token bounded by `expiresAt`; native signing floors milliseconds and
|
|
71
|
+
* caps it to the caller's session. Older servers refuse without a TTL fallback. */
|
|
71
72
|
create(expiresAt: Date): Promise<SandboxPreviewToken>;
|
|
72
73
|
list(): Promise<never>;
|
|
73
74
|
/** Revokes the preview's tokens. Runtime cannot revoke one token alone, so
|
package/dist/blaxel/preview.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { guard, isNotFound, NotSupportedError, responseError } from "./errors.js";
|
|
2
2
|
import { PREVIEW } from "./lookup.js";
|
|
3
|
-
/** Runtime's shortest and longest token, in seconds. */
|
|
4
|
-
const TOKEN_MIN = 60;
|
|
5
|
-
const TOKEN_MAX = 7 * 86_400;
|
|
6
3
|
function checkName(name) {
|
|
7
4
|
if (!/^[A-Za-z0-9._-]{1,48}$/.test(name))
|
|
8
5
|
throw new NotSupportedError(`The preview name "${name}"`, "Use 1 to 48 letters, digits, dots, dashes or underscores.");
|
|
@@ -56,19 +53,24 @@ export class SandboxPreviewTokens {
|
|
|
56
53
|
get resourceName() {
|
|
57
54
|
return this.#ctx.sandboxName;
|
|
58
55
|
}
|
|
59
|
-
/** A token
|
|
56
|
+
/** A token bounded by `expiresAt`; native signing floors milliseconds and
|
|
57
|
+
* caps it to the caller's session. Older servers refuse without a TTL fallback. */
|
|
60
58
|
async create(expiresAt) {
|
|
61
|
-
const
|
|
62
|
-
if (
|
|
63
|
-
throw
|
|
64
|
-
const
|
|
65
|
-
|
|
59
|
+
const requested = expiresAt.getTime();
|
|
60
|
+
if (!Number.isFinite(requested))
|
|
61
|
+
throw responseError(400, "A preview token needs a valid expiration date.");
|
|
62
|
+
const remaining = requested - Date.now();
|
|
63
|
+
if (remaining < 60_000 || remaining > 7 * 86_400_000)
|
|
64
|
+
throw new NotSupportedError("A preview token lasting less than a minute or more than a week", "Request an expiration at least one minute and at most seven days away.");
|
|
65
|
+
const got = await this.#ctx.run((runtime) => runtime.previews.get(this.#preview.spec.port, undefined, {
|
|
66
|
+
expiresAt: expiresAt.toISOString(),
|
|
67
|
+
}));
|
|
68
|
+
const actual = got.tokenExpiresAt === null ? Number.NaN : Date.parse(got.tokenExpiresAt);
|
|
69
|
+
if (!got.token || !Number.isFinite(actual) || actual > requested)
|
|
70
|
+
throw new NotSupportedError("A preview token with the requested expiration", "The server must issue a token that never outlives the requested expiration; no duration-based fallback was requested.");
|
|
66
71
|
return new SandboxPreviewToken({
|
|
67
72
|
metadata: { name: `token-${Date.now()}`, previewName: this.previewName },
|
|
68
|
-
spec: {
|
|
69
|
-
...(got.token ? { token: got.token } : {}),
|
|
70
|
-
...(got.tokenExpiresAt ? { expiresAt: got.tokenExpiresAt } : {}),
|
|
71
|
-
},
|
|
73
|
+
spec: { token: got.token, expiresAt: got.tokenExpiresAt },
|
|
72
74
|
});
|
|
73
75
|
}
|
|
74
76
|
list() {
|
package/dist/blaxel/process.js
CHANGED
|
@@ -152,14 +152,17 @@ export class SandboxProcess {
|
|
|
152
152
|
}
|
|
153
153
|
/** A call that found the sandbox paused (autoWake off) is made again once,
|
|
154
154
|
* after a wake: it never started, so it cannot run twice. */
|
|
155
|
-
async #retryPaused(work) {
|
|
155
|
+
async #retryPaused(work, signal) {
|
|
156
|
+
signal?.throwIfAborted();
|
|
156
157
|
try {
|
|
157
158
|
return await work();
|
|
158
159
|
}
|
|
159
160
|
catch (error) {
|
|
161
|
+
signal?.throwIfAborted();
|
|
160
162
|
if (codeOf(error) !== "sandbox_paused")
|
|
161
163
|
throw translate(error);
|
|
162
164
|
await this.#ctx.wake();
|
|
165
|
+
signal?.throwIfAborted();
|
|
163
166
|
return guard(work);
|
|
164
167
|
}
|
|
165
168
|
}
|
|
@@ -281,16 +284,20 @@ export class SandboxProcess {
|
|
|
281
284
|
};
|
|
282
285
|
}
|
|
283
286
|
/** A process by pid or name: the registry first, then Runtime's list. */
|
|
284
|
-
async #find(identifier) {
|
|
287
|
+
async #find(identifier, signal) {
|
|
288
|
+
signal?.throwIfAborted();
|
|
285
289
|
const runtime = await this.#ctx.live();
|
|
290
|
+
signal?.throwIfAborted();
|
|
286
291
|
const registry = this.#registry;
|
|
287
292
|
const known = registry.byName.get(identifier) ?? (registry.meta.has(identifier) ? identifier : undefined);
|
|
288
293
|
if (known !== undefined) {
|
|
289
|
-
const process = await this.#retryPaused(() => runtime.processes.get(known));
|
|
294
|
+
const process = await this.#retryPaused(() => runtime.processes.get(known, { signal }), signal);
|
|
295
|
+
signal?.throwIfAborted();
|
|
290
296
|
registry.processes.set(known, process);
|
|
291
297
|
return { id: known, info: process.info, process };
|
|
292
298
|
}
|
|
293
|
-
const all = await this.#retryPaused(() => runtime.processes.list());
|
|
299
|
+
const all = await this.#retryPaused(() => runtime.processes.list({ signal }), signal);
|
|
300
|
+
signal?.throwIfAborted();
|
|
294
301
|
let found;
|
|
295
302
|
for (const info of all) {
|
|
296
303
|
const parsed = parseProcessLine(info.command);
|
|
@@ -323,7 +330,9 @@ export class SandboxProcess {
|
|
|
323
330
|
const running = found.info.state === "running";
|
|
324
331
|
if (running && !untilExit && found.info.outputBytes <= found.info.firstOffset)
|
|
325
332
|
return out;
|
|
333
|
+
signal?.throwIfAborted();
|
|
326
334
|
const runtime = await this.#ctx.live();
|
|
335
|
+
signal?.throwIfAborted();
|
|
327
336
|
const abort = new AbortController();
|
|
328
337
|
const forward = () => abort.abort();
|
|
329
338
|
signal?.throwIfAborted();
|
|
@@ -369,9 +378,30 @@ export class SandboxProcess {
|
|
|
369
378
|
/** The process, with the output it has written so far. */
|
|
370
379
|
async get(identifier, options = {}) {
|
|
371
380
|
options.signal?.throwIfAborted();
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
381
|
+
const signal = options.signal;
|
|
382
|
+
let onAbort;
|
|
383
|
+
const interrupted = new Promise((_, reject) => {
|
|
384
|
+
onAbort = () => reject(new Error("Process read was interrupted.", { cause: signal?.reason }));
|
|
385
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
386
|
+
});
|
|
387
|
+
try {
|
|
388
|
+
const reading = (async () => {
|
|
389
|
+
const found = await this.#find(identifier, signal);
|
|
390
|
+
const out = await this.#output(found, false, signal);
|
|
391
|
+
signal?.throwIfAborted();
|
|
392
|
+
return this.#settled(this.#response(found.info, this.#registry.meta.get(found.id), out));
|
|
393
|
+
})();
|
|
394
|
+
return await Promise.race([reading, interrupted]);
|
|
395
|
+
}
|
|
396
|
+
catch (error) {
|
|
397
|
+
if (signal?.aborted)
|
|
398
|
+
throw signal.reason ?? error;
|
|
399
|
+
throw error;
|
|
400
|
+
}
|
|
401
|
+
finally {
|
|
402
|
+
if (onAbort)
|
|
403
|
+
signal?.removeEventListener("abort", onAbort);
|
|
404
|
+
}
|
|
375
405
|
}
|
|
376
406
|
/** Waits for the process to end (at most `maxWait` ms, -1 for no limit) and
|
|
377
407
|
* answers it with its output. The process is never stopped by the wait. */
|
|
@@ -390,10 +420,24 @@ export class SandboxProcess {
|
|
|
390
420
|
signal?.throwIfAborted();
|
|
391
421
|
signal?.addEventListener("abort", forward, { once: true });
|
|
392
422
|
const timer = maxWait === -1 ? undefined : setTimeout(() => abort.abort(late()), maxWait);
|
|
423
|
+
let onAbort;
|
|
424
|
+
const interrupted = new Promise((_, reject) => {
|
|
425
|
+
onAbort = () => {
|
|
426
|
+
const reason = abort.signal.reason;
|
|
427
|
+
reject(reason instanceof Error
|
|
428
|
+
? reason
|
|
429
|
+
: new Error("Process wait was interrupted.", { cause: reason }));
|
|
430
|
+
};
|
|
431
|
+
abort.signal.addEventListener("abort", onAbort, { once: true });
|
|
432
|
+
});
|
|
393
433
|
try {
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
434
|
+
const result = (async () => {
|
|
435
|
+
const found = await this.#find(identifier, abort.signal);
|
|
436
|
+
const out = await this.#output(found, true, abort.signal);
|
|
437
|
+
abort.signal.throwIfAborted();
|
|
438
|
+
return await this.#settled(this.#response(found.info, this.#registry.meta.get(found.id), out));
|
|
439
|
+
})();
|
|
440
|
+
return await Promise.race([result, interrupted]);
|
|
397
441
|
}
|
|
398
442
|
catch (error) {
|
|
399
443
|
if (abort.signal.aborted)
|
|
@@ -402,6 +446,8 @@ export class SandboxProcess {
|
|
|
402
446
|
}
|
|
403
447
|
finally {
|
|
404
448
|
clearTimeout(timer);
|
|
449
|
+
if (onAbort)
|
|
450
|
+
abort.signal.removeEventListener("abort", onAbort);
|
|
405
451
|
signal?.removeEventListener("abort", forward);
|
|
406
452
|
}
|
|
407
453
|
}
|
package/dist/blaxel/sandbox.d.ts
CHANGED
|
@@ -32,6 +32,8 @@ export type SandboxForkOptions = {
|
|
|
32
32
|
/** A snapshot's id or, of this sandbox's snapshots, its name. */
|
|
33
33
|
snapshotId?: string;
|
|
34
34
|
envs?: Env[];
|
|
35
|
+
/** Blaxel's fork override; Runtime cannot enforce this override during a fork. */
|
|
36
|
+
lifecycle?: SandboxLifecycle;
|
|
35
37
|
};
|
|
36
38
|
export type SandboxArchiveOptions = {
|
|
37
39
|
wait?: boolean;
|
package/dist/blaxel/sandbox.js
CHANGED
|
@@ -750,6 +750,8 @@ export class SandboxInstance {
|
|
|
750
750
|
* (files, memory and running processes), or started from a snapshot with
|
|
751
751
|
* `snapshotId`. `envs` are added over this sandbox's. */
|
|
752
752
|
async fork(targetName, options = {}) {
|
|
753
|
+
if (options.lifecycle !== undefined)
|
|
754
|
+
throw new NotSupportedError("Overriding lifecycle during a fork", "Omit lifecycle to use the existing Runtime fork behavior.");
|
|
753
755
|
refuseApplication(options);
|
|
754
756
|
const envs = envRecord(options.envs);
|
|
755
757
|
checkEnvNames(Object.keys(envs));
|
package/dist/blaxel/snapshot.js
CHANGED
|
@@ -22,13 +22,11 @@ export function snapshotModel(snapshot, sandboxName) {
|
|
|
22
22
|
}
|
|
23
23
|
/** Envs as a record; a nameless one is refused. */
|
|
24
24
|
export function envRecord(envs) {
|
|
25
|
-
|
|
26
|
-
for (const env of envs ?? []) {
|
|
25
|
+
return Object.fromEntries((envs ?? []).map((env) => {
|
|
27
26
|
if (!env.name)
|
|
28
27
|
throw responseError(400, "every env needs a name");
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
return out;
|
|
28
|
+
return [env.name, env.value ?? ""];
|
|
29
|
+
}));
|
|
32
30
|
}
|
|
33
31
|
/** Appends envs to a sandbox's env file: they win over the ones it has. */
|
|
34
32
|
export async function appendEnvs(runtime, envs) {
|
package/dist/cli.d.ts
CHANGED