@sandboxrouter/sdk 0.1.0 → 0.3.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.
Files changed (97) hide show
  1. package/README.md +271 -62
  2. package/dist/bench.js +4 -4
  3. package/dist/catalog.d.ts +232 -3
  4. package/dist/catalog.js +569 -14
  5. package/dist/check.d.ts +28 -0
  6. package/dist/check.js +185 -0
  7. package/dist/cli.js +149 -12
  8. package/dist/computers.d.ts +55 -13
  9. package/dist/computers.js +132 -6
  10. package/dist/conformance.d.ts +46 -0
  11. package/dist/conformance.js +65 -0
  12. package/dist/cost.d.ts +67 -0
  13. package/dist/cost.js +153 -0
  14. package/dist/gc.d.ts +29 -0
  15. package/dist/gc.js +54 -0
  16. package/dist/index.d.ts +34 -2
  17. package/dist/index.js +34 -2
  18. package/dist/leaderboard.d.ts +6 -0
  19. package/dist/leaderboard.js +5 -4
  20. package/dist/ledger.d.ts +38 -0
  21. package/dist/ledger.js +89 -0
  22. package/dist/providers/agent37.d.ts +40 -0
  23. package/dist/providers/agent37.js +224 -0
  24. package/dist/providers/agentcore.d.ts +71 -0
  25. package/dist/providers/agentcore.js +446 -0
  26. package/dist/providers/alibaba.d.ts +18 -0
  27. package/dist/providers/alibaba.js +106 -0
  28. package/dist/providers/azure.d.ts +43 -0
  29. package/dist/providers/azure.js +330 -0
  30. package/dist/providers/beam.d.ts +11 -0
  31. package/dist/providers/beam.js +179 -0
  32. package/dist/providers/blaxel.d.ts +3 -1
  33. package/dist/providers/blaxel.js +32 -22
  34. package/dist/providers/boat.d.ts +25 -0
  35. package/dist/providers/boat.js +263 -0
  36. package/dist/providers/cloudflare.d.ts +30 -0
  37. package/dist/providers/cloudflare.js +152 -0
  38. package/dist/providers/codesandbox.d.ts +59 -0
  39. package/dist/providers/codesandbox.js +287 -0
  40. package/dist/providers/daytona.d.ts +25 -4
  41. package/dist/providers/daytona.js +449 -28
  42. package/dist/providers/dedalus.d.ts +12 -0
  43. package/dist/providers/dedalus.js +244 -0
  44. package/dist/providers/deno.d.ts +50 -0
  45. package/dist/providers/deno.js +170 -0
  46. package/dist/providers/docker.d.ts +17 -0
  47. package/dist/providers/docker.js +154 -0
  48. package/dist/providers/e2b.d.ts +70 -3
  49. package/dist/providers/e2b.js +376 -8
  50. package/dist/providers/exedev.d.ts +23 -0
  51. package/dist/providers/exedev.js +290 -0
  52. package/dist/providers/freestyle.d.ts +49 -0
  53. package/dist/providers/freestyle.js +261 -0
  54. package/dist/providers/hopx.d.ts +15 -0
  55. package/dist/providers/hopx.js +177 -0
  56. package/dist/providers/instabox.d.ts +76 -0
  57. package/dist/providers/instabox.js +360 -0
  58. package/dist/providers/koyeb.d.ts +71 -0
  59. package/dist/providers/koyeb.js +379 -0
  60. package/dist/providers/lambda.d.ts +51 -0
  61. package/dist/providers/lambda.js +467 -0
  62. package/dist/providers/langsmith.d.ts +16 -0
  63. package/dist/providers/langsmith.js +149 -0
  64. package/dist/providers/limrun.d.ts +77 -0
  65. package/dist/providers/limrun.js +626 -0
  66. package/dist/providers/maritime.d.ts +5 -1
  67. package/dist/providers/maritime.js +39 -8
  68. package/dist/providers/mobilerun.d.ts +44 -0
  69. package/dist/providers/mobilerun.js +417 -0
  70. package/dist/providers/modal.js +12 -4
  71. package/dist/providers/morph.js +12 -4
  72. package/dist/providers/northflank.d.ts +21 -0
  73. package/dist/providers/northflank.js +259 -0
  74. package/dist/providers/novita.d.ts +12 -0
  75. package/dist/providers/novita.js +81 -0
  76. package/dist/providers/openscale.d.ts +17 -0
  77. package/dist/providers/openscale.js +87 -0
  78. package/dist/providers/orgo.d.ts +45 -0
  79. package/dist/providers/orgo.js +414 -0
  80. package/dist/providers/sail.d.ts +32 -0
  81. package/dist/providers/sail.js +372 -0
  82. package/dist/providers/superserve.d.ts +25 -0
  83. package/dist/providers/superserve.js +337 -0
  84. package/dist/providers/tensorlake.js +17 -14
  85. package/dist/providers/upstash.d.ts +30 -0
  86. package/dist/providers/upstash.js +258 -0
  87. package/dist/providers/vercel.js +14 -3
  88. package/dist/providers/workspaces.d.ts +60 -0
  89. package/dist/providers/workspaces.js +662 -0
  90. package/dist/router.d.ts +36 -5
  91. package/dist/router.js +174 -25
  92. package/dist/server.d.ts +1 -1
  93. package/dist/server.js +22 -3
  94. package/dist/types.d.ts +18 -6
  95. package/dist/util.d.ts +28 -1
  96. package/dist/util.js +56 -7
  97. package/package.json +106 -17
