UNPKG

@tanstack/ai-sandbox

Version:

Provider-agnostic sandbox layer for TanStack AI — run harness adapters inside isolated sandboxes (defineSandbox, defineWorkspace, withSandbox) with a uniform SandboxHandle, workspace bootstrap, policy, and resumable lifecycle.

221 lines (170 loc) 9.78 kB
# @tanstack/ai-sandbox Provider-agnostic sandbox layer for [TanStack AI](https://tanstack.com/ai). Run coding-agent harness adapters (Grok Build, Claude Code, Codex, OpenCode, Gemini CLI) **inside** an isolated environment with a real filesystem, shell, and cloned repo — and stream their work back through `chat()`. ```typescript import { chat } from '@tanstack/ai' import { grokBuildText } from '@tanstack/ai-grok-build' import { createSecrets, defineSandbox, defineWorkspace, githubRepo, withSandbox, } from '@tanstack/ai-sandbox' import { dockerSandbox } from '@tanstack/ai-sandbox-docker' const sandbox = defineSandbox({ id: 'repo-agent', provider: dockerSandbox({ image: 'node:22' }), workspace: defineWorkspace({ source: githubRepo({ repo: 'owner/repo', ref: 'main' }), packageManager: 'pnpm', setup: ['corepack enable', 'pnpm install'], scripts: { test: 'pnpm test', build: 'pnpm build' }, secrets: createSecrets({ XAI_API_KEY: process.env.XAI_API_KEY ?? '', }), }), lifecycle: { reuse: 'thread', snapshot: 'after-setup' }, }) const stream = chat({ threadId: 'my-thread', adapter: grokBuildText('grok-build'), messages: [{ role: 'user', content: 'Fix the failing test.' }], middleware: [withSandbox(sandbox)], }) ``` ## Installation ```bash npm install @tanstack/ai @tanstack/ai-sandbox @tanstack/ai-grok-build ``` Pick a **provider** package for where the sandbox runs: | Package | Use when | | ------------------------------------ | -------------------------------------- | | `@tanstack/ai-sandbox-local-process` | Dev loop on your host (no isolation) | | `@tanstack/ai-sandbox-docker` | Isolated containers, snapshots, resume | | `@tanstack/ai-sandbox-cloudflare` | Cloudflare Workers + Containers | | `@tanstack/ai-sandbox-vercel` | Vercel Sandbox | | `@tanstack/ai-sandbox-daytona` | Daytona cloud sandboxes, snapshots | | `@tanstack/ai-sandbox-upstash-box` | Upstash Box cloud sandboxes, snapshots | | `@tanstack/ai-sandbox-sprites` | Sprites stateful sandboxes | **Harness adapters** are separate packages. The default path is **Grok Build** (`@tanstack/ai-grok-build`); others include `@tanstack/ai-claude-code`, `@tanstack/ai-codex`, and `@tanstack/ai-opencode`. All require `withSandbox(...)` middleware — `chat()` fails fast without it. ## Three moving parts | Part | What it is | How you configure it | | ------------------- | -------------------------------------------------------- | ----------------------------------------------------------------- | | **Provider** | Isolation primitive — host, container, cloud VM | `dockerSandbox()`, `localProcessSandbox()`, … | | **Workspace** | What the agent boots into — repo, setup, secrets, skills | `defineWorkspace({ … })` | | **Harness adapter** | Which agent CLI runs and how output is translated | `grokBuildText()` (default), `claudeCodeText()`, `codexText()`, … | `defineSandbox()` binds provider + workspace (+ optional policy, lifecycle, hooks). `withSandbox(definition)` is the `chat()` middleware that creates or resumes the sandbox for each run. ## Core APIs ### Workspace Describe the working tree once, portably: ```typescript defineWorkspace({ source: githubRepo({ repo: 'owner/app' }), // shallow clone by default setup: ({ serial, parallel }) => { serial('pnpm install') parallel(['pnpm build', 'pnpm typecheck']) }, scripts: { test: 'pnpm test' }, // surfaced in AGENTS.md; policy aliases instructions: 'Run tests before proposing changes.', skills: [ gitSkill({ repo: 'owner/skills', secret: secrets.GH }), mcpSkill('api', { url: 'https://mcp.example.com', headers: { Authorization: bearer(secrets.TOKEN) }, }), fileSkill({ path: '.hints.md', content: '# Hints\nPrefer pnpm.' }), ], plugins: ['@anthropic/plugin-foo'], // Claude Code only; other harnesses warn+skip secrets: createSecrets({ GH: process.env.GH_TOKEN ?? '' }), }) ``` Skills and plugins are **projected** into each harness's native format at run time (`.grok/config.toml`, `.mcp.json`, `.codex/config.toml`, `opencode.json`, …). Bootstrap writes `AGENTS.md` and clones `gitSkill` repos; harness adapters handle the rest. ### Policy Guard what the agent may run: ```typescript const policy = defineSandboxPolicy({ default: 'allow', }) defineSandbox({ id: 'agent', provider, workspace, policy }) ``` Headless Grok Build and Codex stay on auto-approve when `default` is `'allow'` and there is no `ask` list. Isolation is the outer sandbox (Docker, Daytona, and so on). Use Claude Code when you need command-level deny. Claude Code can use an interactive policy: ```typescript defineSandboxPolicy({ commands: { allow: ['pnpm test', 'git diff'], ask: ['pnpm install'], deny: ['sudo *', 'rm -rf *'], }, capabilities: { fileWrite: 'allow', network: 'ask' }, default: 'ask', }) ``` Precedence is `deny` > `ask` > `allow`. Each harness adapter maps policy onto its native permission system (coarse flags for Grok Build/Codex; full interactive `approval-requested` on Claude Code). Provider-specific privilege and network rules live in the [providers](../../docs/sandbox/providers.md) guide. ### Lifecycle ```typescript lifecycle: { reuse: 'thread', // resume one sandbox per threadId snapshot: 'after-setup', // skip bootstrap on subsequent runs (when provider supports it) keepAlive: '30m', destroyOnComplete: false, } ``` ### Portable snapshots Use portable snapshots when a later run must rebuild completed files after the provider sandbox is gone. Create one snapshots object, pass it to `withPersistence` and `withSandbox`, and put `withPersistence` first. ```typescript import { withPersistence } from '@tanstack/ai-persistence' import { memorySandboxSnapshots, withSandbox } from '@tanstack/ai-sandbox' const snapshots = await memorySandboxSnapshots({ sandbox, instances }) const middleware = [ withPersistence(snapshots.persistence), withSandbox(sandbox, { instances, snapshots }), ] ``` A successful terminal run saves regular files, empty directories, saved conversation data, and thread artifacts. Restore runs only in a new private sandbox. A live resumed sandbox keeps its current files. Read [Keep Files After Reload](https://tanstack.com/ai/latest/docs/sandbox/portable-snapshots-configure) for the full server setup. ### Secrets Use `createSecrets()` so values stay behind opaque `SecretRef` tokens. They are never written to snapshots, the sandbox store, or event logs. The sandbox layer resolves them onto the live handle at create, resume, and snapshot restore: ```typescript const secrets = createSecrets({ XAI_API_KEY: process.env.XAI_API_KEY ?? '' }) // secrets.XAI_API_KEY is a ref, not the string ``` ### Host tool bridge `chat()` server tools can be bridged into the in-sandbox agent over MCP. The agent calls `mcp__tanstack__<tool>`; execution runs back on the host where your closures, DB, and secrets live. ## Run flow ```text chat({ adapter: grokBuildText(), middleware: [withSandbox(sandbox)] }) │ ├─ withSandbox.setup → resume → restore snapshot → create + bootstrap ├─ adapter.chatStream → spawn `grok` (or other harness CLI) inside sandbox; stream AG-UI chunks └─ withSandbox.onFinish → snapshot / destroy per lifecycle ``` ## Subpath exports | Import | Purpose | | ---------------------------- | ----------------------------------------------------- | | `@tanstack/ai-sandbox` | Core sandbox APIs | | `@tanstack/ai-sandbox/ngrok` | Optional ngrok tunnel helper for remote tool bridging | ## Documentation Full guides on [tanstack.com/ai](https://tanstack.com/ai/latest/docs/sandbox/overview): - [Quick Start](https://tanstack.com/ai/latest/docs/sandbox/quick-start) — Grok Build in Docker - [Providers](https://tanstack.com/ai/latest/docs/sandbox/providers) - [Workspace](https://tanstack.com/ai/latest/docs/sandbox/workspace) - [Provisioning](https://tanstack.com/ai/latest/docs/sandbox/provisioning) (skills, MCP, plugins) - [Policy](https://tanstack.com/ai/latest/docs/sandbox/policy) - [Tools](https://tanstack.com/ai/latest/docs/sandbox/tools) (host tool bridge) - [Lifecycle & snapshots](https://tanstack.com/ai/latest/docs/sandbox/lifecycle) - [Portable sandbox snapshots](https://tanstack.com/ai/latest/docs/sandbox/portable-snapshots) - [Pick which files to keep](https://tanstack.com/ai/latest/docs/sandbox/portable-snapshots-files) ## Examples | Example | What it demonstrates | | ----------------------------- | --------------------------------------------------------------------- | | `examples/sandbox-web` | Build-and-preview with harness × provider matrix (Grok default in UI) | | `examples/sandbox-cloudflare` | Edge deploy with live preview URL | ## When to use a sandbox Use a sandbox when the agent needs to **act on a real codebase** — run commands, edit files, clone repos, start dev servers. For read-only Q&A over code you already have in context, a normal `chat()` with server tools is enough. Persistence (durable `SandboxStore` / `LockStore`, event-log replay) is out of scope for v1 but every seam is persistence-ready via optional capabilities.