|
22 | 22 | | --- | --- | --- | --- | |
23 | 23 | | Local process | `@tanstack/ai-sandbox-local-process` | none (host) | The fast, no-Docker dev loop. Trusted/dev use only. | |
24 | 24 | | Docker | `@tanstack/ai-sandbox-docker` | container | Real isolation; commit-based snapshots, fork, resume-by-id. | |
25 | | -| Daytona | `@tanstack/ai-sandbox-daytona` | cloud sandbox | Managed [Daytona](https://www.daytona.io/) sandboxes; port preview links, resume-by-id. Needs `DAYTONA_API_KEY`. | |
| 25 | +| Daytona | `@tanstack/ai-sandbox-daytona` | cloud sandbox | Managed [Daytona](https://www.daytona.io/) sandboxes; snapshots after setup, port preview links, resume-by-id. Needs `DAYTONA_API_KEY`. | |
26 | 26 | | Vercel | `@tanstack/ai-sandbox-vercel` | microVM | Managed [Vercel Sandbox](https://vercel.com/docs/sandbox) microVMs; exposed-port domains, resume-by-id (persistent). Needs `VERCEL_TOKEN` + team/project. | |
27 | 27 | | Sprites | `@tanstack/ai-sandbox-sprites` | stateful sandbox | Managed [Sprites](https://sprites.dev) (Fly.io) sandboxes; durable filesystem, in-place checkpoints, single proxied public-URL port, resume-by-id. Needs `SPRITES_API_KEY`. | |
28 | 28 |
|
@@ -139,19 +139,80 @@ const isolated = dockerSandbox({ image: 'node:22' }) |
139 | 139 | ```ts |
140 | 140 | import { daytonaSandbox } from '@tanstack/ai-sandbox-daytona' |
141 | 141 |
|
142 | | -const daytona = daytonaSandbox({ apiKey: process.env.DAYTONA_API_KEY }) |
| 142 | +const daytona = daytonaSandbox({ |
| 143 | + apiKey: process.env.DAYTONA_API_KEY, |
| 144 | + snapshot: 'daytona-medium', |
| 145 | + autoStopInterval: 0, |
| 146 | +}) |
143 | 147 | ``` |
144 | 148 |
|
145 | 149 | - **Isolation:** a managed cloud sandbox on a remote VM you do not run yourself. |
146 | | -- **Auth / env:** needs `DAYTONA_API_KEY`. Harness credentials are injected as |
147 | | - workspace secrets; there is no host login to fall back on. |
148 | | -- **Snapshot / resume:** no snapshots; resume-by-id reconnects to a still-running |
149 | | - sandbox (not a restored point-in-time snapshot), plus port preview links for |
150 | | - live previews. |
| 150 | +- **Auth / env:** needs `DAYTONA_API_KEY`. Harness credentials are workspace |
| 151 | + secrets. They go onto the live handle and into each `exec` call through the |
| 152 | + SDK env argument. Spawn sources a workdir env file. Secret values never |
| 153 | + enter create-time `envVars` or the stored command string. Git clone auth |
| 154 | + uses the native username and password arguments. |
| 155 | +- **Snapshot / resume:** point-in-time snapshots and restore. |
| 156 | + `daytonaSandbox({ snapshot })` selects the Daytona image to create from |
| 157 | + (for example `'daytona-medium'`). After `setup`, the sandbox layer takes its |
| 158 | + own snapshot when `lifecycle.snapshot` is `'after-setup'` (the default on |
| 159 | + this provider). That snapshot stops the sandbox, captures the filesystem, |
| 160 | + then starts it again. Resume-by-id starts a `stopped` or `archived` |
| 161 | + sandbox, then returns the handle. Daytona stops an idle sandbox after 15 |
| 162 | + minutes unless you set `autoStopInterval` (minutes; `0` turns auto-stop |
| 163 | + off). Set `ephemeral: true` to delete the sandbox when it stops. |
| 164 | +- **Network:** `policy.capabilities.network === 'deny'` sets |
| 165 | + `networkBlockAll` on create. The provider reports `networkPolicy: true`. |
| 166 | +- **Working directory:** the virtual root `/workspace` maps to |
| 167 | + `/home/daytona/workspace` by default. Set `workdir` on `daytonaSandbox()` if |
| 168 | + you need a different path. Native `sandbox.fs` and `sandbox.git` use that |
| 169 | + mapped path. |
| 170 | +- **Stdin:** spawned processes accept stdin through |
| 171 | + `sendSessionCommandInput`. `writableStdin` is `true`. |
| 172 | +- **Privileges:** the Daytona user is not root. Setup commands that install |
| 173 | + packages must use `sudo -n`. Do not deny `sudo *` on a Daytona |
| 174 | + [policy](./policy). |
151 | 175 | - **Bridge:** the sandbox is remote, so a [bridged tool](./tools) call can't reach |
152 | 176 | your laptop's `localhost`. In local dev, tunnel the bridge (see [tools](./tools)); |
153 | 177 | a deployed orchestrator is reachable out of the box. |
154 | 178 |
|
| 179 | +```ts |
| 180 | +import { chat } from '@tanstack/ai' |
| 181 | +import { grokBuildText } from '@tanstack/ai-grok-build' |
| 182 | +import { |
| 183 | + defineSandbox, |
| 184 | + defineSandboxPolicy, |
| 185 | + defineWorkspace, |
| 186 | + gitSkill, |
| 187 | + withSandbox, |
| 188 | +} from '@tanstack/ai-sandbox' |
| 189 | +import { daytonaSandbox } from '@tanstack/ai-sandbox-daytona' |
| 190 | + |
| 191 | +const sandbox = defineSandbox({ |
| 192 | + id: 'daytona-agent', |
| 193 | + provider: daytonaSandbox({ |
| 194 | + apiKey: process.env.DAYTONA_API_KEY, |
| 195 | + snapshot: 'daytona-medium', |
| 196 | + }), |
| 197 | + workspace: defineWorkspace({ |
| 198 | + skills: [gitSkill({ repo: 'owner/skills-pack' })], |
| 199 | + }), |
| 200 | + policy: defineSandboxPolicy({ |
| 201 | + default: 'allow', |
| 202 | + commands: { deny: ['rm -rf /'] }, |
| 203 | + }), |
| 204 | +}) |
| 205 | + |
| 206 | +const stream = chat({ |
| 207 | + adapter: grokBuildText('grok-build'), |
| 208 | + messages: [{ role: 'user', content: 'List the project files.' }], |
| 209 | + middleware: [withSandbox(sandbox)], |
| 210 | +}) |
| 211 | +``` |
| 212 | + |
| 213 | +Grok Build and Codex do not enforce `commands.deny`. Isolation is the Daytona |
| 214 | +sandbox. Use Claude Code when you need command-level deny. |
| 215 | + |
155 | 216 | ## Vercel |
156 | 217 |
|
157 | 218 | ```ts |
@@ -211,7 +272,7 @@ Providers declare what they support via `capabilities()`. The flags are: |
211 | 272 | | `env` | Inject environment variables. | |
212 | 273 | | `ports` | Expose/forward ports (preview URLs). | |
213 | 274 | | `backgroundProcesses` | Keep long-running processes alive between calls. | |
214 | | -| `writableStdin` | A spawned process exposes a writable host→process stdin. `true` for local-process and Docker; `false` on remote/edge providers (Daytona, Vercel, Cloudflare), where stdin-fed harnesses deliver the prompt via a file + shell redirection instead. | |
| 275 | +| `writableStdin` | A spawned process exposes a writable host→process stdin. `true` for local-process, Docker, and Daytona. `false` on Vercel and Cloudflare, where stdin-fed harnesses deliver the prompt via a file + shell redirection. | |
215 | 276 | | `killableProcesses` | A spawned process can be forcibly stopped via `SpawnHandle.kill()` **and** aborted mid-flight via the `signal` passed to `spawn`. | |
216 | 277 | | `snapshots` | Capture and restore point-in-time snapshots. | |
217 | 278 | | `networkPolicy` | Enforce network allow/deny rules. | |
|
0 commit comments