@bunny-agent/daemon

中文文档
0.10.3-beta1发布于 2026/11/07Apache-2.0TypeScriptES Module近 30 天下载 6701

BunnyAgent Daemon - Unified API gateway for sandbox services (file, git, volumes)

原始使用文档
文档目录22 节
  1. Where this runs
  2. Bunny Agent container image ( vikadata/bunny-agent )
  3. Runner environment
  4. Architecture
  5. 1. Big Picture — How Buda talks to a sandbox
  6. 2. Inside the daemon — request routing
  7. 3. Package dependency graph
  8. 4. Deployment modes
  9. 5. Internal code structure
  10. Usage
  11. Option A: runner-cli (local terminal)
  12. Option B: daemon standalone (container)
  13. Option C: embed in Next.js
  14. Option D: just run an agent directly (no daemon needed)
  15. API Reference
  16. Agent /api/coding/*
  17. Filesystem /api/fs/*
  18. Git /api/git/*
  19. Volumes /api/volumes/*
  20. Health
  21. Environment Variables
  22. Development

@bunny-agent/daemon

Unified API gateway for sandbox containers. Runs inside the sandock Next.js app — either embedded as a Next.js route handler (local/dev mode) or as a standalone process inside a sandbox container (production).


Where this runs

~/Documents/kapps/apps/buda/        ← buda.im Next.js app
    app/api/daemon/[...path]/
        route.ts                    ← embeds @bunny-agent/daemon via createNextHandler()
                                       (local dev mode, no extra process)

sandbox container                   ← production
    bunny-agent-daemon :3080          ← standalone process
    (accessed via sandock.ai proxy)

The same @bunny-agent/daemon package works in both modes — Next.js embed for local development, standalone HTTP server for production containers.

Bunny Agent container image (vikadata/bunny-agent)

The Dockerfiles under docker/bunny-agent-claude/ (Dockerfile, Dockerfile.local, Dockerfile.template) install @bunny-agent/daemon and start bunny-agent-daemon in the background when the container starts (alongside the existing CDP / bunny-agent CLI setup). The HTTP API listens on 0.0.0.0:3080 by default (EXPOSE 3080). Override with BUNNY_AGENT_DAEMON_HOST, BUNNY_AGENT_DAEMON_PORT, and BUNNY_AGENT_ROOT if needed.

Runner environment

POST /api/coding/run starts the runner with the daemon process process.env. Configure API keys and runner settings on the daemon (or container image env), not via per-request HTTP headers from @bunny-agent/manager.


Architecture

1. Big Picture — How Buda talks to a sandbox

  buda.im
     │
     │  HTTPS
     ▼
  sandock.ai
  /api/v1/sandbox/http/proxy/{sandbox-id}/3080/
     │
     │  HTTP (proxied)
     ▼
┌─────────────────────────────────────────────────────┐
│           sandbox container                         │
│                                                     │
│   ┌────────────────────────────────────┐            │
│   │       bunny-agent-daemon :3080       │            │
│   │       (unified API gateway)        │            │
│   └────────────────────────────────────┘            │
│                                                     │
│   chromium :9223 (internal) --remote-allow-origins=*│
│   nginx :9222 → :9223  (rewrites Host: localhost)   │
└─────────────────────────────────────────────────────┘

External callers only ever see one port: 3080. Everything else is internal.


2. Inside the daemon — request routing

incoming HTTP request
        │
        ▼
┌───────────────────────────────────────────────────────┐
│                  bunny-agent-daemon                     │
│                                                       │
│  POST /api/coding/run  ──────────────────────────┐ │
│  POST /api/coding/acp  ────────────────────────┐ │ │
│                                                   │ │ │
│  GET|POST /api/fs/*   ──────────────────────────┐│ │ │
│  GET|POST /api/git/*  ──────────────────────┐   ││ │ │
│  GET|POST /api/volumes/*  ──────────────┐   │   ││ │ │
│  GET /healthz  ─────────────────────┐   │   │   ││ │ │
│                                     │   │   │   ││ │ │
│                                     ▼   ▼   ▼   ││ │ │
│                               ┌─────────────┐   ││ │ │
│                               │ DaemonRouter│   ││ │ │
│                               │ (core logic)│   ││ │ │
│                               └──────┬──────┘   ││ │ │
│                                      │          ││ │ │
│              ┌───────────────────────┤          ││ │ │
│              ▼                       ▼          ▼│ │ │
│         node:fs/promises    isomorphic-git   SSE │ │ │
│         (file ops)              (git ops) (server-ai-sdk)
│                                                   │ │ │
│                                     ACP JSON-RPC ◄┘ │ │
│                                     (server-acp) ◄──┘ │
│                                          │            │
│                                          ▼            │
│                                    @bunny-agent/       │
│                                    runner-harness ◄────┘
│                                    claude/pi/          │
│                                    gemini/codex        │
└───────────────────────────────────────────────────────┘

/api/coding/run (server-ai-sdk) and /api/coding/acp (server-acp) are two independent wire protocols over the same runner dispatch — see docs/runner-maturity.md for the full protocol comparison.


3. Package dependency graph

packages/
│
├── runner-claude   ──┐
├── runner-codex    ──┤
├── runner-gemini   ──┼──► runner-core ◄──┬── apps/runner-cli
├── runner-pi       ──┤                   │
└── runner-opencode ──┘                   └── apps/bunny-agent-daemon

runner-core is the shared dispatch layer — no I/O, no stdout, just createRunner() → AsyncIterable<string>.


4. Deployment modes

┌──────────────────────────────────────────────────────────────────┐
│  Mode A: Standalone process (container / local)                  │
│                                                                  │
│  entrypoint.sh                                                   │
│  ├── chromium :9223 (internal) --remote-allow-origins=* &        │
│  ├── nginx :9222 → :9223 (rewrites Host: localhost) &            │
│  └── bunny-agent-daemon          ← node process, listens :3080     │
│                                                                  │
│  caller: curl / Buda SDK / any HTTP client                       │
│  → http://sandbox:3080/api/fs/read?path=file.txt                 │
└──────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────┐
│  Mode B: Embedded in Next.js (local dev / sandock-cli)           │
│                                                                  │
│  app/api/daemon/[...path]/route.ts                               │
│  └── createNextHandler({ root: process.cwd() })                  │
│                                                                  │
│  No extra process. DaemonRouter runs inside Next.js.             │
│  caller: browser / fetch                                         │
│  → /api/daemon/fs/read?path=file.txt                             │
└──────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────┐
│  Mode C: runner-cli (local terminal, no daemon needed)           │
│                                                                  │
│  bunny-agent run --runner claude -- "Build a REST API"             │
│  └── stdout: AI SDK UI NDJSON stream, one-shot                   │
│                                                                  │
│  bunny-agent acp --runner claude                                   │
│  └── stdio: long-lived ACP agent (JSON-RPC), for editors that     │
│      spawn agent binaries directly — Zed, JetBrains, ...          │
│                                                                  │
│  Runs directly on local filesystem. No HTTP server.              │
└──────────────────────────────────────────────────────────────────┘

5. Internal code structure

apps/bunny-agent-daemon/
├── src/
│   ├── cli.ts          entry point — reads env, starts http.Server
│   ├── server.ts       createDaemon() — http.Server wrapping DaemonRouter
│   ├── router.ts       DaemonRouter — framework-agnostic route table
│   ├── nextjs.ts       createNextHandler() — Next.js adapter
│   ├── utils.ts        path safety, ApiEnvelope helpers
│   └── routes/
│       ├── health.ts   GET /healthz
│       ├── fs.ts       GET|POST /api/fs/*
│       ├── volumes.ts  GET|POST /api/volumes/*
│       └── git.ts      POST /api/git/*  (isomorphic-git)
└── src/__tests__/
    └── daemon.test.ts  13 integration tests (no mocks, real fs + git)

POST /api/coding/run and POST /api/coding/acp aren't defined in src/routes/ — server.ts/nextjs.ts mount them directly from @bunny-agent/server-ai-sdk and @bunny-agent/server-acp, the packages that own the AI SDK and ACP wire protocols respectively (both dispatch through runner-harness, same as everything else here).


Usage

Option A: runner-cli (local terminal)

cd templates/coder
npx bunny-agent run -- "Build a REST API"
npx bunny-agent run --runner pi -- "Analyze this dataset"
npx bunny-agent run --runner gemini --model gemini-2.0-flash -- "Review my code"
npx bunny-agent run --resume <session-id> -- "Continue"

Output: raw AI SDK UI NDJSON stream to stdout.

Option B: daemon standalone (container)

# see docs/entrypoint.example.sh for the full script
#
# Chromium runs on internal port 9223 with --remote-allow-origins=* so
# the WebSocket origin check passes. nginx proxies 0.0.0.0:9222 → 9223
# and rewrites the Host header to "localhost" to satisfy Chromium's
# DNS-rebinding security check. Without this rewrite, external clients
# (Host: container-ip:9222) are rejected even when the port is open.
chromium --headless --no-sandbox \
  --user-data-dir=/tmp/bunny-agent-chromium \
  --disk-cache-dir=/tmp/bunny-agent-chromium-cache \
  --disk-cache-size=104857600 \
  --media-cache-size=104857600 \
  --remote-debugging-port=9223 \
  --remote-allow-origins=* &
nginx  # proxies :9222 → :9223 with Host rewrite
exec bunny-agent-daemon

The CDP browser uses an ephemeral profile under /tmp so browser state does not accumulate in ~/.config/chromium. Disk and media caches are each capped at 100 MiB.

# Run an agent — SSE stream
curl -N -X POST http://localhost:3080/api/coding/run \
  -H 'Content-Type: application/json' \
  -d '{"runner":"claude","userInput":"List files in /workspace"}'

# File ops
curl -X POST http://localhost:3080/api/fs/write \
  -H 'Content-Type: application/json' \
  -d '{"path":"hello.txt","content":"hello world"}'

curl "http://localhost:3080/api/fs/read?path=hello.txt"

# Git
curl -X POST http://localhost:3080/api/git/clone \
  -H 'Content-Type: application/json' \
  -d '{"repo_parent":".","url":"https://github.com/user/repo","depth":1}'

Option C: embed in Next.js

// app/api/daemon/[...path]/route.ts
import { createNextHandler } from "@bunny-agent/daemon/nextjs";

const handler = createNextHandler({ root: process.cwd() });
export const GET = handler;
export const POST = handler;

Covers /api/fs/*, /api/git/*, /api/volumes/* at /api/daemon/*. No extra process.

Option D: just run an agent directly (no daemon needed)

If you only need to run an agent — no file API, no HTTP server — use runner-core directly:

import { createRunner } from "@bunny-agent/runner-core";

const stream = createRunner({
  runner: "claude",           // or "pi", "gemini", "codex", "opencode"
  model: "claude-sonnet-4-20250514",
  userInput: "Build a REST API",
  cwd: "/workspace",
  env: process.env as Record<string, string>,
});

// Collect all chunks into a full response
const chunks: string[] = [];
for await (const chunk of stream) {
  chunks.push(chunk);
}
const fullResponse = chunks.join("");

// Or parse each NDJSON line as it arrives
for await (const chunk of stream) {
  for (const line of chunk.split("\n").filter(Boolean)) {
    const msg = JSON.parse(line);
    console.log(msg);
  }
}

runner-core is the shared core used by both runner-cli and bunny-agent-daemon. Use it directly when you don't need the HTTP gateway.


API Reference

All JSON responses: { "ok": true, "data": {}, "error": null }

Agent /api/coding/*

POST /api/coding/run

Run an agent and stream the output as SSE (AI SDK UI NDJSON format).

Request body:

FieldTypeDefaultDescription
userInputstringrequiredThe task / prompt
runnerstring"claude"claude · codex · gemini · pi · opencode
modelstring"claude-sonnet-4-20250514"Model name for the runner
cwdstringBUNNY_AGENT_ROOTWorking directory inside the sandbox
systemPromptstring—Override system prompt
maxTurnsnumber—Max agent turns
allowedToolsstring[]—Restrict which tools the agent can use
resumestring—Session ID to resume
skillPathsstring[]—Extra skill paths (pi runner)

Example:

# Stream with curl (-N disables buffering)
curl -N -X POST http://localhost:3080/api/coding/run \
  -H 'Content-Type: application/json' \
  -d '{
    "runner": "claude",
    "userInput": "List all TypeScript files and summarize what each does",
    "cwd": "/workspace/myproject"
  }'

# Use pi runner with a different model
curl -N -X POST http://localhost:3080/api/coding/run \
  -H 'Content-Type: application/json' \
  -d '{
    "runner": "pi",
    "model": "gemini-2.0-flash",
    "userInput": "Refactor this codebase to use async/await"
  }'

Response: application/x-ndjson chunked stream — each line is an AI SDK UI message, compatible with Vercel AI SDK useChat / streamText.

POST /api/coding/acp

Serves the same runners as an Agent Client Protocol agent over Streamable HTTP, for ACP clients (Zed, JetBrains, and the Neovim/Emacs/VS Code plugins) instead of AI SDK UI clients. Backed by @bunny-agent/server-acp; the request/response bodies are ACP JSON-RPC 2.0 messages, not the RunRequest shape used by /api/coding/run.

curl -N -X POST http://localhost:3080/api/coding/acp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{}}}'

session/new has no runner/model field in the ACP spec, so select one through the _meta extension, namespaced under "bunny-agent":

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "session/new",
  "params": {
    "cwd": "/workspace",
    "mcpServers": [],
    "_meta": { "bunny-agent": { "runner": "pi", "model": "gemini-2.0-flash" } }
  }
}

Omitted or malformed _meta falls back to the daemon's configured default runner/model. See docs/runner-maturity.md for the full protocol comparison, and apps/runner-cli/README.md for bunny-agent acp, the stdio equivalent used when an editor spawns the agent as a subprocess instead of talking HTTP.

Filesystem /api/fs/*

MethodPathParams
GET/api/fs/list?path=src&volume=vol-001
GET/api/fs/read?path=file.txt
GET/api/fs/stat?path=file.txt
GET/api/fs/exists?path=file.txt
GET/api/fs/find?pattern=todo&limit=100
POST/api/fs/write{"path":"a.txt","content":"hello"}
POST/api/fs/append{"path":"log.txt","content":"line\n"}
POST/api/fs/mkdir{"path":"a/b/c"}
POST/api/fs/remove{"path":"tmp","recursive":true}
POST/api/fs/move{"from":"a.txt","to":"b.txt"}
POST/api/fs/copy{"from":"a.txt","to":"b.txt"}
POST/api/fs/uploadmultipart/form-data — see below
PUT/api/fs/write-stream?path=file.bin&max_bytes=536870912 — raw streamed body

PUT /api/fs/write-stream

Upload a single large file by streaming the raw request body directly to disk. The daemon writes to a temporary .part file and renames it only after the stream completes, so the full upload is never buffered in memory.

Query parameters:

FieldTypeRequiredDescription
pathstringYesTarget file path under the daemon root or volume
volumestringNoVolume name for multi-tenant isolation
create_dirsstringNoCreate parent dirs (default: "true")
max_bytesnumberNoPer-request size limit, capped at 512 MiB

Example:

curl -X PUT "http://localhost:3080/api/fs/write-stream?path=uploads/video.mp4" \
  --data-binary @video.mp4

curl -X PUT "http://localhost:3080/api/fs/write-stream?path=docs/archive.zip&volume=vol-001" \
  --data-binary @archive.zip

Response:

{
  "ok": true,
  "data": {
    "path": "/workspace/uploads/video.mp4",
    "size": 314572800
  },
  "error": null
}

POST /api/fs/upload

Upload one or more files via multipart/form-data.

Form fields:

FieldTypeRequiredDescription
pathstringNoTarget directory (default: .)
volumestringNoVolume name for multi-tenant isolation
create_dirsstringNoCreate parent dirs (default: "true")
filefileYesOne or more files to upload

Example:

# Upload a single file
curl -X POST http://localhost:3080/api/fs/upload \
  -F "path=uploads" \
  -F "[email protected]"

# Upload multiple files
curl -X POST http://localhost:3080/api/fs/upload \
  -F "path=data" \
  -F "[email protected]" \
  -F "[email protected]"

# Upload to a specific volume
curl -X POST http://localhost:3080/api/fs/upload \
  -F "path=docs" \
  -F "volume=vol-001" \
  -F "[email protected]"

Response:

{
  "ok": true,
  "data": {
    "files": [
      { "fieldname": "file", "filename": "report.csv", "path": "/workspace/data/report.csv", "size": 1234 }
    ]
  },
  "error": null
}

All fs endpoints accept optional volume for multi-tenant isolation.

Git /api/git/*

MethodPathBody
POST/api/git/status{"repo":"myrepo"}
POST/api/git/exec{"repo":"myrepo","args":["log","--oneline"]}
POST/api/git/clone{"repo_parent":".","url":"https://...","depth":1}
POST/api/git/init{"repo":"myrepo","initial_branch":"main"}

Git endpoints are implemented with isomorphic-git and return the shared GitCommandResult envelope exported from @bunny-agent/daemon/shared/git-types. /api/git/exec supports common allowlisted subcommands and returns a nonzero command result for flag combinations that cannot be represented through isomorphic-git.

Volumes /api/volumes/*

MethodPathBody
GET/api/volumes/list
POST/api/volumes/ensure{"volume":"vol-001"}
POST/api/volumes/remove{"volume":"vol-001"}

Health

GET /healthz

The existing { ok, data, error } response includes readiness and capabilities in one request. data.status, data.root, data.volumesRoot, and data.capabilities.searchContracts remain unchanged. New fields are:

{
  "data": {
    "capabilities": {
      "codingControl": { "version": 1, "supportedPiSteer": true }
    }
  }
}

Clients can retain the health response and reuse its capability declarations instead of making separate search and control capability requests. Run-specific readiness and receipts still require /api/coding/control status requests.

codingControl checks the same durable control journal used by coding runs and /api/coding/control. Journal initialization is shared and runs once, including concurrent health and legacy capability requests. A journal initialization failure reports supportedPiSteer: false while health remains HTTP 200; it never starts a runner or calls a model. The legacy control capabilities operation remains available for older clients.


Environment Variables

VariableDefaultDescription
BUNNY_AGENT_DAEMON_PORT3080Listen port
BUNNY_AGENT_ROOT./.bunny-agent-daemon (under cwd)Daemon filesystem root; Docker images set e.g. /workspace
ANTHROPIC_API_KEY—For claude runner
GEMINI_API_KEY—For gemini / pi runner
OPENAI_API_KEY—For codex runner

Development

cd apps/bunny-agent-daemon
pnpm install && pnpm build

BUNNY_AGENT_ROOT=/tmp/test bunny-agent-daemon
curl http://localhost:3080/healthz

pnpm test   # 13 integration tests

版本与发布记录

@bunny-agent/daemon 共发布 115 个版本,首次发布于 2026/04/14;过去 12 个月发布了 115 个版本。

版本发布日期距今
0.10.3-beta1latest2026/11/071 天前
0.10.22026/10/2811 天前
0.10.1-beta.22026/10/2811 天前
0.10.1-beta.12026/10/2712 天前
0.10.0-release.62026/10/2613 天前
0.10.0-release.52026/10/2316 天前

关于 @bunny-agent/daemon 的常见问题

@bunny-agent/daemon 是什么?

BunnyAgent Daemon - Unified API gateway for sandbox services (file, git, volumes)

如何安装 @bunny-agent/daemon?

在项目目录运行 npm install @bunny-agent/daemon,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 bunny-agent-daemon,可以用 npx bunny-agent-daemon 直接运行。

@bunny-agent/daemon 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@bunny-agent/daemon 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@bunny-agent/daemon 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@bunny-agent/daemon 可以商用吗?

@bunny-agent/daemon 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@bunny-agent/daemon 的下载量有多少?

根据 npm 官方下载统计,@bunny-agent/daemon 在 2026/10/09 – 2026/11/07 这 30 天内被下载 6,701 次,日均约 223 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@bunny-agent/daemon 还在维护吗?

最新版本 0.10.3-beta1 发布于 2026/11/07(1 天前);至今共发布 115 个版本,其中过去 12 个月发布了 115 个;首次发布于 2026/04/14。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。