0%

跟着OpenCode学智能体设计和开发6:服务器API

REST API 端点:OpenAPI 规范与使用

OpenCode 服务器提供了一个基于 OpenAPI 3.1.1 规范构建的全面 REST API,支持与 AI 编码 Agent、会话管理和项目操作的程序化交互。该架构支持同步 HTTP 请求以及通过 Server-Sent Events 和 WebSockets 进行实时通信,为多样化的集成场景提供了灵活性。

来源:openapi.json, server.ts

API 架构概览

OpenCode API 遵循模块化设计,组织为不同的资源域,每个域服务于 AI 辅助开发工作流中的特定功能。服务器实现使用 Hono 框架和 hono-openapi 中间件,从路由定义和 Zod schemas 自动生成 OpenAPI 规范,确保类型安全和文档一致性。

API 服务器实现了基于目录的上下文中间件,从查询参数或 x-opencode-directory header 中提取工作目录,从而在单个服务器实例中支持多项目。请求通过特定于实例的处理程序路由,这些处理程序按需延迟初始化项目资源。

来源:server.ts, client.ts

OpenAPI 规范

OpenAPI 规范维护在 packages/sdk/openapi.json 中,并从路由定义自动生成。该规范遵循 OpenAPI 3.1.1 标准,包含全面的元数据、请求/响应 schemas 和每个端点的代码示例。

规范元数据:

属性 描述
OpenAPI Version 3.1.1 改进了 JSON Schema 支持的最新 OpenAPI 规范
Title opencode API 标识符
Description opencode api 简要描述
Version 1.0.0 用于兼容性跟踪的 API 版本

该规范在 /doc 端点公开,用于交互式探索和客户端代码生成。这使得开发者可以使用标准 OpenAPI 工具(如 OpenAPI Generator 或 Swagger Codegen)自动生成多种语言的客户端 SDK。

来源:openapi.json, server.ts

全局端点

全局端点管理与特定项目或会话无关的服务器级操作。这些端点提供健康监控、生命周期管理和系统级事件订阅。

健康检查

GET /global/health

返回服务器健康状态和版本信息,使负载均衡器和监控系统能够验证服务器可用性。

响应 Schema:

1
2
3
4
{
healthy: true, // 如果服务器运行则始终为 true
version: string // 来自 Installation.VERSION 的服务器版本
}

示例请求:

1
2
3
4
5
import { createOpencodeClient } from "@opencode-ai/sdk"

const client = createOpencodeClient()
const health = await client.global.health()
console.log(health.healthy, health.version)

来源:server.ts, openapi.json

全局事件流

GET /global/event

建立 Server-Sent Events (SSE) 连接,以接收来自 OpenCode 系统的实时更新。该端点流式传输事件,包括服务器连接、心跳和系统级通知。

响应: text/event-stream,包含遵循 GlobalEvent schema 模式的 JSON 载荷。

流事件:

  • server.connected - 初始连接确认
  • server.heartbeat - 30 秒间隔心跳(防止 WKWebView 超时)
  • 来自全局事件总线的自定义事件类型

连接每 30 秒维持一次心跳,以防止连接超时,这对于基于 WebView 的客户端(如 iOS Safari)尤为重要。

来源:server.ts, openapi.json

全局销毁

POST /global/dispose

干净地关闭所有 OpenCode 实例,释放包括文件监视器、LSP 服务器和 Agent 进程在内的资源。此操作不可逆,应谨慎使用。

响应: boolean,表示销毁是否成功。

该端点在全局总线上触发 global.disposed 事件,并调用 Instance.disposeAll() 来终止所有活动的项目实例。

来源:server.ts, openapi.json

项目端点

项目端点管理 OpenCode 项目,包括列出可用项目、检索当前项目上下文和更新项目元数据。

列出项目

GET /project

检索已使用 OpenCode 打开的所有项目列表,按最近访问时间排序。

参数:

  • directory (query, optional): 用于上下文的工作目录路径

响应: 包含项目元数据的 Project 对象数组。

获取当前项目

GET /project/current

返回 OpenCode 当前正在处理的活跃项目。此端点对于需要显示活跃项目上下文的 UI 组件很有用。

参数:

  • directory (query, optional): 用于上下文的工作目录路径

响应: 单个 Project 对象。

更新项目

PATCH /project/{projectID}

更新项目属性,例如名称、图标和颜色。这主要用于 UI 自定义和项目标识。

参数:

  • directory (query, optional): 工作目录路径
  • projectID (path, required): 项目标识符

请求体:

1
2
3
4
5
6
7
{
name?: string,
icon?: {
url?: string,
color?: string
}
}

响应: 包含修改属性的更新后的 Project 对象。

来源:project.ts, openapi.json

会话端点

