Skip to content

Latest commit

 

History

History
108 lines (71 loc) · 5.23 KB

File metadata and controls

108 lines (71 loc) · 5.23 KB

第 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 组合成 RootHttpApiInstanceHttpApi

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 理解成:

Location = { directory, workspaceID? }
  • 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 为例:

  1. groups/session.ts 定义路径、query 和 payload schema;
  2. api.tsSessionApi 加到 InstanceHttpApi
  3. handlers/session.ts 解析 Location / Session service;
  4. V1 入口可能调用 SessionPrompt.prompt,V2 入口则把 prompt 写入 SessionInput 并 wake execution;
  5. 事件通过 EventV2Bridge / Sync / SSE 对外广播;
  6. generated client / sdk-next 让 UI 用同一契约调用。

本章小结

Server 是“runtime + API + 生命周期”,Location 是多项目支持的坐标系,HttpApi 是所有宿主的共同边界。理解这三件事,才能看懂为什么 TUI 不该直接 import Session service,也能理解一个本地 CLI 为什么仍然走完整的服务路径。

源码锚点