@ bunny- agent/ 守护进程
沙盒容器的统一API网关. 运行 内部 页:1 沙发 Next.js app——要么嵌入为Next.js 路由处理器(local/dev model),要么作为沙盒容器内部的独立流程(生产).
运行在哪里
~/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)
一样 @bunny-agent/daemon 软件包在两种模式中都起作用——Next.js嵌入本地开发,独立的HTTP服务器用于生产容器.
Bunny 代理容器图像(vikadata/bunny-agent(中文(简体) )
粘贴文件下方 docker/bunny-agent-claude/ (单位:千美元)Dockerfile, (中文(简体) ), Dockerfile.local, (中文(简体) ), Dockerfile.template安装 @bunny-agent/daemon 开始 bunny-agent-daemon 在容器启动时的背景中(与现有的CDP一起) bunny-agent CLI设置). HTTP API 监听 0.0.0.0:3080 默认(EXPOSE 3080) (中文(简体) ). 覆盖 BUNNY_AGENT_DAEMON_HOST, (中文(简体) ), BUNNY_AGENT_DAEMON_PORT,以及 BUNNY_AGENT_ROOT 如果需要的话.
运行环境
POST /api/coding/run 启动守护进程 process.env。在守护进程上配置 API 密钥和运行器设置(或容器图像内),而不是通过 HTTP 头来自 @bunny-agent/manager。 。 。 .
建筑
1. 大图 -- -- 布达如何与沙盒交谈
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) │
└─────────────────────────────────────────────────────┘
外部打电话的人只见过 一个端口: 3080其它一切都是内部的.
2. 守护进程内部——请求路径
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 (服务器-ai-sdk)和 /api/coding/acp (服务器-app)是两个
在同一跑道调度上的独立电线协议——见
docs/runner-maturity.md 页:1
完整的协议比较.
3. 软件包依赖图
packages/
│
├── runner-claude ──┐
├── runner-codex ──┤
├── runner-gemini ──┼──► runner-core ◄──┬── apps/runner-cli
├── runner-pi ──┤ │
└── runner-opencode ──┘ └── apps/bunny-agent-daemon
runner-core 是共享调度层——没有I/O,没有stdout,只是 createRunner() → AsyncIterable<string>。 。 。 .
4. 部署模式
┌──────────────────────────────────────────────────────────────────┐
│ 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. 内部代码结构
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 财务报告和审定财务报表 POST /api/coding/acp 未定义于
src/routes/ —— 说 server.ts编号 :nextjs.ts 直接从
@bunny-agent/server-ai-sdk 财务报告和审定财务报表 @bunny-agent/server-acp,这些包
分别拥有AI SDK和非加太电线协议(均通过
runner-harness,与这里的其他一切一样).
使用量
备选办法A:跑道-cli(当地终点站)
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"
输出:生AI SDK UI NDJSON流出.
选项B:守护进程独立(容器)
# 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
CDP 浏览器使用 ephemeral 配置文件 /tmp 因此浏览器状态没有
累积于 ~/.config/chromium。磁盘和媒体缓存在
100个米B.
# 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}'
备选案文C:嵌入下个.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;
封面 /api/fs/*, (中文(简体) ), /api/git/*, (中文(简体) ), /api/volumes/* 时间 /api/daemon/*。没有额外的进程.
选项 D: 直接运行代理( 不需要守护进程)
如果您只需要运行一个代理—— 没有文件 API, 没有 HTTP 服务器—— 使用 runner-core 直接 :
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 是双方使用的共享核心 runner-cli 财务报告和审定财务报表 bunny-agent-daemon在不需要HTTP网关时直接使用.
API 参考
JSON的所有回应: { "ok": true, "data": {}, "error": null }
探员 /api/coding/*
POST /api/coding/run
运行代理并流出输出为SSE(AI SDK UI NDJSON格式).
请求机构 :
| 外地 | 类型 | 默认 | 说明 |
|---|---|---|---|
userInput | 字符串 | 所需经费 | 任务/ 提示 |
runner | 字符串 | "claude" | claude • 妇女 codex • 妇女 gemini • 妇女 pi • 妇女 opencode |
model | 字符串 | "claude-sonnet-4-20250514" | 跑者模型名称 |
cwd | 字符串 | BUNNY_AGENT_ROOT | 沙盒内的工作目录 |
systemPrompt | 字符串 | —— 说 | 覆盖系统提示 |
maxTurns | 编号 | —— 说 | 最大代理转弯 |
allowedTools | 字符串 [] | —— 说 | 限制代理人可以使用的工具 |
resume | 字符串 | —— 说 | 要恢复的会话 ID |
skillPaths | 字符串 [] | —— 说 | 额外技能路径( pi 跑道) |
示例
# 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"
}'
回应: application/x-ndjson 块流——每行是AI SDK UI消息,可兼容Vercel AI SDK useChat 编号 : streamText。 。 。 .
POST /api/coding/acp
跑步和跑步一样 代理客户协议
为非加太公司客户(Zed、JetBrains和
Neovim/Emacs/VS代码插件),而不是AI SDK UI客户端. 后卫
@bunny-agent/server-acp;请求/答复机构为ACP JSON-RPC 2
消息,而不是内容 RunRequest 使用的形状 /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 在非加太区域没有跑道/模型领域,所以选择一个通过
页:1 _meta 扩展名, 名称空格 "bunny-agent"数字 :
{
"jsonrpc": "2.0",
"id": 2,
"method": "session/new",
"params": {
"cwd": "/workspace",
"mcpServers": [],
"_meta": { "bunny-agent": { "runner": "pi", "model": "gemini-2.0-flash" } }
}
}
外出或畸形 _meta 返回到守护进程配置的默认值
跑道/型号。 见
docs/runner-maturity.md
用于全面协议比较,以及
apps/runner-cli/README.md 页:1
bunny-agent acp,当编辑器产出该剂时使用的 stdio等同物
作为子进程而不是说话的 HTTP.
文件系统 /api/fs/*
| 方法 | 路径 | 参数 |
|---|---|---|
| 获取 | /api/fs/list | ?path=src&volume=vol-001 |
| 获取 | /api/fs/read | ?path=file.txt |
| 获取 | /api/fs/stat | ?path=file.txt |
| 获取 | /api/fs/exists | ?path=file.txt |
| 获取 | /api/fs/find | ?pattern=todo&limit=100 |
| 邮政 | /api/fs/write | {"path":"a.txt","content":"hello"} |
| 邮政 | /api/fs/append | {"path":"log.txt","content":"line\n"} |
| 邮政 | /api/fs/mkdir | {"path":"a/b/c"} |
| 邮政 | /api/fs/remove | {"path":"tmp","recursive":true} |
| 邮政 | /api/fs/move | {"from":"a.txt","to":"b.txt"} |
| 邮政 | /api/fs/copy | {"from":"a.txt","to":"b.txt"} |
| 邮政 | /api/fs/upload | multipart/form-data - 见下文 |
| 点 | /api/fs/write-stream | ?path=file.bin&max_bytes=536870912 活生生体 |
PUT /api/fs/write-stream
通过将原始请求正文直接流到磁盘来上传单个大文件.
守护进程写给一个临时 .part 文件并仅在文件之后重命名
流完成,所以全上传在内存中永远不会被缓冲.
查询参数 :
| 外地 | 类型 | 需求 | 说明 |
|---|---|---|---|
path | 字符串 | 对 | 守护进程根或音量下的目标文件路径 |
volume | 字符串 | 没有 | 多租户隔离的卷名 |
create_dirs | 字符串 | 没有 | 创建父 dirs( 默认 ): "true"(中文(简体) ) |
max_bytes | 编号 | 没有 | 按要求限制大小,上限为512米B |
示例
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
回应:
{
"ok": true,
"data": {
"path": "/workspace/uploads/video.mp4",
"size": 314572800
},
"error": null
}
POST /api/fs/upload
将一个或多个文件通过 multipart/form-data。 。 。 .
窗体字段 :
| 外地 | 类型 | 需求 | 说明 |
|---|---|---|---|
path | 字符串 | 没有 | 目标目录( 默认 ): .(中文(简体) ) |
volume | 字符串 | 没有 | 多租户隔离的卷名 |
create_dirs | 字符串 | 没有 | 创建父 dirs( 默认 ): "true"(中文(简体) ) |
file | 文件 | 对 | 要上传的一个或多个文件 |
示例
# 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]"
回应:
{
"ok": true,
"data": {
"files": [
{ "fieldname": "file", "filename": "report.csv", "path": "/workspace/data/report.csv", "size": 1234 }
]
},
"error": null
}
所有 fs 端点接受可选 volume 多租户隔离.
图形 /api/git/*
| 方法 | 路径 | 体质 |
|---|---|---|
| 邮政 | /api/git/status | {"repo":"myrepo"} |
| 邮政 | /api/git/exec | {"repo":"myrepo","args":["log","--oneline"]} |
| 邮政 | /api/git/clone | {"repo_parent":".","url":"https://...","depth":1} |
| 邮政 | /api/git/init | {"repo":"myrepo","initial_branch":"main"} |
Git 端点以 isomorphic-git 并返还共享
GitCommandResult 导出的信封 @bunny-agent/daemon/shared/git-types。 。 。 .
/api/git/exec 支持常见的允许列表子命令并返回一个非零
无法通过表示的旗帜组合的命令结果
isomorphic-git。 。 。 .
音量 /api/volumes/*
| 方法 | 路径 | 体质 |
|---|---|---|
| 获取 | /api/volumes/list | |
| 邮政 | /api/volumes/ensure | {"volume":"vol-001"} |
| 邮政 | /api/volumes/remove | {"volume":"vol-001"} |
卫生
GET /healthz
现有 { ok, data, error } 反应包括准备状态和能力
在一个请求中. data.status, (中文(简体) ), data.root, (中文(简体) ), data.volumesRoot,以及
data.capabilities.searchContracts 保持不变。 新领域有:
{
"data": {
"capabilities": {
"codingControl": { "version": 1, "supportedPiSteer": true }
}
}
}
客户可以保留健康反应并重新使用其能力申报
而不是提出单独的搜索和控制能力请求。 运行特定
准备和收据仍需要 /api/coding/control 状态请求.
codingControl 检查编码运行所使用的相同的持久控制日记
/api/coding/control。期刊初始化共享并运行一次,包括
同时提出的保健和遗产能力要求。 日记初始化失败
报告 supportedPiSteer: false - 健康仍然是HTTP 200
跑步或者叫模特儿 遗产控制 capabilities 行动
可供老年客户使用.
环境变量
| 变量 | 默认 | 说明 |
|---|---|---|
BUNNY_AGENT_DAEMON_PORT | 3080 | 听从端口 |
BUNNY_AGENT_ROOT | ./.bunny-agent-daemon (根据cwd) | 守护进程文件系统根; Docker 图像设置例如 . /workspace |
ANTHROPIC_API_KEY | —— 说 | 为克劳德跑道 |
GEMINI_API_KEY | —— 说 | 为双子座/皮跑者 |
OPENAI_API_KEY | —— 说 | 代码跑道 |
发展
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