会话端点提供对 OpenCode 对话的全面管理,包括创建、查询、分支和删除。会话代表与消息历史记录、工具调用和状态相关联的单独 AI 交互。

列出会话

GET /session

检索所有会话,并具有可选的过滤功能。会话默认按最近更新时间排序。

参数:

  • directory (query, optional): 工作目录路径
  • start (query, optional): 过滤在此时间戳之后(含)更新的会话(自纪元以来的毫秒数)
  • search (query, optional): 按标题过滤会话(不区分大小写)
  • limit (query, optional): 要返回的会话最大数量

响应: Session 对象数组。

获取会话状态

GET /session/status

检索所有会话的当前状态,包括活跃、空闲和已完成状态。这对于监控活跃 AI 操作和显示进度指示器很有用。

参数:

  • directory (query, optional): 工作目录路径

响应: 将会话 ID 映射到 SessionStatus 对象的对象。

获取会话

GET /session/{sessionID}

检索有关特定会话的详细信息,包括元数据、消息历史记录和状态。

参数:

  • directory (query, optional): 工作目录路径
  • sessionID (path, required): 会话标识符,匹配模式 ^ses.*

响应: 完整的 Session 对象。

创建会话

POST /session

创建一个新的 OpenCode 会话,用于与 AI 助手交互。会话可以使用父引用进行初始化,以便进行分支工作流和自定义权限配置。

参数:

  • directory (query, optional): 工作目录路径

请求体:

1
2
3
4
5
{
parentID?: string, // 用于分支的可选父会话 ID
title?: string, // 会话标题
permission?: PermissionRuleset // 自定义权限配置
}

响应: 新创建的 Session 对象。

更新会话

PATCH /session/{sessionID}

更新会话属性,例如标题或归档时间戳。这通常用于会话管理和清理操作。

参数:

  • sessionID (path, required): 会话标识符

请求体:

1
2
3
4
5
6
{
title?: string,
time?: {
archived?: number // 归档时间戳
}
}

响应: 更新后的 Session 对象。

删除会话

DELETE /session/{sessionID}

永久删除会话及所有关联数据,包括消息、工具调用历史记录和缓存状态。此操作无法撤销。

参数:

  • directory (query, optional): 工作目录路径
  • sessionID (path, required): 会话标识符

响应: boolean,表示删除是否成功。

会话操作

几个额外的会话操作支持高级工作流:

  • 获取子会话 (GET /session/{sessionID}/children):检索从指定父会话分支出来的所有会话
  • 获取会话待办事项 (GET /session/{sessionID}/todo):检索包含会话操作项的待办事项列表
  • 初始化会话 (POST /session/{sessionID}/init):分析应用程序并创建包含项目特定配置的 AGENTS.md
  • 分支会话 (POST /session/{sessionID}/fork):从现有会话的特定消息点创建新会话
  • 还原会话 (POST /session/{sessionID}/revert):将会话状态还原到以前的某个消息点

来源:server.ts, openapi.json

配置端点

配置端点管理 OpenCode 的运行时设置和首选项,允许在不重新启动服务器的情况下动态调整行为。

获取配置

GET /config

检索当前的 OpenCode 配置设置,包括提供程序设置、模型首选项和 UI 选项。

响应: 包含所有配置参数的 Config.Info 对象。

更新配置

PATCH /config

更新 OpenCode 配置设置。更改将立即应用并持久化到存储中。

请求体: 包含更新值的 Config.Info 对象。

响应: 更新后的 Config.Info 对象。

配置更新会影响服务器实例中的所有操作,包括 Agent 行为、提供程序身份验证和工具权限。

来源:server.ts

PTY (伪终端) 端点

PTY 端点管理终端会话,用于在 OpenCode 的 Agent 工作流中执行 shell 命令和进程。这些功能提供实时终端交互能力。

列出 PTY 会话

GET /pty

检索所有活动的伪终端会话,包括其当前状态和进程信息。

响应: Pty.Info 对象数组。

创建 PTY 会话

POST /pty

创建一个新的伪终端会话,用于运行 shell 命令。PTY 会话是具有自己的工作目录和环境的隔离进程。

请求体: 指定会话参数的 Pty.CreateInput。

响应: 新创建会话的 Pty.Info 对象。

获取 PTY 会话

GET /pty/{ptyID}

检索有关特定 PTY 会话的详细信息。

参数:

  • ptyID (path, required): 会话标识符

响应: Pty.Info 对象。

更新 PTY 会话

PUT /pty/{ptyID}

更新现有 PTY 会话的属性,例如窗口大小或工作目录。

参数:

  • ptyID (path, required): 会话标识符

请求体: 包含更新属性的 Pty.UpdateInput。

响应: 更新后的 Pty.Info 对象。

移除 PTY 会话

DELETE /pty/{ptyID}

