Code Reader
首页
帮助
设计文档
首页
帮助
设计文档
  • Client-Server 通信机制

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 向客户端推送多种类型的事件。

主要事件类型包括:

事件来源事件类型说明文档
Sessionsession.created会话创建Session
session.updated会话更新Session
session.deleted会话删除Session
session.diff会话差异更新Session
session.error会话错误Session
Messagemessage.updated消息更新Message
message.partUpdated消息部分更新Message
message.partRemoved消息部分删除Message
message.removed消息删除Message
Questionquestion.asked问题询问Question
question.replied问题回复Question
question.rejected问题拒绝Question
Permissionpermission.asked权限询问Permission
permission.replied权限回复Permission
PTYpty.created, pty.updated, pty.deleted伪终端PTY
Projectproject.worktree.added, project.worktree.removed工作树Project
Configconfig.updated配置更新Config
Commandcommand.executed命令执行Command
Globalglobal.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
}

相关文档

  • Session - 会话对象
  • Message - 消息对象
  • Question - 问题系统
  • PTY - 虚拟终端
  • Permission - 权限系统
  • Config - 配置系统

变更历史

版本变更内容日期
v1初始 Client-Server 通信机制文档-