mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-15 06:04:43 +00:00
SDK: Add ensureServer() for automatic server recovery (#260)
* SDK sandbox provisioning: built-in providers, docs restructure, and quickstart overhaul - Add built-in sandbox providers (local, docker, e2b, daytona, vercel, cloudflare) to the TypeScript SDK so users import directly instead of passing client instances - Restructure docs: rename architecture to orchestration-architecture, add new architecture page for server overview, improve getting started flow - Rewrite quickstart to be TypeScript-first with provider CodeGroup and custom provider accordion - Update all examples to use new provider APIs - Update persist drivers and foundry for new SDK surface Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix SDK typecheck errors and update persist drivers for insertEvent signature - Fix insertEvent call in client.ts to pass sessionId as first argument - Update Daytona provider create options to use Partial type (image has default) - Update StrictUniqueSessionPersistDriver in tests to match new insertEvent signature - Sync persist packages, openapi spec, and docs with upstream changes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add Modal and ComputeSDK built-in providers, update examples and docs - Add `sandbox-agent/modal` provider using Modal SDK with node:22-slim image - Add `sandbox-agent/computesdk` provider using ComputeSDK's unified sandbox API - Update Modal and ComputeSDK examples to use new SDK providers - Update Modal and ComputeSDK deploy docs with provider-based examples - Add Modal to quickstart CodeGroup and docs.json navigation - Add provider test entries for Modal and ComputeSDK - Remove old standalone example files (modal.ts, computesdk.ts) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix Modal provider: pre-install agents in image, fire-and-forget exec for server - Pre-install agents in Dockerfile commands so they are cached across creates - Use fire-and-forget exec (no wait) to keep server alive in Modal sandbox - Add memoryMiB option (default 2GB) to avoid OOM during agent install Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Sync upstream changes: multiplayer docs, logos, openapi spec, foundry config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * SDK: Add ensureServer() for automatic server recovery Add ensureServer() to SandboxProvider interface to handle cases where the sandbox-agent server stops or goes to sleep. The SDK now calls this method after 3 consecutive health-check failures, allowing providers to restart the server if needed. Most built-in providers (E2B, Daytona, Vercel, Modal, ComputeSDK) implement this. Docker and Cloudflare manage server lifecycle differently, and Local uses managed child processes. Also update docs for quickstart, architecture, multiplayer, and session persistence; mark persist-* packages as deprecated; and add ensureServer implementations to all applicable providers. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> * wip --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3426cbc6ec
commit
cf7e2a92c6
112 changed files with 3739 additions and 3537 deletions
|
|
@ -1,127 +0,0 @@
|
|||
---
|
||||
title: "Agent Capabilities"
|
||||
description: "Models, modes, and thought levels supported by each agent."
|
||||
---
|
||||
|
||||
Capabilities are subject to change as the agents are updated. See [Agent Sessions](/agent-sessions) for full session configuration API details.
|
||||
|
||||
|
||||
<Info>
|
||||
_Last updated: March 5th, 2026. See [Generating a live report](#generating-a-live-report) for up-to-date reference._
|
||||
</Info>
|
||||
|
||||
## Claude
|
||||
|
||||
| Category | Values |
|
||||
|----------|--------|
|
||||
| **Models** | `default`, `sonnet`, `opus`, `haiku` |
|
||||
| **Modes** | `default`, `acceptEdits`, `plan`, `dontAsk`, `bypassPermissions` |
|
||||
| **Thought levels** | Unsupported |
|
||||
|
||||
### Configuring Effort Level For Claude
|
||||
|
||||
Claude does not natively support changing effort level after a session starts, so configure it in the filesystem before creating the session.
|
||||
|
||||
```ts
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
const cwd = "/path/to/workspace";
|
||||
await mkdir(path.join(cwd, ".claude"), { recursive: true });
|
||||
await writeFile(
|
||||
path.join(cwd, ".claude", "settings.json"),
|
||||
JSON.stringify({ effortLevel: "high" }, null, 2),
|
||||
);
|
||||
|
||||
const sdk = await SandboxAgent.connect({ baseUrl: "http://127.0.0.1:2468" });
|
||||
await sdk.createSession({
|
||||
agent: "claude",
|
||||
sessionInit: { cwd, mcpServers: [] },
|
||||
});
|
||||
```
|
||||
|
||||
<Accordion title="Supported file locations (highest precedence last)">
|
||||
|
||||
1. `~/.claude/settings.json`
|
||||
2. `<session cwd>/.claude/settings.json`
|
||||
3. `<session cwd>/.claude/settings.local.json`
|
||||
|
||||
</Accordion>
|
||||
|
||||
## Codex
|
||||
|
||||
| Category | Values |
|
||||
|----------|--------|
|
||||
| **Models** | `gpt-5.3-codex` (default), `gpt-5.3-codex-spark`, `gpt-5.2-codex`, `gpt-5.1-codex-max`, `gpt-5.2`, `gpt-5.1-codex-mini` |
|
||||
| **Modes** | `read-only` (default), `auto`, `full-access` |
|
||||
| **Thought levels** | `low`, `medium`, `high` (default), `xhigh` |
|
||||
|
||||
## OpenCode
|
||||
|
||||
| Category | Values |
|
||||
|----------|--------|
|
||||
| **Models** | See below |
|
||||
| **Modes** | `build` (default), `plan` |
|
||||
| **Thought levels** | Unsupported |
|
||||
|
||||
<Accordion title="See all models">
|
||||
|
||||
| Provider | Models |
|
||||
|----------|--------|
|
||||
| **Anthropic** | `anthropic/claude-3-5-haiku-20241022`, `anthropic/claude-3-5-haiku-latest`, `anthropic/claude-3-5-sonnet-20240620`, `anthropic/claude-3-5-sonnet-20241022`, `anthropic/claude-3-7-sonnet-20250219`, `anthropic/claude-3-7-sonnet-latest`, `anthropic/claude-3-haiku-20240307`, `anthropic/claude-3-opus-20240229`, `anthropic/claude-3-sonnet-20240229`, `anthropic/claude-haiku-4-5`, `anthropic/claude-haiku-4-5-20251001`, `anthropic/claude-opus-4-0`, `anthropic/claude-opus-4-1`, `anthropic/claude-opus-4-1-20250805`, `anthropic/claude-opus-4-20250514`, `anthropic/claude-opus-4-5`, `anthropic/claude-opus-4-5-20251101`, `anthropic/claude-opus-4-6`, `anthropic/claude-sonnet-4-0`, `anthropic/claude-sonnet-4-20250514`, `anthropic/claude-sonnet-4-5`, `anthropic/claude-sonnet-4-5-20250929` |
|
||||
| **OpenAI** | `openai/gpt-5.1-codex`, `openai/gpt-5.1-codex-max`, `openai/gpt-5.1-codex-mini`, `openai/gpt-5.2`, `openai/gpt-5.2-codex`, `openai/gpt-5.3-codex` |
|
||||
| **Cerebras** | `cerebras/gpt-oss-120b`, `cerebras/qwen-3-235b-a22b-instruct-2507`, `cerebras/zai-glm-4.7` |
|
||||
| **OpenCode Zen** | `opencode/big-pickle`, `opencode/claude-3-5-haiku`, `opencode/claude-haiku-4-5`, `opencode/claude-opus-4-1`, `opencode/claude-opus-4-5`, `opencode/claude-opus-4-6`, `opencode/claude-sonnet-4`, `opencode/claude-sonnet-4-5`, `opencode/gemini-3-flash`, `opencode/gemini-3-pro` (default), `opencode/glm-4.6`, `opencode/glm-4.7`, `opencode/gpt-5`, `opencode/gpt-5-codex`, `opencode/gpt-5-nano`, `opencode/gpt-5.1`, `opencode/gpt-5.1-codex`, `opencode/gpt-5.1-codex-max`, `opencode/gpt-5.1-codex-mini`, `opencode/gpt-5.2`, `opencode/gpt-5.2-codex`, `opencode/kimi-k2`, `opencode/kimi-k2-thinking`, `opencode/kimi-k2.5`, `opencode/kimi-k2.5-free`, `opencode/minimax-m2.1`, `opencode/minimax-m2.1-free`, `opencode/trinity-large-preview-free` |
|
||||
|
||||
</Accordion>
|
||||
|
||||
## Cursor
|
||||
|
||||
| Category | Values |
|
||||
|----------|--------|
|
||||
| **Models** | See below |
|
||||
| **Modes** | Unsupported |
|
||||
| **Thought levels** | Unsupported |
|
||||
|
||||
<Accordion title="See all models">
|
||||
|
||||
| Group | Models |
|
||||
|-------|--------|
|
||||
| **Auto** | `auto` |
|
||||
| **Composer** | `composer-1.5`, `composer-1` |
|
||||
| **GPT-5.3 Codex** | `gpt-5.3-codex`, `gpt-5.3-codex-low`, `gpt-5.3-codex-high`, `gpt-5.3-codex-xhigh`, `gpt-5.3-codex-fast`, `gpt-5.3-codex-low-fast`, `gpt-5.3-codex-high-fast`, `gpt-5.3-codex-xhigh-fast` |
|
||||
| **GPT-5.2** | `gpt-5.2`, `gpt-5.2-high`, `gpt-5.2-codex`, `gpt-5.2-codex-low`, `gpt-5.2-codex-high`, `gpt-5.2-codex-xhigh`, `gpt-5.2-codex-fast`, `gpt-5.2-codex-low-fast`, `gpt-5.2-codex-high-fast`, `gpt-5.2-codex-xhigh-fast` |
|
||||
| **GPT-5.1** | `gpt-5.1-high`, `gpt-5.1-codex-max`, `gpt-5.1-codex-max-high` |
|
||||
| **Claude** | `opus-4.6-thinking` (default), `opus-4.6`, `opus-4.5`, `opus-4.5-thinking`, `sonnet-4.5`, `sonnet-4.5-thinking` |
|
||||
| **Other** | `gemini-3-pro`, `gemini-3-flash`, `grok` |
|
||||
|
||||
</Accordion>
|
||||
|
||||
## Amp
|
||||
|
||||
| Category | Values |
|
||||
|----------|--------|
|
||||
| **Models** | `amp-default` |
|
||||
| **Modes** | `default`, `bypass` |
|
||||
| **Thought levels** | Unsupported |
|
||||
|
||||
## Pi
|
||||
|
||||
| Category | Values |
|
||||
|----------|--------|
|
||||
| **Models** | `default` |
|
||||
| **Modes** | Unsupported |
|
||||
| **Thought levels** | Unsupported |
|
||||
|
||||
## Generating a live report
|
||||
|
||||
Requires a running Sandbox Agent server. `--endpoint` defaults to `http://127.0.0.1:2468`.
|
||||
|
||||
```bash
|
||||
sandbox-agent api agents report
|
||||
```
|
||||
|
||||
<Note>
|
||||
The live report reflects what the agent adapter returns for the current credentials. Some models may be gated by subscription (e.g. Claude's `opus` requires a paid plan) and will not appear in the report if the credentials don't have access.
|
||||
</Note>
|
||||
|
|
@ -21,10 +21,7 @@ const sdk = await SandboxAgent.connect({
|
|||
|
||||
const session = await sdk.createSession({
|
||||
agent: "codex",
|
||||
sessionInit: {
|
||||
cwd: "/",
|
||||
mcpServers: [],
|
||||
},
|
||||
cwd: "/",
|
||||
});
|
||||
|
||||
console.log(session.id, session.agentSessionId);
|
||||
|
|
|
|||
20
docs/agents/amp.mdx
Normal file
20
docs/agents/amp.mdx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
title: "Amp"
|
||||
description: "Use Amp as a sandbox agent."
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```typescript
|
||||
const session = await client.createSession({
|
||||
agent: "amp",
|
||||
});
|
||||
```
|
||||
|
||||
## Capabilities
|
||||
|
||||
| Category | Values |
|
||||
|----------|--------|
|
||||
| **Models** | `amp-default` |
|
||||
| **Modes** | `default`, `bypass` |
|
||||
| **Thought levels** | Unsupported |
|
||||
49
docs/agents/claude.mdx
Normal file
49
docs/agents/claude.mdx
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
---
|
||||
title: "Claude"
|
||||
description: "Use Claude Code as a sandbox agent."
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```typescript
|
||||
const session = await client.createSession({
|
||||
agent: "claude",
|
||||
});
|
||||
```
|
||||
|
||||
## Capabilities
|
||||
|
||||
| Category | Values |
|
||||
|----------|--------|
|
||||
| **Models** | `default`, `sonnet`, `opus`, `haiku` |
|
||||
| **Modes** | `default`, `acceptEdits`, `plan`, `dontAsk`, `bypassPermissions` |
|
||||
| **Thought levels** | Unsupported |
|
||||
|
||||
## Configuring effort level
|
||||
|
||||
Claude does not support changing effort level after a session starts. Configure it in the filesystem before creating the session.
|
||||
|
||||
```ts
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
const cwd = "/path/to/workspace";
|
||||
await mkdir(path.join(cwd, ".claude"), { recursive: true });
|
||||
await writeFile(
|
||||
path.join(cwd, ".claude", "settings.json"),
|
||||
JSON.stringify({ effortLevel: "high" }, null, 2),
|
||||
);
|
||||
|
||||
const session = await client.createSession({
|
||||
agent: "claude",
|
||||
cwd,
|
||||
});
|
||||
```
|
||||
|
||||
<Accordion title="Supported settings file locations (highest precedence last)">
|
||||
|
||||
1. `~/.claude/settings.json`
|
||||
2. `<session cwd>/.claude/settings.json`
|
||||
3. `<session cwd>/.claude/settings.local.json`
|
||||
|
||||
</Accordion>
|
||||
20
docs/agents/codex.mdx
Normal file
20
docs/agents/codex.mdx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
title: "Codex"
|
||||
description: "Use OpenAI Codex as a sandbox agent."
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```typescript
|
||||
const session = await client.createSession({
|
||||
agent: "codex",
|
||||
});
|
||||
```
|
||||
|
||||
## Capabilities
|
||||
|
||||
| Category | Values |
|
||||
|----------|--------|
|
||||
| **Models** | `gpt-5.3-codex` (default), `gpt-5.3-codex-spark`, `gpt-5.2-codex`, `gpt-5.1-codex-max`, `gpt-5.2`, `gpt-5.1-codex-mini` |
|
||||
| **Modes** | `read-only` (default), `auto`, `full-access` |
|
||||
| **Thought levels** | `low`, `medium`, `high` (default), `xhigh` |
|
||||
34
docs/agents/cursor.mdx
Normal file
34
docs/agents/cursor.mdx
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
title: "Cursor"
|
||||
description: "Use Cursor as a sandbox agent."
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```typescript
|
||||
const session = await client.createSession({
|
||||
agent: "cursor",
|
||||
});
|
||||
```
|
||||
|
||||
## Capabilities
|
||||
|
||||
| Category | Values |
|
||||
|----------|--------|
|
||||
| **Models** | See below |
|
||||
| **Modes** | Unsupported |
|
||||
| **Thought levels** | Unsupported |
|
||||
|
||||
<Accordion title="All models">
|
||||
|
||||
| Group | Models |
|
||||
|-------|--------|
|
||||
| **Auto** | `auto` |
|
||||
| **Composer** | `composer-1.5`, `composer-1` |
|
||||
| **GPT-5.3 Codex** | `gpt-5.3-codex`, `gpt-5.3-codex-low`, `gpt-5.3-codex-high`, `gpt-5.3-codex-xhigh`, `gpt-5.3-codex-fast`, `gpt-5.3-codex-low-fast`, `gpt-5.3-codex-high-fast`, `gpt-5.3-codex-xhigh-fast` |
|
||||
| **GPT-5.2** | `gpt-5.2`, `gpt-5.2-high`, `gpt-5.2-codex`, `gpt-5.2-codex-low`, `gpt-5.2-codex-high`, `gpt-5.2-codex-xhigh`, `gpt-5.2-codex-fast`, `gpt-5.2-codex-low-fast`, `gpt-5.2-codex-high-fast`, `gpt-5.2-codex-xhigh-fast` |
|
||||
| **GPT-5.1** | `gpt-5.1-high`, `gpt-5.1-codex-max`, `gpt-5.1-codex-max-high` |
|
||||
| **Claude** | `opus-4.6-thinking` (default), `opus-4.6`, `opus-4.5`, `opus-4.5-thinking`, `sonnet-4.5`, `sonnet-4.5-thinking` |
|
||||
| **Other** | `gemini-3-pro`, `gemini-3-flash`, `grok` |
|
||||
|
||||
</Accordion>
|
||||
31
docs/agents/opencode.mdx
Normal file
31
docs/agents/opencode.mdx
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
title: "OpenCode"
|
||||
description: "Use OpenCode as a sandbox agent."
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```typescript
|
||||
const session = await client.createSession({
|
||||
agent: "opencode",
|
||||
});
|
||||
```
|
||||
|
||||
## Capabilities
|
||||
|
||||
| Category | Values |
|
||||
|----------|--------|
|
||||
| **Models** | See below |
|
||||
| **Modes** | `build` (default), `plan` |
|
||||
| **Thought levels** | Unsupported |
|
||||
|
||||
<Accordion title="All models">
|
||||
|
||||
| Provider | Models |
|
||||
|----------|--------|
|
||||
| **Anthropic** | `anthropic/claude-3-5-haiku-20241022`, `anthropic/claude-3-5-haiku-latest`, `anthropic/claude-3-5-sonnet-20240620`, `anthropic/claude-3-5-sonnet-20241022`, `anthropic/claude-3-7-sonnet-20250219`, `anthropic/claude-3-7-sonnet-latest`, `anthropic/claude-3-haiku-20240307`, `anthropic/claude-3-opus-20240229`, `anthropic/claude-3-sonnet-20240229`, `anthropic/claude-haiku-4-5`, `anthropic/claude-haiku-4-5-20251001`, `anthropic/claude-opus-4-0`, `anthropic/claude-opus-4-1`, `anthropic/claude-opus-4-1-20250805`, `anthropic/claude-opus-4-20250514`, `anthropic/claude-opus-4-5`, `anthropic/claude-opus-4-5-20251101`, `anthropic/claude-opus-4-6`, `anthropic/claude-sonnet-4-0`, `anthropic/claude-sonnet-4-20250514`, `anthropic/claude-sonnet-4-5`, `anthropic/claude-sonnet-4-5-20250929` |
|
||||
| **OpenAI** | `openai/gpt-5.1-codex`, `openai/gpt-5.1-codex-max`, `openai/gpt-5.1-codex-mini`, `openai/gpt-5.2`, `openai/gpt-5.2-codex`, `openai/gpt-5.3-codex` |
|
||||
| **Cerebras** | `cerebras/gpt-oss-120b`, `cerebras/qwen-3-235b-a22b-instruct-2507`, `cerebras/zai-glm-4.7` |
|
||||
| **OpenCode Zen** | `opencode/big-pickle`, `opencode/claude-3-5-haiku`, `opencode/claude-haiku-4-5`, `opencode/claude-opus-4-1`, `opencode/claude-opus-4-5`, `opencode/claude-opus-4-6`, `opencode/claude-sonnet-4`, `opencode/claude-sonnet-4-5`, `opencode/gemini-3-flash`, `opencode/gemini-3-pro` (default), `opencode/glm-4.6`, `opencode/glm-4.7`, `opencode/gpt-5`, `opencode/gpt-5-codex`, `opencode/gpt-5-nano`, `opencode/gpt-5.1`, `opencode/gpt-5.1-codex`, `opencode/gpt-5.1-codex-max`, `opencode/gpt-5.1-codex-mini`, `opencode/gpt-5.2`, `opencode/gpt-5.2-codex`, `opencode/kimi-k2`, `opencode/kimi-k2-thinking`, `opencode/kimi-k2.5`, `opencode/kimi-k2.5-free`, `opencode/minimax-m2.1`, `opencode/minimax-m2.1-free`, `opencode/trinity-large-preview-free` |
|
||||
|
||||
</Accordion>
|
||||
20
docs/agents/pi.mdx
Normal file
20
docs/agents/pi.mdx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
title: "Pi"
|
||||
description: "Use Pi as a sandbox agent."
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```typescript
|
||||
const session = await client.createSession({
|
||||
agent: "pi",
|
||||
});
|
||||
```
|
||||
|
||||
## Capabilities
|
||||
|
||||
| Category | Values |
|
||||
|----------|--------|
|
||||
| **Models** | `default` |
|
||||
| **Modes** | Unsupported |
|
||||
| **Thought levels** | Unsupported |
|
||||
|
|
@ -1,64 +1,63 @@
|
|||
---
|
||||
title: "Architecture"
|
||||
description: "How the client, sandbox, server, and agent fit together."
|
||||
icon: "microchip"
|
||||
description: "How the Sandbox Agent server, SDK, and agent processes fit together."
|
||||
---
|
||||
|
||||
Sandbox Agent runs as an HTTP server inside your sandbox. Your app talks to it remotely.
|
||||
Sandbox Agent is a lightweight HTTP server that runs **inside** a sandbox. It:
|
||||
|
||||
- **Agent management**: Installs, spawns, and stops coding agent processes
|
||||
- **Sessions**: Routes prompts to agents and streams events back in real time
|
||||
- **Sandbox APIs**: Filesystem, process, and terminal access for the sandbox environment
|
||||
|
||||
## Components
|
||||
|
||||
- `Your client`: your app code using the `sandbox-agent` SDK.
|
||||
- `Sandbox`: isolated runtime (E2B, Daytona, Docker, etc.).
|
||||
- `Sandbox Agent server`: process inside the sandbox exposing HTTP transport.
|
||||
- `Agent`: Claude/Codex/OpenCode/Amp process managed by Sandbox Agent.
|
||||
|
||||
```mermaid placement="top-right"
|
||||
flowchart LR
|
||||
CLIENT["Sandbox Agent SDK"]
|
||||
SERVER["Sandbox Agent server"]
|
||||
AGENT["Agent process"]
|
||||
```mermaid
|
||||
flowchart LR
|
||||
CLIENT["Your App"]
|
||||
|
||||
subgraph SANDBOX["Sandbox"]
|
||||
direction TB
|
||||
SERVER --> AGENT
|
||||
direction TB
|
||||
SERVER["Sandbox Agent Server"]
|
||||
AGENT["Agent Process<br/>(Claude, Codex, etc.)"]
|
||||
SERVER --> AGENT
|
||||
end
|
||||
|
||||
CLIENT -->|HTTP| SERVER
|
||||
CLIENT -->|"SDK (HTTP)"| SERVER
|
||||
```
|
||||
|
||||
## Suggested Topology
|
||||
- **Your app**: Uses the `sandbox-agent` TypeScript SDK to talk to the server over HTTP.
|
||||
- **Sandbox**: An isolated runtime (local process, Docker, E2B, Daytona, Vercel, Cloudflare).
|
||||
- **Sandbox Agent server**: A single binary inside the sandbox that manages agent lifecycles, routes prompts, streams events, and exposes filesystem/process/terminal APIs.
|
||||
- **Agent process**: A coding agent (Claude Code, Codex, etc.) spawned by the server. Each session maps to one agent process.
|
||||
|
||||
Run the SDK on your backend, then call it from your frontend.
|
||||
## What `SandboxAgent.start()` does
|
||||
|
||||
This extra hop is recommended because it keeps auth/token logic on the backend and makes persistence simpler.
|
||||
1. **Provision**: The provider creates a sandbox (starts a container, creates a VM, etc.)
|
||||
2. **Install**: The Sandbox Agent binary is installed inside the sandbox
|
||||
3. **Boot**: The server starts listening on an HTTP port
|
||||
4. **Health check**: The SDK waits for `/v1/health` to respond
|
||||
5. **Ready**: The SDK returns a connected client
|
||||
|
||||
```mermaid placement="top-right"
|
||||
flowchart LR
|
||||
BROWSER["Browser"]
|
||||
subgraph BACKEND["Your backend"]
|
||||
direction TB
|
||||
SDK["Sandbox Agent SDK"]
|
||||
end
|
||||
subgraph SANDBOX_SIMPLE["Sandbox"]
|
||||
SERVER_SIMPLE["Sandbox Agent server"]
|
||||
end
|
||||
For the `local` provider, provisioning is a no-op and the server runs as a local subprocess.
|
||||
|
||||
BROWSER --> BACKEND
|
||||
BACKEND --> SDK --> SERVER_SIMPLE
|
||||
### Server recovery
|
||||
|
||||
If the server process stops, the SDK automatically calls the provider's `ensureServer()` after 3 consecutive health-check failures. Most built-in providers implement this. Custom providers can add `ensureServer(sandboxId)` to their `SandboxProvider` object.
|
||||
|
||||
## Server HTTP API
|
||||
|
||||
See the [HTTP API reference](/api-reference) for the full list of server endpoints.
|
||||
|
||||
## Agent installation
|
||||
|
||||
Agents are installed lazily on first use. To avoid the cold-start delay, pre-install them:
|
||||
|
||||
```bash
|
||||
sandbox-agent install-agent --all
|
||||
```
|
||||
|
||||
### Backend requirements
|
||||
The `rivetdev/sandbox-agent:0.3.2-full` Docker image ships with all agents pre-installed.
|
||||
|
||||
Your backend layer needs to handle:
|
||||
## Production-ready agent orchestration
|
||||
|
||||
- **Long-running connections**: prompts can take minutes.
|
||||
- **Session affinity**: follow-up messages must reach the same session.
|
||||
- **State between requests**: session metadata and event history must persist across requests.
|
||||
- **Graceful recovery**: sessions should resume after backend restarts.
|
||||
|
||||
We recommend [Rivet](https://rivet.dev) over serverless because actors natively support the long-lived connections, session routing, and state persistence that agent workloads require.
|
||||
|
||||
## Session persistence
|
||||
|
||||
For storage driver options and replay behavior, see [Persisting Sessions](/session-persistence).
|
||||
For production deployments, see [Orchestration Architecture](/orchestration-architecture) for recommended topology, backend requirements, and session persistence patterns.
|
||||
|
|
|
|||
|
|
@ -259,7 +259,7 @@ Example output:
|
|||
}
|
||||
```
|
||||
|
||||
See [Agent Capabilities](/agent-capabilities) for a full reference of supported models, modes, and thought levels per agent.
|
||||
See individual agent pages (e.g. [Claude](/agents/claude), [Codex](/agents/codex)) for supported models, modes, and thought levels.
|
||||
|
||||
#### api agents install
|
||||
|
||||
|
|
|
|||
|
|
@ -80,9 +80,7 @@ await sdk.setMcpConfig(
|
|||
|
||||
const session = await sdk.createSession({
|
||||
agent: "claude",
|
||||
sessionInit: {
|
||||
cwd: "/workspace",
|
||||
},
|
||||
cwd: "/workspace",
|
||||
});
|
||||
|
||||
await session.prompt([
|
||||
|
|
@ -145,9 +143,7 @@ await sdk.writeFsFile({ path: "/opt/skills/random-number/SKILL.md" }, skill);
|
|||
```ts
|
||||
const session = await sdk.createSession({
|
||||
agent: "claude",
|
||||
sessionInit: {
|
||||
cwd: "/workspace",
|
||||
},
|
||||
cwd: "/workspace",
|
||||
});
|
||||
|
||||
await session.prompt([
|
||||
|
|
|
|||
|
|
@ -31,7 +31,38 @@ RUN sandbox-agent install-agent claude && sandbox-agent install-agent codex
|
|||
EXPOSE 8000
|
||||
```
|
||||
|
||||
## TypeScript example
|
||||
## TypeScript example (with provider)
|
||||
|
||||
For standalone scripts, use the `cloudflare` provider:
|
||||
|
||||
```bash
|
||||
npm install sandbox-agent@0.3.x @cloudflare/sandbox
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { cloudflare } from "sandbox-agent/cloudflare";
|
||||
|
||||
const sdk = await SandboxAgent.start({
|
||||
sandbox: cloudflare(),
|
||||
});
|
||||
|
||||
try {
|
||||
const session = await sdk.createSession({ agent: "codex" });
|
||||
const response = await session.prompt([
|
||||
{ type: "text", text: "Summarize this repository" },
|
||||
]);
|
||||
console.log(response.stopReason);
|
||||
} finally {
|
||||
await sdk.destroySandbox();
|
||||
}
|
||||
```
|
||||
|
||||
The `cloudflare` provider uses `containerFetch` under the hood, automatically stripping `AbortSignal` to avoid dropped streaming updates.
|
||||
|
||||
## TypeScript example (Durable Objects)
|
||||
|
||||
For Workers with Durable Objects, use `SandboxAgent.connect(...)` with a custom `fetch` backed by `sandbox.containerFetch(...)`:
|
||||
|
||||
```typescript
|
||||
import { getSandbox, type Sandbox } from "@cloudflare/sandbox";
|
||||
|
|
@ -109,7 +140,6 @@ app.all("*", (c) => c.env.ASSETS.fetch(c.req.raw));
|
|||
export default app;
|
||||
```
|
||||
|
||||
Create the SDK client inside the Worker using custom `fetch` backed by `sandbox.containerFetch(...)`.
|
||||
This keeps all Sandbox Agent calls inside the Cloudflare sandbox routing path and does not require a `baseUrl`.
|
||||
|
||||
## Troubleshooting streaming updates
|
||||
|
|
|
|||
|
|
@ -1,160 +1,61 @@
|
|||
---
|
||||
title: "ComputeSDK"
|
||||
description: "Deploy the daemon using ComputeSDK's provider-agnostic sandbox API."
|
||||
description: "Deploy Sandbox Agent using ComputeSDK's provider-agnostic sandbox API."
|
||||
---
|
||||
|
||||
[ComputeSDK](https://computesdk.com) provides a unified interface for managing sandboxes across multiple providers. Write once, deploy anywhere—switch providers by changing environment variables.
|
||||
[ComputeSDK](https://computesdk.com) provides a unified interface for managing sandboxes across multiple providers. Write once, deploy anywhere by changing environment variables.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- `COMPUTESDK_API_KEY` from [console.computesdk.com](https://console.computesdk.com)
|
||||
- Provider API key (one of: `E2B_API_KEY`, `DAYTONA_API_KEY`, `VERCEL_TOKEN`, `MODAL_TOKEN_ID` + `MODAL_TOKEN_SECRET`, `BLAXEL_API_KEY`, `CSB_API_KEY`)
|
||||
- `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` for the coding agents
|
||||
- `ANTHROPIC_API_KEY` or `OPENAI_API_KEY`
|
||||
|
||||
## TypeScript Example
|
||||
## TypeScript example
|
||||
|
||||
```bash
|
||||
npm install sandbox-agent@0.3.x computesdk
|
||||
```
|
||||
|
||||
```typescript
|
||||
import {
|
||||
compute,
|
||||
detectProvider,
|
||||
getMissingEnvVars,
|
||||
getProviderConfigFromEnv,
|
||||
isProviderAuthComplete,
|
||||
isValidProvider,
|
||||
PROVIDER_NAMES,
|
||||
type ExplicitComputeConfig,
|
||||
type ProviderName,
|
||||
} from "computesdk";
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { computesdk } from "sandbox-agent/computesdk";
|
||||
|
||||
const PORT = 3000;
|
||||
const REQUEST_TIMEOUT_MS =
|
||||
Number.parseInt(process.env.COMPUTESDK_TIMEOUT_MS || "", 10) || 120_000;
|
||||
|
||||
/**
|
||||
* Detects and validates the provider to use.
|
||||
* Priority: COMPUTESDK_PROVIDER env var > auto-detection from API keys
|
||||
*/
|
||||
function resolveProvider(): ProviderName {
|
||||
const providerOverride = process.env.COMPUTESDK_PROVIDER;
|
||||
|
||||
if (providerOverride) {
|
||||
if (!isValidProvider(providerOverride)) {
|
||||
throw new Error(
|
||||
`Unsupported provider "${providerOverride}". Supported: ${PROVIDER_NAMES.join(", ")}`
|
||||
);
|
||||
}
|
||||
if (!isProviderAuthComplete(providerOverride)) {
|
||||
const missing = getMissingEnvVars(providerOverride);
|
||||
throw new Error(
|
||||
`Missing credentials for "${providerOverride}". Set: ${missing.join(", ")}`
|
||||
);
|
||||
}
|
||||
return providerOverride as ProviderName;
|
||||
}
|
||||
|
||||
const detected = detectProvider();
|
||||
if (!detected) {
|
||||
throw new Error(
|
||||
`No provider credentials found. Set one of: ${PROVIDER_NAMES.map((p) => getMissingEnvVars(p).join(", ")).join(" | ")}`
|
||||
);
|
||||
}
|
||||
return detected as ProviderName;
|
||||
}
|
||||
|
||||
function configureComputeSDK(): void {
|
||||
const provider = resolveProvider();
|
||||
|
||||
const config: ExplicitComputeConfig = {
|
||||
provider,
|
||||
computesdkApiKey: process.env.COMPUTESDK_API_KEY,
|
||||
requestTimeoutMs: REQUEST_TIMEOUT_MS,
|
||||
};
|
||||
|
||||
// Add provider-specific config from environment
|
||||
const providerConfig = getProviderConfigFromEnv(provider);
|
||||
if (Object.keys(providerConfig).length > 0) {
|
||||
(config as any)[provider] = providerConfig;
|
||||
}
|
||||
|
||||
compute.setConfig(config);
|
||||
}
|
||||
|
||||
configureComputeSDK();
|
||||
|
||||
// Build environment variables to pass to sandbox
|
||||
const envs: Record<string, string> = {};
|
||||
if (process.env.ANTHROPIC_API_KEY) envs.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
||||
if (process.env.OPENAI_API_KEY) envs.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
||||
|
||||
// Create sandbox
|
||||
const sandbox = await compute.sandbox.create({
|
||||
envs: Object.keys(envs).length > 0 ? envs : undefined,
|
||||
const sdk = await SandboxAgent.start({
|
||||
sandbox: computesdk({
|
||||
create: { envs },
|
||||
}),
|
||||
});
|
||||
|
||||
// Helper to run commands with error handling
|
||||
const run = async (cmd: string, options?: { background?: boolean }) => {
|
||||
const result = await sandbox.runCommand(cmd, options);
|
||||
if (typeof result?.exitCode === "number" && result.exitCode !== 0) {
|
||||
throw new Error(`Command failed: ${cmd} (exit ${result.exitCode})\n${result.stderr || ""}`);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// Install sandbox-agent
|
||||
await run("curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh");
|
||||
|
||||
// Install agents conditionally based on available API keys
|
||||
if (envs.ANTHROPIC_API_KEY) {
|
||||
await run("sandbox-agent install-agent claude");
|
||||
try {
|
||||
const session = await sdk.createSession({ agent: "claude" });
|
||||
const response = await session.prompt([
|
||||
{ type: "text", text: "Summarize this repository" },
|
||||
]);
|
||||
console.log(response.stopReason);
|
||||
} finally {
|
||||
await sdk.destroySandbox();
|
||||
}
|
||||
if (envs.OPENAI_API_KEY) {
|
||||
await run("sandbox-agent install-agent codex");
|
||||
}
|
||||
|
||||
// Start the server in the background
|
||||
await run(`sandbox-agent server --no-token --host 0.0.0.0 --port ${PORT}`, { background: true });
|
||||
|
||||
// Get the public URL for the sandbox
|
||||
const baseUrl = await sandbox.getUrl({ port: PORT });
|
||||
|
||||
// Wait for server to be ready
|
||||
const deadline = Date.now() + REQUEST_TIMEOUT_MS;
|
||||
while (Date.now() < deadline) {
|
||||
try {
|
||||
const response = await fetch(`${baseUrl}/v1/health`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data?.status === "ok") break;
|
||||
}
|
||||
} catch {
|
||||
// Server not ready yet
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
}
|
||||
|
||||
// Connect to the server
|
||||
const client = await SandboxAgent.connect({ baseUrl });
|
||||
|
||||
// Detect which agent to use based on available API keys
|
||||
const agent = envs.ANTHROPIC_API_KEY ? "claude" : "codex";
|
||||
|
||||
// Create a session and start coding
|
||||
await client.createSession("my-session", { agent });
|
||||
|
||||
await client.postMessage("my-session", {
|
||||
message: "Summarize this repository",
|
||||
});
|
||||
|
||||
for await (const event of client.streamEvents("my-session")) {
|
||||
console.log(event.type, event.data);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
await sandbox.destroy();
|
||||
```
|
||||
|
||||
## Supported Providers
|
||||
The `computesdk` provider handles sandbox creation, Sandbox Agent installation, agent setup, and server startup automatically. ComputeSDK routes to your configured provider behind the scenes.
|
||||
|
||||
Before calling `SandboxAgent.start()`, configure ComputeSDK with your provider:
|
||||
|
||||
```typescript
|
||||
import { compute } from "computesdk";
|
||||
|
||||
compute.setConfig({
|
||||
provider: "e2b", // or auto-detect via detectProvider()
|
||||
computesdkApiKey: process.env.COMPUTESDK_API_KEY,
|
||||
});
|
||||
```
|
||||
|
||||
## Supported providers
|
||||
|
||||
ComputeSDK auto-detects your provider from environment variables:
|
||||
|
||||
|
|
@ -169,46 +70,7 @@ ComputeSDK auto-detects your provider from environment variables:
|
|||
|
||||
## Notes
|
||||
|
||||
- **Provider resolution order**: `COMPUTESDK_PROVIDER` env var takes priority, otherwise auto-detection from API keys.
|
||||
- **Conditional agent installation**: Only agents with available API keys are installed, reducing setup time.
|
||||
- **Command error handling**: The example validates exit codes and throws on failures for easier debugging.
|
||||
- **Provider resolution**: Set `COMPUTESDK_PROVIDER` to force a specific provider, or let ComputeSDK auto-detect from API keys.
|
||||
- `sandbox.runCommand(..., { background: true })` keeps the server running while your app continues.
|
||||
- `sandbox.getUrl({ port })` returns a public URL for the sandbox port.
|
||||
- Always destroy the sandbox when you are done to avoid leaking resources.
|
||||
- If sandbox creation times out, set `COMPUTESDK_TIMEOUT_MS` to a higher value (default: 120000ms).
|
||||
|
||||
## Explicit Provider Selection
|
||||
|
||||
To force a specific provider instead of auto-detection, set the `COMPUTESDK_PROVIDER` environment variable:
|
||||
|
||||
```bash
|
||||
export COMPUTESDK_PROVIDER=e2b
|
||||
```
|
||||
|
||||
Or configure programmatically using `getProviderConfigFromEnv()`:
|
||||
|
||||
```typescript
|
||||
import { compute, getProviderConfigFromEnv, type ExplicitComputeConfig } from "computesdk";
|
||||
|
||||
const config: ExplicitComputeConfig = {
|
||||
provider: "e2b",
|
||||
computesdkApiKey: process.env.COMPUTESDK_API_KEY,
|
||||
requestTimeoutMs: 120_000,
|
||||
};
|
||||
|
||||
// Automatically populate provider-specific config from environment
|
||||
const providerConfig = getProviderConfigFromEnv("e2b");
|
||||
if (Object.keys(providerConfig).length > 0) {
|
||||
(config as any).e2b = providerConfig;
|
||||
}
|
||||
|
||||
compute.setConfig(config);
|
||||
```
|
||||
|
||||
## Direct Mode (No ComputeSDK API Key)
|
||||
|
||||
To bypass the ComputeSDK gateway and use provider SDKs directly, see the provider-specific examples:
|
||||
|
||||
- [E2B](/deploy/e2b)
|
||||
- [Daytona](/deploy/daytona)
|
||||
- [Vercel](/deploy/vercel)
|
||||
- Always destroy the sandbox when done to avoid leaking resources.
|
||||
|
|
|
|||
|
|
@ -15,40 +15,37 @@ See [Daytona network limits](https://www.daytona.io/docs/en/network-limits/).
|
|||
|
||||
## TypeScript example
|
||||
|
||||
```typescript
|
||||
import { Daytona } from "@daytonaio/sdk";
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
```bash
|
||||
npm install sandbox-agent@0.3.x @daytonaio/sdk
|
||||
```
|
||||
|
||||
const daytona = new Daytona();
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { daytona } from "sandbox-agent/daytona";
|
||||
|
||||
const envVars: Record<string, string> = {};
|
||||
if (process.env.ANTHROPIC_API_KEY) envVars.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
||||
if (process.env.OPENAI_API_KEY) envVars.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
||||
|
||||
const sandbox = await daytona.create({ envVars });
|
||||
const sdk = await SandboxAgent.start({
|
||||
sandbox: daytona({
|
||||
create: { envVars },
|
||||
}),
|
||||
});
|
||||
|
||||
await sandbox.process.executeCommand(
|
||||
"curl -fsSL https://releases.rivet.dev/sandbox-agent/0.3.x/install.sh | sh"
|
||||
);
|
||||
|
||||
await sandbox.process.executeCommand("sandbox-agent install-agent claude");
|
||||
await sandbox.process.executeCommand("sandbox-agent install-agent codex");
|
||||
|
||||
await sandbox.process.executeCommand(
|
||||
"nohup sandbox-agent server --no-token --host 0.0.0.0 --port 3000 >/tmp/sandbox-agent.log 2>&1 &"
|
||||
);
|
||||
|
||||
await new Promise((r) => setTimeout(r, 2000));
|
||||
|
||||
const baseUrl = (await sandbox.getSignedPreviewUrl(3000, 4 * 60 * 60)).url;
|
||||
const sdk = await SandboxAgent.connect({ baseUrl });
|
||||
|
||||
const session = await sdk.createSession({ agent: "claude" });
|
||||
await session.prompt([{ type: "text", text: "Summarize this repository" }]);
|
||||
|
||||
await sandbox.delete();
|
||||
try {
|
||||
const session = await sdk.createSession({ agent: "claude" });
|
||||
const response = await session.prompt([
|
||||
{ type: "text", text: "Summarize this repository" },
|
||||
]);
|
||||
console.log(response.stopReason);
|
||||
} finally {
|
||||
await sdk.destroySandbox();
|
||||
}
|
||||
```
|
||||
|
||||
The `daytona` provider uses the `rivetdev/sandbox-agent:0.3.2-full` image by default and starts the server automatically.
|
||||
|
||||
## Using snapshots for faster startup
|
||||
|
||||
```typescript
|
||||
|
|
|
|||
|
|
@ -15,43 +15,43 @@ Run the published full image with all supported agents pre-installed:
|
|||
docker run --rm -p 3000:3000 \
|
||||
-e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
|
||||
-e OPENAI_API_KEY="$OPENAI_API_KEY" \
|
||||
rivetdev/sandbox-agent:0.3.1-full \
|
||||
rivetdev/sandbox-agent:0.3.2-full \
|
||||
server --no-token --host 0.0.0.0 --port 3000
|
||||
```
|
||||
|
||||
The `0.3.1-full` tag pins the exact version. The moving `full` tag is also published for contributors who want the latest full image.
|
||||
The `0.3.2-full` tag pins the exact version. The moving `full` tag is also published for contributors who want the latest full image.
|
||||
|
||||
## TypeScript with dockerode
|
||||
## TypeScript with the Docker provider
|
||||
|
||||
```bash
|
||||
npm install sandbox-agent@0.3.x dockerode get-port
|
||||
```
|
||||
|
||||
```typescript
|
||||
import Docker from "dockerode";
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { docker } from "sandbox-agent/docker";
|
||||
|
||||
const docker = new Docker();
|
||||
const PORT = 3000;
|
||||
|
||||
const container = await docker.createContainer({
|
||||
Image: "rivetdev/sandbox-agent:0.3.1-full",
|
||||
Cmd: ["server", "--no-token", "--host", "0.0.0.0", "--port", `${PORT}`],
|
||||
Env: [
|
||||
`ANTHROPIC_API_KEY=${process.env.ANTHROPIC_API_KEY}`,
|
||||
`OPENAI_API_KEY=${process.env.OPENAI_API_KEY}`,
|
||||
`CODEX_API_KEY=${process.env.CODEX_API_KEY}`,
|
||||
].filter(Boolean),
|
||||
ExposedPorts: { [`${PORT}/tcp`]: {} },
|
||||
HostConfig: {
|
||||
AutoRemove: true,
|
||||
PortBindings: { [`${PORT}/tcp`]: [{ HostPort: `${PORT}` }] },
|
||||
},
|
||||
const sdk = await SandboxAgent.start({
|
||||
sandbox: docker({
|
||||
env: [
|
||||
`ANTHROPIC_API_KEY=${process.env.ANTHROPIC_API_KEY}`,
|
||||
`OPENAI_API_KEY=${process.env.OPENAI_API_KEY}`,
|
||||
].filter(Boolean),
|
||||
}),
|
||||
});
|
||||
|
||||
await container.start();
|
||||
try {
|
||||
const session = await sdk.createSession({ agent: "codex" });
|
||||
await session.prompt([{ type: "text", text: "Summarize this repository." }]);
|
||||
} finally {
|
||||
await sdk.destroySandbox();
|
||||
}
|
||||
```
|
||||
|
||||
const baseUrl = `http://127.0.0.1:${PORT}`;
|
||||
const sdk = await SandboxAgent.connect({ baseUrl });
|
||||
The `docker` provider uses the `rivetdev/sandbox-agent:0.3.2-full` image by default. Override with `image`:
|
||||
|
||||
const session = await sdk.createSession({ agent: "codex" });
|
||||
await session.prompt([{ type: "text", text: "Summarize this repository." }]);
|
||||
```typescript
|
||||
docker({ image: "my-custom-image:latest" })
|
||||
```
|
||||
|
||||
## Building a custom image with everything preinstalled
|
||||
|
|
|
|||
|
|
@ -10,42 +10,37 @@ description: "Deploy Sandbox Agent inside an E2B sandbox."
|
|||
|
||||
## TypeScript example
|
||||
|
||||
```bash
|
||||
npm install sandbox-agent@0.3.x @e2b/code-interpreter
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { Sandbox } from "@e2b/code-interpreter";
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { e2b } from "sandbox-agent/e2b";
|
||||
|
||||
const envs: Record<string, string> = {};
|
||||
if (process.env.ANTHROPIC_API_KEY) envs.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
||||
if (process.env.OPENAI_API_KEY) envs.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
||||
|
||||
const sandbox = await Sandbox.create({ allowInternetAccess: true, envs });
|
||||
|
||||
await sandbox.commands.run(
|
||||
"curl -fsSL https://releases.rivet.dev/sandbox-agent/0.3.x/install.sh | sh"
|
||||
);
|
||||
|
||||
await sandbox.commands.run("sandbox-agent install-agent claude");
|
||||
await sandbox.commands.run("sandbox-agent install-agent codex");
|
||||
|
||||
await sandbox.commands.run(
|
||||
"sandbox-agent server --no-token --host 0.0.0.0 --port 3000",
|
||||
{ background: true, timeoutMs: 0 }
|
||||
);
|
||||
|
||||
const baseUrl = `https://${sandbox.getHost(3000)}`;
|
||||
const sdk = await SandboxAgent.connect({ baseUrl });
|
||||
|
||||
const session = await sdk.createSession({ agent: "claude" });
|
||||
const off = session.onEvent((event) => {
|
||||
console.log(event.sender, event.payload);
|
||||
const sdk = await SandboxAgent.start({
|
||||
sandbox: e2b({
|
||||
create: { envs },
|
||||
}),
|
||||
});
|
||||
|
||||
await session.prompt([{ type: "text", text: "Summarize this repository" }]);
|
||||
off();
|
||||
|
||||
await sandbox.kill();
|
||||
try {
|
||||
const session = await sdk.createSession({ agent: "claude" });
|
||||
const response = await session.prompt([
|
||||
{ type: "text", text: "Summarize this repository" },
|
||||
]);
|
||||
console.log(response.stopReason);
|
||||
} finally {
|
||||
await sdk.destroySandbox();
|
||||
}
|
||||
```
|
||||
|
||||
The `e2b` provider handles sandbox creation, Sandbox Agent installation, agent setup, and server startup automatically.
|
||||
|
||||
## Faster cold starts
|
||||
|
||||
For faster startup, create a custom E2B template with Sandbox Agent and target agents pre-installed.
|
||||
|
|
|
|||
|
|
@ -32,12 +32,15 @@ Or with npm/Bun:
|
|||
|
||||
## With the TypeScript SDK
|
||||
|
||||
The SDK can spawn and manage the server as a subprocess:
|
||||
The SDK can spawn and manage the server as a subprocess using the `local` provider:
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { local } from "sandbox-agent/local";
|
||||
|
||||
const sdk = await SandboxAgent.start();
|
||||
const sdk = await SandboxAgent.start({
|
||||
sandbox: local(),
|
||||
});
|
||||
|
||||
const session = await sdk.createSession({
|
||||
agent: "claude",
|
||||
|
|
@ -47,7 +50,21 @@ await session.prompt([
|
|||
{ type: "text", text: "Summarize this repository." },
|
||||
]);
|
||||
|
||||
await sdk.dispose();
|
||||
await sdk.destroySandbox();
|
||||
```
|
||||
|
||||
This starts the server on an available local port and connects automatically.
|
||||
|
||||
Pass options to customize the local provider:
|
||||
|
||||
```typescript
|
||||
const sdk = await SandboxAgent.start({
|
||||
sandbox: local({
|
||||
port: 3000,
|
||||
log: "inherit",
|
||||
env: {
|
||||
ANTHROPIC_API_KEY: process.env.MY_ANTHROPIC_KEY,
|
||||
},
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
|
|
|||
|
|
@ -10,88 +10,43 @@ description: "Deploy Sandbox Agent inside a Modal sandbox."
|
|||
|
||||
## TypeScript example
|
||||
|
||||
```typescript
|
||||
import { ModalClient } from "modal";
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
const modal = new ModalClient();
|
||||
const app = await modal.apps.fromName("sandbox-agent", { createIfMissing: true });
|
||||
|
||||
const image = modal.images
|
||||
.fromRegistry("ubuntu:22.04")
|
||||
.dockerfileCommands([
|
||||
"RUN apt-get update && apt-get install -y curl ca-certificates",
|
||||
"RUN curl -fsSL https://releases.rivet.dev/sandbox-agent/0.2.x/install.sh | sh",
|
||||
]);
|
||||
|
||||
const envs: Record<string, string> = {};
|
||||
if (process.env.ANTHROPIC_API_KEY) envs.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
||||
if (process.env.OPENAI_API_KEY) envs.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
||||
|
||||
const secrets = Object.keys(envs).length > 0
|
||||
? [await modal.secrets.fromObject(envs)]
|
||||
: [];
|
||||
|
||||
const sb = await modal.sandboxes.create(app, image, {
|
||||
encryptedPorts: [3000],
|
||||
secrets,
|
||||
});
|
||||
|
||||
const exec = async (cmd: string) => {
|
||||
const p = await sb.exec(["bash", "-c", cmd], { stdout: "pipe", stderr: "pipe" });
|
||||
const exitCode = await p.wait();
|
||||
if (exitCode !== 0) {
|
||||
const stderr = await p.stderr.readText();
|
||||
throw new Error(`Command failed (exit ${exitCode}): ${cmd}\n${stderr}`);
|
||||
}
|
||||
};
|
||||
|
||||
await exec("sandbox-agent install-agent claude");
|
||||
await exec("sandbox-agent install-agent codex");
|
||||
|
||||
await sb.exec(
|
||||
["bash", "-c", "sandbox-agent server --no-token --host 0.0.0.0 --port 3000 &"],
|
||||
);
|
||||
|
||||
const tunnels = await sb.tunnels();
|
||||
const baseUrl = tunnels[3000].url;
|
||||
|
||||
const sdk = await SandboxAgent.connect({ baseUrl });
|
||||
|
||||
const session = await sdk.createSession({ agent: "claude" });
|
||||
const off = session.onEvent((event) => {
|
||||
console.log(event.sender, event.payload);
|
||||
});
|
||||
|
||||
await session.prompt([{ type: "text", text: "Summarize this repository" }]);
|
||||
off();
|
||||
|
||||
await sb.terminate();
|
||||
```bash
|
||||
npm install sandbox-agent@0.3.x modal
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { modal } from "sandbox-agent/modal";
|
||||
|
||||
const secrets: Record<string, string> = {};
|
||||
if (process.env.ANTHROPIC_API_KEY) secrets.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
||||
if (process.env.OPENAI_API_KEY) secrets.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
||||
|
||||
const sdk = await SandboxAgent.start({
|
||||
sandbox: modal({
|
||||
create: { secrets },
|
||||
}),
|
||||
});
|
||||
|
||||
try {
|
||||
const session = await sdk.createSession({ agent: "claude" });
|
||||
const response = await session.prompt([
|
||||
{ type: "text", text: "Summarize this repository" },
|
||||
]);
|
||||
console.log(response.stopReason);
|
||||
} finally {
|
||||
await sdk.destroySandbox();
|
||||
}
|
||||
```
|
||||
|
||||
The `modal` provider handles app creation, image building, sandbox provisioning, agent installation, server startup, and tunnel networking automatically.
|
||||
|
||||
## Faster cold starts
|
||||
|
||||
Modal caches image layers, so the `dockerfileCommands` that install `curl` and `sandbox-agent` only run on the first build. Subsequent sandbox creates reuse the cached image.
|
||||
|
||||
## Running the test
|
||||
|
||||
The example includes a health-check test. First, build the SDK:
|
||||
|
||||
```bash
|
||||
pnpm --filter sandbox-agent build
|
||||
```
|
||||
|
||||
Then run the test with your Modal credentials:
|
||||
|
||||
```bash
|
||||
MODAL_TOKEN_ID=<your-token-id> MODAL_TOKEN_SECRET=<your-token-secret> npx vitest run
|
||||
```
|
||||
|
||||
Run from `examples/modal/`. The test will skip if credentials are not set.
|
||||
Modal caches image layers, so the Dockerfile commands that install `curl` and `sandbox-agent` only run on the first build. Subsequent sandbox creates reuse the cached image.
|
||||
|
||||
## Notes
|
||||
|
||||
- Modal sandboxes use [gVisor](https://gvisor.dev/) for strong isolation.
|
||||
- Ports are exposed via encrypted tunnels (`encryptedPorts`). Use `sb.tunnels()` to get the public HTTPS URL.
|
||||
- Environment variables (API keys) are passed as Modal [Secrets](https://modal.com/docs/guide/secrets) rather than plain env vars for security.
|
||||
- Always call `sb.terminate()` when done to avoid leaking sandbox resources.
|
||||
- Ports are exposed via encrypted tunnels (`encryptedPorts`). The provider uses `sb.tunnels()` to get the public HTTPS URL.
|
||||
- Environment variables (API keys) are passed as Modal [Secrets](https://modal.com/docs/guide/secrets) for security.
|
||||
|
|
|
|||
|
|
@ -10,52 +10,40 @@ description: "Deploy Sandbox Agent inside a Vercel Sandbox."
|
|||
|
||||
## TypeScript example
|
||||
|
||||
```typescript
|
||||
import { Sandbox } from "@vercel/sandbox";
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
const envs: Record<string, string> = {};
|
||||
if (process.env.ANTHROPIC_API_KEY) envs.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
||||
if (process.env.OPENAI_API_KEY) envs.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
||||
|
||||
const sandbox = await Sandbox.create({
|
||||
runtime: "node24",
|
||||
ports: [3000],
|
||||
});
|
||||
|
||||
const run = async (cmd: string, args: string[] = []) => {
|
||||
const result = await sandbox.runCommand({ cmd, args, env: envs });
|
||||
if (result.exitCode !== 0) {
|
||||
throw new Error(`Command failed: ${cmd} ${args.join(" ")}`);
|
||||
}
|
||||
};
|
||||
|
||||
await run("sh", ["-c", "curl -fsSL https://releases.rivet.dev/sandbox-agent/0.3.x/install.sh | sh"]);
|
||||
await run("sandbox-agent", ["install-agent", "claude"]);
|
||||
await run("sandbox-agent", ["install-agent", "codex"]);
|
||||
|
||||
await sandbox.runCommand({
|
||||
cmd: "sandbox-agent",
|
||||
args: ["server", "--no-token", "--host", "0.0.0.0", "--port", "3000"],
|
||||
env: envs,
|
||||
detached: true,
|
||||
});
|
||||
|
||||
const baseUrl = sandbox.domain(3000);
|
||||
const sdk = await SandboxAgent.connect({ baseUrl });
|
||||
|
||||
const session = await sdk.createSession({ agent: "claude" });
|
||||
|
||||
const off = session.onEvent((event) => {
|
||||
console.log(event.sender, event.payload);
|
||||
});
|
||||
|
||||
await session.prompt([{ type: "text", text: "Summarize this repository" }]);
|
||||
off();
|
||||
|
||||
await sandbox.stop();
|
||||
```bash
|
||||
npm install sandbox-agent@0.3.x @vercel/sandbox
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { vercel } from "sandbox-agent/vercel";
|
||||
|
||||
const env: Record<string, string> = {};
|
||||
if (process.env.ANTHROPIC_API_KEY) env.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
||||
if (process.env.OPENAI_API_KEY) env.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
||||
|
||||
const sdk = await SandboxAgent.start({
|
||||
sandbox: vercel({
|
||||
create: {
|
||||
runtime: "node24",
|
||||
env,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
try {
|
||||
const session = await sdk.createSession({ agent: "claude" });
|
||||
const response = await session.prompt([
|
||||
{ type: "text", text: "Summarize this repository" },
|
||||
]);
|
||||
console.log(response.stopReason);
|
||||
} finally {
|
||||
await sdk.destroySandbox();
|
||||
}
|
||||
```
|
||||
|
||||
The `vercel` provider handles sandbox creation, Sandbox Agent installation, agent setup, and server startup automatically.
|
||||
|
||||
## Authentication
|
||||
|
||||
Vercel Sandboxes support OIDC token auth (recommended) and access-token auth.
|
||||
|
|
|
|||
|
|
@ -58,20 +58,32 @@
|
|||
"icon": "server",
|
||||
"pages": [
|
||||
"deploy/local",
|
||||
"deploy/computesdk",
|
||||
"deploy/e2b",
|
||||
"deploy/daytona",
|
||||
"deploy/vercel",
|
||||
"deploy/cloudflare",
|
||||
"deploy/docker",
|
||||
"deploy/boxlite"
|
||||
"deploy/modal",
|
||||
"deploy/boxlite",
|
||||
"deploy/computesdk"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Agent",
|
||||
"pages": ["agent-sessions", "attachments", "skills-config", "mcp-config", "custom-tools"]
|
||||
"pages": [
|
||||
"agent-sessions",
|
||||
{
|
||||
"group": "Agents",
|
||||
"icon": "robot",
|
||||
"pages": ["agents/claude", "agents/codex", "agents/opencode", "agents/cursor", "agents/amp", "agents/pi"]
|
||||
},
|
||||
"attachments",
|
||||
"skills-config",
|
||||
"mcp-config",
|
||||
"custom-tools"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "System",
|
||||
|
|
@ -79,12 +91,12 @@
|
|||
},
|
||||
{
|
||||
"group": "Orchestration",
|
||||
"pages": ["architecture", "session-persistence", "observability", "multiplayer", "security"]
|
||||
"pages": ["orchestration-architecture", "session-persistence", "observability", "multiplayer", "security"]
|
||||
},
|
||||
{
|
||||
"group": "Reference",
|
||||
"pages": [
|
||||
"agent-capabilities",
|
||||
"architecture",
|
||||
"cli",
|
||||
"inspector",
|
||||
"opencode-compatibility",
|
||||
|
|
|
|||
|
|
@ -27,9 +27,7 @@ await sdk.setMcpConfig(
|
|||
// Create a session using the configured MCP servers
|
||||
const session = await sdk.createSession({
|
||||
agent: "claude",
|
||||
sessionInit: {
|
||||
cwd: "/workspace",
|
||||
},
|
||||
cwd: "/workspace",
|
||||
});
|
||||
|
||||
await session.prompt([
|
||||
|
|
|
|||
|
|
@ -20,8 +20,40 @@ Use [actor keys](https://rivet.dev/docs/actors/keys) to map each workspace to on
|
|||
|
||||
```ts Actor (server)
|
||||
import { actor, setup } from "rivetkit";
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { RivetSessionPersistDriver, type RivetPersistState } from "@sandbox-agent/persist-rivet";
|
||||
import { SandboxAgent, type SessionPersistDriver, type SessionRecord, type SessionEvent, type ListPageRequest, type ListPage, type ListEventsRequest } from "sandbox-agent";
|
||||
|
||||
interface RivetPersistData { sessions: Record<string, SessionRecord>; events: Record<string, SessionEvent[]>; }
|
||||
type RivetPersistState = { _sandboxAgentPersist: RivetPersistData };
|
||||
|
||||
class RivetSessionPersistDriver implements SessionPersistDriver {
|
||||
private readonly stateKey: string;
|
||||
private readonly ctx: { state: Record<string, unknown> };
|
||||
constructor(ctx: { state: Record<string, unknown> }, options: { stateKey?: string } = {}) {
|
||||
this.ctx = ctx;
|
||||
this.stateKey = options.stateKey ?? "_sandboxAgentPersist";
|
||||
if (!this.ctx.state[this.stateKey]) {
|
||||
this.ctx.state[this.stateKey] = { sessions: {}, events: {} };
|
||||
}
|
||||
}
|
||||
private get data(): RivetPersistData { return this.ctx.state[this.stateKey] as RivetPersistData; }
|
||||
async getSession(id: string) { const s = this.data.sessions[id]; return s ? { ...s } : undefined; }
|
||||
async listSessions(request: ListPageRequest = {}): Promise<ListPage<SessionRecord>> {
|
||||
const sorted = Object.values(this.data.sessions).sort((a, b) => a.createdAt - b.createdAt || a.id.localeCompare(b.id));
|
||||
const offset = Number(request.cursor ?? 0);
|
||||
const limit = request.limit ?? 100;
|
||||
const slice = sorted.slice(offset, offset + limit);
|
||||
return { items: slice, nextCursor: offset + slice.length < sorted.length ? String(offset + slice.length) : undefined };
|
||||
}
|
||||
async updateSession(session: SessionRecord) { this.data.sessions[session.id] = { ...session }; if (!this.data.events[session.id]) this.data.events[session.id] = []; }
|
||||
async listEvents(request: ListEventsRequest): Promise<ListPage<SessionEvent>> {
|
||||
const all = [...(this.data.events[request.sessionId] ?? [])].sort((a, b) => a.eventIndex - b.eventIndex || a.id.localeCompare(b.id));
|
||||
const offset = Number(request.cursor ?? 0);
|
||||
const limit = request.limit ?? 100;
|
||||
const slice = all.slice(offset, offset + limit);
|
||||
return { items: slice, nextCursor: offset + slice.length < all.length ? String(offset + slice.length) : undefined };
|
||||
}
|
||||
async insertEvent(sessionId: string, event: SessionEvent) { const events = this.data.events[sessionId] ?? []; events.push({ ...event, payload: JSON.parse(JSON.stringify(event.payload)) }); this.data.events[sessionId] = events; }
|
||||
}
|
||||
|
||||
type WorkspaceState = RivetPersistState & {
|
||||
sandboxId: string;
|
||||
|
|
@ -111,5 +143,5 @@ await conn.prompt({
|
|||
## Notes
|
||||
|
||||
- Keep sandbox calls actor-only. Browser clients should not call Sandbox Agent directly.
|
||||
- Use `@sandbox-agent/persist-rivet` so session history persists in actor state.
|
||||
- Copy the Rivet persist driver from the example above into your project so session history persists in actor state.
|
||||
- For client connection patterns, see [Rivet JavaScript client](https://rivet.dev/docs/clients/javascript).
|
||||
|
|
|
|||
43
docs/orchestration-architecture.mdx
Normal file
43
docs/orchestration-architecture.mdx
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
title: "Orchestration Architecture"
|
||||
description: "Production topology, backend requirements, and session persistence."
|
||||
icon: "sitemap"
|
||||
---
|
||||
|
||||
This page covers production topology and backend requirements. Read [Architecture](/architecture) first for an overview of how the server, SDK, and agent processes fit together.
|
||||
|
||||
## Suggested Topology
|
||||
|
||||
Run the SDK on your backend, then call it from your frontend.
|
||||
|
||||
This extra hop is recommended because it keeps auth/token logic on the backend and makes persistence simpler.
|
||||
|
||||
```mermaid placement="top-right"
|
||||
flowchart LR
|
||||
BROWSER["Browser"]
|
||||
subgraph BACKEND["Your backend"]
|
||||
direction TB
|
||||
SDK["Sandbox Agent SDK"]
|
||||
end
|
||||
subgraph SANDBOX_SIMPLE["Sandbox"]
|
||||
SERVER_SIMPLE["Sandbox Agent server"]
|
||||
end
|
||||
|
||||
BROWSER --> BACKEND
|
||||
BACKEND --> SDK --> SERVER_SIMPLE
|
||||
```
|
||||
|
||||
### Backend requirements
|
||||
|
||||
Your backend layer needs to handle:
|
||||
|
||||
- **Long-running connections**: prompts can take minutes.
|
||||
- **Session affinity**: follow-up messages must reach the same session.
|
||||
- **State between requests**: session metadata and event history must persist across requests.
|
||||
- **Graceful recovery**: sessions should resume after backend restarts.
|
||||
|
||||
We recommend [Rivet](https://rivet.dev) over serverless because actors natively support the long-lived connections, session routing, and state persistence that agent workloads require.
|
||||
|
||||
## Session persistence
|
||||
|
||||
For storage driver options and replay behavior, see [Persisting Sessions](/session-persistence).
|
||||
|
|
@ -1,281 +1,370 @@
|
|||
---
|
||||
title: "Quickstart"
|
||||
description: "Start the server and send your first message."
|
||||
description: "Get a coding agent running in a sandbox in under a minute."
|
||||
icon: "rocket"
|
||||
---
|
||||
|
||||
<Steps>
|
||||
<Step title="Install skill (optional)">
|
||||
<Step title="Install">
|
||||
<Tabs>
|
||||
<Tab title="npx">
|
||||
<Tab title="npm">
|
||||
```bash
|
||||
npx skills add rivet-dev/skills -s sandbox-agent
|
||||
npm install sandbox-agent@0.3.x
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="bunx">
|
||||
<Tab title="bun">
|
||||
```bash
|
||||
bunx skills add rivet-dev/skills -s sandbox-agent
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Step>
|
||||
|
||||
<Step title="Set environment variables">
|
||||
Each coding agent requires API keys to connect to their respective LLM providers.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Local shell">
|
||||
```bash
|
||||
export ANTHROPIC_API_KEY="sk-ant-..."
|
||||
export OPENAI_API_KEY="sk-..."
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="E2B">
|
||||
```typescript
|
||||
import { Sandbox } from "@e2b/code-interpreter";
|
||||
|
||||
const envs: Record<string, string> = {};
|
||||
if (process.env.ANTHROPIC_API_KEY) envs.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
||||
if (process.env.OPENAI_API_KEY) envs.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
||||
|
||||
const sandbox = await Sandbox.create({ envs });
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="Daytona">
|
||||
```typescript
|
||||
import { Daytona } from "@daytonaio/sdk";
|
||||
|
||||
const envVars: Record<string, string> = {};
|
||||
if (process.env.ANTHROPIC_API_KEY) envVars.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
||||
if (process.env.OPENAI_API_KEY) envVars.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
||||
|
||||
const daytona = new Daytona();
|
||||
const sandbox = await daytona.create({
|
||||
snapshot: "sandbox-agent-ready",
|
||||
envVars,
|
||||
});
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="Docker">
|
||||
```bash
|
||||
docker run -p 2468:2468 \
|
||||
-e ANTHROPIC_API_KEY="sk-ant-..." \
|
||||
-e OPENAI_API_KEY="sk-..." \
|
||||
rivetdev/sandbox-agent:0.3.1-full \
|
||||
server --no-token --host 0.0.0.0 --port 2468
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Extracting API keys from current machine">
|
||||
Use `sandbox-agent credentials extract-env --export` to extract your existing API keys (Anthropic, OpenAI, etc.) from local Claude Code or Codex config files.
|
||||
</Accordion>
|
||||
<Accordion title="Testing without API keys">
|
||||
Use the `mock` agent for SDK and integration testing without provider credentials.
|
||||
</Accordion>
|
||||
<Accordion title="Multi-tenant and per-user billing">
|
||||
For per-tenant token tracking, budget enforcement, or usage-based billing, see [LLM Credentials](/llm-credentials) for gateway options like OpenRouter, LiteLLM, and Portkey.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
</Step>
|
||||
|
||||
<Step title="Run the server">
|
||||
<Tabs>
|
||||
<Tab title="curl">
|
||||
Install and run the binary directly.
|
||||
|
||||
```bash
|
||||
curl -fsSL https://releases.rivet.dev/sandbox-agent/0.3.x/install.sh | sh
|
||||
sandbox-agent server --no-token --host 0.0.0.0 --port 2468
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="npx">
|
||||
Run without installing globally.
|
||||
|
||||
```bash
|
||||
npx @sandbox-agent/cli@0.3.x server --no-token --host 0.0.0.0 --port 2468
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="bunx">
|
||||
Run without installing globally.
|
||||
|
||||
```bash
|
||||
bunx @sandbox-agent/cli@0.3.x server --no-token --host 0.0.0.0 --port 2468
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="npm i -g">
|
||||
Install globally, then run.
|
||||
|
||||
```bash
|
||||
npm install -g @sandbox-agent/cli@0.3.x
|
||||
sandbox-agent server --no-token --host 0.0.0.0 --port 2468
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="bun add -g">
|
||||
Install globally, then run.
|
||||
|
||||
```bash
|
||||
bun add -g @sandbox-agent/cli@0.3.x
|
||||
bun add sandbox-agent@0.3.x
|
||||
# Allow Bun to run postinstall scripts for native binaries (required for SandboxAgent.start()).
|
||||
bun pm -g trust @sandbox-agent/cli-linux-x64 @sandbox-agent/cli-linux-arm64 @sandbox-agent/cli-darwin-arm64 @sandbox-agent/cli-darwin-x64 @sandbox-agent/cli-win32-x64
|
||||
sandbox-agent server --no-token --host 0.0.0.0 --port 2468
|
||||
bun pm trust @sandbox-agent/cli-linux-x64 @sandbox-agent/cli-linux-arm64 @sandbox-agent/cli-darwin-arm64 @sandbox-agent/cli-darwin-x64 @sandbox-agent/cli-win32-x64
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Step>
|
||||
|
||||
<Tab title="Node.js (local)">
|
||||
For local development, use `SandboxAgent.start()` to spawn and manage the server as a subprocess.
|
||||
<Step title="Start the sandbox">
|
||||
`SandboxAgent.start()` provisions a sandbox, starts a lightweight [Sandbox Agent server](/architecture) inside it, and connects your SDK client.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Local">
|
||||
```bash
|
||||
npm install sandbox-agent@0.3.x
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { local } from "sandbox-agent/local";
|
||||
|
||||
const sdk = await SandboxAgent.start();
|
||||
// Runs on your machine. Inherits process.env automatically.
|
||||
const client = await SandboxAgent.start({
|
||||
sandbox: local(),
|
||||
});
|
||||
```
|
||||
|
||||
See [Local deploy guide](/deploy/local)
|
||||
</Tab>
|
||||
|
||||
<Tab title="Bun (local)">
|
||||
For local development, use `SandboxAgent.start()` to spawn and manage the server as a subprocess.
|
||||
|
||||
<Tab title="E2B">
|
||||
```bash
|
||||
bun add sandbox-agent@0.3.x
|
||||
# Allow Bun to run postinstall scripts for native binaries (required for SandboxAgent.start()).
|
||||
bun pm trust @sandbox-agent/cli-linux-x64 @sandbox-agent/cli-linux-arm64 @sandbox-agent/cli-darwin-arm64 @sandbox-agent/cli-darwin-x64 @sandbox-agent/cli-win32-x64
|
||||
npm install sandbox-agent@0.3.x @e2b/code-interpreter
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { e2b } from "sandbox-agent/e2b";
|
||||
|
||||
const sdk = await SandboxAgent.start();
|
||||
// Provisions a cloud sandbox on E2B, installs the server, and connects.
|
||||
const client = await SandboxAgent.start({
|
||||
sandbox: e2b(),
|
||||
});
|
||||
```
|
||||
|
||||
See [E2B deploy guide](/deploy/e2b)
|
||||
</Tab>
|
||||
|
||||
<Tab title="Build from source">
|
||||
If you're running from source instead of the installed CLI.
|
||||
|
||||
<Tab title="Daytona">
|
||||
```bash
|
||||
cargo run -p sandbox-agent -- server --no-token --host 0.0.0.0 --port 2468
|
||||
npm install sandbox-agent@0.3.x @daytonaio/sdk
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { daytona } from "sandbox-agent/daytona";
|
||||
|
||||
// Provisions a Daytona workspace with the server pre-installed.
|
||||
const client = await SandboxAgent.start({
|
||||
sandbox: daytona(),
|
||||
});
|
||||
```
|
||||
|
||||
See [Daytona deploy guide](/deploy/daytona)
|
||||
</Tab>
|
||||
|
||||
<Tab title="Vercel">
|
||||
```bash
|
||||
npm install sandbox-agent@0.3.x @vercel/sandbox
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { vercel } from "sandbox-agent/vercel";
|
||||
|
||||
// Provisions a Vercel sandbox with the server installed on boot.
|
||||
const client = await SandboxAgent.start({
|
||||
sandbox: vercel(),
|
||||
});
|
||||
```
|
||||
|
||||
See [Vercel deploy guide](/deploy/vercel)
|
||||
</Tab>
|
||||
|
||||
<Tab title="Modal">
|
||||
```bash
|
||||
npm install sandbox-agent@0.3.x modal
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { modal } from "sandbox-agent/modal";
|
||||
|
||||
// Builds a container image with agents pre-installed (cached after first run),
|
||||
// starts a Modal sandbox from that image, and connects.
|
||||
const client = await SandboxAgent.start({
|
||||
sandbox: modal(),
|
||||
});
|
||||
```
|
||||
|
||||
See [Modal deploy guide](/deploy/modal)
|
||||
</Tab>
|
||||
|
||||
<Tab title="Cloudflare">
|
||||
```bash
|
||||
npm install sandbox-agent@0.3.x @cloudflare/sandbox
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { cloudflare } from "sandbox-agent/cloudflare";
|
||||
import { SandboxClient } from "@cloudflare/sandbox";
|
||||
|
||||
// Uses the Cloudflare Sandbox SDK to provision and connect.
|
||||
// The Cloudflare SDK handles server lifecycle internally.
|
||||
const cfSandboxClient = new SandboxClient();
|
||||
const client = await SandboxAgent.start({
|
||||
sandbox: cloudflare({ sdk: cfSandboxClient }),
|
||||
});
|
||||
```
|
||||
|
||||
See [Cloudflare deploy guide](/deploy/cloudflare)
|
||||
</Tab>
|
||||
|
||||
<Tab title="Docker">
|
||||
```bash
|
||||
npm install sandbox-agent@0.3.x dockerode get-port
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { docker } from "sandbox-agent/docker";
|
||||
|
||||
// Runs a Docker container locally. Good for testing.
|
||||
const client = await SandboxAgent.start({
|
||||
sandbox: docker(),
|
||||
});
|
||||
```
|
||||
|
||||
See [Docker deploy guide](/deploy/docker)
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
Binding to `0.0.0.0` allows the server to accept connections from any network interface, which is required when running inside a sandbox where clients connect remotely.
|
||||
<div style={{ height: "1rem" }} />
|
||||
|
||||
**More info:**
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Configuring token">
|
||||
Tokens are usually not required. Most sandbox providers (E2B, Daytona, etc.) already secure networking at the infrastructure layer.
|
||||
<Accordion title="Passing LLM credentials">
|
||||
Agents need API keys for their LLM provider. Each provider passes credentials differently:
|
||||
|
||||
If you expose the server publicly, use `--token "$SANDBOX_TOKEN"` to require authentication:
|
||||
```typescript
|
||||
// Local — inherits process.env automatically
|
||||
|
||||
```bash
|
||||
sandbox-agent server --token "$SANDBOX_TOKEN" --host 0.0.0.0 --port 2468
|
||||
// E2B
|
||||
e2b({ create: { envs: { ANTHROPIC_API_KEY: "..." } } })
|
||||
|
||||
// Daytona
|
||||
daytona({ create: { envVars: { ANTHROPIC_API_KEY: "..." } } })
|
||||
|
||||
// Vercel
|
||||
vercel({ create: { env: { ANTHROPIC_API_KEY: "..." } } })
|
||||
|
||||
// Modal
|
||||
modal({ create: { secrets: { ANTHROPIC_API_KEY: "..." } } })
|
||||
|
||||
// Docker
|
||||
docker({ env: ["ANTHROPIC_API_KEY=..."] })
|
||||
```
|
||||
|
||||
Then pass the token when connecting:
|
||||
For multi-tenant billing, per-user keys, and gateway options, see [LLM Credentials](/llm-credentials).
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Implementing a custom provider">
|
||||
Implement the `SandboxProvider` interface to use any sandbox platform:
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent, type SandboxProvider } from "sandbox-agent";
|
||||
|
||||
const myProvider: SandboxProvider = {
|
||||
name: "my-provider",
|
||||
async create() {
|
||||
// Provision a sandbox, install & start the server, return an ID
|
||||
return "sandbox-123";
|
||||
},
|
||||
async destroy(sandboxId) {
|
||||
// Tear down the sandbox
|
||||
},
|
||||
async getUrl(sandboxId) {
|
||||
// Return the Sandbox Agent server URL
|
||||
return `https://${sandboxId}.my-platform.dev:3000`;
|
||||
},
|
||||
};
|
||||
|
||||
const client = await SandboxAgent.start({
|
||||
sandbox: myProvider,
|
||||
});
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Connecting to an existing server">
|
||||
If you already have a Sandbox Agent server running, connect directly:
|
||||
|
||||
```typescript
|
||||
const client = await SandboxAgent.connect({
|
||||
baseUrl: "http://127.0.0.1:2468",
|
||||
});
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Starting the server manually">
|
||||
<Tabs>
|
||||
<Tab title="TypeScript">
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: "http://your-server:2468",
|
||||
token: process.env.SANDBOX_TOKEN,
|
||||
});
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="curl">
|
||||
```bash
|
||||
curl "http://your-server:2468/v1/health" \
|
||||
-H "Authorization: Bearer $SANDBOX_TOKEN"
|
||||
curl -fsSL https://releases.rivet.dev/sandbox-agent/0.3.x/install.sh | sh
|
||||
sandbox-agent server --no-token --host 0.0.0.0 --port 2468
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="CLI">
|
||||
<Tab title="npx">
|
||||
```bash
|
||||
sandbox-agent --token "$SANDBOX_TOKEN" api agents list \
|
||||
--endpoint http://your-server:2468
|
||||
npx @sandbox-agent/cli@0.3.x server --no-token --host 0.0.0.0 --port 2468
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Docker">
|
||||
```bash
|
||||
docker run -p 2468:2468 \
|
||||
-e ANTHROPIC_API_KEY="sk-ant-..." \
|
||||
-e OPENAI_API_KEY="sk-..." \
|
||||
rivetdev/sandbox-agent:0.3.2-full \
|
||||
server --no-token --host 0.0.0.0 --port 2468
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Accordion>
|
||||
<Accordion title="CORS">
|
||||
If you're calling the server from a browser, see the [CORS configuration guide](/cors).
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
</Step>
|
||||
|
||||
<Step title="Install agents (optional)">
|
||||
Supported agent IDs: `claude`, `codex`, `opencode`, `amp`, `pi`, `cursor`, `mock`.
|
||||
<Step title="Create a session and send a prompt">
|
||||
<CodeGroup>
|
||||
|
||||
To preinstall agents:
|
||||
```typescript Claude
|
||||
const session = await client.createSession({
|
||||
agent: "claude",
|
||||
});
|
||||
|
||||
```bash
|
||||
sandbox-agent install-agent --all
|
||||
```
|
||||
session.onEvent((event) => {
|
||||
console.log(event.sender, event.payload);
|
||||
});
|
||||
|
||||
If agents are not installed up front, they are lazily installed when creating a session.
|
||||
const result = await session.prompt([
|
||||
{ type: "text", text: "Summarize the repository and suggest next steps." },
|
||||
]);
|
||||
|
||||
console.log(result.stopReason);
|
||||
```
|
||||
|
||||
```typescript Codex
|
||||
const session = await client.createSession({
|
||||
agent: "codex",
|
||||
});
|
||||
|
||||
session.onEvent((event) => {
|
||||
console.log(event.sender, event.payload);
|
||||
});
|
||||
|
||||
const result = await session.prompt([
|
||||
{ type: "text", text: "Summarize the repository and suggest next steps." },
|
||||
]);
|
||||
|
||||
console.log(result.stopReason);
|
||||
```
|
||||
|
||||
```typescript OpenCode
|
||||
const session = await client.createSession({
|
||||
agent: "opencode",
|
||||
});
|
||||
|
||||
session.onEvent((event) => {
|
||||
console.log(event.sender, event.payload);
|
||||
});
|
||||
|
||||
const result = await session.prompt([
|
||||
{ type: "text", text: "Summarize the repository and suggest next steps." },
|
||||
]);
|
||||
|
||||
console.log(result.stopReason);
|
||||
```
|
||||
|
||||
```typescript Cursor
|
||||
const session = await client.createSession({
|
||||
agent: "cursor",
|
||||
});
|
||||
|
||||
session.onEvent((event) => {
|
||||
console.log(event.sender, event.payload);
|
||||
});
|
||||
|
||||
const result = await session.prompt([
|
||||
{ type: "text", text: "Summarize the repository and suggest next steps." },
|
||||
]);
|
||||
|
||||
console.log(result.stopReason);
|
||||
```
|
||||
|
||||
```typescript Amp
|
||||
const session = await client.createSession({
|
||||
agent: "amp",
|
||||
});
|
||||
|
||||
session.onEvent((event) => {
|
||||
console.log(event.sender, event.payload);
|
||||
});
|
||||
|
||||
const result = await session.prompt([
|
||||
{ type: "text", text: "Summarize the repository and suggest next steps." },
|
||||
]);
|
||||
|
||||
console.log(result.stopReason);
|
||||
```
|
||||
|
||||
```typescript Pi
|
||||
const session = await client.createSession({
|
||||
agent: "pi",
|
||||
});
|
||||
|
||||
session.onEvent((event) => {
|
||||
console.log(event.sender, event.payload);
|
||||
});
|
||||
|
||||
const result = await session.prompt([
|
||||
{ type: "text", text: "Summarize the repository and suggest next steps." },
|
||||
]);
|
||||
|
||||
console.log(result.stopReason);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
See [Agent Sessions](/agent-sessions) for the full sessions API.
|
||||
</Step>
|
||||
|
||||
<Step title="Create a session">
|
||||
<Step title="Clean up">
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: "http://127.0.0.1:2468",
|
||||
});
|
||||
|
||||
const session = await sdk.createSession({
|
||||
agent: "claude",
|
||||
sessionInit: {
|
||||
cwd: "/",
|
||||
mcpServers: [],
|
||||
},
|
||||
});
|
||||
|
||||
console.log(session.id);
|
||||
await client.destroySandbox(); // tears down the sandbox and disconnects
|
||||
```
|
||||
|
||||
Use `client.dispose()` instead to disconnect without destroying the sandbox (for reconnecting later).
|
||||
</Step>
|
||||
|
||||
<Step title="Send a message">
|
||||
```typescript
|
||||
const result = await session.prompt([
|
||||
{ type: "text", text: "Summarize the repository and suggest next steps." },
|
||||
]);
|
||||
|
||||
console.log(result.stopReason);
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Read events">
|
||||
```typescript
|
||||
const off = session.onEvent((event) => {
|
||||
console.log(event.sender, event.payload);
|
||||
});
|
||||
|
||||
const page = await sdk.getEvents({
|
||||
sessionId: session.id,
|
||||
limit: 50,
|
||||
});
|
||||
|
||||
console.log(page.items.length);
|
||||
off();
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Test with Inspector">
|
||||
Open the Inspector UI at `/ui/` on your server (for example, `http://localhost:2468/ui/`) to inspect sessions and events in a GUI.
|
||||
<Step title="Inspect with the UI">
|
||||
Open the Inspector at `/ui/` on your server (e.g. `http://localhost:2468/ui/`) to view sessions and events in a GUI.
|
||||
|
||||
<Frame>
|
||||
<img src="/images/inspector.png" alt="Sandbox Agent Inspector" />
|
||||
|
|
@ -283,16 +372,44 @@ icon: "rocket"
|
|||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Full example
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { e2b } from "sandbox-agent/e2b";
|
||||
|
||||
const client = await SandboxAgent.start({
|
||||
sandbox: e2b({
|
||||
create: {
|
||||
envs: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY },
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
try {
|
||||
const session = await client.createSession({ agent: "claude" });
|
||||
|
||||
session.onEvent((event) => {
|
||||
console.log(`[${event.sender}]`, JSON.stringify(event.payload));
|
||||
});
|
||||
|
||||
const result = await session.prompt([
|
||||
{ type: "text", text: "Write a function that checks if a number is prime." },
|
||||
]);
|
||||
|
||||
console.log("Done:", result.stopReason);
|
||||
} finally {
|
||||
await client.destroySandbox();
|
||||
}
|
||||
```
|
||||
|
||||
## Next steps
|
||||
|
||||
<CardGroup cols={3}>
|
||||
<Card title="Session Persistence" icon="database" href="/session-persistence">
|
||||
Configure in-memory, Rivet Actor state, IndexedDB, SQLite, and Postgres persistence.
|
||||
<CardGroup cols={2}>
|
||||
<Card title="SDK Overview" icon="compass" href="/sdk-overview">
|
||||
Full TypeScript SDK API surface.
|
||||
</Card>
|
||||
<Card title="Deploy to a Sandbox" icon="box" href="/deploy/local">
|
||||
Deploy your agent to E2B, Daytona, Docker, Vercel, or Cloudflare.
|
||||
</Card>
|
||||
<Card title="SDK Overview" icon="compass" href="/sdk-overview">
|
||||
Use the latest TypeScript SDK API.
|
||||
Deploy to E2B, Daytona, Docker, Vercel, or Cloudflare.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
|
|
|||
|
|
@ -23,12 +23,6 @@ The TypeScript SDK is centered on `sandbox-agent` and its `SandboxAgent` class.
|
|||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Optional persistence drivers
|
||||
|
||||
```bash
|
||||
npm install @sandbox-agent/persist-indexeddb@0.3.x @sandbox-agent/persist-sqlite@0.3.x @sandbox-agent/persist-postgres@0.3.x
|
||||
```
|
||||
|
||||
## Optional React components
|
||||
|
||||
```bash
|
||||
|
|
@ -68,15 +62,12 @@ const sdk = await SandboxAgent.connect({
|
|||
controller.abort();
|
||||
```
|
||||
|
||||
With persistence:
|
||||
With persistence (see [Persisting Sessions](/session-persistence) for driver options):
|
||||
|
||||
```ts
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { SQLiteSessionPersistDriver } from "@sandbox-agent/persist-sqlite";
|
||||
import { SandboxAgent, InMemorySessionPersistDriver } from "sandbox-agent";
|
||||
|
||||
const persist = new SQLiteSessionPersistDriver({
|
||||
filename: "./sessions.db",
|
||||
});
|
||||
const persist = new InMemorySessionPersistDriver();
|
||||
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: "http://127.0.0.1:2468",
|
||||
|
|
@ -84,25 +75,40 @@ const sdk = await SandboxAgent.connect({
|
|||
});
|
||||
```
|
||||
|
||||
Local autospawn (Node.js only):
|
||||
Local spawn with a sandbox provider:
|
||||
|
||||
```ts
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { local } from "sandbox-agent/local";
|
||||
|
||||
const localSdk = await SandboxAgent.start();
|
||||
const sdk = await SandboxAgent.start({
|
||||
sandbox: local(),
|
||||
});
|
||||
|
||||
await localSdk.dispose();
|
||||
// sdk.sandboxId — prefixed provider ID (e.g. "local/127.0.0.1:2468")
|
||||
|
||||
await sdk.destroySandbox(); // tears down sandbox + disposes client
|
||||
```
|
||||
|
||||
`SandboxAgent.start(...)` requires a `sandbox` provider. Built-in providers:
|
||||
|
||||
| Import | Provider |
|
||||
|--------|----------|
|
||||
| `sandbox-agent/local` | Local subprocess |
|
||||
| `sandbox-agent/docker` | Docker container |
|
||||
| `sandbox-agent/e2b` | E2B sandbox |
|
||||
| `sandbox-agent/daytona` | Daytona workspace |
|
||||
| `sandbox-agent/vercel` | Vercel Sandbox |
|
||||
| `sandbox-agent/cloudflare` | Cloudflare Sandbox |
|
||||
|
||||
Use `sdk.dispose()` to disconnect without destroying the sandbox, or `sdk.destroySandbox()` to tear down both.
|
||||
|
||||
## Session flow
|
||||
|
||||
```ts
|
||||
const session = await sdk.createSession({
|
||||
agent: "mock",
|
||||
sessionInit: {
|
||||
cwd: "/",
|
||||
mcpServers: [],
|
||||
},
|
||||
cwd: "/",
|
||||
});
|
||||
|
||||
const prompt = await session.prompt([
|
||||
|
|
@ -223,6 +229,7 @@ Parameters:
|
|||
- `token` (optional): Bearer token for authenticated servers
|
||||
- `headers` (optional): Additional request headers
|
||||
- `fetch` (optional): Custom fetch implementation used by SDK HTTP and session calls
|
||||
- `skipHealthCheck` (optional): set `true` to skip the startup `/v1/health` wait
|
||||
- `waitForHealth` (optional, defaults to enabled): waits for `/v1/health` before HTTP helpers and session setup proceed; pass `false` to disable or `{ timeoutMs }` to bound the wait
|
||||
- `signal` (optional): aborts the startup `/v1/health` wait used by `connect()`
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ description: "Backend-first auth and access control patterns."
|
|||
icon: "shield"
|
||||
---
|
||||
|
||||
As covered in [Architecture](/architecture), run the Sandbox Agent client on your backend, not in the browser.
|
||||
As covered in [Orchestration Architecture](/orchestration-architecture), run the Sandbox Agent client on your backend, not in the browser.
|
||||
|
||||
This keeps sandbox credentials private and gives you one place for authz, rate limiting, and audit logging.
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ export const workspace = actor({
|
|||
|
||||
const session = await sdk.createSession({
|
||||
agent: "claude",
|
||||
sessionInit: { cwd: "/workspace" },
|
||||
cwd: "/workspace",
|
||||
});
|
||||
|
||||
session.onEvent((event) => {
|
||||
|
|
|
|||
|
|
@ -10,14 +10,22 @@ With persistence enabled, sessions can be restored after runtime/session loss. S
|
|||
|
||||
Each driver stores:
|
||||
|
||||
- `SessionRecord` (`id`, `agent`, `agentSessionId`, `lastConnectionId`, `createdAt`, optional `destroyedAt`, optional `sessionInit`)
|
||||
- `SessionRecord` (`id`, `agent`, `agentSessionId`, `lastConnectionId`, `createdAt`, optional `destroyedAt`, optional `sandboxId`, optional `sessionInit`, optional `configOptions`, optional `modes`)
|
||||
- `SessionEvent` (`id`, `eventIndex`, `sessionId`, `connectionId`, `sender`, `payload`, `createdAt`)
|
||||
|
||||
## Persistence drivers
|
||||
|
||||
### In-memory
|
||||
### Rivet
|
||||
|
||||
Best for local dev and ephemeral workloads.
|
||||
Recommended for sandbox orchestration with actor state. See [Multiplayer](/multiplayer) for a full Rivet actor example with persistence in actor state.
|
||||
|
||||
### IndexedDB (browser)
|
||||
|
||||
Best for browser apps that should survive reloads. See the [Inspector source](https://github.com/rivet-dev/sandbox-agent/tree/main/frontend/packages/inspector/src/persist-indexeddb.ts) for a complete IndexedDB driver you can copy into your project.
|
||||
|
||||
### In-memory (built-in)
|
||||
|
||||
Best for local dev and ephemeral workloads. No extra dependencies required.
|
||||
|
||||
```ts
|
||||
import { InMemorySessionPersistDriver, SandboxAgent } from "sandbox-agent";
|
||||
|
|
@ -33,91 +41,17 @@ const sdk = await SandboxAgent.connect({
|
|||
});
|
||||
```
|
||||
|
||||
### Rivet
|
||||
|
||||
Recommended for sandbox orchestration with actor state.
|
||||
|
||||
```bash
|
||||
npm install @sandbox-agent/persist-rivet@0.3.x
|
||||
```
|
||||
|
||||
```ts
|
||||
import { actor } from "rivetkit";
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { RivetSessionPersistDriver, type RivetPersistState } from "@sandbox-agent/persist-rivet";
|
||||
|
||||
type PersistedState = RivetPersistState & {
|
||||
sandboxId: string;
|
||||
baseUrl: string;
|
||||
};
|
||||
|
||||
export default actor({
|
||||
createState: async () => {
|
||||
return {
|
||||
sandboxId: "sbx_123",
|
||||
baseUrl: "http://127.0.0.1:2468",
|
||||
} satisfies Partial<PersistedState>;
|
||||
},
|
||||
createVars: async (c) => {
|
||||
const persist = new RivetSessionPersistDriver(c);
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: c.state.baseUrl,
|
||||
persist,
|
||||
});
|
||||
|
||||
const session = await sdk.resumeOrCreateSession({ id: "default", agent: "codex" });
|
||||
|
||||
const unsubscribe = session.onEvent((event) => {
|
||||
c.broadcast("session.event", event);
|
||||
});
|
||||
|
||||
return { sdk, session, unsubscribe };
|
||||
},
|
||||
actions: {
|
||||
sendMessage: async (c, message: string) => {
|
||||
await c.vars.session.prompt([{ type: "text", text: message }]);
|
||||
},
|
||||
},
|
||||
onSleep: async (c) => {
|
||||
c.vars.unsubscribe?.();
|
||||
await c.vars.sdk.dispose();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### IndexedDB
|
||||
|
||||
Best for browser apps that should survive reloads.
|
||||
|
||||
```bash
|
||||
npm install @sandbox-agent/persist-indexeddb@0.3.x
|
||||
```
|
||||
|
||||
```ts
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { IndexedDbSessionPersistDriver } from "@sandbox-agent/persist-indexeddb";
|
||||
|
||||
const persist = new IndexedDbSessionPersistDriver({
|
||||
databaseName: "sandbox-agent-session-store",
|
||||
});
|
||||
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: "http://127.0.0.1:2468",
|
||||
persist,
|
||||
});
|
||||
```
|
||||
|
||||
### SQLite
|
||||
|
||||
Best for local/server Node apps that need durable storage without a DB server.
|
||||
|
||||
```bash
|
||||
npm install @sandbox-agent/persist-sqlite@0.3.x
|
||||
npm install better-sqlite3
|
||||
```
|
||||
|
||||
```ts
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { SQLiteSessionPersistDriver } from "@sandbox-agent/persist-sqlite";
|
||||
import { SQLiteSessionPersistDriver } from "./persist.ts";
|
||||
|
||||
const persist = new SQLiteSessionPersistDriver({
|
||||
filename: "./sandbox-agent.db",
|
||||
|
|
@ -129,17 +63,19 @@ const sdk = await SandboxAgent.connect({
|
|||
});
|
||||
```
|
||||
|
||||
See the [full SQLite example](https://github.com/rivet-dev/sandbox-agent/tree/main/examples/persist-sqlite) for the complete driver implementation you can copy into your project.
|
||||
|
||||
### Postgres
|
||||
|
||||
Use when you already run Postgres and want shared relational storage.
|
||||
|
||||
```bash
|
||||
npm install @sandbox-agent/persist-postgres@0.3.x
|
||||
npm install pg
|
||||
```
|
||||
|
||||
```ts
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { PostgresSessionPersistDriver } from "@sandbox-agent/persist-postgres";
|
||||
import { PostgresSessionPersistDriver } from "./persist.ts";
|
||||
|
||||
const persist = new PostgresSessionPersistDriver({
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
|
|
@ -152,6 +88,8 @@ const sdk = await SandboxAgent.connect({
|
|||
});
|
||||
```
|
||||
|
||||
See the [full Postgres example](https://github.com/rivet-dev/sandbox-agent/tree/main/examples/persist-postgres) for the complete driver implementation you can copy into your project.
|
||||
|
||||
### Custom driver
|
||||
|
||||
Implement `SessionPersistDriver` for custom backends.
|
||||
|
|
@ -160,11 +98,11 @@ Implement `SessionPersistDriver` for custom backends.
|
|||
import type { SessionPersistDriver } from "sandbox-agent";
|
||||
|
||||
class MyDriver implements SessionPersistDriver {
|
||||
async getSession(id) { return null; }
|
||||
async getSession(id) { return undefined; }
|
||||
async listSessions(request) { return { items: [] }; }
|
||||
async updateSession(session) {}
|
||||
async listEvents(request) { return { items: [] }; }
|
||||
async insertEvent(event) {}
|
||||
async insertEvent(sessionId, event) {}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -35,9 +35,7 @@ await sdk.setSkillsConfig(
|
|||
// Create a session using the configured skills
|
||||
const session = await sdk.createSession({
|
||||
agent: "claude",
|
||||
sessionInit: {
|
||||
cwd: "/workspace",
|
||||
},
|
||||
cwd: "/workspace",
|
||||
});
|
||||
|
||||
await session.prompt([
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue