switchyard-agent
v0.0.1
Published
Local HTTP/SSE bridge for Codex, Cursor Agent, and Claude Code.
Maintainers
Readme
Switchyard
Switchyard 是一个本地 Agent 桥接服务。它把 Codex、Cursor Agent、Claude Code 三种命令行 Agent 统一暴露成 HTTP API,并用 Server-Sent Events 推送模型输出和运行事件。
适合用来接入自己的本地 Agent 控制台、桌面应用、IDE 插件原型,或者把不同 CLI Agent 归一到同一个服务端协议里。
运行什么
| Provider | 生命周期 | 传输方式 | 说明 |
| --- | --- | --- | --- |
| codex | 持久会话 | codex --yolo app-server --listen stdio:// | 常驻 app-server 进程,通过 JSON-RPC 发起 turn。 |
| cursor | 每任务进程 | cursor agent ... --output-format stream-json | 每次消息启动一个进程,拿到 provider session id 后使用 resume。 |
| claude | 每任务进程 | claude -p ... --output-format stream-json | 每次任务通过 stdin 写入一条 stream-json 用户消息。 |
所有 session 都保存在内存里。不实现数据库、鉴权、队列或多用户隔离。
安装
pnpm install构建全部产物:
pnpm build:all启动后端:
pnpm serve默认地址是 http://127.0.0.1:8787。
开发前端控制台:
pnpm dev:webVite 前端运行在 http://127.0.0.1:5173,并把 /api、/health、/sessions 代理到后端。
CLI
构建后运行:
node dist/cli.js serve --host 127.0.0.1 --port 8787从 npm 安装后运行:
npx -p switchyard-agent switchyard serve --host 127.0.0.1 --port 8787包里主命令是 switchyard,同时保留 tom-agent-mini 作为兼容别名。
API 总览
稳定前缀:/api/v1
兼容别名:
GET /health/sessions/*
路由:
| Method | Path | 用途 |
| --- | --- | --- |
| GET | /api/v1/health | 健康检查。 |
| GET | /api/v1/providers | 查看 provider 能力摘要。 |
| GET | /api/v1/openapi.json | 简单 OpenAPI 3.1 文档。 |
| GET | /api/v1/fs/directories?path=... | 浏览本地目录,供 UI 选择工作目录。 |
| POST | /api/v1/fs/directories | 创建子目录。 |
| POST | /api/v1/sessions | 创建 Agent 会话。 |
| GET | /api/v1/sessions | 列出会话。 |
| GET | /api/v1/sessions/:id | 获取单个会话快照。 |
| GET | /api/v1/sessions/:id/events | 订阅 SSE 事件流。 |
| POST | /api/v1/sessions/:id/messages | 发送用户消息。 |
| POST | /api/v1/sessions/:id/interrupt | 中断当前任务。 |
| DELETE | /api/v1/sessions/:id | 销毁并删除会话。 |
Session Snapshot
会话接口返回统一结构:
type AgentSessionSnapshot = {
id: string;
provider: "codex" | "cursor" | "claude";
cwd: string;
executable: string;
state: "created" | "starting" | "idle" | "busy" | "closing" | "closed" | "failed";
providerSessionId: string | null;
activeTurnId: string | null;
createdAt: string;
updatedAt: string;
eventsUrl: string;
};前端或其他客户端使用 eventsUrl 建立 EventSource。
创建会话
请求:
POST /api/v1/sessions
Content-Type: application/json{
"provider": "codex",
"cwd": "/tmp",
"prompt": "请用一句话介绍当前目录。",
"model": "可选模型名",
"systemPrompt": "可选系统提示",
"executable": "可选 CLI 名称或路径"
}字段说明:
provider默认是codex。cwd默认是服务端进程当前目录;如果传入,必须存在且是目录。prompt会在会话启动后自动发送。model会透传给对应 provider。systemPrompt会作为 Codex base instructions,Claude 则使用--append-system-prompt。executable默认分别是codex、cursor、claude。
curl 示例:
curl -s http://127.0.0.1:8787/api/v1/sessions \
-H 'Content-Type: application/json' \
-d '{"provider":"codex","cwd":"/tmp","prompt":"请用一句话介绍当前目录。"}'订阅事件
SSE 地址:
curl -N http://127.0.0.1:8787/api/v1/sessions/<id>/events浏览器示例:
const source = new EventSource("/api/v1/sessions/<id>/events");
source.addEventListener("text_delta", (event) => {
const data = JSON.parse(event.data);
console.log(data.text);
});
source.addEventListener("error", (event) => {
console.error(JSON.parse(event.data));
});每个 SSE frame 格式:
event: text_delta
data: {"id":"...","at":"2026-06-07T00:00:00.000Z","type":"text_delta","text":"hello"}常见事件:
| Event | 含义 |
| --- | --- |
| ready | SSE 已连接。 |
| created | 会话已创建。 |
| state | 会话状态变化。 |
| session | 获取到 provider session/thread id。 |
| text_delta | 模型流式文本,前端应该增量渲染它。 |
| text | 完整模型文本。 |
| raw | provider 原始 stream-json payload。 |
| stderr | provider 进程 stderr。 |
| process_start | per-task provider 进程启动。 |
| process_exit | per-task provider 进程退出。 |
| error | 归一化错误事件。 |
服务端每个会话保留最近 500 条事件,新 SSE 客户端连接后会自动 replay。
发送消息
请求:
POST /api/v1/sessions/:id/messages
Content-Type: application/json{ "text": "继续,用更短的表达。" }也可以使用 message 作为 text 的别名。
curl 示例:
curl -s http://127.0.0.1:8787/api/v1/sessions/<id>/messages \
-H 'Content-Type: application/json' \
-d '{"text":"继续,用更短的表达。"}'响应:
{ "accepted": true }Codex 在 busy 时会走 turn/steer。Cursor 和 Claude 是 per-task 进程,busy 时会拒绝新消息。
图片附件
消息可以携带图片 data URL:
{
"text": "描述这张截图。",
"attachments": [
{
"name": "screen.png",
"mimeType": "image/png",
"dataUrl": "data:image/png;base64,iVBORw0KGgo..."
}
]
}限制:
- 每次最多 8 张图片。
- 单张最多 6 MB。
- 支持
image/png、image/jpeg、image/webp、image/gif。 - 服务端会检查文件头,确认内容和 MIME 类型匹配。
服务端会把图片写入临时目录,然后把本地文件路径追加到消息文本里交给 Agent。
中断和删除
中断当前任务:
curl -s http://127.0.0.1:8787/api/v1/sessions/<id>/interrupt \
-X POST \
-H 'Content-Type: application/json' \
-d '{}'删除会话:
curl -s http://127.0.0.1:8787/api/v1/sessions/<id> -X DELETE删除 Codex 会话时,会尝试调用 thread/dispose,终止进程,并移除内存 session。
目录选择 API
浏览目录:
curl -s 'http://127.0.0.1:8787/api/v1/fs/directories?path=/tmp'创建子目录:
curl -s http://127.0.0.1:8787/api/v1/fs/directories \
-H 'Content-Type: application/json' \
-d '{"path":"/tmp","name":"switchyard-demo"}'这个 API 主要给本地 UI 使用。它能读取目录名和创建文件夹,因此请保持服务只在本机可访问。
最小 Node 客户端
const baseUrl = "http://127.0.0.1:8787";
const createRes = await fetch(`${baseUrl}/api/v1/sessions`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
provider: "codex",
cwd: process.cwd(),
prompt: "列出这个项目里最重要的文件。"
})
});
const session = await createRes.json();
console.log(session.id, session.eventsUrl);
await fetch(`${baseUrl}/api/v1/sessions/${session.id}/messages`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: "现在建议一个小改进。" })
});Node 里订阅 SSE 可以使用 eventsource 这类 EventSource 兼容包。
如何复刻
- 用 pnpm workspace 创建 TypeScript Hono 后端,并把 Vite React 前端放在
web/。 - 后端主 API 使用
/api/v1前缀,兼容路由只在必要时保留。 - 把每个 provider 建模为共享
BaseSession和EventBus的 session。 - Codex 使用持久
app-server,通过 stdio JSON-RPC 驱动。 - Cursor 和 Claude 使用 per-task process,并把
stream-json归一成统一事件。 - session 默认只放内存,除非明确要做持久化。
- 用户可见输出走
text_delta;诊断信息走raw、stderr、error。 - 服务默认绑定
127.0.0.1。
安全模型
Switchyard 面向可信本地使用。
- 默认 host 是
127.0.0.1。 - 带不可信浏览器
Origin的变更请求会被拒绝。 - 非空 JSON body 必须使用
application/json。 - 图片上传有大小限制,并会校验文件签名。
- 服务可以启动本地 CLI 进程,不要当作公开多用户服务。
如果绑定到 0.0.0.0,CLI 会打印警告。只应在可信网络里这样做。
脚本
pnpm typecheck
pnpm build
pnpm build:web
pnpm build:all
pnpm smoke
pnpm serve
pnpm serve:bun发布
pnpm build:all
pnpm smoke
npm pack --dry-run
npm publish --registry https://registry.npmjs.org/npm 包名是 [email protected]。