终止并移除 PTY 会话,向子进程发送适当的信号。

参数:

  • ptyID (path, required): 会话标识符

响应: boolean,表示移除是否成功。

连接到 PTY 会话

GET /pty/{ptyID}/connect

建立 WebSocket 连接,以便与 PTY 会话进行实时交互。这支持流式终端输入/输出,用于交互式命令执行。

参数:

  • ptyID (path, required): 会话标识符

协议: 支持双向消息流式传输的 WebSocket。

来源:server.ts

工具注册端点

工具端点提供对可用工具注册表的访问,Agent 可以使用这些工具与代码库交互,包括文件操作、搜索、LSP 集成等。

列出工具 ID

GET /experimental/tool/ids

检索所有可用工具标识符的列表,包括内置工具和来自插件的动态注册工具。

响应: 工具 ID 字符串数组。

列出工具

GET /experimental/tool

检索特定提供程序和模型组合的可用工具的详细信息。工具包括用于 AI Agent 集成的 JSON schema 参数。

参数:

  • provider (query, required): AI 提供程序名称(例如 “anthropic”、”openai”)
  • model (query, required): 用于参数格式的模型标识符

响应: ToolListItem 对象数组:

1
2
3
4
5
{
id: string, // 工具标识符
description: string, // 供 AI 使用的工具描述
parameters: object // 参数的 JSON Schema
}

工具 schemas 是从 Zod 定义动态生成的,并使用 zodToJsonSchema 转换为 JSON Schema 格式,确保与各种 AI 提供程序 SDK 兼容。

来源:server.ts

实例和路径端点

实例管理端点控制单个 OpenCode 项目实例,提供生命周期管理和路径信息。

销毁实例

POST /instance/dispose

清理并处置当前的 OpenCode 实例,释放活跃项目的资源。与全局销毁不同,这仅影响当前项目上下文。

响应: boolean,表示销毁是否成功。

获取路径

GET /path

检索当前 OpenCode 实例的路径信息,包括工作目录和配置路径。

响应: Path 对象:

1
2
3
4
5
6
7
{
home: string, // 用户主目录
state: string, // OpenCode 状态目录
config: string, // 配置文件路径
worktree: string, // Git worktree 路径(如果适用)
directory: string // 当前工作目录
}

来源:server.ts

VCS 和 Worktree 端点

版本控制系统端点提供 Git 集成,用于管理分支、worktrees 和存储库状态。

获取 VCS 信息

GET /vcs

检索当前项目的版本控制系统信息,包括当前 git 分支和存储库状态。

响应: 包含分支信息的 Vcs.Info 对象。

创建 Worktree

POST /experimental/worktree

为当前项目创建一个新的 git worktree,实现隔离的开发工作流和沙盒实验。

请求体: 包含 worktree 配置的 Worktree.create.schema。

响应: Worktree.Info 对象。

列出 Worktree

GET /experimental/worktree

列出当前项目的所有沙盒 worktrees,提供对隔离开发环境的可见性。

响应: worktree 目录路径数组。

来源:server.ts

错误处理

API 使用 NamedError 类和一致的响应格式实现了标准化的错误处理系统。所有错误响应都包含结构化数据,以帮助调试和客户端错误恢复。

错误响应 Schema:

状态码 Schema 描述
400 BadRequestError 验证错误、无效参数或格式错误的请求
404 NotFoundError 未找到资源(会话、项目、PTY 会话)
500 UnknownError 带有堆栈跟踪的意外服务器错误

BadRequestError 结构:

1
2
3
4
5
{
data: any, // 导致错误的请求数据
errors: Array<Record<string, any>>, // 特定的验证错误
success: false // 错误始终为 false
}

错误中间件拦截异常并将其映射到适当的 HTTP 响应,同时通过 Zod schema 验证维护类型安全,从而提供详细的错误信息。

来源:error.ts

TypeScript SDK 用法

OpenCode SDK 提供了一个从 OpenAPI 规范生成的类型安全客户端库,自动处理序列化、错误处理和身份验证。

SDK 在配置时会自动将 directory 参数注入请求头,通过 createOpencodeClient 函数简化了多项目 API 的使用。

客户端初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { createOpencodeClient, OpencodeClient } from "@opencode-ai/sdk"

// 基本客户端初始化
const client = createOpencodeClient({
baseUrl: "http://localhost:4096",
directory: "/path/to/project" // 可选目录上下文
})

// 自定义 fetch 配置(禁用长时间运行操作的超时)
const customClient = createOpencodeClient({
fetch: (req) => {
req.timeout = false
return fetch(req)
},
headers: {
"x-opencode-directory": "/custom/path"
}
})

常见 SDK 模式

会话管理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 使用过滤器列出会话
const sessions = await client.session.list({
search: "refactor",
limit: 10
})