package/README.md CHANGED
@@ -1,85 +1,170 @@
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: Agent37, Alibaba Cloud, AWS (Bedrock AgentCore and Lambda MicroVMs), Azure Container Apps, Beam, Blaxel, Boat, Cloudflare, CodeSandbox, Daytona, Dedalus Machines, Deno, E2B, exe.dev, Fly.io Sprites, Freestyle, HOPX, InstaBox, Koyeb, Maritime, Modal, Morph, Northflank, Novita, OpenScale, Runloop, Sail, Superserve, Tensorlake, Upstash Box 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
+
5
+ Not every adapter has run against a real account yet. The [Prices](#prices) table shows which have passed the real-account check and which are built from docs and tested against mocks only.
4
6
 
5
7
  ```ts
6
8
  import { Router } from "@sandboxrouter/sdk";
7
9
 
8
- const router = new Router(); // reads provider keys from env
9
- await using sbx = await router.create({ vcpu: 2, memoryGb: 4 });
10
-
11
- const r = await sbx.exec("python3 -c 'print(6*7)'");
12
- console.log(r.stdout, "ran on", sbx.provider); // 42 ran on maritime
10
+ const router = new Router(); // uses whichever provider keys you've set
11
+ const sbx = await router.create({ vcpu: 2, memoryGb: 4 });
12
+ try {
13
+ const r = await sbx.exec("python3 -c 'print(6*7)'");
14
+ console.log(r.stdout, "ran on", sbx.provider);
15
+ } finally {
16
+ await sbx.kill();
17
+ }
13
18
  ```
14
19
 
15
- Change providers without changing code. If one returns a 429, the next one takes the sandbox.
20
+ Runs as plain JavaScript on Node 20+. (In TypeScript 5.2+, or Node 24+, `await using sbx = ...` kills the sandbox for you.)
21
+
22
+ Switch providers by changing which keys are set, not your code (custom images are the exception: each provider names its own). If one returns a 429, the next one takes the sandbox.
23
+
24
+ Site: [sandboxrouter.vercel.app](https://sandboxrouter.vercel.app) · npm: [`@sandboxrouter/sdk`](https://www.npmjs.com/package/@sandboxrouter/sdk)
16
25
 
17
26
  ## Why
18
27
 
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.
28
+ 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
29
 
21
30
  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
31
 
23
32
  - **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).
33
+ - **Routing.** `cheapest` (the default: lowest list price for your size) or `fastest` (lowest cold start you measured with `bench`), or name providers yourself.
25
34
  - **Fallback.** A failed create moves to the next candidate, and every attempt is recorded on `sbx.attempts`.
26
35
  - **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.
36
+ - **Your keys.** Your accounts, your bills. Nothing is proxied through a third party.
28
37
  - **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/).
38
+ - **Computers.** Linux and Windows desktops, Android phones and iPhone simulators for computer-use models, from 7 providers, routed by price per OS: actions, screenshots, and (where the provider has one) a link for a person to watch or take over.
39
+ - **Open benchmark.** `bench --share` produces a report; cold starts on the site come from reports submitted to [`results/`](results/).
40
+
41
+ ## Fair by construction
42
+
43
+ No provider gets a better slot, and none pays for placement.
44
+
45
+ - 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**.
46
+ - `fastest` uses only cold starts **you** measured with `bench`, then price.
47
+ - 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.
48
+ - Docker is never picked automatically, because it always "costs" $0 and isolates less than a cloud micro-VM. Name it to use it.
49
+
50
+ If a price is wrong, open a PR with the source link.
31
51
 
32
52
  ## Prices
33
53
 
34
- Estimated list price while running, 2 vCPU / 4 GB (`npx @sandboxrouter/sdk providers --vcpu 2 --memory 4`):
54
+ Estimated list price while running, 2 vCPU / 4 GB (`npx @sandboxrouter/sdk providers --vcpu 2 --memory 4`). Providers anyone can sign up for come first, cheapest first; waitlisted or closed sign-ups follow. The router uses the same price order, but only among providers you have keys for.
35
55
 
