@sandboxrouter/sdk 0.1.0 → 0.2.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/README.md CHANGED
@@ -1,85 +1,112 @@
1
1
  # sandboxrouter
2
2
 
3
- **OpenRouter for sandboxes.** One API for every place an AI agent can run code: Maritime, E2B, Daytona, Modal, Vercel Sandbox, Morph, Fly.io Sprites, Blaxel, Tensorlake and Runloop. Routes on price, latency or capability, and falls back to the next provider when one is down or rate-limited. Also drives desktops and phones for computer-use agents.
3
+ **One API for every sandbox.** A free, open-source router for the places an AI agent can run code: Blaxel, Boat, Cloudflare, Daytona, E2B, exe.dev, Fly.io Sprites, HOPX, Maritime, Modal, Morph, Northflank, Runloop, Tensorlake and Vercel, plus Docker on your own machine. It picks a provider by price, speed or capability, and falls back to the next one when a provider is down or rate-limited.
4
4
 
5
5
  ```ts
6
6
  import { Router } from "@sandboxrouter/sdk";
7
7
 
8
- const router = new Router(); // reads provider keys from env
8
+ const router = new Router(); // uses whichever provider keys you've set
9
9
  await using sbx = await router.create({ vcpu: 2, memoryGb: 4 });
10
10
 
11
11
  const r = await sbx.exec("python3 -c 'print(6*7)'");
12
- console.log(r.stdout, "ran on", sbx.provider); // 42 ran on maritime
12
+ console.log(r.stdout, "ran on", sbx.provider);
13
13
  ```
14
14
 
15
15
  Change providers without changing code. If one returns a 429, the next one takes the sandbox.
16
16
 
