@opencoredev/sandbox-sdk 0.2.0 → 1.0.0-beta.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 (166) hide show
  1. package/CHANGELOG.md +32 -11
  2. package/README.md +350 -25
  3. package/dist/ai.d.ts +5 -0
  4. package/dist/ai.js +1 -0
  5. package/dist/chunk-59t3rqja.js +2 -0
  6. package/dist/chunk-cwx6cc31.js +2 -0
  7. package/dist/chunk-exv1kf89.js +60 -0
  8. package/dist/chunk-gc8zdxyb.js +2 -0
  9. package/dist/chunk-hjzhc3qg.js +40 -0
  10. package/dist/chunk-md6fmprc.js +1 -0
  11. package/dist/chunk-t5vr9nd8.js +8 -0
  12. package/dist/chunk-v6y7eqxw.js +27 -0
  13. package/dist/chunk-yed8mvs7.js +2 -0
  14. package/dist/chunk-yrsb6hws.js +5 -0
  15. package/dist/chunk-z9rjzn28.js +2 -0
  16. package/dist/errors.d.ts +60 -0
  17. package/dist/files.d.ts +57 -0
  18. package/dist/git.d.ts +28 -0
  19. package/dist/handle.d.ts +4 -0
  20. package/dist/index.d.ts +17 -0
  21. package/dist/index.js +1 -0
  22. package/dist/keys.d.ts +5 -0
  23. package/dist/logs.d.ts +8 -0
  24. package/dist/mastra.d.ts +43 -0
  25. package/dist/mastra.js +1 -0
  26. package/dist/memory.d.ts +34 -0
  27. package/dist/memory.js +11 -0
  28. package/dist/openai.d.ts +39 -0
  29. package/dist/openai.js +1 -0
  30. package/dist/peer.d.ts +5 -0
  31. package/dist/processes.d.ts +71 -0
  32. package/dist/provider.d.ts +138 -0
  33. package/dist/provider.js +0 -0
  34. package/dist/providers/agentos/errors.d.ts +4 -0
  35. package/dist/providers/agentos/index.d.ts +11 -0
  36. package/dist/providers/agentos/index.js +2 -0
  37. package/dist/providers/boat/index.d.ts +23 -0
  38. package/dist/providers/boat/index.js +1 -0
  39. package/dist/providers/boat-api/errors.d.ts +6 -0
  40. package/dist/providers/boat-api/exec.d.ts +11 -0
  41. package/dist/providers/boat-api/index.d.ts +22 -0
  42. package/dist/providers/boxd/errors.d.ts +3 -0
  43. package/dist/providers/boxd/execute.d.ts +3 -0
  44. package/dist/providers/boxd/guest.d.ts +14 -0
  45. package/dist/providers/boxd/index.d.ts +23 -0
  46. package/dist/providers/boxd/index.js +85 -0
  47. package/dist/providers/boxd/transfers.d.ts +19 -0
  48. package/dist/providers/daytona/buffered.d.ts +3 -0
  49. package/dist/providers/daytona/errors.d.ts +3 -0
  50. package/dist/providers/daytona/exec.d.ts +14 -0
  51. package/dist/providers/daytona/index.d.ts +24 -0
  52. package/dist/providers/daytona/index.js +88 -0
  53. package/dist/providers/e2b/errors.d.ts +3 -0
  54. package/dist/providers/e2b/index.d.ts +15 -0
  55. package/dist/providers/e2b/index.js +1 -0
  56. package/dist/providers/freestyle/deadline.d.ts +4 -0
  57. package/dist/providers/freestyle/errors.d.ts +16 -0
  58. package/dist/providers/freestyle/execute.d.ts +12 -0
  59. package/dist/providers/freestyle/index.d.ts +14 -0
  60. package/dist/providers/freestyle/index.js +133 -0
  61. package/dist/providers/freestyle/wire.d.ts +10 -0
  62. package/dist/providers/local/docker.d.ts +32 -0
  63. package/dist/providers/local/index.d.ts +17 -0
  64. package/dist/providers/local/index.js +2 -0
  65. package/dist/providers/railway/errors.d.ts +4 -0
  66. package/dist/providers/railway/index.d.ts +24 -0
  67. package/dist/providers/railway/index.js +1 -0
  68. package/dist/providers/sandboxer/index.d.ts +33 -0
  69. package/dist/providers/sandboxer/index.js +1 -0
  70. package/dist/providers/superserve/bootstrap.d.ts +11 -0
  71. package/dist/providers/superserve/errors.d.ts +3 -0
  72. package/dist/providers/superserve/execute.d.ts +13 -0
  73. package/dist/providers/superserve/index.d.ts +23 -0
  74. package/dist/providers/superserve/index.js +126 -0
  75. package/dist/providers/tenki/deadline.d.ts +2 -0
  76. package/dist/providers/tenki/errors.d.ts +10 -0
  77. package/dist/providers/tenki/exec.d.ts +4 -0
  78. package/dist/providers/tenki/files.d.ts +3 -0
  79. package/dist/providers/tenki/index.d.ts +28 -0
  80. package/dist/providers/tenki/index.js +172 -0
  81. package/dist/providers/tenki/ready.d.ts +7 -0
  82. package/dist/providers/tenki/stream.d.ts +3 -0
  83. package/dist/providers/upstash/buffered.d.ts +3 -0
  84. package/dist/providers/upstash/errors.d.ts +3 -0
  85. package/dist/providers/upstash/exec.d.ts +18 -0
  86. package/dist/providers/upstash/files.d.ts +15 -0
  87. package/dist/providers/upstash/index.d.ts +38 -0
  88. package/dist/providers/upstash/index.js +111 -0
  89. package/dist/providers/vercel/errors.d.ts +3 -0
  90. package/dist/providers/vercel/index.d.ts +14 -0
  91. package/dist/providers/vercel/index.js +1 -0
  92. package/dist/providers/vercel/provider.d.ts +10 -0
  93. package/dist/providers/vercel/transport.d.ts +40 -0
  94. package/dist/run.d.ts +41 -0
  95. package/dist/testing.d.ts +17 -0
  96. package/dist/testing.js +6 -0
  97. package/dist/tool-security.d.ts +9 -0
  98. package/dist/tools.d.ts +117 -0
  99. package/dist/tools.js +1 -0
  100. package/dist/types.d.ts +189 -0
  101. package/package.json +164 -106
  102. package/dist/agentos-CbrZW7R0.mjs +0 -390
  103. package/dist/agentos-CbrZW7R0.mjs.map +0 -1
  104. package/dist/ai/harness.d.mts +0 -16
  105. package/dist/ai/harness.mjs +0 -69
  106. package/dist/ai/harness.mjs.map +0 -1
  107. package/dist/ai/index.d.mts +0 -54
  108. package/dist/ai/index.mjs +0 -4
  109. package/dist/ai-DeFa8yPk.mjs +0 -243
  110. package/dist/ai-DeFa8yPk.mjs.map +0 -1
  111. package/dist/capabilities-BWvOVFbH.d.mts +0 -15
  112. package/dist/capabilities-CaSNd6J1.mjs +0 -125
  113. package/dist/capabilities-CaSNd6J1.mjs.map +0 -1
  114. package/dist/capabilities-xs9kaHrK.mjs +0 -28
  115. package/dist/capabilities-xs9kaHrK.mjs.map +0 -1
  116. package/dist/errors-BuF9bYOQ.mjs +0 -68
  117. package/dist/errors-BuF9bYOQ.mjs.map +0 -1
  118. package/dist/eve/index.d.mts +0 -19
  119. package/dist/eve/index.mjs +0 -155
  120. package/dist/eve/index.mjs.map +0 -1
  121. package/dist/index-CkTwqD0X.d.mts +0 -25
  122. package/dist/index-Dh9xob2s.d.mts +0 -31
  123. package/dist/index.d.mts +0 -5
  124. package/dist/index.mjs +0 -40
  125. package/dist/index.mjs.map +0 -1
  126. package/dist/managed-provider-BsZGVQ4I.mjs +0 -148
  127. package/dist/managed-provider-BsZGVQ4I.mjs.map +0 -1
  128. package/dist/mastra/index.d.mts +0 -118
  129. package/dist/mastra/index.mjs +0 -662
  130. package/dist/mastra/index.mjs.map +0 -1
  131. package/dist/metadata.d.mts +0 -34
  132. package/dist/metadata.mjs +0 -135
  133. package/dist/metadata.mjs.map +0 -1
  134. package/dist/provider-DOBErkBO.d.mts +0 -91
  135. package/dist/provider-utils-CfB9nvJx.mjs +0 -57
  136. package/dist/provider-utils-CfB9nvJx.mjs.map +0 -1
  137. package/dist/providers/agentos/index.d.mts +0 -7
  138. package/dist/providers/agentos/index.mjs +0 -9
  139. package/dist/providers/box/index.d.mts +0 -34
  140. package/dist/providers/box/index.mjs +0 -348
  141. package/dist/providers/box/index.mjs.map +0 -1
  142. package/dist/providers/daytona/index.d.mts +0 -21
  143. package/dist/providers/daytona/index.mjs +0 -206
  144. package/dist/providers/daytona/index.mjs.map +0 -1
  145. package/dist/providers/e2b/index.d.mts +0 -17
  146. package/dist/providers/e2b/index.mjs +0 -169
  147. package/dist/providers/e2b/index.mjs.map +0 -1
  148. package/dist/providers/local/index.d.mts +0 -22
  149. package/dist/providers/local/index.mjs +0 -22
  150. package/dist/providers/local/index.mjs.map +0 -1
  151. package/dist/providers/railway/index.d.mts +0 -29
  152. package/dist/providers/railway/index.mjs +0 -211
  153. package/dist/providers/railway/index.mjs.map +0 -1
  154. package/dist/providers/upstash/index.d.mts +0 -17
  155. package/dist/providers/upstash/index.mjs +0 -211
  156. package/dist/providers/upstash/index.mjs.map +0 -1
  157. package/dist/providers/vercel/index.d.mts +0 -28
  158. package/dist/providers/vercel/index.mjs +0 -186
  159. package/dist/providers/vercel/index.mjs.map +0 -1
  160. package/dist/sandbox-DdEqoXJF.d.mts +0 -26
  161. package/dist/sandbox-DgtY5hvt.mjs +0 -112
  162. package/dist/sandbox-DgtY5hvt.mjs.map +0 -1
  163. package/dist/testing/index.d.mts +0 -27
  164. package/dist/testing/index.mjs +0 -173
  165. package/dist/testing/index.mjs.map +0 -1
  166. package/dist/types-Bv6I2U1c.d.mts +0 -103
