Client-Server 通信机制
OpenCode 采用客户端-服务器架构,通过事件总线实现双向通信和实时更新。
概述
OpenCode 分为两部分:
- Server:后端服务,处理业务逻辑、工具执行、会话管理等
- Client:前端客户端,提供 UI 和用户交互
- 通信方式:通过 Server-Sent Events (SSE) 和 Server-Sent Events 总线实现实时双向通信
定义位置
- Server:
packages/opencode/src/server/server.ts - Server Events:
packages/opencode/src/server/event.ts - Client:
packages/ui/和packages/desktop/ - Bus 系统:
packages/opencode/src/bus/bus-event.ts
核心通信机制
1. Server-Sent Events (SSE)
Server 通过 Server-Sent Events 向客户端推送事件,使用 Server-Sent Events 协议。
事件定义:packages/opencode/src/server/event.ts:5-7
export const Event = {
Connected: BusEvent.define("server.connected", z.object({})),
Disposed: BusEvent.define("global.disposed", z.object({})),
}
使用场景:
- Server.Connected:客户端连接时通知
- Server.Disposed:Server 关闭时清理资源
2. Server-Sent Events
Server 通过 Server-Sent Events 向客户端推送多种类型的事件。
主要事件类型包括:
| 事件来源 | 事件类型 | 说明 | 文档 |
|---|---|---|---|
| Session | session.created | 会话创建 | Session |
session.updated | 会话更新 | Session | |
session.deleted | 会话删除 | Session | |
session.diff | 会话差异更新 | Session | |
session.error | 会话错误 | Session | |
| Message | message.updated | 消息更新 | Message |
message.partUpdated | 消息部分更新 | Message | |
message.partRemoved | 消息部分删除 | Message | |
message.removed | 消息删除 | Message | |
| Question | question.asked | 问题询问 | Question |
question.replied | 问题回复 | Question | |
question.rejected | 问题拒绝 | Question | |
| Permission | permission.asked | 权限询问 | Permission |
permission.replied | 权限回复 | Permission | |
| PTY | pty.created, pty.updated, pty.deleted | 伪终端 | PTY |
| Project | project.worktree.added, project.worktree.removed | 工作树 | Project |
| Config | config.updated | 配置更新 | Config |
| Command | command.executed | 命令执行 | Command |
| Global | global.disposed | 全局事件 | - |
3. HTTP API + WebSocket
Server 提供 REST API 和 WebSocket 端点:
REST API(packages/opencode/src/server/server.ts):
// Session 路由
app.get("/", ...) // 列出会话
app.get("/status", ...) // 获取会话状态
app.get("/:sessionID", ...) // 获取会话详情
app.get("/:sessionID/children", ...) // 获取子会话
// Message 路由
app.post("/:sessionID/init", ...) // 初始化会话
app.post("/:sessionID/prompt", ...) // 发送提示给 LLM
// UI 路由
app.get("/events", ...) // 订阅事件
// 系统路由
app.get("/config", ...) // 获取配置
app.patch("/config", ...) // 更新配置
WebSocket(用于实时事件流):
// 通过 streamSSE 推送事件到客户端
return streamSSE(c, async (stream) => {
for await (const event of allEvents) {
await stream.write({
event: event.type,
data: event.properties,
})
}
})
事件总线(Bus)
Bus.publish
发布事件到所有订阅者:
定义:packages/opencode/src/bus/bus-event.ts:10-43
export function publish<Type extends string, Properties extends ZodType>(type: Type, properties: Properties): void {
for (const subscriber of subscribers.values()) {
subscriber({
type,
properties,
timestamp: Date.now(),
})
}
}
使用示例:
// 发布会话创建事件
Bus.publish(Session.Event.Created, {
info: sessionInfo,
})
Bus.subscribe
订阅特定类型的事件:
定义:packages/opencode/src/bus/bus-event.ts:45-57
export function subscribe<Type extends string, Properties extends ZodType>(
type: Type,
handler: (event: { type: Type; properties: Properties; timestamp: number }) => void,
): () => void {
// 添加订阅者到对应类型列表
subscribers.get(type).push(handler)
// 返回取消订阅的函数
return () => {
const list = subscribers.get(type)
const index = list.indexOf(handler)
if (index >= 0) list.splice(index, 1)
}
}
使用示例:
// 订阅消息部分更新事件
const unsubscribe = Bus.subscribe(MessageV2.Event.PartUpdated, async (event) => {
if (event.properties.part.type === "tool") {
console.log("Tool part updated:", event.properties)
}
})
// 后续取消订阅
unsubscribe()
Bus.subscribeAll
订阅所有事件:
定义:packages/opencode/src/bus/bus-event.ts:59-63
export function subscribeAll(handler: (event: BusEvent) => void): () => void {
allSubscribers.push(handler)
return () => {
const index = allSubscribers.indexOf(handler)
if (index >= 0) allSubscribers.splice(index, 1)
}
}
典型使用场景
场景 1:Server 端发布事件
// Server 端:packages/opencode/src/session/index.ts
Bus.publish(Session.Event.Created, {
info: sessionInfo,
})
场景 2:Client 端订阅事件
// Client 端:packages/ui/src/context/global-sync.tsx
Bus.subscribeAll(async (event) => {
switch (event.type) {
case "session.created":
// 处理会话创建
break
case "message.partUpdated":
// 处理消息部分更新
break
case "question.asked":
// 处理问题询问
break
}
})
场景 3:HTTP API 调用
// Client 使用 SDK 调用
import { createSdk } from "@opencode-ai/sdk"
const sdk = createSdk({
serverURL: "http://localhost:4096",
})
// 列出所有会话
const sessions = await sdk.session.list()
// 获取特定会话
const session = await sdk.session.get({ sessionID: "sess_abc123" })
场景 4:双向通信流程
事件流程示例
消息部分更新流程
Question 交互流程
Session 更新流程
Client 端实现
事件订阅(packages/ui/)
// packages/ui/src/context/global-sync.tsx
Bus.subscribeAll(async (event) => {
switch (event.type) {
case "session.created":
handleSessionCreated(event.properties)
break
case "message.partUpdated":
handleMessagePartUpdated(event.properties)
break
case "question.asked":
handleQuestionAsked(event.properties)
break
case "question.replied":
handleQuestionReplied(event.properties)
break
}
})
响应式状态管理
Client 使用响应式状态管理事件:
// packages/ui/src/store/context.tsx
const [session] = createSignal<SessionContext>()
const [message] = createSignal<Record<string, MessageContext>>()
const [question] = createSignal<Record<string, QuestionContext>>()
// 根据事件更新状态
switch (event.type) {
case "session.created":
session.update((ctx) => ({
...ctx,
[event.properties.info.id]: event.properties.info,
}))
break
}
相关文档
变更历史
| 版本 | 变更内容 | 日期 |
|---|---|---|
| v1 | 初始 Client-Server 通信机制文档 | - |