// 创建新会话
const session = await client.session.create({
title: "New Development Session",
permission: customPermissions
})

// 分支现有会话
const forked = await client.session.fork({
sessionID: "ses_abc123",
messageID: "msg_def456"
})

项目操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 列出所有项目
const projects = await client.project.list()

// 获取当前项目
const current = await client.project.current()

// 更新项目元数据
await client.project.update({
projectID: "proj_123",
body: {
name: "Updated Project Name",
icon: { color: "#FF5733" }
}
})

实时事件流式传输:

1
2
3
4
5
6
7
8
9
// 订阅全局事件
const eventStream = await client.global.event({
onMessage: (event) => {
console.log("Global event:", event.payload.type)
},
onError: (error) => {
console.error("Stream error:", error)
}
})

来源:client.ts, openapi.json

CORS 和安全性

API 实现了支持本地开发和生产部署的全面 CORS 策略。源验证遵循特定模式,以确保安全的跨源访问。

允许的源:

生产部署必须使用 HTTPS 并进行适当的源验证。CORS 中间件强制执行源匹配,以防止来自恶意站点的未经授权的跨源请求。

请求头:

  • x-opencode-directory:指定工作目录上下文,覆盖查询参数
  • 标准 HTTP 头(Content-Type、Authorization)被传递

来源:server.ts

WebSocket 通信:实时更新与事件

OpenCode 利用 WebSocket 和服务器发送事件(SSE)在客户端与服务器之间提供实时双向通信。这种架构实现了实时终端会话、事件驱动更新以及全系统的即时通知。该实现结合了 Hono 的 WebSocket 能力和 SSE 流式传输,同时支持传统的 WebSocket 连接和基于 HTTP 的事件流。

来源:packages/opencode/src/server/server.ts, packages/opencode/src/server/server.ts

WebSocket 架构概览

OpenCode 中的实时通信层包含两个互补机制:用于低延迟双向数据传输(主要用于 PTY 会话)的 WebSocket 连接,以及用于系统事件单向流式传输的 服务器发送事件(SSE)。这种混合方法针对不同的用例进行了优化——WebSocket 为交互式会话提供真正的双向通信,而 SSE 提供高效且防火墙友好的事件广播。

WebSocket 系统通过服务器设置中的 Hono upgradeWebSocket 中间件和 websocket 配置进行初始化。PTY 系统与 WebSocket 订阅者保持活跃会话,向连接的客户端广播终端输出并接受用户输入。

来源:packages/opencode/src/server/server.ts, packages/opencode/src/server/server.ts

PTY WebSocket 连接

主要的 WebSocket 实现实现了与伪终端(PTY)会话的实时交互。每个 PTY 会话可以有多个 WebSocket 订阅者,允许多个客户端同时观察同一个终端会话。这种架构支持交互式终端使用和协作查看。

WebSocket 生命周期:

  1. 建立连接:客户端连接到 /pty/:ptyID/connect 端点
  2. 会话验证:服务器验证 PTY 会话是否存在
  3. 处理器分配:返回连接生命周期回调
  4. 数据流传输:终端输出广播给所有订阅者
  5. 输入处理:客户端消息转发到 PTY 进程
  6. 清理:自动移除断开连接的订阅者

PTY 连接处理器管理完整的 WebSocket 生命周期:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
upgradeWebSocket((c) => {
const id = c.req.param("ptyID")
let handler: ReturnType<typeof Pty.connect>
if (!Pty.get(id)) throw new Error("Session not found")
return {
onOpen(_event, ws) {
handler = Pty.connect(id, ws)
},
onMessage(event) {
handler?.onMessage(String(event.data))
},
onClose() {
handler?.onClose()
}
}
})

来源:packages/opencode/src/server/server.ts, packages/opencode/src/pty/index.ts

PTY 会话状态管理

每个活跃的 PTY 会话都维护订阅者集和输出缓冲区。系统实现了具有可配置限制(默认 2MB)的缓冲区管理策略,以处理客户端临时断开连接的场景。当客户端连接时,它们会从断开连接点接收缓冲输出,确保终端状态的连续性。

PTY 接口定义了会话元数据:

字段 类型 描述
id string 唯一的 PTY 会话标识符
title string 会话的显示标题
command string 正在执行的 Shell 命令
args string[] 命令参数
cwd string 当前工作目录
status “running” “exited” -
pid number PTY 进程的进程 ID

来源:packages/opencode/src/pty/index.ts, packages/opencode/src/pty/index.ts

多订阅者支持

PTY 系统支持到单个终端会话的多个并发 WebSocket 连接。这实现了屏幕共享和协作调试等用例。系统高效地管理订阅者集:

1
2
3
4
5
6
interface ActiveSession {
info: Info
process: IPty
buffer: string
subscribers: Set<WSContext>
}

