用 @opencode-ai/sdk 从脚本驱动 opencode:oh-my-openagent 仓库中的 SDK 参考与源码级实践
本文基于 oh-my-openagent 仓库 opencode-qa 技能中的 SDK 参考文档(.agents/skills/opencode-qa/references/sdk.md),完整讲清 @opencode-ai/sdk 的包入口、两种连接方式、客户端命名空间、常用方法与核心类型,以及如何从 OpenAPI 规范生成该 SDK;并结合仓库内 packages/omo-opencode 的真实源码用法,给出可运行的最小 QA 脚本和版本核验建议,帮助你在 Bun/TypeScript 脚本中安全地以类型化方式驱动 opencode 服务器。
定位:reference only,优先 CLI/curl 脚本
opencode-qa 技能的核心 QA 面是"经过实测的 CLI/curl 脚本",SDK 在其中被明确标注为 reference only(仅供参考):
A TypeScript/Bun way to drive opencode for QA. Prefer the tested CLI/curl scripts for portability; reach for the SDK when you want typed access from a Bun script.
也就是说,技能的主路由器(见 SKILL.md)把 QA 场景映射到带 --self-test 的 shell 脚本,而当你需要在 Bun 脚本里获得类型化访问(typed access)时才应使用 SDK。原文档特别强调了一条贯穿全文的重要规则:
IMPORTANT: method signatures differ between SDK versions and between the published docs and the generated client. ALWAYS check the installed version's types (
node_modules/@opencode-ai/sdk) before relying on a signature, and verify againstGET /doc(the OpenAPI spec the SDK is generated from).
方法签名会在不同 SDK 版本之间、以及"已发布的文档"与"生成的客户端"之间产生差异。因此在依赖任何签名之前,必须检查已安装版本的类型定义,并对照 GET /doc(即 SDK 所依据生成的 OpenAPI 规范)核验。仓库根 package.json 与 packages/omo-opencode/package.json 均将依赖锁定为 "@opencode-ai/sdk": "1.18.22",这正是"以已安装版本类型为准"的具体落点。
包入口与导出
@opencode-ai/sdk 提供以下子路径导出(subpath exports):
.(src/index.ts)./client./server./v2(src/v2/index.ts)./v2/client./v2/server./v2/gen/client
根入口与 v2 入口都导出三个工厂函数:createOpencode()、createOpencodeClient(...)、createOpencodeServer(...)。
从源码结构看,这三个工厂各自承担不同职责:
createOpencodeServer():拉起opencode serve ...子进程,并等待启动行(startup line)出现后才返回;createOpencodeClient({ baseUrl }):包装生成的客户端(generated client),重写 directory/workspace 请求头,并安装错误拦截(error interception);createOpencode():opencode 自身的运行时客户端构造入口。
仓库中的实际调用印证了后两者的形态。packages/omo-opencode/src/cli/run/server-connection.ts 在运行时分别调用:
deps.createOpencode({ signal, port, hostname: "127.0.0.1" }) // L78
deps.createOpencodeClient({ baseUrl: attach }) // L94
deps.createOpencodeClient({ baseUrl: `http://127.0.0.1:${port}` }) // L123/L130/L152
这与文档描述的 createOpencode() / createOpencodeClient({ baseUrl }) 签名一致,也说明"attach 到已有服务器"与"自启服务器"两条路径在同一连接模块内并存。
两种连接方式
原文档给出了两种标准连接模式:
import { createOpencodeClient, createOpencodeServer } from "@opencode-ai/sdk/v2"
// A) embedded server (spawns opencode serve)
const server = await createOpencodeServer()
const client = createOpencodeClient({ baseUrl: server.url })
// ... use client ...
server.close()
// B) connect to an already-running server
const client2 = createOpencodeClient({ baseUrl: "http://127.0.0.1:4096" })
- 方式 A(内嵌服务器):
createOpencodeServer()自行 spawnopencode serve并等待其就绪,用完server.close()收尾; - 方式 B(连接已有服务器):直接传
baseUrl指向已运行的实例,端口 4096 是默认约定端口。
方式 B 与 server-api.md 中描述的服务器面完全对应:opencode serve --port 4096 --hostname 127.0.0.1,认证凭 OPENCODE_SERVER_PASSWORD 环境变量启用,实例级路由通过 ?directory= 查询参数或 x-opencode-directory 头传递。文档中提到 createOpencodeClient 会"重写 directory/workspace headers",正服务于这套每请求工作区路由协议——仓库源码 packages/omo-opencode/src/shared/live-server-route.ts 中构造客户端时同时传入 baseUrl 与 directory,即是该能力的直接体现。
客户端命名空间
顶层 OpencodeClient 上的命名空间如下(原文档逐字列出):
auth, app, global, event, config, experimental, tool, worktree, find, file, instance, path, vcs, command, lsp, formatter, mcp, project, pty, question, permission, provider, session, part, sync, v2, tui.
这些命名空间与 server-api.md 中 /doc 返回的路由目录高度对应:session/part 对应 Session 与 Prompting 路由,event 对应 /event SSE 流,pty/tui 对应 PTY 与 TUI control 路由,v2 则承载新版 /api/... 读写面。
常用方法(形状随版本变化)
原文档列出的常用方法清单(完整继承,形状以版本为准):
client.global.health()、client.global.event()client.app.log(...)、client.app.agents(...)、client.app.skills(...)client.config.get()、client.config.providers()client.event.subscribe()— 订阅/event上的 SSE 流;用for await (const event of events.stream) { event.type, event.properties }迭代client.session(旧表面,legacy surface):list, create, status, get, update, delete, children, todo, diff, messages, message, deleteMessage, prompt, promptAsync, command, shell, fork, abort, init, share, unshare, summarize, revert, unrevertclient.v2.session(新版读写/流表面):list, prompt, compact, wait, context, messagesclient.part.delete(...)、client.part.update(...)
仓库源码对上述方法名提供了大量实锤用法:
client.event.subscribe({ query: { directory } })— 见 packages/omo-opencode/src/cli/run/runner.ts,订阅事件流时携带 directory 查询参数;client.session.create({...})— 见 packages/omo-opencode/src/cli/run/session-resolver.ts、packages/omo-opencode/src/features/background-agent/manager.ts 与 spawner.ts;client.session.messages({ ... })— 见 packages/omo-opencode/src/features/btw-side/context-injector.ts、packages/omo-opencode/src/features/background-agent/parent-wake-session-inspector.ts;client.session.list(...)— 见 packages/omo-opencode/src/features/btw-side/tui-picker.ts。
这说明 legacy 表面的 session 命名空间是当前代码库的主力使用面;v2 命名空间同时存在(仓库中有 import { OpencodeClient as V2OpencodeClient } from "@opencode-ai/sdk/v2" 与 import type { Client as V2GeneratedClient } from "@opencode-ai/sdk/v2/gen/client" 的导入),印证了"legacy 与 v2 两套表面并存"的描述。
类型导入方面,仓库代码频繁从包根导入 AgentConfig、Message、Part、Session、AssistantMessage、Event、Project、Todo、SessionPromptAsyncData 等类型,例如 import type { AgentConfig } from "@opencode-ai/sdk",说明类型导出的实际使用面比文档示例更广,可放心作为类型契约引用(仍以安装版类型为准)。
最小 QA 片段
原文档给出了一段最小 QA 脚本,并注明"参数形状可能因版本而异,请视为起点而非契约":
import { createOpencodeClient, createOpencodeServer } from "@opencode-ai/sdk/v2"
const server = await createOpencodeServer()
const client = createOpencodeClient({ baseUrl: server.url })
try {
const session = await client.session.create({ title: "QA session" })
await client.session.promptAsync({
sessionID: session.id,
parts: [{ type: "text", text: "Say hello in one line." }],
})
const sessions = await client.session.list({ limit: 10 })
console.log(sessions[0]?.title)
const messages = await client.v2.session.messages({
sessionID: session.id,
limit: 20,
})
console.log(messages.items.length)
} finally {
server.close()
}
这段脚本恰好串起了三种典型操作:session.create 建会话、session.promptAsync 发"发后即忘"提示(对应 HTTP 面 POST /session/:id/prompt_async 返回 204 的语义,见 server-api.md)、v2.session.messages 读取消息并取 items.length。finally 中 server.close() 保证内嵌服务器被回收——这与 opencode-qa 技能"任何会 spawn opencode 的 QA 都必须做隔离与清理"的黄金规则一致(见 SKILL.md 的 Golden rules 一节)。
核心类型
原文档列出三组关键类型(完整继承):
- Legacy Session:
id, slug, projectID, directory, title, version, time.created/updated,可选workspaceID, path, parentID, summary, cost, tokens, share, agent, model, metadata, permission, revert; - Message =
UserMessage | AssistantMessage(role 为"user" | "assistant";assistant 额外携带time.completed?、modelID、providerID、agent、tokens、finish?、error?); - Part 联合类型:
TextPart, ReasoningPart, FilePart, ToolPart, StepStartPart, StepFinishPart, SnapshotPart, PatchPart, AgentPart, RetryPart, CompactionPart, SubtaskPart。
Part 联合的 12 个成员与 opencode 的事件语义一一对应(工具调用、推理、快照/补丁、压缩、重试等),这也解释了为什么 QA 脚本(如 SKILL.md Case A 中 opencode run --format json)能按 text / tool_use / step_start / step_finish / reasoning / error 等类型对逐行事件做断言——SDK 类型面与 CLI JSON 事件面是同一领域模型的两种表达。
生成机制:从 OpenAPI 到 TypeScript 客户端
@opencode-ai/sdk 是生成物。原文档说明其生成管线:
packages/sdk/js/script/build.tsrunsbun dev generate > openapi.jsonfrom the opencode repo, feeds it to@hey-api/openapi-ts.createClient, writes output topackages/sdk/js/src/v2/gen, patches an SSE generic, prettifies and typechecks. Regenerate with./packages/sdk/js/script/build.ts.
即:在 opencode 源仓库执行 bun dev generate 导出 openapi.json,喂给 @hey-api/openapi-ts 的 createClient,输出写入 packages/sdk/js/src/v2/gen,再修补一个 SSE 泛型、格式化并做类型检查。这条管线解释了文档开头"签名以安装版类型与 GET /doc 为准"的必要性:v2/gen 下的客户端是逐版本再生的,路径与参数形状跟随 OpenAPI 规范演进。注意上述 packages/sdk/js/... 路径位于 opencode 源仓库,不在本仓库内;本仓库只消费其发布产物。
仓库内的真实使用模式:健康探测与会话亲和
除了文档覆盖的用法,仓库源码还展示了一个值得借鉴的 SDK 客户端工程化模式——packages/omo-opencode/src/shared/live-server-route.ts 的"live 路由"机制:
- 构造客户端时注入路由与认证(L248-L252):
const client = createOpencodeClientSdk({
baseUrl: registration.serverUrl.toString(),
directory: registration.directory,
})
injectServerAuthIntoClient(client)
directory 参数对应文档所述"重写 directory/workspace headers"的行为;injectServerAuthIntoClient 则把 Basic Auth 注入 SDK 客户端,呼应 server-api 文档"认证调用使用 -u opencode:$PASS"的语义。
-
健康探测(L103 起):向
{serverUrl}/global/health发起带 1.5s 超时的fetch,等价于文档中client.global.health()的底层调用;401/403 时判定"认证无法满足,禁用 live 路由"。 -
会话亲和探测(L155 起):
GET /session/{sessionID},只有明确的 404 才降级到进程内客户端——因为健康的/global/health只能证明"某个监听器在应答",不能证明它拥有目标会话。
从源码结构看,这套"先探测再分发"的逻辑正是 SDK createOpencodeClient 在真实系统里被包裹、缓存与降级使用的范例:SDK 客户端不是拿来即用就结束,而是要与探活、TTL 缓存(PROBE_TTL_MS / AFFINITY_TTL_MS 各 60 秒)和失败回退一起设计。
版本锁定与核验清单
综合原文档与仓库现状,落地 SDK 调用前建议按以下清单核验:
- 确认安装版本:本仓库根 package.json 与 packages/omo-opencode/package.json 均锁定
@opencode-ai/sdk@1.18.22;你的项目应以自己node_modules/@opencode-ai/sdk的.d.ts为准,而非任何文档。 - 对照
GET /doc:GET /doc返回完整 OpenAPI 规范(smoke 测试断言其包含至少 100 条路径,见 SKILL.md 的 Scripts index),是请求/响应 schema 的权威来源。 - 区分 legacy 与 v2 表面:
client.session.*(旧)与client.v2.session.*(新)并存,读消息、等待完成等在新表面(list, prompt, compact, wait, context, messages),写操作与生命周期管理仍在旧表面;跨版本迁移时优先检查二者是否同时可用。 - 认证与隔离:服务器仅在设置
OPENCODE_SERVER_PASSWORD时强制认证,否则无保护运行;用 SDK 对已有服务器做 QA 时,任何会 spawn opencode 的场景都应置于隔离环境(Docker 或隔离 XDG 沙箱),避免污染真实会话库。 - 事件流迭代:
client.event.subscribe()返回可读 SSE 流,用for await (const event of events.stream)消费event.type/event.properties;若要证明某个插件钩子/动作事件确实触发,事件流观察(Case B)比 TUI 断言更可靠,事件类型目录见 events-hooks.md。
小结
@opencode-ai/sdk 为 opencode 提供了类型化的程序化入口:createOpencodeServer() 内嵌起服、createOpencodeClient({ baseUrl }) 连接现有实例,客户端按 auth/app/global/event/session/part/v2 等命名空间组织,legacy 与 v2 两套 session 表面并存;它由 OpenAPI 规范经 @hey-api/openapi-ts 生成,因此"签名随版本漂移、以安装版类型与 GET /doc 为准"是必须遵守的第一原则。oh-my-openagent 仓库自身的用法——从 server-connection.ts 的连接构造,到 live-server-route.ts 的健康探测与会话亲和,再到各 feature 模块中 session.create / session.messages / event.subscribe 的高频调用——为这份参考文档提供了仓库内的实现级印证。对于版本稳定的 QA,仍建议以技能中经过自测的 curl/CLI 脚本为首选,把 SDK 作为需要类型安全时的补充手段,并对每个 SDK 调用先做 GET /doc 交叉核验。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00