36
- | Provider | $/hr | Notes | Source |
37
- |---|---|---|---|
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) |
56
+ How to read it:
48
57
 
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.
58
+ - **Up to $X** means the provider bills CPU only while it's busy, so this is the price with every core busy. An agent that mostly waits on its model pays less.
59
+ - **Plan needed** is the monthly plan you must be on before the API will create sandboxes. "Credited" means the fee comes back as usage. "None" means a free tier or plain pay-as-you-go works. For a small project, the plan can cost more than the usage.
60
+ - **Real-account check** is the last time the 14-step [`check`](#compatibility) passed against a real account. "Not run yet" means the adapter is built from the provider's docs and tested against mocks only.
50
61
 
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.
62
+ | Provider | $/hr | Plan needed | Real-account check | Notes | Source |
63
+ |---|---|---|---|---|---|
64
+ | Agent37 | $0.007 | none | not run yet | Whole-minute billing: monthly shape total / 730 / 60; running estimate includes default disk (2 vCPU / 4 GB / 4 GB disk = $4.76/mo); sleeping/stopped disk $0.09/GB-mo; gVisor; $5 minimum top-up, wallet must hold one running day | [pricing](https://www.agent37.com/docs/agents-api/billing) |
65
+ | Boat | $0.018 | $20/mo (credited) | not run yet | 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) |
66
+ | Maritime | $0.022 | $20/mo (credited) | passed (2026-11-06) | Per second, no minimum runtime; running disk included; Starter $20/mo incl. $20 usage; paused disk $0.08/GB-month (default idle action deletes sandbox) | [pricing](https://maritime.sh/pricing) |
67
+ | Sailboxes (Sail Research) | up to $0.062 | none | passed (2026-11-06) | Per second on what the box uses (CPU time, RAM it holds, disk $0.0007/GiB-h), billed per minute; RAM counts the VM's file cache, up to the 16 GiB minimum limit, so file-heavy work can hold more than you asked for; paused/sleeping free; creation $0.01 at 2-4 vCPU on the free plan ($5/mo credit with a card), waived on Pro $250/mo | [pricing](https://docs.sailresearch.com/sailboxes-pricing.md) |
68
+ | Northflank | $0.067 | none | not run yet | Per second on Northflank Cloud, ephemeral disk included; sandboxes run as deployment services (microVMs) | [pricing](https://northflank.com/pricing) |
69
+ | Morph Cloud | $0.100 | none | not run yet | $0.05/MCU; MCU-hours = max(vCPU, RAM/4 GB, disk/16 GB); snapshots extra | [pricing](https://cloud.morph.so/web/pricing) |
70
+ | exe.dev | $0.105 | $15/mo | not run yet | Standalone reserved size, per second: $0.105/h per 2 vCPU / 4 GiB unit; stopped CPU/RAM free, retained disk separate; Personal from $15/mo (up to 16 vCPU / 32 GiB), Work $75/mo (32 / 64 without approval) | [pricing](https://exe.dev/docs/standalone-vms.md) |
71
+ | Novita AI Agent Sandbox | $0.117 | none | not run yet | Per second ($0.0000098/vCPU/s, $0.0000032/GiB/s); size comes from the template; paused = storage only | [pricing](https://docs.novita.ai/guides/sandbox-pricing) |
72
+ | Freestyle | $0.134 | none | not run yet | Per second on allocated fixed snapshot size; estimate includes disk at $0.000086/GiB-h; free monthly 200 vCPU-h, 400 GiB-h RAM, 60,000 GiB-h disk; Hobby $50/mo incl. $50 (max 8/16), Pro $500 (max 32/64); paused storage only | [pricing](https://www.freestyle.sh/docs/vms/pricing-and-limits) |
73
+ | Beam | $0.140 | $20/mo (credited) | not run yet | Per second, Performance tier (sandbox default); Developer plan $20/mo incl. $20 credits; GPUs billed separately | [pricing](https://www.beam.cloud/pricing) |
74
+ | CodeSandbox SDK (Together) | $0.149 | none | not run yet | Fixed tiers: Pico 2 vCPU / 1 GB $0.0743/h, Nano 2 / 4 $0.1486/h, up to 64 / 128 $4.7552/h; credits $0.01486, billed per started minute; Build free, 10 concurrent; Scale $170/mo incl. 1,100 credits | [pricing](https://docs.together.ai/docs/together-code-sandbox-legacy) |
75
+ | Fly.io Sprites | up to $0.164 | none | not run yet | Bills actual CPU and memory use; this is the full-use price; storage extra | [pricing](https://fly.io/sprites) |
76
+ | Blaxel | $0.166 | none | passed (2026-11-06) | Billed on memory only ($0.0000115/GB-s), CPU scales with memory (~2 GB per core) | [pricing](https://blaxel.ai/pricing) |
77
+ | HOPX | $0.166 | none | not run yet | Per second, plus storage; size comes from the template, not the request | [pricing](https://hopx.ai/pricing) |
78
+ | Superserve | $0.166 | maybe $20/mo (credited) | not run yet | Per second active; superserve/base fixed at 2 vCPU / 4 GiB / 8 GiB disk; retained storage $0.000108/GiB-h (shared template plus changed blocks); Developer $20/mo incl. $20, max 4/16 with team templates; recurring Free allowance not published on pricing page | [pricing](https://superserve.ai/pricing) |
79
+ | LangSmith Sandboxes | $0.189 | none | not run yet | Per second; memory must be 2 to 6 GiB per vCPU; storage $0.000123/GiB-h; 8 LSU ($8) of sandbox usage a month included; Developer plan max 10 sandboxes | [pricing](https://www.langchain.com/pricing) |
80
+ | Tensorlake | up to $0.200 | none | not run yet | Bills active CPU (floor 20% of allocation); this is the full-use price; Pro plan is lower | [pricing](https://www.tensorlake.ai/pricing) |
81
+ | Upstash Box | up to $0.200 | none | not run yet | Billed per active CPU core-hour only (memory and idle time free); estimate assumes every core of the box busy; fixed sizes 2/4, 4/8, 8/16 | [pricing](https://upstash.com/pricing/box) |
82
+ | Cloudflare Sandbox | up to $0.216 | $5/mo | not run yet | 10 ms billing on active CPU and allocated instance memory; full-CPU estimate excludes allocated disk ($0.000252/GB-h), Worker and Durable Objects charges; needs Workers Paid ($5/mo) and bridges/cloudflare | [pricing](https://developers.cloudflare.com/containers/platform/pricing/) |
83
+ | Amazon Bedrock AgentCore Code Interpreter | up to $0.217 | none | not run yet | Per second; CPU billed on actual use (I/O wait free), memory on peak; fixed 2 vCPU / 8 GB session, this is the 100%-CPU price | [pricing](https://aws.amazon.com/bedrock/agentcore/pricing/) |
84
+ | Modal | up to $0.238 | none | not run yet | Sandbox rates, per second ($0.00003942 per physical core = 2 vCPUs); GPUs extra; region pinning x1.15-1.75; Starter includes $30/mo credit | [pricing](https://modal.com/pricing) |
85
+ | AWS Lambda MicroVMs | $0.252 | none | not run yet | us-east-1 ARM; billed on the image's baseline size (2 GB per vCPU) while running, per-second burst up to 4x; suspended VMs pay snapshot storage; needs a MicroVM image you build once | [pricing](https://aws.amazon.com/lambda/pricing/) |
86
+ | Deno Sandbox | up to $0.300 | $20/mo | not run yet | Billed on CPU time, not wall clock (estimate assumes both vCPUs busy); fixed 2 vCPU, 0.75-4 GB, max 30 min lifetime; needs a paid Deno Deploy plan | [pricing](https://deno.com/sandbox) |
87
+ | Runloop | $0.317 | none | not run yet | Per second, plus disk; pause (suspend) is a Pro-plan feature | [pricing](https://www.runloop.ai/pricing) |
88
+ | Vercel Sandbox | up to $0.341 | none | not run yet | iad1; active CPU, provisioned RAM in minimum 1-minute increments (2 GB/vCPU); full-CPU estimate; Hobby free within quotas, Pro $20/mo usage credit; creations $0.60/million and retained snapshots $0.08/GB-month extra | [pricing](https://vercel.com/docs/vercel-sandbox/pricing) |
89
+ | InstaBox (sign-up waitlisted) | $0.017 | none | not run yet | Per second while running on fixed sizes (2/4, 4/8, 8/16); paused or stopped boxes bill only /data beyond the included disk ($0.00011/GiB-h); Pro $20/mo includes $20 credits; free plan: 1 box at 4/8 | [pricing](https://instabox.sh/docs/pricing.md) |
90
+ | Koyeb Sandboxes (not taking new accounts) | $0.058 | $29/mo | not run yet | Per second; fixed Standard instance sizes (1 vCPU + 1 GB = $0.0144/h); sandboxes need a paid plan (Pro $29/mo); public preview | [pricing](https://www.koyeb.com/docs/reference/instances) |
91
+ | Dedalus Machines (sign-up waitlisted) | $0.149 | none | not run yet | Per second while awake; sleeping machines bill storage only (10 GiB included on Hobby, 20 on Pro); Hobby plan max 4 vCPU / 16 GiB; private beta | [pricing](https://docs.dedaluslabs.ai/dcs/dm/pricing) |
92
+ | OpenScale Sandboxes | skipped (default size 2 vCPU / 1 GB; more needs image.openscale) | none | passed (2026-11-06) | Per second on allocated size; no platform fee or minimum; paused = free; size comes from the template (E2B-compatible API); Singapore only; needs a pay-as-you-go key | [pricing](https://openscale.so/sandboxes) |
93
+ | Azure Container Apps dynamic sessions | skipped (max 1 vCPU) | none | not run yet | Flat $0.03 per session-hour (built-in Shell pools), billed per allocated session in 1-hour increments, East US; needs a session pool you create once | [pricing](https://learn.microsoft.com/en-us/azure/container-apps/billing#dynamic-sessions) |
94
+ | Daytona | skipped (default size 1 vCPU / 1 GB; more needs image.daytona) | none | failed 1 step (2026-11-06) | Per second, plus disk above 5 GiB | [pricing](https://www.daytona.io/pricing) |
95
+ | Alibaba Cloud Agent Sandbox | skipped (max 2 GB memory) | none | not run yet | Per second; Default QoS, Hong Kong and regions outside mainland China; disk separately $0.00025308/GiB-h; code-interpreter-v1 template is 2 vCPU / 2 GiB with 10 GiB disk (E2B-compatible API) | [pricing](https://www.alibabacloud.com/help/en/agent-sandbox/product-overview/billing-overview) |
96
+ | E2B | skipped (default size 2 vCPU / 0.5 GB; more needs image.e2b) | none | passed (2026-11-06) | Per second; free Hobby tier ($100 one-time credit, 1-hour sessions, 20 at once); Pro $150/mo for longer sessions | [pricing](https://e2b.dev/pricing) |
97
+ | Docker (local) | free | none | passed (2026-11-06) | Runs on your own machine; free, but shares your kernel, so weaker isolation than a cloud micro-VM | [pricing](https://docs.docker.com/engine/) |
98
+
99
+ Checked 2026-11-04 to 2026-11-06.
52
100
 
53
101
  ## Install
54
102
 
55
103
  ```bash
56
104
  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
105
+ # then the SDK of each provider you use (Agent37, Azure, AgentCore, Boat, Cloudflare, Dedalus, exe.dev, Freestyle, InstaBox, Koyeb,
106
+ # Lambda, Maritime, Sail, Superserve, Upstash and Docker need none; the AWS ones need the signing packages below):
107
+ npm i e2b # E2B (also Alibaba Cloud and OpenScale)
108
+ npm i @daytonaio/sdk # Daytona
109
+ npm i modal # Modal
110
+ npm i @vercel/sandbox # Vercel Sandbox
111
+ npm i morphcloud # Morph Cloud
112
+ npm i @fly/sprites # Fly.io Sprites (its package asks for Node 24+)
113
+ npm i @blaxel/core # Blaxel
65
114
  npm i @tensorlakeai/tensorlake # Tensorlake
66
- npm i @runloop/api-client # Runloop
115
+ npm i @runloop/api-client # Runloop
116
+ npm i @hopx-ai/sdk # HOPX
117
+ npm i @northflank/js-client # Northflank
118
+ npm i @beamcloud/beam-js # Beam
119
+ npm i @codesandbox/sdk # CodeSandbox
120
+ npm i @deno/sandbox # Deno Sandbox
121
+ npm i novita-sandbox # Novita
122
+ # Alibaba Cloud uses the e2b package
123
+ npm i @smithy/signature-v4 @aws-crypto/sha256-js @aws-sdk/credential-provider-node # AgentCore, Lambda MicroVMs
67
124
  ```
68
125
 
69
- Set the keys for the providers you want. Unconfigured providers are skipped.
126
+ 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).
127
+
128
+ Three providers need a one-time setup in your cloud account, described at the top of each adapter: Azure needs a Shell session pool ([`src/providers/azure.ts`](src/providers/azure.ts)), Lambda MicroVMs need an image built from the bundled server ([`src/providers/lambda.ts`](src/providers/lambda.ts), `writeLambdaMicrovmBundle()`), and AgentCore is opt-in: set `AGENTCORE_CODE_INTERPRETER_ID=aws.codeinterpreter.v1` so plain AWS credentials don't route work there by surprise.
129
+
130
+ Set the keys for the providers you want. Providers without keys are skipped.
70
131
 
71
132
  | Provider | Env |
72
133
  |---|---|
73
- | Maritime | `MARITIME_API_KEY` (an `mk_` key with the `sandboxes` scope: `maritime keys create`) |
74
- | E2B | `E2B_API_KEY` |
134
+ | Agent37 | `AGENT37_API_KEY` |
135
+ | Alibaba Cloud Agent Sandbox | `ALIBABA_SANDBOX_API_KEY` |
136
+ | Amazon Bedrock AgentCore Code Interpreter | `AGENTCORE_CODE_INTERPRETER_ID` + `AWS_REGION` + `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY`, or `AGENTCORE_CODE_INTERPRETER_ID` + `AWS_REGION` + `AWS_PROFILE` |
137
+ | AWS Lambda MicroVMs | `AWS_REGION` + `LAMBDA_MICROVM_IMAGE` + `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY`, or `AWS_REGION` + `LAMBDA_MICROVM_IMAGE` + `AWS_PROFILE` |
138
+ | Azure Container Apps dynamic sessions | `AZURE_SESSIONS_POOL_ENDPOINT` + `AZURE_SESSIONS_TOKEN`, or `AZURE_SESSIONS_POOL_ENDPOINT` + `AZURE_TENANT_ID` + `AZURE_CLIENT_ID` + `AZURE_CLIENT_SECRET` |
139
+ | Beam | `BEAM_TOKEN` + `BEAM_WORKSPACE_ID` |
140
+ | Blaxel | `BL_API_KEY` + `BL_WORKSPACE` |
141
+ | Boat | `BOAT_API_KEY` |
142
+ | Cloudflare Sandbox | `CLOUDFLARE_SANDBOX_URL` + `CLOUDFLARE_SANDBOX_TOKEN` |
143
+ | CodeSandbox SDK (Together) | `CSB_API_KEY` |
75
144
  | 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` |
145
+ | Dedalus Machines | `DEDALUS_API_KEY` |
146
+ | Deno Sandbox | `DENO_DEPLOY_TOKEN` |
147
+ | Docker (local) | none: Docker running locally |
148
+ | E2B | `E2B_API_KEY` |
149
+ | exe.dev | `EXE_TOKEN` |
79
150
  | Fly.io Sprites | `SPRITES_TOKEN` |
80
- | Blaxel | `BL_API_KEY` + `BL_WORKSPACE` (optional `BL_REGION`, default `us-pdx-1`) |
81
- | Tensorlake | `TENSORLAKE_API_KEY` |
151
+ | Freestyle | `FREESTYLE_API_KEY` |
152
+ | HOPX | `HOPX_API_KEY` |
153
+ | InstaBox | `INSTABOX_API_KEY` |
154
+ | Koyeb Sandboxes | `KOYEB_API_TOKEN` |
155
+ | LangSmith Sandboxes | `LANGSMITH_API_KEY` |
156
+ | Maritime | `MARITIME_API_KEY` |
157
+ | Modal | `MODAL_TOKEN_ID` + `MODAL_TOKEN_SECRET` |
158
+ | Morph Cloud | `MORPH_API_KEY` |
159
+ | Northflank | `NORTHFLANK_API_TOKEN` + `NORTHFLANK_PROJECT_ID` |
160
+ | Novita AI Agent Sandbox | `NOVITA_API_KEY` |
161
+ | OpenScale Sandboxes | `OPENSCALE_API_KEY` |
82
162
  | Runloop | `RUNLOOP_API_KEY` |
163
+ | Sailboxes (Sail Research) | `SAIL_API_KEY` |
164
+ | Superserve | `SUPERSERVE_API_KEY` |
165
+ | Tensorlake | `TENSORLAKE_API_KEY` |
166
+ | Upstash Box | `UPSTASH_BOX_API_KEY` |
167
+ | Vercel Sandbox | `VERCEL_OIDC_TOKEN`, or `VERCEL_TOKEN` + `VERCEL_TEAM_ID` + `VERCEL_PROJECT_ID` |
83
168
 
84
169
  ## Library
85
170
 
@@ -93,10 +178,17 @@ const a = await router.create({ vcpu: 8, memoryGb: 16 }, { strategy: "cheapest"
93
178
  const b = await router.create({ gpu: "A10G", image: { modal: "pytorch/pytorch:latest" } });
94
179
 
95
180
  // Pin providers in order and turn fallback off
96
- const c = await router.create({}, { provider: ["maritime", "e2b"], fallback: false });
181
+ const c = await router.create({}, { provider: ["e2b", "daytona"], fallback: false });
182
+
183
+ // Fallback moves on when a provider is down: a timeout, a network error, 429 or 5xx, and a create
184
+ // that takes longer than createTimeoutSeconds (default 120; a sandbox that turns up later is
185
+ // killed, or left in the ledger for `gc`). A setup error (400, 401, 402, 403, 404, 422: a bad key,
186
+ // no billing, a bad image) stops instead, so a mistake isn't hidden by a pricier provider.
187
+ // fallback: "any" falls back on every error.
188
+ const e = await router.create({}, { fallback: "any", createTimeoutSeconds: 300 });
97
189
 
98
190
  // Per-provider images: each provider names its templates differently
99
- const d = await router.create({ image: { maritime: "python", e2b: "code-interpreter-v1", modal: "python:3.13-slim" } });
191
+ const d = await router.create({ image: { e2b: "code-interpreter-v1", modal: "python:3.13-slim" } });
100
192
 
101
193
  // Files
102
194
  await a.writeFile("/tmp/data.csv", "x,y\n1,2\n");
@@ -122,7 +214,19 @@ await a.kill();
122
214
 
123
215
  ## Computers (desktops and phones)
124
216
 
125
- For computer-use agents: a machine with a screen that your model drives with clicks and keys. Maritime provides Linux desktops, Windows desktops and Android phones on the same key. Actions use the Anthropic computer-use shape.
217
+ For computer-use agents: a machine with a screen that your model drives with clicks and keys. `router.computer({ os })` starts one on the cheapest provider for that OS among those you have keys for, ties alphabetical, with fallback. Actions use the Anthropic computer-use shape on every provider.
218
+
219
+ | Provider | Linux | Windows | Android | iOS | Default size | Live view |
220
+ |---|---|---|---|---|---|---|
221
+ | AWS WorkSpaces (agent access) | $0.138 | $0.150 | | | 2 vCPU · 4 GB | watch only |
222
+ | Daytona | $0.067 | | | | 1 vCPU · 1 GB | watch/control link |
223
+ | E2B Desktop | $0.533 | | | | 8 vCPU · 8 GB | watch/control link |
224
+ | Limrun | | | $3.00 | $6.00 | phone emulator / iPhone simulator | interactive link |
225
+ | Maritime | $0.120 | $0.180 | $0.150 | | 2 vCPU · 4 GB | view-only and takeover links |
226
+ | Mobilerun | | | $1.80 | | cloud phone | embedded player |
227
+ | Orgo | $0.055 | plan | | | 2 vCPU · 8 GB | VNC (full control) |
228
+
229
+ Per hour while awake, from each provider's pricing page (sources in [`src/computers.ts`](src/computers.ts)). Sizes differ, so compare price and size together. Only Maritime and AWS enforce view-only on the server; on the others a "watch" link can be turned into control, so treat it like a password. Mobilerun and AWS have no shell; Limrun's iOS has no shell. AWS needs a fleet and stack you provision once (steps at the top of [`src/providers/workspaces.ts`](src/providers/workspaces.ts)).
126
230
 
127
231
  ```ts
128
232
  const desk = await router.computer({ os: "linux", externalUserId: "user_42" }); // same id, same desktop
@@ -137,7 +241,7 @@ const { url } = await desk.viewer("control", { reason: "Please sign in" });
137
241
 
138
242
  ```bash
139
243
  npx @sandboxrouter/sdk desktop --os linux --user user_42 --control # prints a takeover link
140
- npx @sandboxrouter/sdk screenshot maritime:<id> --out screen.png
244
+ npx @sandboxrouter/sdk screenshot <provider>:<id> --out screen.png
141
245
  ```
142
246
 
143
247
  ## CLI
@@ -146,19 +250,92 @@ npx @sandboxrouter/sdk screenshot maritime:<id> --out screen.png
146
250
  npx @sandboxrouter/sdk providers # price table + which keys are set
147
251
  npx @sandboxrouter/sdk route --gpu T4 # dry run
148
252
  npx @sandboxrouter/sdk run "uname -a && nproc" # start, run, print, kill
149
- npx @sandboxrouter/sdk run "pytest -q" --provider e2b,maritime --keep
253
+ npx @sandboxrouter/sdk run "pytest -q" --provider e2b,daytona --keep
150
254
  npx @sandboxrouter/sdk bench --runs 5 # cold start + exec latency table
151
255
  npx @sandboxrouter/sdk bench --share --label "us-east-1" # also write a report for results/
152
256
  npx @sandboxrouter/sdk leaderboard results --out leaderboard.html
153
257
  npx @sandboxrouter/sdk serve --port 8787 # HTTP API
258
+ npx @sandboxrouter/sdk ls # sandboxes started here that were never killed
259
+ npx @sandboxrouter/sdk gc # kill the ones past their lifetime (--all: every one)
260
+ ```
261
+
262
+ ### Cleaning up leaks
263
+
264
+ If a script crashes before `kill()`, its sandbox keeps billing. Every router keeps a ledger of the sandboxes it starts in `~/.sandboxrouter/sandboxes.jsonl` (set `SANDBOXROUTER_HOME` to move it, `SANDBOXROUTER_LEDGER=off` or `new Router({ ledger: false })` to turn it off), and `kill()` closes the entry.
265
+
266
+ ```bash
267
+ sandboxrouter ls # id, age, lifetime left, cost so far
268
+ sandboxrouter gc --dry-run # what would be killed
269
+ sandboxrouter gc # kill sandboxes past their requested lifetime
270
+ sandboxrouter gc --all # kill every open one (end of a CI job)
271
+ sandboxrouter gc --older-than 60 # kill those up for an hour or more
154
272
  ```
155
273
 
274
+ A sandbox the provider already stopped is just dropped from the ledger. `sandboxrouter run` kills its sandbox on Ctrl-C. The ledger only knows sandboxes started on this machine (or wherever `SANDBOXROUTER_HOME` points), so run `gc --all` at the end of CI jobs. Every adapter hands `timeoutMinutes` to the provider, which stops, deletes or pauses the sandbox when it runs out (the table under "How each adapter maps the spec" says which), except Fly.io Sprites, which has none (an idle sprite stops billing CPU but keeps its storage), and exe.dev, where the VM powers itself off when the lifetime runs out (best effort; a stopped VM bills disk until it's deleted).
275
+
156
276
  ### Bench
157
277
 
158
278
  `bench` starts real sandboxes on every configured provider, measures time to a usable sandbox and the round trip of `echo ok`, kills them, and prints a markdown leaderboard. Results are saved to `~/.sandboxrouter/bench.json`, which is what `strategy: "fastest"` routes on. Latency is measured from wherever you run it, so network distance counts.
159
279
 
160
280
  `--share` writes a JSON report (timings, your label, Node version and platform; never keys, env or error text). Open a PR adding it to [`results/`](results/). `sandboxrouter leaderboard` builds one static HTML page from every report there, taking the median of each submission's medians so one submitter can't dominate. Host the page anywhere static files go.
161
281
 
282
+ ### Real cost
283
+
284
+ Prices in the catalog are list prices. `cost` checks them against real bills: it starts one sandbox per provider at a known size, records the size the sandbox really has (`nproc` and `MemTotal`), keeps every core busy for a fixed time (some providers bill active CPU only), kills it, and predicts the cost from list price × wall time. `cost settle` then reads what the provider charged for that sandbox, from Daytona's usage analytics, Blaxel's metrics API and Maritime's metered usage. E2B and OpenScale have no billing API, so their bill is read from the dashboard. Bills post late on some providers (Daytona can take up to 48 h), so rerun `settle` until every row has one.
285
+
286
+ ```bash
287
+ sandboxrouter cost run --minutes 10 # every configured cloud provider, 2 vCPU / 4 GB
288
+ sandboxrouter cost settle results/cost/cost-2026-11-06-16-58.json
289
+ ```
290
+
291
+ ### Compatibility
292
+
293
+ `check` runs the same 14 steps against a real account: create; stdout, stderr and exit code; env and cwd; UTF-8 output; live streaming; a binary file round-trip; a 1 MB file; reading a missing file is an error; files written by `writeFile` are visible to commands and the other way round; a command timeout rejects with the contract's error; the timed-out command was really stopped; pause and resume; kill; and the killed sandbox is really gone.
294
+
295
+ ```bash
296
+ sandboxrouter check --provider koyeb # one provider
297
+ sandboxrouter check --all --save # every configured provider; writes results/conformance/<id>.json
298
+ ```
299
+
300
+ It prints one line per step and exits 1 if any fails. `checkProvider(router, id)` does the same from code. Saved runs make this table (`node --import tsx scripts/readme-tables.ts --write`), and a nightly workflow re-runs it with the repo's provider keys, committing only when a result changes (so the date is when the current result was first seen). Every adapter also has mock-API tests in `npm test`; this table is only real runs.
301
+
302
+ | Provider | Real-account check (14 steps) | Result since |
303
+ |---|---|---|
304
+ | Agent37 | not run yet | |
305
+ | Boat | not run yet | |
306
+ | Maritime | 14/14 passed | 2026-11-06, v0.2.0 |
307
+ | Sailboxes (Sail Research) | 14/14 passed | 2026-11-06, v0.3.0 |
308
+ | Northflank | not run yet | |
309
+ | Morph Cloud | not run yet | |
310
+ | exe.dev | not run yet | |
311
+ | Novita AI Agent Sandbox | not run yet | |
312
+ | Freestyle | not run yet | |
313
+ | Beam | not run yet | |
314
+ | CodeSandbox SDK (Together) | not run yet | |
315
+ | Fly.io Sprites | not run yet | |
316
+ | Blaxel | 13/13 passed; n/a: pause and resume | 2026-11-06, v0.2.0 |
317
+ | HOPX | not run yet | |
318
+ | Superserve | not run yet | |
319
+ | LangSmith Sandboxes | not run yet | |
320
+ | Tensorlake | not run yet | |
321
+ | Upstash Box | not run yet | |
322
+ | Cloudflare Sandbox | not run yet | |
323
+ | Amazon Bedrock AgentCore Code Interpreter | not run yet | |
324
+ | Modal | not run yet | |
325
+ | AWS Lambda MicroVMs | not run yet | |
326
+ | Deno Sandbox | not run yet | |
327
+ | Runloop | not run yet | |
328
+ | Vercel Sandbox | not run yet | |
329
+ | InstaBox | not run yet | |
330
+ | Koyeb Sandboxes | not run yet | |
331
+ | Dedalus Machines | not run yet | |
332
+ | OpenScale Sandboxes | 14/14 passed | 2026-11-06, v0.2.0 |
333
+ | Azure Container Apps dynamic sessions | not run yet | |
334
+ | Daytona | 0/14 passed; failed: create | 2026-11-06, v0.2.0 |
335
+ | Alibaba Cloud Agent Sandbox | not run yet | |
336
+ | E2B | 14/14 passed | 2026-11-06, v0.2.0 |
337
+ | Docker (local) | 14/14 passed | 2026-11-06, v0.2.0 |
338
+
162
339
  ## HTTP API
163
340
 
164
341
  ```bash
@@ -169,7 +346,7 @@ SANDBOXROUTER_TOKEN=secret npx @sandboxrouter/sdk serve
169
346
  |---|---|---|
170
347
  | `GET` | `/v1/providers` | |
171
348
  | `POST` | `/v1/route` | same as create; returns the candidate order |
172
- | `POST` | `/v1/sandboxes` | `{ vcpu, memoryGb, gpu, env, timeoutMinutes, image, metadata, provider, strategy, fallback, require }` |
349
+ | `POST` | `/v1/sandboxes` | `{ vcpu, memoryGb, gpu, env, timeoutMinutes, image, metadata, provider, strategy, fallback, createTimeoutSeconds, require }` |
173
350
  | `POST` | `/v1/sandboxes/:id/exec` | `{ cmd, cwd, env, timeoutSeconds, stream }`. With `stream: true` the answer is `text/event-stream`: `stdout` and `stderr` events, then `result` |
174
351
  | `POST` | `/v1/sandboxes/:id/pause` | |
175
352
  | `PUT` | `/v1/sandboxes/:id/files?path=` | raw bytes |
@@ -187,10 +364,10 @@ SANDBOXROUTER_TOKEN=secret npx @sandboxrouter/sdk serve
187
364
  ```bash
188
365
  curl -s localhost:8787/v1/sandboxes -H "Authorization: Bearer secret" \
189
366
  -d '{"vcpu":2,"strategy":"cheapest"}'
190
- # {"id":"maritime:sbx-k3m7q2ab","provider":"maritime","estimatedHourlyUsd":0.022,"attempts":[...]}
367
+ # {"id":"e2b:iz5sb5a4bt3pmjx7c5ms2","provider":"e2b","estimatedHourlyUsd":0.1656,"attempts":[...]}
191
368
  ```
192
369
 
193
- Provider keys stay on the server. Clients only hold `SANDBOXROUTER_TOKEN`. It binds to `127.0.0.1` by default.
370
+ Provider keys stay on the server. Clients only hold `SANDBOXROUTER_TOKEN`. It binds to `127.0.0.1` by default. Without a token it only starts on a loopback address with CORS off: `--cors` (any browser page could otherwise start sandboxes on your keys) and a public `--host` both need `SANDBOXROUTER_TOKEN`.
194
371
 
195
372
  ### Python
196
373
 
@@ -214,23 +391,51 @@ with sr.create(vcpu=2, memory_gb=4, strategy="cheapest") as sbx:
214
391
  | Modal | `cpu`, `memoryMiB`, `gpu` | `timeoutMs` | registry tag | no | Uses Modal app `sandboxrouter` (override with `SANDBOXROUTER_MODAL_APP`). |
215
392
  | Vercel | `vcpus` (2 GB each) | `timeout` | `runtime` | no | Max 8 vCPU. |
216
393
  | Morph | via a cached base snapshot per shape | `ttlSeconds` | snapshot id | yes (memory + disk) | First create of a new shape builds a snapshot. Files over SSH. |
217
- | Fly.io Sprites | `cpus`, `ramMB` (may be ignored) | none: `kill()` deletes | no | no | Sprites idle for free on their own. |
218
- | Blaxel | memory (CPU follows) | `ttl` | image | yes (archive, disk only) | Idle sandboxes go to free standby on their own. |
394
+ | Fly.io Sprites | `cpus`, `ramMB` (may be ignored) | none: `kill()` or `gc` deletes | no | no | Sprites idle for free on their own. |
395
+ | Blaxel | memory (CPU follows) | `ttl` | image | no (archiving is a per-workspace preview) | Idle sandboxes go to free standby on their own. |
219
396
  | Tensorlake | `cpus`, `memoryMb` | idle timeout | image | yes (memory + disk) | Output is line-based. |
220
397
  | Runloop | snapped to Runloop's custom sizes | `keep_alive_time_seconds` (max 48 h) | blueprint | yes (Pro plan) | Commands start in the home directory. |
398
+ | HOPX | set by the template | timeout | template | yes (memory + disk) | Size requests aren't applied, so the router only sends default-size work. |
399
+ | 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. |
400
+ | 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)). |
401
+ | 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. |
402
+ | exe.dev | `--cpu`, `--memory` (2 GiB per vCPU max) | the VM powers itself off (best effort); `kill()` or `gc` deletes | `--image` | no | Plain REST over exe.dev's `/exec`; long commands run detached and are polled. |
403
+ | Agent37 | smallest of 4 fixed shapes that fits (max 4 vCPU) | auto-sleep after idle (5 min to 24 h) | template (`agent37-base` default) | yes (sleep: memory + disk) | Plain REST; no streaming; files move as base64 over exec in chunks. |
404
+ | Alibaba Cloud | set by the template | `timeoutMs` (max 24 h) | template | no (allowlist only) | E2B-compatible API via the `e2b` SDK; region from `ALIBABA_SANDBOX_REGION` (default ap-southeast-1). |
405
+ | AgentCore | fixed 2 vCPU / 8 GB | `sessionTimeoutSeconds` (max 8 h) | no | no | SigV4, plain fetch; files move as base64 over commands. Opt-in. |
406
+ | Azure sessions | fixed by the pool | pool cooldown (5 to 60 min idle) | no | no | Shell session pool; output over 4 KB is fetched from files. |
407
+ | Beam | `cpu`, `memory` | `keepWarmSeconds` | registry image | no | Uses Beam app `sandboxrouter` (override with `SANDBOXROUTER_BEAM_APP`). |
408
+ | CodeSandbox | smallest of 7 fixed tiers that fits | hibernation timeout | template | yes (hibernate: memory + disk) | Exec output goes through files because the SDK's terminal merges streams. |
409
+ | Dedalus Machines | `vcpu`, `memory_mib` | autosleep after idle (compute stops, disk kept) | no | yes (sleep/wake) | Plain REST; streams by polling the events feed; files move as base64 over stdin/stdout. Private beta. |
410
+ | Deno | fixed 2 vCPU, 0.75 to 4 GB | up to 30 min | snapshot or volume | no | WebSocket RPC via `@deno/sandbox`. |
411
+ | Freestyle | smallest Ubuntu snapshot that fits | `ttlSeconds` | snapshot | yes (memory + disk) | Plain REST; long commands run as background requests and are polled. |
412
+ | InstaBox | smallest fixed size that fits (2/4, 4/8, 8/16; free plan falls back to 4/8) | `deleteAfterSeconds` (60 s to 24 h) | template or snapshot (`sn_...`) | yes (suspend: memory + disk) | Plain REST; exec via `bash -lc`, max 600 s, SSE streaming; files API raw bytes. Pauses itself after 30 idle min. |
413
+ | Koyeb | smallest Standard instance that fits | auto-delete delay (max 24 h) | any Docker image | no | Plain REST; streams over SSE. Public preview. |
414
+ | Lambda MicroVMs | image baseline (2 GB per vCPU) | `maximumDurationInSeconds` | your MicroVM image | yes (suspend/resume) | Runs the bundled in-VM server for exec and files. |
415
+ | Novita | set by the template | `timeoutMs` | template | yes (memory + disk) | `novita-sandbox` SDK (an E2B fork). |
416
+ | Sail | `vcpu_count`; memory is a limit of at least 16 GiB | `max_lifetime_seconds` (counts paused time) | `debian` (default) or `devbox` base | yes (memory + disk) | Plain REST, no SDK. Streams NDJSON. Billed on what the box uses, not the limits. A paused box resumes on the next call. |
417
+ | Upstash Box | smallest of 3 fixed sizes that fits | TTL (max 3 days) | runtime name (node, python, ...) | no | Plain REST; billed on active CPU only. |
418
+ | 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
419
 
222
420
  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
421
 
224
422
  ## Roadmap
225
423
 
226
- - [x] More providers: Morph, Fly.io Sprites, Blaxel, Tensorlake, Runloop
227
- - [x] Desktops and phones (Maritime: Linux, Windows, Android)
424
+ - [x] More providers: Morph, Fly.io Sprites, Blaxel, Tensorlake, Runloop, HOPX, Northflank, Boat, exe.dev, Cloudflare (via a bridge Worker), Docker (local)
425
+ - [x] More providers: Koyeb, Freestyle, Deno, Beam, CodeSandbox, Upstash Box, Novita, Alibaba Cloud, Azure Container Apps sessions, AWS Bedrock AgentCore, AWS Lambda MicroVMs
426
+ - [x] YC sandbox startups: Agent37 (F26), Dedalus Machines (S25), InstaBox (P26), OpenScale (S22), Superserve (F26)
427
+ - [x] Sail Research Sailboxes
428
+ - [ ] More E2B-compatible clouds (PPIO, UCloud, Baidu) and Google Agent Engine sandboxes
429
+ - [x] Neutral routing: price decides, ties alphabetical, no privileged slot
430
+ - [x] Desktops and phones from 7 providers (Linux, Windows, Android, iOS), routed by price per OS
431
+ - [ ] Windows 365 for Agents and Corellium as opt-in computer providers
228
432
  - [x] Streaming exec output
229
433
  - [x] Pause/resume where providers support it
230
434
  - [x] Python client
231
435
  - [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
- - [ ] Computers on more providers (E2B Desktop, Daytona computer use)
436
+ - [x] Leak cleanup: a local ledger, `ls` and `gc`
437
+ - [x] One 14-step real-account check for every adapter, with a compatibility table and a nightly run
438
+ - [ ] Every adapter run against a real account (see the Compatibility table)
234
439
  - [ ] Snapshots and forks as a routed operation
235
440
  - [ ] A hosted copy of the leaderboard page
236
441
 
@@ -250,12 +455,16 @@ SANDBOXROUTER_TOKEN=secret npx @sandboxrouter/sdk serve --cors http://localhost:
250
455
 
251
456
  ```bash
252
457
  npm i
253
- npm test # router, HTTP server, leaderboard, Maritime sandboxes + computers against contract mocks
254
- npm run test:py # Python client against the real HTTP server
458
+ npm test # router, HTTP server, and every adapter against a local mock of its API
459
+ npm run test:py # Python client against the real HTTP server
255
460
  npm run build
256
461
  npm run dev -- providers
462
+ npm run price:check # compare every provider's pricing page with the last check
463
+ node --import tsx scripts/readme-tables.ts --write # regenerate the README's Prices, Env and Compatibility tables
257
464
  ```
258
465
 
259
- Adding a provider means implementing `Provider` from [`src/types.ts`](src/types.ts) and adding a catalog entry with a pricing source link.
466
+ Prices are list prices copied from each provider's public pricing page, with the date they were checked. A GitHub workflow runs `price:check` every Monday and opens an issue when a pricing page changes or a price is more than 45 days old; a person then updates [`src/catalog.ts`](src/catalog.ts).
467
+
468
+ See [CONTRIBUTING.md](CONTRIBUTING.md) to test a provider, fix a price or add one, [SECURITY.md](SECURITY.md) to report a vulnerability, and [CHANGELOG.md](CHANGELOG.md) for releases.
260
469
 
261
- MIT
470
+ [MIT](LICENSE)
package/dist/bench.js CHANGED
@@ -1,4 +1,4 @@
1
- import { CATALOG, hourlyCost } from "./catalog.js";
1
+ import { CATALOG, fixedSize, hourlyCost } from "./catalog.js";
2
2
  import { loadBench, median, saveBench } from "./bench-store.js";
3
3
  import { Router } from "./router.js";
4
4
  /**
@@ -23,7 +23,7 @@ export async function bench(opts) {
23
23
  let box;
24
24
  try {
25
25
  const t0 = performance.now();
26
- box = await router.create(spec, { provider: id, fallback: false });
26
+ box = await router.create({ ...fixedSize(id), ...spec }, { provider: id, fallback: false });
27
27
  createMs.push(Math.round(performance.now() - t0));
28
28
  const t1 = performance.now();
29
29
  const r = await box.exec("echo ok");
@@ -46,7 +46,7 @@ export async function bench(opts) {
46
46
  failures,
47
47
  medianCreateMs: median(createMs),
48
48
  medianExecMs: median(execMs),
49
- hourlyUsd: hourlyCost(id, spec),
49
+ hourlyUsd: hourlyCost(id, { ...fixedSize(id), ...spec }),
50
50
  lastError,
51
51
  });
52
52
  }
@@ -57,7 +57,7 @@ export function benchMarkdown(rows) {
57
57
  const name = (id) => CATALOG.find((c) => c.id === id).name;
58
58
  const ms = (n) => (n === undefined ? "n/a" : `${n} ms`);
59
59
  const lines = [
60
- "| # | Provider | Cold start (median) | Exec round trip | $/hr (2 vCPU, 4 GB) | Success |",
60
+ "| # | Provider | Cold start (median) | Exec round trip | $/hr (list, size run) | Success |",
61
61
  "|---|---|---|---|---|---|",
62
62
  ...rows.map((r, i) => `| ${i + 1} | ${name(r.provider)} | ${ms(r.medianCreateMs)} | ${ms(r.medianExecMs)} | $${r.hourlyUsd.toFixed(3)} | ${r.runs - r.failures}/${r.runs} |`),
63
63
  ];