当终端数据从 PTY 进程到达时,它会分发给所有活跃的订阅者。连接已关闭的订阅者会自动从集合中移除。如果没有订阅者连接,数据会在缓冲区中累积,直到达到配置的限制,然后循环最旧的数据。

来源:packages/opencode/src/pty/index.ts, packages/opencode/src/pty/index.ts

服务器发送事件 (SSE)

对于事件广播,OpenCode 通过 Hono 的 streamSSE 功能使用 SSE。这为单向通信提供了一种比 WebSocket 更轻量的替代方案,特别适合系统范围的通知和状态更新。

全局事件流

/global/event 端点提供对 全局总线 的访问,该总线跨所有 OpenCode 实例广播事件。此流非常适合监控系统范围的更改和接收跨实例通知。

主要特性:

  • 初始连接事件 (server.connected) 确认成功订阅
  • 心跳机制每 30 秒发送一次保活信号,以防止 WKWebView 超时
  • 客户端断开连接时自动清理
  • 与目录无关的事件传播
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
streamSSE(c, async (stream) => {
stream.writeSSE({
data: JSON.stringify({
payload: {
type: "server.connected",
properties: {},
},
}),
})
async function handler(event: any) {
await stream.writeSSE({
data: JSON.stringify(event),
})
}
GlobalBus.on("event", handler)

const heartbeat = setInterval(() => {
stream.writeSSE({
data: JSON.stringify({
payload: {
type: "server.heartbeat",
properties: {},
},
}),
})
}, 30000)

await new Promise<void>((resolve) => {
stream.onAbort(() => {
clearInterval(heartbeat)
GlobalBus.off("event", handler)
resolve()
})
})
})

来源:packages/opencode/src/server/server.ts, packages/opencode/src/bus/global.ts

实例特定事件流

/event 端点订阅 实例总线,该总线广播特定于当前项目目录和 OpenCode 实例的事件。此流提供作用于工作上下文的事件,例如会话更改、权限请求和 LSP 服务器状态更新。

实例事件流包含一个 处置时关闭 机制——当实例被处置时,流会自动关闭以防止传递陈旧事件。

来源:packages/opencode/src/server/server.ts

事件系统架构

OpenCode 实现了一个基于 EventEmitter 构建的 类型化事件系统,支持结构化、经过架构验证的事件定义。该系统确保通过 WebSocket 和 SSE 通道流动的所有事件类型的类型安全和文档记录。

事件定义模式

事件使用带有 Zod 架构的 BusEvent.define 助手定义:

1
2
3
4
export const Event = {
Connected: BusEvent.define("server.connected", z.object({})),
Disposed: BusEvent.define("global.disposed", z.object({})),
}

事件注册表维护所有已定义的事件类型,支持运行时验证和架构生成。BusEvent.payloads() 函数生成涵盖所有已注册事件的可辨识联合架构,该架构用于 OpenAPI 规范。

来源:packages/opencode/src/server/server.ts, packages/opencode/src/bus/bus-event.ts, packages/opencode/src/bus/bus-event.ts

PTY 事件广播

PTY 系统为生命周期更改发出事件:

  • pty.created - 创建新终端会话时发布
  • pty.updated - 会话属性(标题、大小)更改时发布
  • pty.exited - 终端进程退出时发布
  • pty.deleted - 删除会话时发布

这些事件通过事件总线流动,并可供 SSE 订阅者使用,从而实现对应用程序中终端会话状态更改的实时监控。

来源:packages/opencode/src/pty/index.ts, packages/opencode/src/pty/index.ts

客户端集成

客户端通过 React/Solid 应用程序架构中专门的上下文提供者和钩子与 WebSocket 和 SSE 流集成。

终端 WebSocket 客户端

终端组件管理与 PTY 会话的 WebSocket 连接:

1
2
3
4
5
6
7
export const Terminal = (props: TerminalProps) => {
const sdk = useSDK()
const theme = useTheme()
let ws: WebSocket | undefined
let term: Term | undefined
// ... connection management
}

终端处理 WebSocket 生命周期事件、主题适配以及使用 Ghostty Web 组件的终端仿真器集成。

来源:packages/app/src/components/terminal.tsx, packages/app/src/components/terminal.tsx

事件订阅上下文

同步上下文管理用于实时状态同步的 SSE 订阅:

1
2
3
4
5
6
7
8
9
export const { use: useSync, provider: SyncProvider } = createSimpleContext({
name: "Sync",
init: () => {
const globalSync = useGlobalSync()
const sdk = useSDK()
const [store, setStore] = globalSync.child(sdk.directory)
// ... SSE subscription logic
}
})

这使得组件能够订阅实例特定的事件,并自动响应服务器事件更新其状态。

