第 3 章:Server 与 API —— 一个 Runtime,多种连接方式¶
1. Server 的职责不是“监听端口”这么简单¶
packages/opencode/src/server/server.ts 同时负责:
- 创建 Effect HTTP server 和 Node listener;
- 构造
HttpApiApp的 route handler; - 组装
AppNodeBuilder、WebSocket tracker 和 middleware; - 为每个 listener 安装新的环境 ConfigProvider;
- 可选发布 mDNS;
- 关闭 HTTP socket、WebSocket、Scope 和 mDNS 资源。
因此 server 是运行时生命周期边界:它把一堆可组合 service 变成一个可以被 CLI、TUI、Web、Desktop 或 ACP 连接的实例。
2. API 不是手写的 route map,而是声明式组合¶
packages/opencode/src/server/routes/instance/httpapi/api.ts 把许多 API group 组合成 RootHttpApi 和 InstanceHttpApi:
RootHttpApi
├─ ControlApi
├─ ControlPlaneApi
├─ GlobalApi
├─ SchemaErrorMiddleware
└─ Authorization
InstanceHttpApi
├─ Config / Experimental / File / Instance
├─ MCP / Project / ProjectCopy
├─ PTY / Question / Permission / Provider
├─ Session / Sync / TUI / Workspace
└─ LocationMiddleware + SessionLocationMiddleware
每个 group 再由 handlers/* 实现。这个拆法的好处是“接口形状”和“业务实现”分开;新增接口时先修改 API schema,再生成 client,最后在 handler 中提供实现。
3. Location:API 请求的隐形坐标¶
一个 server 可以服务多个 directory、project、workspace 和 worktree。仅用 sessionID 不足以决定应该访问哪个数据库、配置、工具 registry 或 filesystem,因此 API 请求要解析 Location。
可以把 Location 理解成:
directory决定当前项目目录和配置发现;workspaceID为未来或当前的多 workspace 语义提供作用域;- Session 路由还会通过
SessionLocationMiddleware验证 session 与请求位置一致。
这比把 directory 放进每个 handler 参数更强:Location 被中间件解析后,以服务依赖或 context 的形式贯穿同一请求。
4. “Remote client”和“Embedded OpenCode”¶
AGENTS.md / CONTEXT.md 对两种客户端有明确区分:
- OpenCode Client:从公共 HttpApi 生成的 Promise / Effect API;
- Embedded OpenCode:使用同一 router 和 handler,但把 transport 换成内存 HTTP client,并额外暴露同进程能力。
所以 Embedded 不是“另写一份本地实现”。它仍然经过 API router 和 handler,只是省略了真实 TCP 网络。这种方式让 CLI / SDK / 单元测试能共享 API 语义。
5. 事件流为什么由 Server 暴露¶
执行过程包含文本 delta、reasoning、tool call、permission、question、status、文件变更等很多事件。UI 如果轮询 messages,会遇到:
- 流式文本延迟高;
- 工具正在运行但持久化 message 尚未完成;
- 多个订阅者重复拉取;
- 断线后不知道从哪里继续。
OpenCode 把 Event V2 作为实时和回放边界,Server 再根据消费者提供 SSE、WebSocket 或 sync API。UI 消费 event,不需要知道 Session Runner 内部的 Effect fiber。
6. API 设计的三条安全线¶
第一条:SchemaErrorMiddleware¶
请求不符合 schema 时,在 API 边界变成结构化错误,而不是让任意解析异常穿透到客户端。
第二条:Authorization¶
Server 可以在 root API 统一处理认证;具体的 permission 则属于 session/tool 语义,两者不要混成“HTTP 鉴权”。
第三条:Location / Session location¶
即使 sessionID 合法,也要检查它是否属于当前请求的 Location,防止跨项目读取或修改 session。
7. 从一条 HTTP 请求读源码¶
以 POST session/:sessionID/message 为例:
groups/session.ts定义路径、query 和 payload schema;api.ts把SessionApi加到InstanceHttpApi;handlers/session.ts解析 Location / Session service;- V1 入口可能调用
SessionPrompt.prompt,V2 入口则把 prompt 写入SessionInput并 wake execution; - 事件通过
EventV2Bridge/Sync/ SSE 对外广播; - generated
client/sdk-next让 UI 用同一契约调用。
本章小结¶
Server 是“runtime + API + 生命周期”,Location 是多项目支持的坐标系,HttpApi 是所有宿主的共同边界。理解这三件事,才能看懂为什么 TUI 不该直接 import Session service,也能理解一个本地 CLI 为什么仍然走完整的服务路径。