opencode 客户端 SDK 深度解析:@opencode-ai/client 的契约驱动代码生成与双入口架构

发布时间:2026/9/7 19:36:58
opencode 客户端 SDK 深度解析:@opencode-ai/client 的契约驱动代码生成与双入口架构 opencode 客户端 SDK 深度解析opencode-ai/client 的契约驱动代码生成与双入口架构【免费下载链接】opencodeThe open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/openc/opencode本篇技术指南以 packages/client/README.md 为主体深入拆解 OpenCode 仓库中的opencode-ai/client包——一个从权威 EffectHttpApi契约直接生成、面向零依赖 Promise 场景与 Effect 运行时场景双轨交付的 HTTP 客户端。读完本文你将掌握该客户端的两个公开入口Promise 根与/effect子路径的适用边界、契约到代码的完整生成链路bun run generate/bun run check:generated、生成物的错误模型以及仓库如何用打包边界测试锁定两套入口的依赖图谱。1. 包定位一个私有的“生成目标”opencode-ai/client在 package.json 中被标记为private: true其自我定位见 README 首行是Private generation target for clients derived directly from OpenCodes authoritative EffectHttpApi——即“直接从 OpenCode 权威 EffectHttpApi派生客户端的私有生成目标”。这意味着该包的核心价值不在手写逻辑而在由契约单向生成的确定性与可验证性生成产物全部落在 src/generated/ 与 src/generated-effect/ 两个目录文件头明确标注// Generated by opencode-ai/httpapi-codegen. Do not edit.生成链路以仓库内的 httpapi-codegen 包 为编译器以 src/contract.ts 中声明的ClientApi为输入契约变更后README 给出了标准工作流运行bun run generate重新生成再运行bun run check:generated检测已提交产物的漂移。对照 package.json 的 scripts 可见check:generated的实际实现就是bun run generate git diff --exit-code -- src/generated src/generated-effect——重新生成后对两个产物目录做零差异断言。依赖关系也印证了“生成物只依赖契约侧”的设计运行时依赖仅有opencode-ai/schema与opencode-ai/protocol两个 workspace 包effect被声明为可选peerDependencypeerDependenciesMeta中标记optional: true版本固定为4.0.0-beta.83这保证纯 Promise 使用者不必安装 Effect。而opencode-ai/server、opencode-ai/core、opencode-ai/httpapi-codegen均只出现在 devDependencies 中——README 所述“构建编译器读取 Server 的具体 API”正是通过开发期依赖实现的不进入任何生产导入图。2. 双入口架构README 的 “Entrypoints” 一节定义了包的公开表面与 package.json 的exports字段一一对应入口exports 映射运行时依赖定位opencode-ai/client./src/index.ts无 Effect、无 Core仅fetch零 Effect 的 Promise 客户端opencode-ai/client/effect./src/effect.tsEffect、Schema、Protocol基于环境提供HttpClient的 Effect 网络客户端2.1 Promise 根入口结构化的 fetch 客户端根入口 src/index.ts 只做两件事转发generated/index的全部导出并把生成的EventsSubscribeOutput类型以OpenCodeEvent别名再导出一次。生成后的 generated/client.ts 展示了这套客户端的完整骨架可直接作为阅读参考export interface ClientOptions { readonly baseUrl: string readonly fetch?: typeof globalThis.fetch // 可注入自定义 fetch测试/代理场景 readonly headers?: HeadersInit // 全局默认请求头 } export interface RequestOptions { readonly signal?: AbortSignal // 逐请求中止 readonly headers?: HeadersInit }make(options)工厂内部以RequestDescriptormethod、path、query、headers、body、successStatus、declaredStatuses描述每个端点请求处理链遵循清晰的错误分类传输层异常fetch 本身抛错→ClientError(Transport, { cause })响应状态命中契约声明的错误状态declaredStatuses→ 直接解析 JSON 错误体抛出其他非预期状态 →ClientError(UnexpectedStatus, { cause: { status } })SSE 端点额外校验Content-Type必须为text/event-stream否则抛ClientError(UnsupportedContentType)。这种“按契约声明的状态码分流”的语义是纯手写 fetch 封装很容易缺失、而由代码生成保证一致性的典型收益。2.2/effect入口规范化解码值 环境注入的 HttpClientREADME 强调 Effect 入口使用canonical decoded values如Session.ID、Location.Ref、Prompt。这些数据类型来自轻量级opencode-ai/schema包并由 src/effect.ts再导出——Agent、Location、Model、Session、SessionMessage、Prompt、AbsolutePath/RelativePath等二十余个命名导出都列在该文件中其目标是让调用方只需依赖客户端公开表面内部模型重组不会迫使调用方迁移。文件头部的注释还固化了一条架构约束/effect永远不得导入 Core 或 Server。生成的 Effect 客户端 generated-effect/client.ts 基于 Effect 的HttpApiClient.ForApitypeof ClientApi把HttpClientError、SchemaError、Sse.Retry统一归一为包的ClientError而OpenCode.make({ baseUrl })即 README 示例中构造客户端的工厂。README 中的官方示例可直接复制运行前提是本地或远端已有 OpenCode Server完整展示了 Effect 侧调用方式import { AbsolutePath, Location, OpenCode, Prompt } from opencode-ai/client/effect const client yield * OpenCode.make({ baseUrl: https://opencode.example }) yield * client.sessions.create({ location: Location.Ref.make({ directory: AbsolutePath.make(/workspace) }), }) yield * client.sessions.prompt({ sessionID, prompt: Prompt.make({ text: Hello }) })注意示例中三个要点baseUrl指向一个已运行的 OpenCode Server 实例sessions.create的location参数用Location.Ref.make包装绝对路径而非裸字符串prompt载荷同样通过Prompt.make构造——调用方全程接触的是规范化解码类型而非原始 JSON 结构。3. 生成表面覆盖了哪些 HTTP 组又刻意排除了什么“生成表面包含 Server 具体 API 的每一个标准 HTTP 组”并非空话。src/contract.ts 用makeDefaultApi从 Protocol 构建客户端本地的ClientApi投影并维护了三份清单组名映射groupNames把 18 个服务端组键映射为客户端命名空间例如server.health→health、server.session→sessions、server.message→messages、server.pty→ptys、server.projectCopy→projectCopies。这就是为什么 README 示例里能直接写client.sessions.*。端点别名endpointNames对少量组内端点重命名如session.messages→list、integration.connect.key→connectKey、permission.saved.remove→removeSaved使生成物读起来符合 REST 客户端惯例。显式排除omitEndpointsnew Set([fs.read, pty.connect, pty.connectToken])。这与 README 中“PTY WebSocket 连接等自定义传输保持在通用 HTTP 客户端之外”的说明互为印证——文件读取与 PTY 建连走的是各自专用传输WebSocket 等不经过通用 HTTP 客户端表面。中间件方面contract 声明了LocationMiddleware与带错误声明InvalidRequestError、SessionNotFoundError的SessionLocationMiddleware。README 的职责划分表述是“Protocol 拥有端点构建与中间件放置Server 提供构建期 API 所用的具体中间件键”——即抽象契约归 Protocol具体键归 Server客户端包自身只持有一份客户端本地投影。4. 代码生成链路从 ClientApi 到两套产物生成入口是 package.json 中generate: bun run script/build.ts实现见 script/build.ts核心流程为compile(ClientApi, { groupNames, endpointNames, omitEndpoints })把契约编译为中间表示emitPromise(contract, { outputTypes: ... })产出 Promise 客户端其中特化了events.subscribe的输出类型以OpenCodeEventEncoded命名并直接import type自opencode-ai/protocol/groups/event避免在生成物中复制事件联合类型emitEffectImported(contract, { module: ../contract, api: ClientApi })产出 Effect 客户端且让它import { ClientApi } from ../contract——这正是 README 所说“生成的 Effect 运行时导入一份由 Protocol 构建的客户端本地投影”write并发concurrency: 2落盘到src/generated与src/generated-effect。“生成等价测试防止传输漂移”generation-equivalence test对应 test/contract-identity.test.ts配合check:generated的 git 零差异断言仓库对“契约、生成脚本、已提交产物”三者一致性做了双重锁定。5. 打包边界测试两套入口的依赖图谱README 最后一句——“Promise 根保持结构化、无 Core 或 Effect 运行时依赖/effect仅依赖 Effect、Schema、Protocol且对浏览器打包安全。打包边界测试强制约束这两套导入图”——有明确的测试佐证test/import-boundaries.test.ts。该测试用 Bun bundler 以browser目标把每个入口打包成临时产物读取 metafile 的 inputs 集合然后断言根入口opencode-ai/client的 inputs 中effect、schema、protocol、core、server五个目录的命中数全部为 0/effect入口的 inputs 中effect、schema、protocol命中数必须大于 0而core、server命中数必须为 0。换言之这不是依赖 lint 层面的静态规则而是真实浏览器打包结果的机器验证保证客户端可安全嵌入浏览器 bundle 而不把服务端实现卷进来。包内测试还包括 promise.test.ts 与 effect.test.ts分别覆盖两条入口的行为正确性。6. 实践要点小结选择入口浏览器或任何不想引入 Effect 的场景用opencode-ai/client纯 Promise fetch可注入自定义fetch/headers/AbortSignalEffect 运行时中需要结构化错误与环境注入HttpClient的场景用opencode-ai/client/effect。类型来源Effect 侧所有构造器Session、Location、Prompt、AbsolutePath等从客户端入口本身导入即可不必直接依赖opencode-ai/schema。契约变更流程修改 Protocol/Server 契约后先bun run generate再bun run check:generatedCI 中两者组合即可拦截产物漂移。不要手写生成物src/generated与src/generated-effect均由 httpapi-codegen 产出所有定制组名、端点别名、排除项、中间件都应回写到 src/contract.ts 后再重新生成。综合来看opencode-ai/client展示了 OpenCode 仓库中“契约单一来源 生成客户端 测试锁定边界”的完整工程范式README 给出入口与示例contract 定义表面与排除项codegen 脚本产出双轨客户端而 identity 测试与打包边界测试则把上述承诺固化为可执行的持续验证。【免费下载链接】opencodeThe open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/openc/opencode创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考