来源:packages/app/src/context/sync.tsx

连接管理和可靠性

心跳机制

两个 SSE 流都实现了 30 秒心跳 以防止连接超时,这对于具有严格超时策略(默认 60 秒)的基于 WebKit 的浏览器尤为重要。这确保长期连接在不活动期间保持活跃。

错误处理和重新连接

WebSocket 连接包括通过可选的 onConnectError 回调进行的错误处理。PTY 系统会自动从订阅者集中移除失败的连接,防止死连接累积。SSE 流在客户端断开连接时使用 stream.onAbort 回调进行清理。

缓冲区管理

当没有订阅者连接时,PTY 系统为终端输出实现了 循环缓冲区:

  • 默认缓冲区限制:2MB
  • 块大小:64KB(用于高效传输)
  • 行为:达到限制时丢弃最旧的数据

当客户端连接时,缓冲区分块发送,以避免使 WebSocket 连接不堪重负。

缓冲区管理策略确保在临时断开后重新连接的客户端可以恢复终端状态,同时防止因孤立会话导致的内存无限增长。

来源:packages/opencode/src/pty/index.ts, packages/opencode/src/pty/index.ts

API 端点参考

WebSocket 端点

端点 方法 目的 WebSocket 处理器
/pty/:ptyID/connect GET 连接到 PTY 会话 upgradeWebSocket

SSE 端点

端点 方法 目的 事件源
/global/event GET 订阅全局事件 GlobalBus
/event GET 订阅实例事件 Instance Bus

PTY 管理端点 (REST)

端点 方法 目的
/pty GET 列出所有 PTY 会话
/pty POST 创建新的 PTY 会话
/pty/:ptyID GET 获取 PTY 会话信息
/pty/:ptyID PUT 更新 PTY 会话
/pty/:ptyID DELETE 删除 PTY 会话

来源:packages/opencode/src/server/server.ts, packages/opencode/src/server/server.ts, packages/opencode/src/server/server.ts

身份验证与安全

OpenCode 平台实施了一个全面的多层安全架构,旨在保护用户账户、API 端点和实时协作会话。本文档探讨了认证机制、授权流程以及安全措施,这些措施保障了平台在 Web 控制台、CLI 客户端和 GitHub Action 集成方面的安全。

认证架构概览

OpenCode 采用基于 OpenAuth.js 框架的联邦认证模型,在支持多个身份提供商的同时保持统一的安全边界。系统区分不同的参与者类型——公共用户、认证账户、工作区用户和系统进程——每种类型都有独特的权限级别和访问模式。

认证架构遵循清晰的关注点分离原则:身份提供商处理凭证验证,OpenAuth.js 服务器管理会话创建和令牌签发,Actor 系统则在所有应用层强制执行授权规则。

来源:auth.ts,api.ts,actor.ts

身份提供商集成

OpenCode 支持与 GitHub 和 Google 的 OAuth 2.0 集成,允许用户使用其现有的受信任身份进行认证。系统验证提供商响应,提取经过验证的电子邮件地址,并跨提供商创建统一的账户记录。

GitHub 认证流程

GitHub 认证利用 OAuth 2.0 授权码流程,请求访问用户资料数据和经过验证的电子邮件地址。在创建账户之前,系统会验证用户是否拥有主要且经过验证的电子邮件。

认证处理程序在 GitHub 认证期间执行关键的安全验证:

  • 电子邮件验证:仅接受具有经过验证的主要电子邮件的账户
  • 账户关联:通过提供商 ID 或电子邮件地址识别现有账户
  • 工作区预配:为新账户创建默认工作区
  • 主题令牌生成:JWT 令牌包含账户 ID 和电子邮件声明

来源:auth.ts

Google 认证流程

Google 认证遵循 OpenID Connect (OIDC),从 ID 令牌中提取主题标识符(sub)和电子邮件。系统通过 email_verified 声明要求验证电子邮件。

Provider Scopes Required Token Type Subject Source
GitHub read:user, user:email OAuth Access Token GitHub User ID
Google openid, email OIDC ID Token Google sub claim

两个提供商最终都创建与提供商特定的主题和电子邮件地址关联的账户记录,从而实现跨提供商账户发现。

来源:auth.ts

账户管理和数据模型

认证系统维护多表数据库架构,支持账户联邦、用户到工作区的关系以及 API 密钥管理。每个表都通过软删除模式强制执行参照完整性。

账户架构结构

认证数据模型跨越三个主要表,具有清晰的关注点分离:

架构设计支持多种安全功能:

  • 账户联邦:多个 auth 记录可以引用同一个 account,从而实现从不同提供商登录
  • 软删除:所有表都包含 time_deleted 以用于数据恢复和审计跟踪
  • 工作区隔离:用户被限制在具有基于角色权限的特定工作区内
  • API 密钥范围:密钥绑定到用户和工作区,防止跨工作区访问