package/CHANGELOG.md CHANGED
@@ -1,17 +1,38 @@
1
- ## @opencoredev/sandbox-sdk@0.2.0
1
+ # Changelog
2
2
 
3
- - Add Ascii Box and Railway Sandbox provider adapters, package exports, capabilities, examples,
4
- live-test entry points, and provider documentation.
5
- - Normalize HTTP response status codes from provider SDK errors.
3
+ ## 1.0.0-beta.0
6
4
 
7
- ## @opencoredev/sandbox-sdk@0.1.1
5
+ First preview of the 1.0 rewrite, published under the `next` tag. The SDK was rewritten from scratch; most of the 0.2 surface changed. The API may still change before 1.0.0. The [upgrade guide](https://github.com/opencoredev/sandbox-sdk/blob/main/apps/docs/content/upgrade-from-0-2.mdx) maps every 0.2 call to its 1.0 form.
8
6
 
9
- - Add `Symbol.asyncDispose` to `Sandbox` so `await using` provides automatic cleanup.
7
+ ### Unreleased
10
8
 
11
- ## @opencoredev/sandbox-sdk@0.1.0
9
+ - Provider options now use `baseUrl` instead of `boxd.baseURL`, `daytona.apiUrl`, and `boat.apiUrl`; `tenki.apiKey` replaces `authToken`, and `boat.machine` replaces `type`. Old names remain deprecated aliases until 1.0.0. When both are supplied, the new name wins.
10
+ - Added the Sandboxer provider at `@opencoredev/sandbox-sdk/sandboxer`. It shares Boat's API transport, including deferred workspace setup, and adds pause and resume, named snapshots, cookie-authenticated previews, and a `region` option (`us-east` or `eu-central`, default `SANDBOXER_REGION`, else nearest).
11
+ - Provider option hovers document defaults and environment variables. All provider subpaths export a named provider type. Missing-credential errors explain the next step; E2B checks its key on first use and reads environment values set after factory construction.
12
12
 
13
- ### Initial release
13
+ ### Breaking changes since 0.2.0
14
14
 
15
- - Run one TypeScript sandbox API across Local, E2B, Daytona, Vercel Sandbox, and Upstash Box.
16
- - Use normalized files, commands, processes, ports, snapshots, and provider capabilities.
17
- - Integrate sandbox sessions with AI SDK, Harness, Eve, and Mastra.
15
+ - `createSandbox(provider, options?)` takes the provider as its first argument instead of `createSandbox({ provider })`. The `cwd` option is gone; every sandbox starts in `/workspace`.
16
+ - `stop()` is split into `destroy()`, which deletes the sandbox, and `close()`, which detaches and leaves it running. `await using` calls `close()` for keyed sandboxes and `destroy()` otherwise.
17
+ - `run` results: `success` is now `ok`, `timeout` is `timeoutMs` (default 120 s), and a timeout resolves with `timedOut: true` instead of throwing. The `{ command, args }` form is replaced by the `` box.$`...` `` tag.
18
+ - `files.text(path)` is now `files.read(path)`; bytes come from `files.read(path, { as: "bytes" })`. `files.mkdir` is gone (`write` creates parents). `write` accepts only strings and `Uint8Array`.
19
+ - `processes.start` is now `box.start(command, { name })`. `output()` is replaced by `lines()` and `logs()`, `kill()` by `stop()`, and process stdin writes are removed.
20
+ - `ports.expose` is now `box.preview(port)`, which returns `url` plus any required `headers`.
21
+ - `snapshots.*` is now `box.snapshot()` and `createSandbox(provider, { from: id })`.
22
+ - `local()` runs a Docker container from `node:22-bookworm` instead of a bundled AgentOS VM. AgentOS moved to the optional `/agentos` entry.
23
+ - `sandbox.raw` is now `box.native`.
24
+ - Removed: the Ascii Box provider, the `/eve` integration, and the helpers `withSandbox`, `supports`, `requireCapability`, `capabilityMode`, `isSandboxError`, and `redactSensitive`. Use `await using`, `box.capabilities.has(...)`, and `error instanceof SandboxError`.
25
+ - Node.js 22+ or Bun 1.3+ is required. The package is ESM only.
26
+
27
+ ### Highlights
28
+
29
+ - Thirteen providers behind one API: E2B, Daytona, Vercel, Upstash Box (durable and ephemeral), Railway, Tenki, boxd, Freestyle, Superserve, Boat, Local (Docker), agentOS, and an in-memory fake for tests.
30
+ - Typed capabilities: `pause`, `snapshot`, and `terminal` exist on the sandbox type only when the provider supports them. Options a provider cannot enforce fail with `unsupported` instead of being dropped.
31
+ - Keys: `createSandbox(provider, { key })` returns the same sandbox on every call, with an optional `locator` for your own store.
32
+ - Secrets passed as `secrets` are redacted from command output, logs, errors, and agent tool results, including after reconnecting.
33
+ - Durable named background processes with readiness checks (`port`, `http`, or `log`) and resumable log cursors that survive a server restart.
34
+ - Files: `edit`, `search`, `watch`, `upload`/`download`, and `mark`/`changes` diffs, plus `box.git` with token-safe `clone` and `push`.
35
+ - Agent tools for AI SDK (`/ai`), Mastra (`/mastra`), and OpenAI Agents (`/openai`), with output budgets, a `root` path guard, and a `beforeRun` command hook.
36
+ - `SandboxError` with 15 stable codes, a `retryable` flag, and a `hint` for every code.
37
+ - Zero runtime dependencies. The root entry is under 8 KB minified, and provider SDKs load only on first use.
38
+ - A shared conformance suite (`/testing`) that every adapter, including third-party ones, can run.
package/README.md CHANGED
@@ -1,49 +1,374 @@
1
- <p align="center">
2
- <img alt="Sandbox SDK with supported provider logos" src="https://raw.githubusercontent.com/opencoredev/sandbox-sdk/main/Background-with-text.png" width="820" />
3
- </p>
1
+ # Sandbox SDK
4
2
 
5
- Run the same TypeScript sandbox code on Local, E2B, Daytona, Vercel Sandbox, Upstash Box, Ascii Box, or Railway Sandboxes.
3
+ One TypeScript API for running code in a sandbox. The same `createSandbox`, `run`, `files`, `start`, and `preview` calls work on E2B, Daytona, Vercel, Upstash Box, Railway, Tenki, boxd, Freestyle, Superserve, Boat, Sandboxer, Docker on your machine, and an in-process fake for tests. Changing provider means changing one import.
4
+
5
+ > **1.0 beta.** This is `1.0.0-beta.0` of the 1.0 rewrite, published under the `next` tag. The API can still change before 1.0.0. `latest` is still 0.2.
6
6
 
7
7
  ## Install
8
8
 
9
- ```bash
10
- bun add @opencoredev/sandbox-sdk
9
+ ```sh
10
+ npm install @opencoredev/sandbox-sdk@next
11
11
  ```
12
12
 
13
- Node.js 22 or 24 is supported. Bun 1.3 or newer is also supported.
13
+ Then add the peer package for each provider you use (see [Providers](#providers)), for example `npm install e2b`. Provider packages are optional peers, so you only download the ones you need. The core has no runtime dependencies.
14
+
15
+ Requires Node.js 22+ or Bun 1.3+, ESM only. The examples use `await using`, which runs natively on Node.js 24 and Bun. On Node.js 22, compile with TypeScript (target ES2022 or lower) or call `box.destroy()` yourself.
14
16
 
15
17
  ## Quickstart
16
18
 
19
+ This runs with no account and no Docker. The `memory` provider is an in-process fake with a small shell (`echo`, `cat`, `printf`, `sleep`, `ls`, `printenv`):
20
+
17
21
  ```ts
18
22
  import { createSandbox } from "@opencoredev/sandbox-sdk";
19
- import { local } from "@opencoredev/sandbox-sdk/local";
23
+ import { memory } from "@opencoredev/sandbox-sdk/memory";
24
+
25
+ await using box = await createSandbox(memory());
26
+
27
+ await box.files.write("hello.txt", "hello from the sandbox\n");
28
+ const result = await box.run("cat hello.txt");
29
+ console.log(result.ok, result.stdout); // true "hello from the sandbox\n"
30
+ ```
31
+
32
+ For a real Linux sandbox, swap the provider. Nothing else changes:
20
33
 
21
- await using sandbox = await createSandbox({ provider: local() });
22
- console.log((await sandbox.run("node --version")).stdout);
34
+ ```ts
35
+ import { createSandbox } from "@opencoredev/sandbox-sdk";
36
+ import { e2b } from "@opencoredev/sandbox-sdk/e2b";
37
+
38
+ // Reads E2B_API_KEY from the environment. Run `npm install e2b` first.
39
+ await using box = await createSandbox(e2b());
40
+
41
+ await box.files.write("hello.py", "print('hello from the sandbox')");
42
+ const result = await box.run("python3 hello.py");
43
+ console.log(result.ok, result.stdout);
23
44
  ```
24
45
 
25
- `await using` stops the sandbox automatically when its scope exits, including when an operation throws.
46
+ `await using` deletes the sandbox when the scope ends, even if your code throws. A command that exits non-zero resolves with `ok: false`; it never throws.
47
+
48
+ ## Switch providers with one variable
49
+
50
+ Pick the provider from an environment variable and keep the sandbox code identical. Each adapter loads through a dynamic `import()`, so only the provider you pick needs its peer package installed. At runtime that holds as written. For the TypeScript compiler, the switch still reaches every listed adapter's declarations, and those reference their peer's types. Set `"skipLibCheck": true` in `tsconfig.json` (most templates already do), or install the peers you list as devDependencies, otherwise `tsc` reports TS2307 for the ones you left out.
51
+
52
+ ```ts
53
+ import { createSandbox, type Sandbox } from "@opencoredev/sandbox-sdk";
54
+ import type { Provider } from "@opencoredev/sandbox-sdk/provider";
26
55
 
27
- Node.js 24 and Bun run this syntax directly. On Node.js 22, compile TypeScript to ES2022 or use the callback-style `withSandbox()` helper.
56
+ async function loadProvider(name: string): Promise<Provider> {
57
+ switch (name) {
58
+ case "memory": return (await import("@opencoredev/sandbox-sdk/memory")).memory();
59
+ case "local": return (await import("@opencoredev/sandbox-sdk/local")).local();
60
+ case "e2b": return (await import("@opencoredev/sandbox-sdk/e2b")).e2b();
61
+ case "daytona": return (await import("@opencoredev/sandbox-sdk/daytona")).daytona();
62
+ case "vercel": return (await import("@opencoredev/sandbox-sdk/vercel")).vercel();
63
+ case "upstash": return (await import("@opencoredev/sandbox-sdk/upstash")).upstash();
64
+ default: throw new Error(`Unknown SANDBOX_PROVIDER "${name}"`);
65
+ }
66
+ }
67
+
68
+ // Portable code takes the base `Sandbox` type: only the methods every provider has.
69
+ async function greet(box: Sandbox): Promise<string> {
70
+ await box.files.write("greeting.txt", `hello from ${box.provider}\n`);
71
+ return (await box.run("cat greeting.txt")).stdout.trim();
72
+ }
73
+
74
+ await using box = await createSandbox(await loadProvider(process.env.SANDBOX_PROVIDER ?? "memory"));
75
+ console.log(await greet(box));
76
+ ```
77
+
78
+ `SANDBOX_PROVIDER=local` runs it in Docker; `SANDBOX_PROVIDER=e2b` runs it on E2B. The repository has a runnable version with every provider and an exhaustive switch in `examples/getting-started`.
79
+
80
+ Two things differ between providers and are worth checking before you go multi-provider:
81
+
82
+ - **Optional methods.** `pause`/`resume` and `snapshot` exist only on providers that support them. See [Pause, resume, and snapshots](#pause-resume-and-snapshots) for how this works with a runtime-selected provider.
83
+ - **Create options.** `size`, `network`, `from`, `timeoutMs`, and `idleTimeoutMs` fail with `unsupported` on providers that cannot enforce them. Provider-agnostic code should leave them out or catch that code.
28
84
 
29
85
  ## Providers
30
86
 
31
- | Provider | Runtime | Best for |
32
- | --------------------------------------------------------------- | ----------------------- | --------------------------------------- |
33
- | [Local](https://sandbox-sdk.app/docs/providers/local) | AgentOS VM | Development, CI, and self-hosting |
34
- | [E2B](https://sandbox-sdk.app/docs/providers/e2b) | Hosted Linux sandbox | Coding agents and isolated jobs |
35
- | [Daytona](https://sandbox-sdk.app/docs/providers/daytona) | Cloud workspace | Persistent projects and GPUs |
36
- | [Vercel Sandbox](https://sandbox-sdk.app/docs/providers/vercel) | Hosted Linux sandbox | Coding agents and persistent workspaces |
37
- | [Upstash Box](https://sandbox-sdk.app/docs/providers/upstash) | Durable cloud container | Serverless agents and long-lived state |
38
- | [Ascii Box](https://sandbox-sdk.app/docs/providers/box) | Persistent cloud VM | Full VMs and protected app previews |
39
- | [Railway](https://sandbox-sdk.app/docs/providers/railway) | Ephemeral cloud VM | Durable jobs and private networking |
87
+ Import each provider from its own subpath. The root entry never loads a provider SDK, and constructing a factory performs no I/O.
88
+
89
+ | Provider | Import from | Factory | Credentials (env) | Peer package | Capabilities | Previews |
90
+ | --- | --- | --- | --- | --- | --- | --- |
91
+ | [E2B](https://sandbox-sdk.app/docs/providers/e2b) | `/e2b` | `e2b()` | `E2B_API_KEY` | `e2b` | pause, snapshot, network-policy | any port |
92
+ | [Daytona](https://sandbox-sdk.app/docs/providers/daytona) | `/daytona` | `daytona()` | `DAYTONA_API_KEY` (optional `DAYTONA_API_URL`, `DAYTONA_TARGET`) | `@daytonaio/sdk` | snapshot, size, network-policy | any port; private sandboxes need `preview.headers` |
93
+ | [Vercel](https://sandbox-sdk.app/docs/providers/vercel) | `/vercel` | `vercel()` | `VERCEL_OIDC_TOKEN`, or `VERCEL_TOKEN` + `VERCEL_TEAM_ID` + `VERCEL_PROJECT_ID` | `@vercel/sandbox` | pause, snapshot, size, network-policy | declare in `ports` (max 15) |
94
+ | [Upstash Box](https://sandbox-sdk.app/docs/providers/upstash) | `/upstash` | `upstash()` | `UPSTASH_BOX_API_KEY` (optional `UPSTASH_BOX_BASE_URL`) | `@upstash/box` | pause, snapshot, size, network-policy; no pause with `ephemeral: true` | any port; bearer token in `preview.headers` unless `public: true` |
95
+ | [Railway](https://sandbox-sdk.app/docs/providers/railway) | `/railway` | `railway()` | `RAILWAY_API_TOKEN`, `RAILWAY_ENVIRONMENT_ID` | `railway` | snapshot | declare in `ports` (max 10) |
96
+ | [Tenki](https://sandbox-sdk.app/docs/providers/tenki) | `/tenki` | `tenki()` | `TENKI_AUTH_TOKEN` or `TENKI_API_KEY` | `@tenkicloud/sandbox` | pause, snapshot, size, network-policy | any port; URL may expire (`preview.expiresAt`) |
97
+ | boxd | `/boxd` | `boxd()` | `BOXD_API_KEY` (optional `BOXD_BASE_URL`) | `@boxd-sh/sdk` | pause, snapshot, size | one port at a time; the URL follows the last port requested |
98
+ | Freestyle | `/freestyle` | `freestyle()` | `FREESTYLE_API_KEY` | `freestyle` | pause, snapshot, network-policy | any port |
99
+ | Superserve | `/superserve` | `superserve()` | `SUPERSERVE_API_KEY` | `@superserve/sdk` | pause, snapshot | any port; token in `preview.headers` |
100
+ | Boat | `/boat` | `boat()` | `BOAT_API_KEY` (optional `BOAT_API_BASE`) | `@boatdev/sdk` | size | any port; cookie in `preview.headers` |
101
+ | Sandboxer | `/sandboxer` | `sandboxer({ region })` | `SANDBOXER_API_KEY` (optional `SANDBOXER_REGION`: `us-east`, `eu-central`) | `@boatdev/sdk` | pause, snapshot, size | any port; private URL, send `preview.headers` |
102
+ | [Local (Docker)](https://sandbox-sdk.app/docs/providers/local) | `/local` | `local()` | none; needs a running Docker Engine (`DOCKER_HOST` or `/var/run/docker.sock`) | none | pause, snapshot, size, network-policy (`"none"` only, no allow-lists) | declare in `ports` (default `[3000]`); URL is `http://127.0.0.1:<port>` |
103
+ | [agentOS](https://sandbox-sdk.app/docs/providers/agentos) | `/agentos` | `agentos()` | none | `@rivet-dev/agentos-core` | network-policy | none; no background processes either |
104
+ | Memory | `/memory` | `memory()` | none | none | network-policy | fake `*.memory.invalid` URL; never fetch it |
105
+
106
+ Capability meanings: `pause` adds `box.pause()` and `box.resume()`, `snapshot` adds `box.snapshot()` and `createSandbox(provider, { from })`, `size` allows the `size` option, and `network-policy` allows `network: "none"` or `{ allow: [...] }`. The SDK also defines `terminal` (`box.terminal()`, an interactive PTY), but no built-in provider supports it yet.
107
+
108
+ Every factory also accepts its credential as an option, such as `e2b({ apiKey })` or `railway({ token, environmentId })`. Endpoints use `baseUrl` (boxd, Daytona, Tenki, Upstash, Boat), and Boat's default machine size uses `machine`:
109
+
110
+ ```ts
111
+ import { boat } from "@opencoredev/sandbox-sdk/boat";
112
+ import { boxd } from "@opencoredev/sandbox-sdk/boxd";
113
+ import { daytona } from "@opencoredev/sandbox-sdk/daytona";
114
+ import { tenki } from "@opencoredev/sandbox-sdk/tenki";
115
+
116
+ const providers = [
117
+ boxd({ baseUrl: "https://boxd.sh:9443" }),
118
+ daytona({ baseUrl: "https://app.daytona.io/api" }),
119
+ boat({ baseUrl: "https://boat.dev/api/v1", machine: "small" }),
120
+ tenki({ apiKey: process.env.TENKI_API_KEY }),
121
+ ];
122
+ ```
123
+
124
+ The old names (`boxd.baseURL`, `daytona.apiUrl`, `boat.apiUrl`, `tenki.authToken`, and `boat.type`) still work as deprecated aliases until 1.0.0. When both names are set, the new name wins.
125
+
126
+ ## Core API
127
+
128
+ ### Create, reconnect, clean up
129
+
130
+ ```ts
131
+ import { connectSandbox, createSandbox } from "@opencoredev/sandbox-sdk";
132
+ import { e2b } from "@opencoredev/sandbox-sdk/e2b";
133
+
134
+ const box = await createSandbox(e2b(), {
135
+ key: "user:123", // the same key returns the same sandbox
136
+ env: { NODE_ENV: "development" },
137
+ secrets: { GITHUB_TOKEN: "ghp_example" }, // redacted from all output and errors
138
+ network: { allow: ["registry.npmjs.org"] },
139
+ timeoutMs: 30 * 60_000, // hard lifetime, where the provider supports it
140
+ });
141
+
142
+ await box.close(); // detach; the sandbox keeps running
143
+ const again = await connectSandbox(e2b(), box.id);
144
+ await again.destroy(); // delete it
145
+ ```
146
+
147
+ `await using box = await createSandbox(...)` disposes the handle when the scope ends:
148
+
149
+ - Without a `key`, it calls `destroy()` and the sandbox is deleted.
150
+ - With a `key`, it calls `close()`, so the sandbox survives for the next request with that key.
151
+
152
+ Use `listSandboxes(provider)` and `reapSandboxes(provider, { olderThanMs })` to find and delete sandboxes this SDK created, such as ones a crashed process never cleaned up.
153
+
154
+ ### Run commands and stream output
155
+
156
+ ```ts
157
+ const result = await box.run("npm test", { cwd: "app" });
158
+ if (!result.ok) console.log(result.exitCode, result.output);
159
+
160
+ const branch = "feature/login";
161
+ await box.$`git checkout ${branch}`; // interpolated values are shell-quoted
162
+
163
+ const install = box.run("npm install");
164
+ for await (const chunk of install) process.stdout.write(chunk.text); // chunk.stream is "stdout" or "stderr"
165
+ const { ok, durationMs } = await install; // the same handle, now finished
166
+ ```
167
+
168
+ `run` returns a lazy handle: nothing starts until you `await` it or iterate it. Commands start in `/workspace` and keep up to 1 MB per stream (`maxOutputBytes`); older output is dropped and `truncated` is set. `stdout` and `stderr` are separate strings, and `output` interleaves them in arrival order.
169
+
170
+ ### Timeouts and cancellation
171
+
172
+ ```ts
173
+ const slow = await box.run("sleep 600", { timeoutMs: 5_000 });
174
+ console.log(slow.ok, slow.timedOut, slow.exitCode); // false true null
175
+
176
+ const chained = await box.run("npm test").cwd("app").timeout(60_000);
177
+
178
+ const controller = new AbortController();
179
+ const pending = box.run("npm run build", { signal: controller.signal });
180
+ controller.abort();
181
+ console.log((await pending).ok); // false; an aborted run also reports timedOut: true
182
+ ```
183
+
184
+ - Commands time out after 120 seconds unless you pass `timeoutMs` or chain `.timeout(ms)`. A timeout or an abort kills the command's process group and resolves with `ok: false`, `exitCode: null`, and `timedOut: true`. It does not throw.
185
+ - `createSandbox(provider, { signal })` rejects with `SandboxError` code `aborted`. A sandbox that arrives after the abort is destroyed for you.
186
+ - `start(..., { readyTimeoutMs })` bounds readiness (default 60 seconds), and `preview(port, { waitMs })` waits for a 2xx.
187
+ - `timeoutMs` and `idleTimeoutMs` on `createSandbox` set the sandbox's own lifetime, not a command timeout. A hard lifetime (`timeoutMs`) works on E2B, Daytona (one minute or more), Tenki, Freestyle, Local, and Memory. An idle timeout (`idleTimeoutMs`) works on Daytona, Railway, Freestyle, Local, and Memory. Every other provider rejects them with `unsupported`.
188
+
189
+ ### Files
190
+
191
+ ```ts
192
+ await box.files.write({ "app/package.json": "{}", "app/index.js": "console.log(1)" });
193
+ const text = await box.files.read("app/index.js");
194
+ const bytes = await box.files.read("app/index.js", { as: "bytes" });
195
+ await box.files.edit("app/index.js", { find: "console.log(1)", replace: "console.log(2)" });
196
+ const matches = await box.files.search("console.log", { path: "app" });
197
+ const entries = await box.files.list("app", { recursive: true });
198
+ await box.files.upload("./local-project", "app", { ignore: ["node_modules"] });
199
+ const diff = await box.files.changes({ since: await box.files.mark() });
200
+ ```
201
+
202
+ Relative paths resolve against `/workspace`. `read` returns a string unless you pass `{ as: "bytes" }`. A missing path fails with `file_not_found`. `edit` fails with `edit_not_found` or `edit_not_unique` unless the text matches exactly once.
203
+
204
+ ### Background processes
205
+
206
+ ```ts
207
+ const dev = await box.start("npm run dev", { name: "dev", ready: { port: 3000 } });
208
+ console.log(dev.ready, dev.status); // true "running"
209
+
210
+ const { text } = await box.process("dev").logs();
211
+ await dev.stop();
212
+ ```
213
+
214
+ `start` returns once the process is ready. Readiness is one of:
215
+
216
+ - `{ port: 3000 }`: the port's preview URL answers with a 2xx.
217
+ - `{ http: { port: 3000, path: "/health" } }`: that path answers with a 2xx.
218
+ - `{ log: /listening/ }`: a log line matches.
219
+
220
+ Without `ready`, `start` returns as soon as the process launches. If the process exits first, `start` fails with `process_exited` and includes the exit code and log tail. Readiness gives up after `readyTimeoutMs` (default 60 seconds) with `timeout`.
221
+
222
+ The name is the process's durable ID (1 to 128 letters, digits, `-`, `_`). It is optional: without one, `start` derives a stable name from the command, such as `npm-1a2b3c4d`, and returns it as `dev.name`. Any client can find it again with `box.process("dev")`, even after your server restarts. `box.processes()` lists them, and a handle has `logs()`, `lines()`, `stop()`, `restart()`, and `wait()`.
223
+
224
+ ### Previews
225
+
226
+ ```ts
227
+ import { createSandbox } from "@opencoredev/sandbox-sdk";
228
+ import { local } from "@opencoredev/sandbox-sdk/local";
229
+
230
+ await using box = await createSandbox(local(), { ports: [3000] });
231
+ await box.start("python3 -m http.server 3000", { name: "web", ready: { port: 3000 } });
232
+
233
+ const preview = await box.preview(3000);
234
+ const response = await fetch(preview.url, { headers: preview.headers });
235
+ console.log(preview.url, response.status); // http://127.0.0.1:<port>/ 200
236
+ ```
237
+
238
+ Always send `preview.headers`. Daytona (private sandboxes), Upstash, Superserve, Boat, and Sandboxer put the access token there, and those providers reject requests without it. Local, Vercel, and Railway only route ports you declared with `createSandbox(provider, { ports })`, so pass `ports` in provider-agnostic code; the other providers ignore it. Servers must listen on `0.0.0.0`, not `localhost`.
239
+
240
+ ### Pause, resume, and snapshots
241
+
242
+ ```ts
243
+ import { createSandbox } from "@opencoredev/sandbox-sdk";
244
+ import { e2b } from "@opencoredev/sandbox-sdk/e2b";
245
+
246
+ await using box = await createSandbox(e2b());
247
+ const snap = await box.snapshot();
248
+ await using copy = await createSandbox(e2b(), { from: snap.id });
249
+
250
+ await box.pause();
251
+ await box.resume();
252
+ ```
253
+
254
+ These methods are gated in the types by the provider's capability union. With a concrete provider, a missing method is a compile error:
255
+
256
+ ```ts
257
+ import { createSandbox } from "@opencoredev/sandbox-sdk";
258
+ import { boat } from "@opencoredev/sandbox-sdk/boat";
259
+
260
+ await using box = await createSandbox(boat());
261
+ // @ts-expect-error Boat has no snapshot capability, so the method does not exist.
262
+ await box.snapshot();
263
+ ```
264
+
265
+ When you pick the provider at runtime, as in [Switch providers](#switch-providers-with-one-variable), the provider's type is the wide `Provider`, so the SDK cannot know which methods exist. `pause`, `resume`, `snapshot`, and `terminal` are then optional on the sandbox, and calling one directly is a compile error. `hasCapability` checks at runtime and narrows the type:
266
+
267
+ ```ts
268
+ import { hasCapability } from "@opencoredev/sandbox-sdk";
269
+
270
+ if (hasCapability(box, "snapshot")) {
271
+ const { id } = await box.snapshot();
272
+ console.log(`saved ${id}`);
273
+ }
274
+ ```
275
+
276
+ `box.capabilities.has("snapshot")` gives the same answer as a boolean, but it cannot narrow `box`, so use `hasCapability` before calling the method.
277
+
278
+ ### Git
279
+
280
+ ```ts
281
+ await box.git.clone("https://github.com/opencoredev/sandbox-sdk", { dir: "repo", depth: 1 });
282
+ await box.git.commit("repo", { message: "Update docs", all: true });
283
+ await box.git.push("repo", { token: process.env.GITHUB_TOKEN });
284
+ ```
285
+
286
+ Tokens reach git through a temporary credential helper, never argv, env, or shell history, and are redacted from output. Git must be installed in the sandbox image; the memory provider has no git.
287
+
288
+ ### Errors
289
+
290
+ ```ts
291
+ import { SandboxError } from "@opencoredev/sandbox-sdk";
292
+
293
+ try {
294
+ await box.files.read("missing.txt");
295
+ } catch (error) {
296
+ if (!(error instanceof SandboxError)) throw error;
297
+ console.error(error.code, error.message, error.hint, error.retryable);
298
+ }
299
+ ```
300
+
301
+ The SDK only throws `SandboxError`. A non-zero exit, a command timeout, or an aborted `run` is a result, not an error. `hint` is the next step on its own, `retryable` says whether the same call can succeed again, and `provider` and `sandboxId` say where it happened. Secrets are redacted from every field.
302
+
303
+ | `code` | Meaning |
304
+ | --- | --- |
305
+ | `auth` | Credentials are missing or rejected. |
306
+ | `not_found` | The sandbox no longer exists, or this handle was closed. |
307
+ | `quota` | A provider limit or billing stopped the request. Usually retryable. |
308
+ | `unavailable` | The provider is down or unreachable. Usually retryable. |
309
+ | `unsupported` | The provider cannot do what was asked: `size`, `network`, a lifetime option, an undeclared preview port. |
310
+ | `timeout` | A provider operation or a readiness check took too long. |
311
+ | `aborted` | Your `AbortSignal` fired during `createSandbox`. |
312
+ | `outcome_unknown` | The request may or may not have happened. Check state before retrying. |
313
+ | `process_exited` | A background process exited before it was ready. |
314
+ | `port_not_ready` | A preview did not answer with a 2xx before `waitMs` ran out. |
315
+ | `edit_not_found`, `edit_not_unique` | `files.edit` matched zero times or more than once. |
316
+ | `file_not_found` | The path does not exist. |
317
+ | `invalid_argument` | The input is invalid. Retrying will not help. |
318
+ | `provider` | Any other provider or adapter failure. Read `message` and `cause`. |
319
+
320
+ ## Give an agent a sandbox
321
+
322
+ Install `zod` and your agent framework, then pass the tools to your agent:
323
+
324
+ ```ts
325
+ import { generateText, stepCountIs } from "ai";
326
+ import { sandboxTools } from "@opencoredev/sandbox-sdk/ai";
327
+
328
+ const { text } = await generateText({
329
+ model: "anthropic/claude-opus-5-5",
330
+ tools: sandboxTools(box, { root: "/workspace/app" }),
331
+ stopWhen: stepCountIs(20),
332
+ prompt: "Add a /health route to the Express app and run its tests.",
333
+ });
334
+ ```
335
+
336
+ | Framework | Import | Returns |
337
+ | --- | --- | --- |
338
+ | AI SDK 7 | `@opencoredev/sandbox-sdk/ai` | tool map for `generateText` / `streamText` |
339
+ | Mastra 1.78+ | `@opencoredev/sandbox-sdk/mastra` | tool map for `new Agent({ tools })` |
340
+ | OpenAI Agents 0.20+ | `@opencoredev/sandbox-sdk/openai` | tool array for `new Agent({ tools })` |
341
+ | Anything else | `@opencoredev/sandbox-sdk/tools` | `createSandboxTools` and Zod `toolSchemas` |
342
+
343
+ The default tools are `run`, `read_file`, `write_file`, `edit_file`, `start_process`, `process_logs`, and `stop_process`. Opt into `search`, `list_files`, `changes`, or `preview` with `allow`. Results are compact and capped for model context, and registered secrets never reach the model. Use `beforeRun` to deny or rewrite commands. `root` limits file paths, but it is not a security boundary; the sandbox is.
344
+
345
+ ## Performance
346
+
347
+ The SDK aims for near-zero overhead on top of each provider's own API. The root entry is under 8 KB minified, providers load on first use, and `createSandbox` adds no provider calls beyond the create itself unless you pass `secrets`, `from`, or `trackChanges: true` (one exec each).
348
+
349
+ Time the SDK adds on top of each provider's own SDK, as median milliseconds. "Cold" is `createSandbox` plus the first `box.run("true")`, compared with the provider SDK's create plus its own exec. "Run" is a warm `run("true")` on an existing sandbox. Negative numbers and small differences are within provider noise.
350
+
351
+ | Provider | Create | Cold: create to first command | Warm `run` |
352
+ | --- | ---: | ---: | ---: |
353
+ | E2B | +24 | +52 | +8 |
354
+ | Daytona | ±0 | ±0 | +38 |
355
+ | Upstash Box | +27 | +69 | +15 |
356
+ | Freestyle | ±0 | +51 | +23 |
357
+ | Tenki | ±0 | +119 | +15 |
358
+ | Superserve | ±0 | +73 | +9 |
359
+ | boxd | +272 | +370 | +52 |
360
+
361
+ boxd has no native metadata field, so a sandbox created with labels or a `key` writes its ownership record with one extra request before `createSandbox` returns. The other providers send labels with the create call itself and run any guest setup inside your first command.
40
362
 
41
- Local is included. Cloud providers use their official SDKs and credentials.
363
+ Measured on 2026-10-22 from one Linux host with `bench/scripts/sdk-overhead.ts`, 5 to 8 cold samples and 10 warm samples per provider. See the `bench/` directory in the repository to reproduce.
42
364
 
43
- ## Documentation
365
+ ## More
44
366
 
45
- Setup, usage, integrations, and API reference are available at [sandbox-sdk.app/docs](https://sandbox-sdk.app/docs).
367
+ - [Documentation](https://sandbox-sdk.app/docs): guides, provider pages, and the [error reference](https://sandbox-sdk.app/docs/reference/errors).
368
+ - Write your own provider: implement `Provider` from `@opencoredev/sandbox-sdk/provider` and test it with `runConformance` from `@opencoredev/sandbox-sdk/testing`.
369
+ - [Report an issue](https://github.com/opencoredev/sandbox-sdk/issues).
370
+ - [Changelog](./CHANGELOG.md).
46
371
 
47
372
  ## License
48
373
 
49
- [MIT](./LICENSE) © OpenCore
374
+ MIT
package/dist/ai.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { type Tool } from "ai";
2
+ import type { Sandbox } from "./types.js";
3
+ import { type SandboxToolsOptions, type ToolResult, type ToolParameters } from "./tools.js";
4
+ export type { SandboxToolsOptions } from "./tools.js";
5
+ export declare function sandboxTools(box: Sandbox, options?: SandboxToolsOptions): Record<string, Tool<ToolParameters, ToolResult, Record<string, never>>>;
package/dist/ai.js ADDED
@@ -0,0 +1 @@
1
+ import{k}from"./chunk-t5vr9nd8.js";import{w}from"./chunk-v6y7eqxw.js";import"./chunk-cwx6cc31.js";import{tool as u}from"ai";function b(p,m){return Object.fromEntries(w(p,m).map((o)=>[o.name,u({description:o.description,inputSchema:o.inputSchema,async*execute(e,s){let r=new AbortController,c=s.abortSignal?AbortSignal.any([r.signal,s.abortSignal]):r.signal,n=new k,a=n.stream(),l=a.next(),i=o.execute(e,(t)=>n.emit({stream:t.stream,text:t.text}),c).finally(()=>n.end());try{while(!0){let t=await l;if(t.done)break;yield{text:t.value.text},l=a.next()}yield await i}finally{r.abort(),await i,await a.return(void 0)}},toModelOutput:({output:e})=>({type:"text",value:e.text})})]))}export{b as sandboxTools};
@@ -0,0 +1,2 @@
1
+ import{e,l,r}from"./chunk-gc8zdxyb.js";var h=new WeakMap,O=(n,t)=>h.get(n)?.get(t);function M(n,t,a){let i=h.get(n);if(!i)i=new Map,h.set(n,i);if(i.delete(t),i.set(t,a),i.size>256){let o=i.keys().next().value;if(o!==void 0)i.delete(o)}}function L(n,t){h.get(n)?.delete(t)}function v(n,t){let a=h.get(n);if(!a)return;for(let[i,o]of a)if(o===t)a.delete(i)}function T(n,t){return n.capabilities.has(t)}var _={"sandbox-sdk":"1"},R=new WeakMap;function j(n,t){for(let a of Object.keys({...t.env,...t.secrets}))if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(a))r("Invalid environment variable name");for(let a of n.capabilities)if(a==="pause"&&(!n.pause||!n.resume)||a==="snapshot"&&!n.snapshot||a==="terminal"&&!n.terminal)throw new e("Invalid adapter capabilities. A declared method is missing. Fix the adapter contract.",{code:"provider"});if(t.size!==void 0&&!n.capabilities.has("size"))throw new e("Creation refused. Provider cannot enforce size. Choose a provider with size support.",{code:"unsupported"});if(t.network!==void 0&&t.network!=="all"&&!n.capabilities.has("network-policy"))throw new e("Creation refused. Provider cannot enforce network policy. Choose a provider with network-policy support.",{code:"unsupported"});if(t.from!==void 0&&!n.capabilities.has("snapshot"))throw new e("Creation refused. Provider cannot restore snapshots. Choose a provider with snapshot support.",{code:"unsupported"});if(t.keyMode==="exact"&&n.keyMode!=="exact"&&!t.locator?.withLock)throw new e("Exact key mode unavailable. Two-function locator storage cannot enforce distributed uniqueness. Supply locator.withLock or use an exact provider.",{code:"unsupported"});for(let a of t.ports??[])if(!Number.isInteger(a)||a<1||a>65535)r("Invalid port");for(let a of[t.timeoutMs,t.idleTimeoutMs])if(a!==void 0&&(!Number.isFinite(a)||a<=0))r("Timeouts must be positive milliseconds")}async function p(n,t={}){let a=new l;a.add(Object.values(t.secrets??{}));async function i(){let u;try{if(j(n,t),t.signal?.aborted)throw new e("Creation aborted. Signal was already aborted. Retry with a fresh signal.",{code:"aborted"});let c;if(t.key){let x=await t.locator?.get(t.key)??O(n,t.key);if(x)try{c=await n.connect(x)}catch(k){if(!(k instanceof e)||k.code!=="not_found")throw k;L(n,t.key)}c??=await n.find(t.key)}let m=!c;if(!c){let{secrets:x,locator:k,keyMode:A,trackChanges:H,...F}=t,C=n.create({...F,env:{...t.env,...x},labels:{...t.labels,..._}}).catch(async(b)=>{if(t.key&&n.keyMode==="exact"&&!t.signal?.aborted){let w=await n.find(t.key);if(w)return m=!1,w}throw b});if(t.signal){let b=t.signal,w=()=>{},K=new Promise((P,z)=>{if(w=()=>z(new e("Creation aborted. The signal was cancelled. Any late sandbox will be destroyed.",{code:"aborted"})),b.addEventListener("abort",w,{once:!0}),b.aborted)w()}),B=C.then(async(P)=>{if(b.aborted){if(m)await n.destroy(P.id);throw new e("Creation aborted. Late sandbox destroyed. Retry with a fresh signal.",{code:"aborted"})}return P});try{c=await Promise.race([B,K])}finally{b.removeEventListener("abort",w)}}else c=await C;if(m)u=c}if(t.signal?.aborted){if(u)await n.destroy(u.id);throw u=void 0,new e("Creation aborted. Sandbox arrived after cancellation and was destroyed. Retry with a fresh signal.",{code:"aborted"})}if(t.key)await t.locator?.set(t.key,c.id),M(n,t.key,c.id);let{makeHandle:I}=await import("./chunk-hjzhc3qg.js");return await I(n,c,m,t,a)}catch(c){if(u)v(n,u.id),await n.destroy(u.id).catch(()=>{});throw a.error(c,n.name)}}if(!t.key)return i();let o=R.get(n);if(!o)o=new Map,R.set(n,o);let s=o.get(t.key)??Promise.resolve(),{key:f,locator:y}=t,d=s.catch(()=>{}).then(()=>t.keyMode==="exact"&&n.keyMode==="best-effort"&&y?.withLock?y.withLock(f,i):i());o.set(t.key,d);try{return await d}finally{if(o.get(t.key)===d)o.delete(t.key)}}async function g(n,t,a={}){let i=new l;i.add(Object.values(a.secrets??{}));try{j(n,a);let o=await n.connect(t),{makeHandle:s}=await import("./chunk-hjzhc3qg.js");return await s(n,o,!1,{...a,key:o.key},i)}catch(o){throw i.error(o,n.name,t)}}async function S(n,t={}){try{return(await n.list({...t.labels,..._})).filter((a)=>a.labels["sandbox-sdk"]==="1")}catch(a){throw new l().error(a,n.name)}}async function E(n,t={}){if(t.olderThanMs!==void 0&&(!Number.isFinite(t.olderThanMs)||t.olderThanMs<0))r("olderThanMs must be non-negative");let a=t.idleOnly?(await import("./chunk-md6fmprc.js")).isRunActive:()=>!1,i=0;try{for(let o of await S(n,t))if(Date.now()-o.createdAt>=(t.olderThanMs??0)){if(t.idleOnly){if(a(n.name,o.id))continue;let s;try{let f=await n.connect(o.id),{makeHandle:y}=await import("./chunk-hjzhc3qg.js");s=await y(n,f,!1,{},new l,!1)}catch(f){if(f instanceof e&&f.code==="not_found"){i++;continue}throw f}try{let[f,y]=await Promise.all([s.processes(),s.files.list("/tmp/.sandbox-sdk/runs").catch(async(d)=>{if(d instanceof e&&(["file_not_found","not_found"].includes(d.code)||d.code==="provider"&&!await s.files.exists("/tmp/.sandbox-sdk/runs")))return[];throw d})]);if(f.some((d)=>d.status==="running"||d.status==="starting")||y.length>0)continue}finally{await s.close()}}if(a(n.name,o.id))continue;v(n,o.id);try{await n.destroy(o.id)}catch(s){if(!(s instanceof e)||s.code!=="not_found")throw s}i++}return i}catch(o){throw new l().error(o,n.name)}}
2
+ export{v,T,p,g,S,E};
@@ -0,0 +1,2 @@
1
+ import{f}from"./chunk-t5vr9nd8.js";import{stripVTControlCharacters as u}from"node:util";var i=new WeakMap;function C(s,r){i.set(s,r)}function p(s){let r=i.get(s);if(!r)throw Error("Unknown sandbox handle. Secret redaction cannot be guaranteed. Use createSandbox or connectSandbox.");return r}function _(s,r){return p(s).text(u(r))}function P(s,r){let n={stdout:"plain",stderr:"plain"},c=f(p(s),r,!0);return{push(a){let t=n[a.stream],o="";for(let e of a.text)switch(t){case"plain":if(e==="\x1B")t="escape";else if(e==="›")t="csi";else if("˜žŸ".includes(e))t="string";else o+=e;break;case"escape":t=e==="["?"csi":"]PX^_".includes(e)?"string":e>=" "&&e<="/"?"escape":"plain";break;case"csi":if(e>="@"&&e<="~")t="plain";break;case"string":if(e==="\x07"||e==="œ")t="plain";else if(e==="\x1B")t="string_escape";break;case"string_escape":t=e==="\\"?"plain":"string";break;default:return t}if(n[a.stream]=t,o)c.push({stream:a.stream,text:o})},flush(){c.flush()}}}
2
+ export{C,_,P};
@@ -0,0 +1,60 @@
1
+ import{e}from"./chunk-gc8zdxyb.js";var a=(o)=>`'${o.replaceAll("'","'\\''")}'`,n=(o,...s)=>`python3 -c ${a(o)} ${s.map(a).join(" ")}`,d=`def stdin_file():
2
+ try: return os.fdopen(os.memfd_create('sandbox-sdk-stdin'),'w+b')
3
+ except (AttributeError,OSError):
4
+ import tempfile
5
+ return tempfile.TemporaryFile()
6
+ class Process:
7
+ def __init__(self,v,env,stdin,session=False):
8
+ if not all(hasattr(os,name) for name in ['posix_spawn','waitstatus_to_exitcode']):
9
+ self.legacy(v,env,stdin,session); return
10
+ directory=os.open('.',os.O_RDONLY)
11
+ r,w=os.pipe(); e,x=os.pipe()
12
+ try:
13
+ os.chdir(v['cwd'])
14
+ error=FileNotFoundError(2,os.strerror(2),'sh')
15
+ signals=[getattr(signal,s) for s in ['SIGPIPE','SIGXFZ','SIGXFSZ'] if hasattr(signal,s)]
16
+ for path in os.get_exec_path(env):
17
+ try:
18
+ self.pid=os.posix_spawn(os.path.join(path,'sh'),['sh','-c',v['command']],env,file_actions=[(os.POSIX_SPAWN_DUP2,stdin.fileno(),0),(os.POSIX_SPAWN_DUP2,w,1),(os.POSIX_SPAWN_DUP2,x,2)],setsid=session,setsigdef=signals)
19
+ break
20
+ except (FileNotFoundError,NotADirectoryError): pass
21
+ except OSError as failure:
22
+ if failure.errno==38: raise NotImplementedError()
23
+ error=failure
24
+ else: raise error
25
+ except NotImplementedError:
26
+ os.close(r); os.close(e)
27
+ self.legacy(v,env,stdin,session); return
28
+ except BaseException:
29
+ os.close(r); os.close(e); raise
30
+ finally:
31
+ os.fchdir(directory); os.close(directory); os.close(w); os.close(x)
32
+ self.stdout=os.fdopen(r,'rb',buffering=0); self.stderr=os.fdopen(e,'rb',buffering=0)
33
+ self.code=None
34
+ def legacy(self,v,env,stdin,session):
35
+ import subprocess
36
+ self.child=subprocess.Popen(['sh','-c',v['command']],cwd=v['cwd'],env=env,stdin=stdin,stdout=subprocess.PIPE,stderr=subprocess.PIPE,start_new_session=session)
37
+ self.pid=self.child.pid; self.stdout=self.child.stdout; self.stderr=self.child.stderr
38
+ def poll(self):
39
+ if hasattr(self,'child'): return self.child.poll()
40
+ if self.code is None:
41
+ pid,status=os.waitpid(self.pid,os.WNOHANG)
42
+ if pid: self.code=os.waitstatus_to_exitcode(status)
43
+ return self.code
44
+ def wait(self):
45
+ if hasattr(self,'child'): return self.child.wait()
46
+ if self.code is None: self.code=os.waitstatus_to_exitcode(os.waitpid(self.pid,0)[1])
47
+ return self.code
48
+ `,c=(o,s,f="provider")=>new e(`${s}. Check the ${o.name} sandbox before retrying.`,{code:f,provider:o.name}),y=`import os,sys,json,stat
49
+ p=sys.argv[1]
50
+ def info(p):
51
+ s=os.lstat(p)
52
+ return dict(path=p,type='symlink' if stat.S_ISLNK(s.st_mode) else 'directory' if stat.S_ISDIR(s.st_mode) else 'file',size=s.st_size,modifiedAt=s.st_mtime*1000)
53
+ try: print(json.dumps([info(os.path.join(p,n)) for n in os.listdir(p)] if sys.argv[2]=='list' else info(p)))
54
+ except FileNotFoundError: print('SDK_FILE_NOT_FOUND')`;function u(o,s){if(!s||typeof s!=="object"||!("path"in s)||typeof s.path!=="string"||!("type"in s)||s.type!=="file"&&s.type!=="directory"&&s.type!=="symlink"||!("size"in s)||typeof s.size!=="number"||!("modifiedAt"in s)||typeof s.modifiedAt!=="number")throw c(o,"Invalid file metadata");return{path:s.path,type:s.type,size:s.size,modifiedAt:s.modifiedAt}}function h(o,s){let f=async(t,r,i)=>{let l=await o(t),p=await l.raw(n(y,r,i));if(p.trim()==="SDK_FILE_NOT_FOUND")throw c(l,"File does not exist","file_not_found");let m=JSON.parse(p);return{g:l,value:m}};return{writeCreatesParents:!0,readRange:(t,r,i)=>s(t,async()=>(await o(t)).read(r,i)),read:(t,r)=>s(t,async()=>(await o(t)).read(r)),write:(t,r,i)=>s(t,async()=>{let l=await o(t);try{await s(t,()=>l.upload(r,i))}catch(p){if(!(p instanceof e)||!["not_found","file_not_found"].includes(p.code))throw p;await l.raw(n("import os,sys; os.makedirs(os.path.dirname(sys.argv[1]),exist_ok=True)",r)),await l.upload(r,i)}}),stat:(t,r)=>s(t,async()=>{let{g:i,value:l}=await f(t,r,"stat");return u(i,l)}),list:(t,r)=>s(t,async()=>{let{g:i,value:l}=await f(t,r,"list");if(!Array.isArray(l))throw c(i,"Invalid directory listing");return l.map((p)=>u(i,p))}),mkdir:(t,r)=>s(t,async()=>{await(await o(t)).raw(n("import os,sys; os.makedirs(sys.argv[1],exist_ok=True)",r))}),remove:(t,r,i)=>s(t,async()=>{await(await o(t)).raw(n(`import os,sys,shutil
55
+ p=sys.argv[1]
56
+ if os.path.isdir(p) and not os.path.islink(p):
57
+ if sys.argv[2]=='true': shutil.rmtree(p)
58
+ else: os.rmdir(p)
59
+ else: os.unlink(p)`,r,String(i.recursive)))})}}
60
+ export{a,n,d,h};
@@ -0,0 +1,2 @@
1
+ var s={auth:"Set the provider's API key environment variable or pass it to the provider factory.",not_found:"Create a new sandbox, or pass a key to createSandbox so it recreates one for you.",quota:"Wait and retry, destroy unused sandboxes with reapSandboxes, or raise the provider plan.",unavailable:"Retry with backoff. Check the provider's status page if it persists.",unsupported:"Remove the option or choose a provider that supports it. Check box.capabilities.",timeout:"Retry, or raise the relevant timeout option.",aborted:"Retry with a fresh AbortSignal if you still need the result.",outcome_unknown:"Check the sandbox state before retrying. If in doubt, destroy it.",process_exited:"Read the exit code and log tail in the message, fix the command, and start it again.",port_not_ready:"Check that the server listens on 0.0.0.0 and the right port, then read its logs.",edit_not_found:"Read the file again and use text that is in it.",edit_not_unique:"Add surrounding lines to find so it matches exactly once.",file_not_found:"Check the path. Relative paths resolve against /workspace.",invalid_argument:"Fix the input. Retrying the same call will not help.",provider:"Read the message and cause. Report it if it looks like an SDK bug."};class e extends Error{code;retryable;hint;provider;sandboxId;constructor(t,n){super(t,{cause:n.cause});this.name="SandboxError",this.code=n.code,this.retryable=n.retryable??!1,this.hint=n.hint??s[n.code]??s.provider,this.provider=n.provider??"unknown",this.sandboxId=n.sandboxId}}class l{values=new Set;add(t){for(let n of t)if(n)this.values.add(n)}text(t){for(let n of[...this.values].sort((a,o)=>o.length-a.length))t=t.split(n).join("[REDACTED]");return t}get secrets(){return[...this.values].sort((t,n)=>n.length-t.length)}get maxLength(){return Math.max(1,...[...this.values].map((t)=>t.length))}error(t,n,a){let o=t instanceof Error?t.message:String(t),d=t instanceof e?o:`${o}. Provider request failed. Check the provider settings and retry.`,i=t instanceof e?t:{code:"provider"};return new e(this.text(d),{code:i.code,retryable:i.retryable,hint:this.text(i.hint??s[i.code]??s.provider),provider:n,sandboxId:a,cause:t instanceof Error&&t.cause!==void 0?this.text(String(t.cause instanceof Error?t.cause.message:t.cause)):void 0})}}function r(t){throw new e(`${t}. The request is invalid. Check the supplied options.`,{code:"invalid_argument"})}
2
+ export{e,l,r};