@bunny-agent/sdk
Plug Coding Agent superpowers into your product — in one afternoon.
Turn Claude Code, Anthropic Agent SDK, Codex CLI, OpenCode, and other Coding Agents into an AI SDK-compatible model you can stream from any backend and render in any React UI.
Why @bunny-agent/sdk?
Coding Agents (Claude Code, Codex CLI, Gemini CLI, OpenCode …) already have:
- ✅ Sophisticated memory & context management
- ✅ Battle-tested tool use (bash, file I/O, web search)
- ✅ MCP server ecosystem
- ✅ Refined prompts from millions of interactions
@bunny-agent/sdk lets you harness all of that inside your own product — as a standard AI SDK model, with zero prompt engineering.
Your App → @bunny-agent/sdk → Coding Agent (Claude / Codex / …)
↕
Sandbox (Local or Cloud)
Features
| Feature | Description |
|---|---|
| 🔌 AI SDK Provider | createBunnyAgent() returns a model you pass to streamText / generateText |
| ⚛️ React Hooks | useBunnyAgentChat, useArtifacts, useWriteTool, useAskUserQuestion |
| 🖥️ Local Mode | Built-in LocalMachine (no isolation) and SrtSandbox (OS-level isolation) — run on your machine for Desktop apps & debugging |
| ☁️ Cloud Sandboxes | Plug in Sandock, E2B, or Daytona for isolated cloud execution |
| 🎨 Agent Templates | Markdown-based templates turn a generic agent into a domain expert |
| 🔄 Multi-turn Sessions | Resume conversations with full filesystem continuity |
Quick Start
1. Install
npm install @bunny-agent/sdk ai
2. Backend — Create an API Route (Next.js)
// app/api/ai/route.ts
import { createBunnyAgent, LocalMachine } from "@bunny-agent/sdk";
import {
convertToModelMessages,
createUIMessageStream,
createUIMessageStreamResponse,
streamText,
} from "ai";
export async function POST(request: Request) {
const { messages } = await request.json();
const sandbox = new LocalMachine({
workdir: process.cwd(),
templatesPath: process.cwd(),
runnerCommand: ["npx", "-y", "@bunny-agent/runner-cli@latest", "run"],
env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
});
const bunnyAgent = createBunnyAgent({
sandbox,
cwd: sandbox.getWorkdir(),
});
const stream = createUIMessageStream({
execute: async ({ writer }) => {
const result = streamText({
model: bunnyAgent("sonnet"),
messages: await convertToModelMessages(messages),
abortSignal: request.signal,
});
writer.merge(result.toUIMessageStream());
},
});
return createUIMessageStreamResponse({ stream });
}
3. Frontend — Chat UI
"use client";
import { useBunnyAgentChat } from "@bunny-agent/sdk/react";
export default function Chat() {
const { messages, isLoading, sendMessage } = useBunnyAgentChat({
apiEndpoint: "/api/ai",
});
return (
<div>
{messages.map((m) => (
<div key={m.id}>
{m.parts.map((p, i) => p.type === "text" && <span key={i}>{p.text}</span>)}
</div>
))}
<form onSubmit={(e) => { e.preventDefault(); sendMessage(e.currentTarget.input.value); }}>
<input name="input" placeholder="Ask anything…" />
<button disabled={isLoading}>Send</button>
</form>
</div>
);
}
4. Run
export ANTHROPIC_API_KEY=sk-ant-xxx
npm run dev
That's it — you now have a full Coding Agent streaming into your app.
Local vs Cloud Sandbox
Local Mode (Desktop apps & debugging)
LocalMachine is built-in — no extra packages needed. The agent runs directly on your machine's filesystem, with your user's permissions and no isolation (it was previously named LocalSandbox; that alias still works but is deprecated). When you want actual OS-level isolation on the local machine, use SrtSandbox instead — same API, but every command is wrapped with Anthropic's sandbox runtime (bubblewrap on Linux, Seatbelt on macOS): writes are restricted to the workdir, network access is blocked unless you allowlist domains.
import { createBunnyAgent, SrtSandbox } from "@bunny-agent/sdk";
const sandbox = new SrtSandbox({
workdir: "/path/to/project",
isolation: {
allowedDomains: ["api.anthropic.com", "*.npmjs.org"],
denyRead: ["~/.ssh", "~/.aws"],
allowWrite: ["~/.npm"], // runners that write to the home dir need this
},
});
Linux needs bubblewrap + socat installed (and on Ubuntu 24.04+, an AppArmor profile that grants userns to bwrap). macOS works out of the box.
import { createBunnyAgent, LocalMachine } from "@bunny-agent/sdk";
const sandbox = new LocalMachine({
workdir: "/path/to/project",
templatesPath: "./my-agent-template",
env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
});
const bunnyAgent = createBunnyAgent({ sandbox, cwd: sandbox.getWorkdir() });
Best for: Electron/Tauri desktop apps, local development, debugging.
Cloud Mode — Sandock (Recommended)
Sandock provides Docker-based cloud sandboxes with persistent volumes.
Why Sandock? Coding Agents rely on filesystem
appendand other POSIX semantics. Sandock is the only cloud sandbox with 100% POSIX-compatible filesystems, ensuring agents run without FS failures. Volumes are persistent and shareable across sandboxes.
npm install @bunny-agent/sandbox-sandock
import { createBunnyAgent } from "@bunny-agent/sdk";
import { SandockSandbox } from "@bunny-agent/sandbox-sandock";
const sandbox = new SandockSandbox({
apiKey: process.env.SANDOCK_API_KEY, // Get yours at https://sandock.ai
image: "ghcr.io/vikadata/bunny-agent:latest", // Pre-built image (fast startup)
skipBootstrap: true,
workdir: "/workspace",
env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
// Optional: persistent volumes
volumes: [
{ volumeName: "my-workspace", volumeMountPath: "/workspace" },
],
});
const bunnyAgent = createBunnyAgent({ sandbox, cwd: sandbox.getWorkdir() });
| Sandock Option | Description |
|---|---|
apiKey | Your Sandock API key (sandock.ai) |
image | Docker image — use ghcr.io/vikadata/bunny-agent:latest for pre-built, or sandockai/sandock-code:latest |
skipBootstrap | true when using pre-built image (skips npm install inside sandbox) |
volumes | Named volumes for persistent workspace & session storage |
keep | true (default) keeps sandbox alive ~30 min after execution |
memoryLimitMb | Memory limit in MB |
Other Cloud Sandboxes
npm install @bunny-agent/sandbox-e2b # E2B cloud sandbox
npm install @bunny-agent/sandbox-daytona # Daytona workspace
import { E2BSandbox } from "@bunny-agent/sandbox-e2b";
const sandbox = new E2BSandbox({ apiKey: process.env.E2B_API_KEY });
import { DaytonaSandbox } from "@bunny-agent/sandbox-daytona";
const sandbox = new DaytonaSandbox({ apiKey: process.env.DAYTONA_API_KEY });
Agent Templates
Templates customize what the agent knows and how it behaves — pure Markdown, no code.
my-agent/
├── CLAUDE.md # System instructions
├── skills/
│ └── sql-expert/
│ └── SKILL.md # Modular capability
└── .claude/
└── mcp.json # MCP server integrations
const sandbox = new LocalMachine({
workdir: process.cwd(),
templatesPath: "./templates/analyst", // Point to your template
});
Built-in templates: default, coder, analyst, researcher, seo-agent.
Request-Scoped MCP Servers
The Pi runner accepts MCP configuration per model request. The configuration travels through daemon JSON or a one-shot runner CLI payload and is never written to the workspace:
const bunnyAgent = createBunnyAgent({
sandbox,
runnerType: "pi",
allowedTools: ["read", "bash", "mcp"],
mcpConfig: {
mcpServers: {
analytics: {
url: "https://mcp.example.com/rpc",
headers: {
"X-API-Key": process.env.ANALYTICS_MCP_KEY!,
},
auth: false,
lifecycle: "lazy",
directTools: false,
},
},
settings: {
hostConfigDiscovery: "off",
outputGuard: true,
},
},
});
Trusted SDK callers may also configure stdio servers with command, args,
env, and cwd; those commands execute inside the selected sandbox. An
explicit allowedTools list must include mcp.
Configuration is request-scoped. A new configuration applies to the next message, including a resumed conversation, while an active turn keeps the configuration it started with. OAuth and active-turn hot replacement are not provided by this API.
Public web routes should not accept this trusted shape directly. Convert a
strict HTTPS-only input into McpConfig, reject localhost/private targets and
unsafe headers, and enforce network egress policy at the sandbox boundary.
React Hooks
All hooks are available from @bunny-agent/sdk/react:
import {
useBunnyAgentChat, // Full chat with streaming
useArtifacts, // Display agent-generated files (reports, charts)
useWriteTool, // Handle file write tool calls
useAskUserQuestion, // Handle interactive questions from the agent
} from "@bunny-agent/sdk/react";
API Reference
Provider
createBunnyAgent — sandbox transport (cloud / local filesystem)
import { createBunnyAgent, LocalMachine } from "@bunny-agent/sdk";
const bunnyAgent = createBunnyAgent({
sandbox: SandboxAdapter, // LocalMachine, SrtSandbox, SandockSandbox, E2BSandbox, etc.
cwd?: string, // Working directory inside sandbox
env?: Record<string, string>, // Environment variables
template?: string, // Template name
resume?: string, // Session ID for multi-turn
verbose?: boolean, // Debug logging
});
const model = bunnyAgent("claude-sonnet-4-20250514");
Daemon HTTP transport (same provider)
With any sandbox adapter (E2B, Sandock, LocalMachine, etc.): pass sandbox + daemonUrl. The URL is resolved inside the sandbox (the vikadata/bunny-agent image starts bunny-agent-daemon on port 3080). The SDK streams via streamCodingRunFromSandbox (curl -N in the sandbox, including LocalMachine), not fetch from your server. It does not call /healthz unless you select Pi search contract "auto", as described below. Use isBunnyAgentDaemonHealthy from @bunny-agent/sdk for an explicit readiness probe before setting daemonUrl (e.g. to fall back to the CLI runner).
import { createBunnyAgent, DEFAULT_BUNNY_AGENT_DAEMON_URL } from "@bunny-agent/sdk";
const bunnyAgent = createBunnyAgent({
sandbox: mySandboxAdapter,
daemonUrl: DEFAULT_BUNNY_AGENT_DAEMON_URL, // http://127.0.0.1:3080 inside the container
runnerType: "claude",
cwd: "/workspace",
});
Omit daemonUrl to use the CLI runner in the same sandbox. createBunnyAgent always requires a sandbox adapter.
Structured web search and answer citations
For Pi daemon execution, select searchContractVersion: "auto" on the provider
or an individual model. The SDK discovers the attached runtime's capabilities
before starting the turn:
const bunnyAgent = createBunnyAgent({
sandbox: mySandboxAdapter,
daemonUrl: DEFAULT_BUNNY_AGENT_DAEMON_URL,
runnerType: "pi",
searchContractVersion: "auto",
});
- A capable daemon uses v1 structured search and answer annotations, with a required stream acknowledgement. A healthy older daemon uses legacy search.
- Discovery sends a read-only
/healthzGET through the same sandbox transport. The turn executes once; it is never replayed to negotiate support. Failed or malformed discovery rejects before execution instead of silently downgrading. - Healthy results are cached for 30 seconds per attached handle, sandbox ID, and daemon URL. An in-place runtime restart can take that long to be discovered.
- Omitting the option preserves legacy behavior without discovery. Explicit
1preserves strict acknowledgement requirements without discovery. CLI execution and other runners remain legacy when"auto"is selected.
See the search citation contract for the capability schema, AI SDK requirements, and citation metadata fields.
Exports
| Entry Point | Exports |
|---|---|
@bunny-agent/sdk | createBunnyAgent, BunnyAgentLanguageModel, submitAnswer, DEFAULT_BUNNY_AGENT_DAEMON_URL; re-exports LocalMachine (from @bunny-agent/sandbox-local) and SrtSandbox (from @bunny-agent/sandbox-srt) |
@bunny-agent/sdk/react | useBunnyAgentChat, useArtifacts, useWriteTool, useAskUserQuestion, DEFAULT_BUNNY_AGENT_DAEMON_URL (re-exported from @bunny-agent/manager) |
Environment Variables
| Variable | Required | Description |
|---|---|---|
ANTHROPIC_API_KEY | Yes | Anthropic API key |
SANDOCK_API_KEY | For Sandock | sandock.ai API key |
E2B_API_KEY | For E2B | E2B API key |
DAYTONA_API_KEY | For Daytona | Daytona API key |
Documentation
📖 Full docs: bunny-agent.dev/docs
License
Apache-2.0