来源:auth.sql.ts,user.sql.ts,key.sql.ts

会话管理和令牌处理

OpenCode 实施了双令牌策略:用于浏览器会话的短期 JWT 令牌和用于 CLI 和 GitHub Action 集成的长期 API 密钥。会话状态在服务器端维护,包含参与者信息的客户端 JWT 声明。

JWT 令牌结构

OpenAuth.js 服务器生成包含主题声明的 JWT 令牌,这些声明编码了已认证参与者的类型和属性:

1
2
3
4
5
6
7
{
"iss": "https://auth.opencode.dev",
"aud": "app",
"sub": "account|account123|[email protected]",
"exp": 1735689600,
"iat": 1735686000
}

主题字段以管道分隔的格式编码参与者类型和属性,从而无需在每个请求上查询数据库即可实现轻量级授权。

会话生命周期

浏览器会话遵循具有安全令牌交换的 OAuth 2.0 授权码流程:

会话中间件验证 JWT 签名并为每个请求提取参与者上下文,将其注入请求上下文以进行下游授权检查。

来源:callback.ts,auth.ts

API 密钥认证

API 密钥为 CLI 客户端、GitHub Actions 和桌面应用程序提供长期认证。系统生成加密安全的随机密钥,这些密钥的范围限定在工作区内的特定用户。

密钥生成和存储

API 密钥是使用加密安全的随机数生成器生成的:

1
2
3
4
5
6
7
8
// Generate secret key: sk- + 64 random characters (upper, lower, numbers)
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
let secretKey = "sk-"
const array = new Uint32Array(64)
crypto.getRandomValues(array)
for (let i = 0, l = array.length; i < l; i++) {
secretKey += chars[array[i] % chars.length]
}

这将生成格式为 sk-[64 个随机字符] 的密钥,提供 256 位的熵(log2(62^64) ≈ 381 bits)。

API 密钥使用流程

CLI 客户端通过在 Authorization 标头中包含 API 密钥进行认证:

系统跟踪密钥使用时间戳以进行审计,并使安全团队能够识别未使用或受损的密钥。密钥的范围限定在工作区内的个人用户,防止权限提升。

API 密钥仅在创建时显示一次。用户必须安全存储它们,因为现有密钥没有检索机制。这遵循了凭证管理的安全最佳实践。

来源:key.ts

基于 Actor 的授权

OpenCode 实施了一个 Actor 系统,该系统在整个应用程序中提供统一的授权上下文。Actor 代表具有特定类型和属性的安全主体,能够实现细粒度的访问控制决策。

Actor 类型和权限

系统定义了四种具有不同权限级别的 Actor 类型:

Actor Type Properties Use Case Typical Privileges
public None Unauthenticated users Read-only public resources
account accountID, email Authenticated users (no workspace context) Workspace listing, workspace creation
user userID, workspaceID, accountID, role Authenticated users within a workspace Workspace-specific resources, role-based permissions
system workspaceID Background processes All operations within workspace

基于角色的访问控制 (RBAC)

工作区实施具有管理员和成员角色的两层 RBAC 模型:

Actor.assertAdmin() 函数强制执行仅限管理员执行的操作,当非管理员尝试执行特权操作时抛出描述性错误。

Actor 上下文注入

Actor 系统使用异步上下文传播,使授权决策在整个请求生命周期中可用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export function provide<R, T extends Info["type"]>(
type: T,
properties: Extract<Info, { type: T }>["properties"],
cb: () => R,
) {
return ctx.provide(
{ type, properties } as any,
() => {
return Log.provide({ ...properties }, () => {
log.info("provided")
return cb()
})
},
)
}

这种模式使得授权检查无需通过函数调用显式传递参与者上下文。

Actor 上下文会自动记录在每个操作中,从而创建将操作与特定用户和工作区相关联的完整审计跟踪。这对于安全调查和合规要求非常有价值。

来源:actor.ts,workspace.ts

GitHub Actions 集成

OpenCode 通过 OpenID Connect (OIDC) 令牌交换提供安全的 GitHub Actions 集成,消除了在 CI/CD 管道中对长期密钥的需求。

OIDC 令牌验证

系统根据 GitHub 的 JWKS 端点验证 GitHub Actions OIDC 令牌:

令牌验证确保:

  • 颁发者有效性:令牌必须源自 GitHub 的 OIDC 提供商
  • 受众限制:令牌必须用于 opencode-github-action
  • 存储库绑定:主题声明嵌入存储库信息,防止跨存储库令牌使用

PAT 令牌支持

为了进行本地测试,系统支持 GitHub 个人访问令牌 (PAT) 认证:

  • 验证 PAT 权限(admin、push 或 maintain)
  • 查找存储库的 GitHub App 安装
  • 交换为安装范围的令牌