17
+ Site: [sandboxrouter.vercel.app](https://sandboxrouter.vercel.app) · npm: [`@sandboxrouter/sdk`](https://www.npmjs.com/package/@sandboxrouter/sdk)
18
+
17
19
  ## Why
18
20
 
19
- Agents need a disposable computer: to run code a model wrote, install packages, run tests, scrape, or do an RL rollout. In 2026 there are more than a dozen sandbox providers, each with its own SDK, auth, lifecycle and price. A 2 vCPU / 4 GB box costs anywhere from **$0.022/hr to $0.34/hr** depending on where you start it.
21
+ Agents need a disposable computer: to run code a model wrote, install packages, run tests, scrape, or do an RL rollout. There are more than a dozen sandbox providers, each with its own SDK, auth, lifecycle and price, and the same 2 vCPU / 4 GB box costs anywhere from a few cents to over thirty cents an hour.
20
22
 
21
23
  So every agent team either locks into one provider or writes its own adapter layer. sandboxrouter is that adapter layer, done once, in the open:
22
24
 
23
25
  - **One interface.** `create`, `exec`, `writeFile`, `readFile`, `pause`, `kill`, the same on every provider. `exec` streams stdout and stderr as they happen.
24
- - **Routing.** `default` (catalog order), `cheapest` (by list price for your shape) or `fastest` (by your own measured cold starts).
26
+ - **Routing.** `cheapest` (the default: lowest list price for your size) or `fastest` (lowest cold start you measured with `bench`), or name providers yourself.
25
27
  - **Fallback.** A failed create moves to the next candidate, and every attempt is recorded on `sbx.attempts`.
26
28
  - **Capability filters.** Ask for a GPU and only GPU-capable providers are tried. Ask for 32 vCPU and providers that can't do it are skipped, with the reason.
27
- - **BYOK.** Your keys and your accounts. Nothing is proxied through a third party.
29
+ - **Your keys.** Your accounts, your bills. Nothing is proxied through a third party.
28
30
  - **HTTP server and Python client.** Run `sandboxrouter serve` and any language gets the same API.
29
- - **Computers.** Linux and Windows desktops and Android phones for computer-use models: actions, screenshots, and a takeover link for a person when the model hits a login.
30
- - **Open leaderboard.** `bench --share` produces a report; the leaderboard is built from submitted reports in [`results/`](results/).
31
+ - **Computers.** Desktops and phones for computer-use models (Maritime today): actions, screenshots, and a takeover link for a person when the model hits a login.
32
+ - **Open benchmark.** `bench --share` produces a report; cold starts on the site come from reports submitted to [`results/`](results/).
33
+
34
+ ## Fair by construction
35
+
36
+ No provider gets a better slot, and none pays for placement.
37
+
38
+ - With no strategy, a sandbox goes to the **lowest list price for the size you asked for**, among providers you have keys for. Ties break **alphabetically**.
39
+ - `fastest` uses only cold starts **you** measured with `bench`, then price.
40
+ - Prices are each provider's public rate, with a source link, in [`src/catalog.ts`](src/catalog.ts). There's a test that the catalog has no privileged ordering.
41
+ - Docker is never picked automatically, because it always "costs" $0 and isolates less than a cloud micro-VM. Name it to use it.
42
+
43
+ If a price is wrong, open a PR with the source link.
31
44
 
32
45
  ## Prices
33
46
 
34
- Estimated list price while running, 2 vCPU / 4 GB (`npx @sandboxrouter/sdk providers --vcpu 2 --memory 4`):
47
+ Estimated list price while running, 2 vCPU / 4 GB (`npx @sandboxrouter/sdk providers --vcpu 2 --memory 4`), in the order the router would try them:
35
48
 
36
49
  | Provider | $/hr | Notes | Source |
37
50
  |---|---|---|---|
38
- | **Maritime** | **$0.022** | Per second, disk included. Firecracker micro-VMs. Also runs stock E2B SDK code. | [pricing](https://maritime.sh/pricing) |
39
- | Morph Cloud | $0.100 | $0.05 per MCU; an MCU-hour is the larger of 1 vCPU, 4 GB RAM or 16 GB disk. | [pricing](https://cloud.morph.so/web/pricing) |
40
- | Fly.io Sprites | $0.165 | Bills actual CPU and memory use; this is the full-use price. Storage extra. | [pricing](https://fly.io/sprites) |
41
- | E2B | $0.166 | Per second. Pro plan $150/mo. | [pricing](https://e2b.dev/pricing) |
42
- | Daytona | $0.166 | Per second, plus disk above 5 GiB. | [pricing](https://www.daytona.io/pricing) |
43
- | Blaxel | $0.166 | Billed on memory only; CPU scales with memory. | [pricing](https://blaxel.ai/pricing) |
44
- | Tensorlake | $0.200 | Bills active CPU (20% floor); this is the full-use price. Pro plan is cheaper. | [pricing](https://www.tensorlake.ai/pricing) |
45
- | Modal | $0.239 | Sandbox rates. GPUs available. | [pricing](https://modal.com/pricing) |
46
- | Runloop | $0.317 | Per second, plus disk. Suspend/resume is a Pro feature. | [pricing](https://www.runloop.ai/pricing) |
47
- | Vercel Sandbox | $0.341 | Bills active CPU only. This is the 100%-CPU price; mostly idle work costs less. | [pricing](https://vercel.com/docs/vercel-sandbox/pricing) |
48
-
49
- Checked 2026-11-04. Prices live in [`src/catalog.ts`](src/catalog.ts). If one is wrong, open a PR with the source link.
50
-
51
- **Why Maritime is the default:** it's first in catalog order because it's the cheapest per hour at every shape in this table (there's a test that checks this against the catalog), so `default` and `cheapest` agree. Don't take that on trust: run `sandboxrouter bench` on your own accounts and route with `strategy: "fastest"` if latency matters more to you than price.
51
+ | Boat | $0.018 | Fixed sizes billed per second ($0.018/h for 2 vCPU / 4 GB up to $0.20/h for 16 / 32); needs a plan from $20/mo, paid back as VM time | [pricing](https://docs.boat.dev/pricing.md) |
52
+ | Maritime | $0.022 | Per second, disk included | [pricing](https://maritime.sh/pricing) |
53
+ | Northflank | $0.067 | Per second on Northflank Cloud, ephemeral disk included; sandboxes run as deployment services (microVMs) | [pricing](https://northflank.com/pricing) |
54
+ | Morph Cloud | $0.100 | $0.05/MCU; MCU-hours = max(vCPU, RAM/4 GB, disk/16 GB); snapshots extra | [pricing](https://cloud.morph.so/web/pricing) |
55
+ | exe.dev | $0.105 | $0.105/h per 2 vCPU + 4 GiB, per second, up to 2 GiB per vCPU; needs an exe.dev subscription | [pricing](https://exe.dev/docs/standalone-vms.md) |
56
+ | Fly.io Sprites | $0.164 | Bills actual CPU and memory use; this is the full-use price; storage extra | [pricing](https://fly.io/sprites) |
57
+ | Blaxel | $0.166 | Billed on memory only ($0.0000115/GB-s), CPU scales with memory (~2 GB per core) | [pricing](https://blaxel.ai/pricing) |
58
+ | Daytona | $0.166 | Per second, plus disk above 5 GiB | [pricing](https://www.daytona.io/pricing) |
59
+ | E2B | $0.166 | Per second; Pro plan $150/mo | [pricing](https://e2b.dev/pricing) |
60
+ | HOPX | $0.166 | Per second, plus storage; size comes from the template, not the request | [pricing](https://hopx.ai/pricing) |
61
+ | Tensorlake | $0.200 | Bills active CPU (floor 20% of allocation); this is the full-use price; Pro plan is lower | [pricing](https://www.tensorlake.ai/pricing) |
62
+ | Cloudflare Sandbox | $0.216 | Bills active CPU and provisioned memory of the instance type; this is the full-CPU price; needs Workers Paid ($5/mo) and the bridge Worker in bridges/cloudflare | [pricing](https://developers.cloudflare.com/containers/platform/pricing/) |
63
+ | Modal | $0.239 | Sandbox rates, GPUs extra; region pinning x1.15-1.75 | [pricing](https://modal.com/pricing) |
64
+ | Runloop | $0.317 | Per second, plus disk; pause (suspend) is a Pro-plan feature | [pricing](https://www.runloop.ai/pricing) |
65
+ | Vercel Sandbox | $0.341 | Bills active CPU only; this is the 100%-CPU price, idle work costs less | [pricing](https://vercel.com/docs/vercel-sandbox/pricing) |
66
+ | Docker (local) | free | Runs on your own machine; free, but shares your kernel, so weaker isolation than a cloud micro-VM | [pricing](https://docs.docker.com/engine/) |
67
+
68
+ Checked 2026-11-04.
52
69
 
53
70
  ## Install
54
71
 
55
72
  ```bash
56
73
  npm i @sandboxrouter/sdk
57
- # then install the SDK of each provider you use (Maritime needs none, it's plain REST):
58
- npm i e2b # E2B
59
- npm i @daytonaio/sdk # Daytona
60
- npm i modal # Modal
61
- npm i @vercel/sandbox # Vercel Sandbox
62
- npm i morphcloud # Morph Cloud
63
- npm i @fly/sprites # Fly.io Sprites (needs Node 24+ per its package)
64
- npm i @blaxel/core # Blaxel
74
+ # then the SDK of each provider you use (Boat, Cloudflare, exe.dev, Maritime and Docker need none):
75
+ npm i e2b # E2B
76
+ npm i @daytonaio/sdk # Daytona
77
+ npm i modal # Modal
78
+ npm i @vercel/sandbox # Vercel Sandbox
79
+ npm i morphcloud # Morph Cloud
80
+ npm i @fly/sprites # Fly.io Sprites (its package asks for Node 24+)
81
+ npm i @blaxel/core # Blaxel
65
82
  npm i @tensorlakeai/tensorlake # Tensorlake
66
- npm i @runloop/api-client # Runloop
83
+ npm i @runloop/api-client # Runloop
84
+ npm i @hopx-ai/sdk # HOPX
85
+ npm i @northflank/js-client # Northflank
67
86
  ```
68
87
 
69
- Set the keys for the providers you want. Unconfigured providers are skipped.
88
+ Cloudflare sandboxes only run inside a Cloudflare Worker, so you deploy a small bridge Worker to your own account first: see [`bridges/cloudflare`](bridges/cloudflare).
89
+
90
+ Set the keys for the providers you want. Providers without keys are skipped.
70
91
 
71
92
  | Provider | Env |
72
93
  |---|---|
73
- | Maritime | `MARITIME_API_KEY` (an `mk_` key with the `sandboxes` scope: `maritime keys create`) |
74
- | E2B | `E2B_API_KEY` |
94
+ | Blaxel | `BL_API_KEY` + `BL_WORKSPACE` |
95
+ | Boat | `BOAT_API_KEY` |
96
+ | Cloudflare Sandbox | `CLOUDFLARE_SANDBOX_URL` + `CLOUDFLARE_SANDBOX_TOKEN` |
75
97
  | Daytona | `DAYTONA_API_KEY` |
76
- | Modal | `MODAL_TOKEN_ID` + `MODAL_TOKEN_SECRET`, or `~/.modal.toml` |
77
- | Vercel | `VERCEL_OIDC_TOKEN`, or `VERCEL_TOKEN` + `VERCEL_TEAM_ID` + `VERCEL_PROJECT_ID` |
78
- | Morph | `MORPH_API_KEY` |
98
+ | Docker (local) | none: Docker running locally |
99
+ | E2B | `E2B_API_KEY` |
100
+ | exe.dev | `EXE_TOKEN` |
79
101
  | Fly.io Sprites | `SPRITES_TOKEN` |
80
- | Blaxel | `BL_API_KEY` + `BL_WORKSPACE` (optional `BL_REGION`, default `us-pdx-1`) |
81
- | Tensorlake | `TENSORLAKE_API_KEY` |
102
+ | HOPX | `HOPX_API_KEY` |
103
+ | Maritime | `MARITIME_API_KEY` |
104
+ | Modal | `MODAL_TOKEN_ID` + `MODAL_TOKEN_SECRET` |
105
+ | Morph Cloud | `MORPH_API_KEY` |
106
+ | Northflank | `NORTHFLANK_API_TOKEN` + `NORTHFLANK_PROJECT_ID` |
82
107
  | Runloop | `RUNLOOP_API_KEY` |
108
+ | Tensorlake | `TENSORLAKE_API_KEY` |
109
+ | Vercel Sandbox | `VERCEL_OIDC_TOKEN`, or `VERCEL_TOKEN` + `VERCEL_TEAM_ID` + `VERCEL_PROJECT_ID` |
83
110
 
84
111
  ## Library
85
112
 
@@ -218,18 +245,24 @@ with sr.create(vcpu=2, memory_gb=4, strategy="cheapest") as sbx:
218
245
  | Blaxel | memory (CPU follows) | `ttl` | image | yes (archive, disk only) | Idle sandboxes go to free standby on their own. |
219
246
  | Tensorlake | `cpus`, `memoryMb` | idle timeout | image | yes (memory + disk) | Output is line-based. |
220
247
  | Runloop | snapped to Runloop's custom sizes | `keep_alive_time_seconds` (max 48 h) | blueprint | yes (Pro plan) | Commands start in the home directory. |
248
+ | HOPX | set by the template | timeout | template | yes (memory + disk) | Size requests aren't applied, so the router only sends default-size work. |
249
+ | Northflank | cheapest `nf-compute` plan that fits | `expiryTime` (5 min to 7 days) | any public image | no | Runs as a deployment service; files move as base64 over exec. |
250
+ | Cloudflare | smallest instance type that fits | alarm in the bridge Worker | Dockerfile in the bridge | no | Through the bridge Worker you deploy ([`bridges/cloudflare`](bridges/cloudflare)). |
251
+ | Boat | smallest of 4 fixed sizes that fits | auto-stop timer | saved snapshot | yes (stop: disk kept, processes end) | Plain REST. Commands that wait are capped at 600 s; files API up to 5 MiB. |
252
+ | exe.dev | `--cpu`, `--memory` (2 GiB per vCPU max) | none: `kill()` deletes | `--image` | no | Plain REST over exe.dev's `/exec`; long commands run detached and are polled. |
253
+ | Docker (local) | `--cpus`, `--memory` | container exits at the lifetime | any image | yes (`docker pause`) | Your machine, shared kernel. Never auto-routed; use `{ provider: "docker" }`. |
221
254
 
222
255
  Capabilities in the catalog describe what each **adapter** implements, not everything a provider sells. Size limits are self-serve defaults and may be higher on your plan.
223
256
 
224
257
  ## Roadmap
225
258
 
226
- - [x] More providers: Morph, Fly.io Sprites, Blaxel, Tensorlake, Runloop
259
+ - [x] More providers: Morph, Fly.io Sprites, Blaxel, Tensorlake, Runloop, HOPX, Northflank, Boat, exe.dev, Cloudflare (via a bridge Worker), Docker (local)
260
+ - [x] Neutral routing: price decides, ties alphabetical, no privileged slot
227
261
  - [x] Desktops and phones (Maritime: Linux, Windows, Android)
228
262
  - [x] Streaming exec output
229
263
  - [x] Pause/resume where providers support it
230
264
  - [x] Python client
231
265
  - [x] Leaderboard built from `bench --share` reports
232
- - [ ] Cloudflare Sandbox. Its SDK runs only inside a Worker with a Durable Object binding, so it needs a small Worker shim the router can call
233
266
  - [ ] Computers on more providers (E2B Desktop, Daytona computer use)
234
267
  - [ ] Snapshots and forks as a routed operation
235
268
  - [ ] A hosted copy of the leaderboard page
package/dist/catalog.d.ts CHANGED
@@ -16,16 +16,86 @@ export interface ProviderInfo {
16
16
  maxVcpu: number;
17
17
  maxMemoryGb: number;
18
18
  };
19
+ /** Runs on your own machine (Docker). Never chosen automatically; name it to use it. */
20
+ local?: boolean;
19
21
  /** Env vars the adapter reads. Any one group being fully set means "configured". */
20
22
  credentials: string[][];
21
23
  docs: string;
22
24
  }
23
- /**
24
- * Catalog order is the default routing order. Maritime is first because it is the cheapest
25
- * managed sandbox per hour in this list at every shape; `npx @sandboxrouter/sdk providers` prints the
26
- * numbers so you can check, and PRs that correct a price are welcome.
27
- */
28
25
  export declare const CATALOG: ProviderInfo[];
26
+ /** Cloudflare only rents fixed instance types: https://developers.cloudflare.com/containers/platform/limits/ */
27
+ export declare const CLOUDFLARE_INSTANCES: readonly [{
28
+ readonly name: "lite";
29
+ readonly vcpu: number;
30
+ readonly memoryGb: 0.25;
31
+ }, {
32
+ readonly name: "basic";
33
+ readonly vcpu: number;
34
+ readonly memoryGb: 1;
35
+ }, {
36
+ readonly name: "standard-1";
37
+ readonly vcpu: number;
38
+ readonly memoryGb: 4;
39
+ }, {
40
+ readonly name: "standard-2";
41
+ readonly vcpu: 1;
42
+ readonly memoryGb: 6;
43
+ }, {
44
+ readonly name: "standard-3";
45
+ readonly vcpu: 2;
46
+ readonly memoryGb: 8;
47
+ }, {
48
+ readonly name: "standard-4";
49
+ readonly vcpu: 4;
50
+ readonly memoryGb: 12;
51
+ }];
52
+ /** Boat's fixed machine sizes and hourly prices: https://docs.boat.dev/machines.md */
53
+ export declare const BOAT_SIZES: readonly [{
54
+ readonly type: "small";
55
+ readonly vcpu: 2;
56
+ readonly memoryGb: 4;
57
+ readonly usdHour: 0.018;
58
+ }, {
59
+ readonly type: "default";
60
+ readonly vcpu: 4;
61
+ readonly memoryGb: 8;
62
+ readonly usdHour: 0.036;
63
+ }, {
64
+ readonly type: "large";
65
+ readonly vcpu: 8;
66
+ readonly memoryGb: 16;
67
+ readonly usdHour: 0.072;
68
+ }, {
69
+ readonly type: "xlarge";
70
+ readonly vcpu: 16;
71
+ readonly memoryGb: 32;
72
+ readonly usdHour: 0.2;
73
+ }];
74
+ export declare function cloudflareInstance(vcpu: number, memoryGb: number): {
75
+ readonly name: "lite";
76
+ readonly vcpu: number;
77
+ readonly memoryGb: 0.25;
78
+ } | {
79
+ readonly name: "basic";
80
+ readonly vcpu: number;
81
+ readonly memoryGb: 1;
82
+ } | {
83
+ readonly name: "standard-1";
84
+ readonly vcpu: number;
85
+ readonly memoryGb: 4;
86
+ } | {
87
+ readonly name: "standard-2";
88
+ readonly vcpu: 1;
89
+ readonly memoryGb: 6;
90
+ } | {
91
+ readonly name: "standard-3";
92
+ readonly vcpu: 2;
93
+ readonly memoryGb: 8;
94
+ } | {
95
+ readonly name: "standard-4";
96
+ readonly vcpu: 4;
97
+ readonly memoryGb: 12;
98
+ };
29
99
  export declare function info(id: ProviderId): ProviderInfo;
30
100
  export declare const DEFAULT_VCPU = 2;
31
101
  export declare const DEFAULT_MEMORY_GB = 4;
package/dist/catalog.js CHANGED
@@ -1,9 +1,25 @@
1
1
  /**
2
- * Catalog order is the default routing order. Maritime is first because it is the cheapest
3
- * managed sandbox per hour in this list at every shape; `npx @sandboxrouter/sdk providers` prints the
4
- * numbers so you can check, and PRs that correct a price are welcome.
2
+ * Every provider is treated the same: the router orders candidates by list price for the requested
3
+ * size (or by your measured cold starts), ties broken alphabetically. Nobody gets a better slot.
4
+ * Prices come from each provider's public pricing page; PRs that correct one are welcome.
5
5
  */
6
- export const CATALOG = [
6
+ const ENTRIES = [
7
+ {
8
+ id: "docker",
9
+ name: "Docker (local)",
10
+ pricing: {
11
+ vcpuHour: 0,
12
+ gbHour: 0,
13
+ note: "runs on your own machine; free, but shares your kernel, so weaker isolation than a cloud micro-VM",
14
+ source: "https://docs.docker.com/engine/",
15
+ checked: "2026-11-04",
16
+ },
17
+ capabilities: ["pause", "custom-image"],
18
+ limits: { maxVcpu: 64, maxMemoryGb: 256 },
19
+ local: true,
20
+ credentials: [],
21
+ docs: "https://docs.docker.com/engine/",
22
+ },
7
23
  {
8
24
  id: "maritime",
9
25
  name: "Maritime",
@@ -157,6 +173,99 @@ export const CATALOG = [
157
173
  docs: "https://docs.runloop.ai",
158
174
  },
159
175
  ];
176
+ ENTRIES.push({
177
+ id: "boat",
178
+ name: "Boat",
179
+ pricing: {
180
+ vcpuHour: 0.009,
181
+ gbHour: 0,
182
+ note: "fixed sizes billed per second ($0.018/h for 2 vCPU / 4 GB up to $0.20/h for 16 / 32); needs a plan from $20/mo, paid back as VM time",
183
+ source: "https://docs.boat.dev/pricing.md",
184
+ checked: "2026-11-04",
185
+ },
186
+ // "pause" here is Boat's stop: the disk is kept, running processes end.
187
+ capabilities: ["pause", "custom-image"],
188
+ limits: { maxVcpu: 16, maxMemoryGb: 32 },
189
+ credentials: [["BOAT_API_KEY"]],
190
+ docs: "https://docs.boat.dev",
191
+ }, {
192
+ id: "exedev",
193
+ name: "exe.dev",
194
+ pricing: {
195
+ vcpuHour: 0.0525,
196
+ gbHour: 0,
197
+ note: "$0.105/h per 2 vCPU + 4 GiB, per second, up to 2 GiB per vCPU; needs an exe.dev subscription",
198
+ source: "https://exe.dev/docs/standalone-vms.md",
199
+ checked: "2026-11-04",
200
+ },
201
+ capabilities: ["custom-image"],
202
+ limits: { maxVcpu: 16, maxMemoryGb: 32 },
203
+ credentials: [["EXE_TOKEN"]],
204
+ docs: "https://exe.dev/docs/https-api.md",
205
+ }, {
206
+ id: "northflank",
207
+ name: "Northflank",
208
+ pricing: {
209
+ vcpuHour: 0.01667,
210
+ gbHour: 0.00833,
211
+ note: "per second on Northflank Cloud, ephemeral disk included; sandboxes run as deployment services (microVMs)",
212
+ source: "https://northflank.com/pricing",
213
+ checked: "2026-11-04",
214
+ },
215
+ capabilities: ["custom-image"],
216
+ limits: { maxVcpu: 8, maxMemoryGb: 16 },
217
+ credentials: [["NORTHFLANK_API_TOKEN", "NORTHFLANK_PROJECT_ID"]],
218
+ docs: "https://northflank.com/docs/v1/application/overview",
219
+ }, {
220
+ id: "hopx",
221
+ name: "HOPX",
222
+ pricing: {
223
+ vcpuHour: 0.0504,
224
+ gbHour: 0.0162,
225
+ note: "per second, plus storage; size comes from the template, not the request",
226
+ source: "https://hopx.ai/pricing",
227
+ checked: "2026-11-04",
228
+ },
229
+ capabilities: ["pause", "custom-image"],
230
+ // HOPX takes no size at create; capped at the default size so bigger requests go elsewhere.
231
+ limits: { maxVcpu: 2, maxMemoryGb: 4 },
232
+ credentials: [["HOPX_API_KEY"]],
233
+ docs: "https://docs.hopx.ai",
234
+ }, {
235
+ id: "cloudflare",
236
+ name: "Cloudflare Sandbox",
237
+ pricing: {
238
+ vcpuHour: 0.072,
239
+ gbHour: 0.009,
240
+ note: "bills active CPU and provisioned memory of the instance type; this is the full-CPU price; needs Workers Paid ($5/mo) and the bridge Worker in bridges/cloudflare",
241
+ source: "https://developers.cloudflare.com/containers/platform/pricing/",
242
+ checked: "2026-11-04",
243
+ },
244
+ capabilities: [],
245
+ limits: { maxVcpu: 4, maxMemoryGb: 12 },
246
+ credentials: [["CLOUDFLARE_SANDBOX_URL", "CLOUDFLARE_SANDBOX_TOKEN"]],
247
+ docs: "https://developers.cloudflare.com/sandbox/",
248
+ });
249
+ export const CATALOG = [...ENTRIES].sort((a, b) => a.name.localeCompare(b.name));
250
+ /** Cloudflare only rents fixed instance types: https://developers.cloudflare.com/containers/platform/limits/ */
251
+ export const CLOUDFLARE_INSTANCES = [
252
+ { name: "lite", vcpu: 1 / 16, memoryGb: 0.25 },
253
+ { name: "basic", vcpu: 1 / 4, memoryGb: 1 },
254
+ { name: "standard-1", vcpu: 1 / 2, memoryGb: 4 },
255
+ { name: "standard-2", vcpu: 1, memoryGb: 6 },
256
+ { name: "standard-3", vcpu: 2, memoryGb: 8 },
257
+ { name: "standard-4", vcpu: 4, memoryGb: 12 },
258
+ ];
259
+ /** Boat's fixed machine sizes and hourly prices: https://docs.boat.dev/machines.md */
260
+ export const BOAT_SIZES = [
261
+ { type: "small", vcpu: 2, memoryGb: 4, usdHour: 0.018 },
262
+ { type: "default", vcpu: 4, memoryGb: 8, usdHour: 0.036 },
263
+ { type: "large", vcpu: 8, memoryGb: 16, usdHour: 0.072 },
264
+ { type: "xlarge", vcpu: 16, memoryGb: 32, usdHour: 0.2 },
265
+ ];
266
+ export function cloudflareInstance(vcpu, memoryGb) {
267
+ return CLOUDFLARE_INSTANCES.find((t) => t.vcpu >= vcpu && t.memoryGb >= memoryGb) ?? CLOUDFLARE_INSTANCES[CLOUDFLARE_INSTANCES.length - 1];
268
+ }
160
269
  export function info(id) {
161
270
  const p = CATALOG.find((c) => c.id === id);
162
271
  if (!p)
@@ -176,6 +285,19 @@ export function hourlyCost(id, spec = {}) {
176
285
  // Blaxel sizes by memory, about 2 GB per core.
177
286
  if (id === "blaxel")
178
287
  mem = Math.max(mem, vcpu * 2);
288
+ // Boat rents the smallest fixed size that fits.
289
+ if (id === "boat") {
290
+ const s = BOAT_SIZES.find((b) => b.vcpu >= vcpu && b.memoryGb >= mem) ?? BOAT_SIZES[BOAT_SIZES.length - 1];
291
+ return s.usdHour;
292
+ }
293
+ // exe.dev bills per vCPU with up to 2 GiB each, so memory beyond that adds vCPUs.
294
+ if (id === "exedev")
295
+ return Math.max(Math.ceil(vcpu), Math.ceil(mem / 2)) * pricing.vcpuHour;
296
+ // Cloudflare bills the instance type that fits, not the exact request.
297
+ if (id === "cloudflare") {
298
+ const t = cloudflareInstance(vcpu, mem);
299
+ return t.vcpu * pricing.vcpuHour + t.memoryGb * pricing.gbHour;
300
+ }
179
301
  // Morph bills whole MCUs: the larger of vCPUs and 4 GB blocks of memory.
180
302
  if (id === "morph")
181
303
  return Math.max(vcpu, Math.ceil(mem / 4)) * pricing.vcpuHour;
package/dist/cli.js CHANGED
@@ -21,7 +21,7 @@ Usage:
21
21
  sandboxrouter serve [--port 8787] [--host 127.0.0.1] [--cors https://site.example,http://localhost:3000]
22
22
 
23
23
  Spec flags: --vcpu N --memory GB --gpu T4 --timeout MIN
24
- Route flags: --provider maritime[,e2b,...] --strategy default|cheapest|fastest
24
+ Route flags: --provider e2b[,modal,...] --strategy cheapest|fastest
25
25
  --no-fallback --require gpu,custom-image
26
26
 
27
27
  Keys: MARITIME_API_KEY, E2B_API_KEY, DAYTONA_API_KEY, MODAL_TOKEN_ID+MODAL_TOKEN_SECRET,
package/dist/index.d.ts CHANGED
@@ -14,4 +14,10 @@ export { SpritesProvider } from "./providers/sprites.js";
14
14
  export { BlaxelProvider } from "./providers/blaxel.js";
15
15
  export { TensorlakeProvider } from "./providers/tensorlake.js";
16
16
  export { RunloopProvider } from "./providers/runloop.js";
17
+ export { DockerProvider } from "./providers/docker.js";
18
+ export { HopxProvider } from "./providers/hopx.js";
19
+ export { CloudflareProvider } from "./providers/cloudflare.js";
20
+ export { NorthflankProvider } from "./providers/northflank.js";
21
+ export { BoatProvider } from "./providers/boat.js";
22
+ export { ExeDevProvider } from "./providers/exedev.js";
17
23
  export * from "./types.js";
package/dist/index.js CHANGED
@@ -14,4 +14,10 @@ export { SpritesProvider } from "./providers/sprites.js";
14
14
  export { BlaxelProvider } from "./providers/blaxel.js";
15
15
  export { TensorlakeProvider } from "./providers/tensorlake.js";
16
16
  export { RunloopProvider } from "./providers/runloop.js";
17
+ export { DockerProvider } from "./providers/docker.js";
18
+ export { HopxProvider } from "./providers/hopx.js";
19
+ export { CloudflareProvider } from "./providers/cloudflare.js";
20
+ export { NorthflankProvider } from "./providers/northflank.js";
21
+ export { BoatProvider } from "./providers/boat.js";
22
+ export { ExeDevProvider } from "./providers/exedev.js";
17
23
  export * from "./types.js";
@@ -0,0 +1,25 @@
1
+ import { type Provider, type ProviderSandbox, type SandboxSpec } from "../types.js";
2
+ export declare class BoatProvider implements Provider {
3
+ private apiKey;
4
+ private baseUrl;
5
+ readonly id: "boat";
6
+ constructor(apiKey?: string | undefined, baseUrl?: string);
7
+ configured(): boolean;
8
+ /** @internal */
9
+ request(method: string, path: string, init?: {
10
+ json?: unknown;
11
+ headers?: Record<string, string>;
12
+ }): Promise<Response>;
13
+ /** @internal */
14
+ json<T>(method: string, path: string, json?: unknown, headers?: Record<string, string>): Promise<T>;
15
+ /** @internal Poll until the sandbox can take commands. Resumes it first when it is stopped. */
16
+ ready(id: string, timeoutMs?: number): Promise<BoatSandboxObject>;
17
+ create(spec: SandboxSpec): Promise<ProviderSandbox>;
18
+ connect(nativeId: string): Promise<ProviderSandbox>;
19
+ }
20
+ interface BoatSandboxObject {
21
+ id: string;
22
+ state: string;
23
+ error?: string | null;
24
+ }
25
+ export {};