此回退机制使得可以使用与生产 CI/CD 工作流相同的安全模型进行本地开发。

来源:api.ts

共享会话安全

OpenCode 通过使用 Cloudflare Durable Objects 实施的基于密钥的访问控制系统,实现了安全的会话共享。

共享链接生命周期

共享会话使用随机生成的 UUID 作为访问密钥:

系统在多个级别强制执行访问控制:

  • 密钥验证:所有修改请求都需要正确的密钥
  • 密钥前缀验证:数据密钥必须匹配预期的会话 ID 模式
  • 管理员覆盖:平台管理员可以在没有用户密钥的情况下删除共享

会话隔离

Durable Objects 通过为每个共享维护单独的存储上下文来确保会话隔离:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
async publish(key: string, content: any) {
const sessionID = await this.getSessionID()
if (
!key.startsWith(`session/info/${sessionID}`) &&
!key.startsWith(`session/message/${sessionID}/`) &&
!key.startsWith(`session/part/${sessionID}/`)
)
return new Response("Error: Invalid key", { status: 400 })

await this.ctx.storage.put(key, content)
const clients = this.ctx.getWebSockets()
for (const client of clients) {
client.send(JSON.stringify({ key, content }))
}
}

即使密钥泄露,这也能防止跨会话数据泄露。

来源:api.ts

密钥管理

平台使用 SST 的 Secret 抽象来管理敏感凭证,确保密钥永远不会以明文形式提交到源代码或部署到基础设施中。

密钥配置

密钥在基础架构代码中定义,并在运行时注入到 worker 环境中:

1
2
3
const GITHUB_APP_ID = new sst.Secret("GITHUB_APP_ID")
const GITHUB_APP_PRIVATE_KEY = new sst.Secret("GITHUB_APP_PRIVATE_KEY")
const ADMIN_SECRET = new sst.Secret("ADMIN_SECRET")

这些密钥在请求处理期间被访问:

1
2
3
4
const auth = createAppAuth({
appId: Resource.GITHUB_APP_ID.value,
privateKey: Resource.GITHUB_APP_PRIVATE_KEY.value,
})

密钥管理策略确保:

  • 特定环境的密钥:开发和生产使用不同的密钥
  • 审计跟踪:密钥访问通过 SST 平台记录
  • 零信任:密钥在构建产物或配置文件中永远不可见

来源:app.ts,api.ts

安全最佳实践和建议

对于应用程序开发者

  • 验证 Actor 上下文:在执行敏感操作之前,始终使用 Actor.assert() 来验证预期的 Actor 类型
  • 使用 RBAC 助手:利用 Actor.assertAdmin() 执行仅限管理员执行的操作,而不是手动检查角色
  • 检查工作区成员资格:在授予访问权限之前,验证用户是否属于目标工作区
  • 记录安全事件:Actor 系统会自动记录上下文——确保敏感操作记录了适当的详细信息

对于 CLI 和集成开发者

  • 安全存储 API 密钥:切勿在配置文件或源代码中硬编码 API 密钥
  • 使用环境变量:通过环境变量或密钥管理器传递 API 密钥
  • 定期轮换密钥:为生产部署实施密钥轮换策略
  • 处理令牌过期:在长时间运行的 CI/CD 作业中 OIDC 令牌过期时,实施优雅的回退

对于工作区管理员

  • 清理未使用的密钥:定期审查并删除未使用的 API 密钥以减少攻击面
  • 管理用户访问:当用户离开组织时,将其从工作区中删除
  • 监控使用情况:跟踪每月使用情况和限制以防止成本超支
  • 审查日志:监控审计日志中是否存在异常活动模式

对于平台用户

  • 使用经过验证的电子邮件:确保您的电子邮件地址已在 OAuth 提供商处验证
  • 保护您的会话:从共享设备和浏览器注销
  • 报告可疑活动:如果您注意到未经授权的访问,请联系支持人员
  • 使用唯一密码:尽管 OpenCode 使用 OAuth,但请确保您的 OAuth 提供商账户拥有强密码且唯一

安全架构总结

Layer Component Security Mechanism Primary Threat Addressed
Identity OAuth/OIDC Providers Cryptographic verification Account compromise
Authentication OpenAuth.js JWT token signing Token forgery
Authorization Actor System Context-aware access control Privilege escalation
API Security API Keys Cryptographic randomness Credential theft
Session Management Durable Objects Per-session isolation Cross-session data leakage
CI/CD Integration OIDC Token Exchange Short-lived tokens Secret leakage in CI/CD

这种多层深度防御方法确保一个层的安全故障由相邻层的保护措施来缓解,从而创建适合企业工作负载的强大安全态势。