Kilo 开源编码代理的 API 客户端生成体系:从 Effect HttpApi 契约到 Promise/Effect 双入口的 `@opencode-ai/client`
2026/9/12 20:04:36 网站建设 项目流程

Kilo 开源编码代理的 API 客户端生成体系:从 Effect HttpApi 契约到 Promise/Effect 双入口的@opencode-ai/client

【免费下载链接】kilocodeKilo is the all-in-one agentic engineering platform. Build, ship, and iterate faster with the most popular open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/ki/kilocode

@opencode-ai/client是 Kilo(kilocode)仓库中面向调用方生成的 TypeScript API 客户端包。它以 OpenCode 服务端权威的 EffectHttpApi契约为唯一事实来源,通过代码生成同时产出「零依赖 Promise 客户端」与「基于 Effect 的富网络客户端」两个入口,并借助生成等价性测试防止传输层漂移。读完本文,你将掌握该客户端包的生成链路、双入口设计动机、请求/SSE/错误处理实现细节,以及如何在业务代码中直接构造规范化输入发起会话、消息、权限、PTY 等全量 HTTP 调用。

包定位:私有生成目标而非手写 SDK

packages/client/README.md 开篇即点明该包的定位:它是「Private generation target for clients derived directly from OpenCode's authoritative EffectHttpApi」。换句话说,这个包的全部客户端表面(client surface)不是手写的,而是由构建编译器根据服务端 HTTP API 契约自动生成的,仓库内该包自身即标注为"private": true(见 packages/client/package.json)。

这意味着任何对服务端路由、参数、返回类型的修改,只要落在 EffectHttpApi契约上,客户端都会自动同步,无需人工维护两份接口定义。README 明确给出了开发者的工作流:

  • 修改契约后运行bun run generate重新生成客户端;
  • 运行bun run check:generated检测已提交的生成产物是否与最新契约漂移(该脚本在生成后执行git diff --exit-code -- src/generated src/generated-effect,见 packages/client/package.json)。

双入口设计:Promise 根入口与 Effect 子路径

包通过exports字段暴露两个语义不同的入口(packages/client/package.json):

入口说明运行时依赖
@opencode-ai/client(也等价于./promise零 Effect 依赖的 Promise 客户端,基于fetch仅标准 Web API,无 Core / Effect 运行时
@opencode-ai/client/effect富 Effect 网络客户端,使用环境提供的HttpClient仅依赖 Effect、Schema、Protocol,浏览器打包安全

两个入口分别对应两个生成产物目录:src/generated/src/generated-effect/。根入口的src/index.ts只是薄薄一层转发:export * from "./generated/index"并补充导出OpenCodeEvent事件类型,外加一个用于兼容上游 session-ui 旧版 Promise 客户端类型的FileDiffInfo联合类型(packages/client/src/index.ts)。

Effect 入口src/effect.ts除了转发generated-effect之外,还集中重导出了@opencode-ai/schema下的全部领域数据类型(AgentSessionPromptLocationPtyProjectCopy等),并导出 Protocol 的OpenCodeEvent事件类型(packages/client/src/effect.ts)。这样调用方只依赖客户端表面即可拿到全部类型,不必直接引用 schema 包。

契约的权威来源与客户端本地投影

Server 的权威 API

README 指出「The build compiler reads@opencode-ai/server/api」,即生成器编译时读取服务端导出的权威Api。测试 packages/client/test/contract-identity.test.ts 验证了权威契约的身份:Api.groups["server.session"].identifier"server.session",且Object.keys(ClientApi.groups)Object.keys(Api.groups)完全一致。

客户端本地契约投影

由于客户端运行时不允许依赖 Core / Server(保证包体积与可移植性),Effect 入口实际使用的是「基于 Protocol 构建的客户端本地投影」ClientApi,定义在 packages/client/src/contract.ts。它通过makeDefaultApi构造,并声明了客户端自身的两个中间件:

  • LocationMiddleware:位置(location)相关请求的中间件服务;
  • SessionLocationMiddleware:会话位置校验中间件,声明了可能抛出的错误类型InvalidRequestErrorSessionNotFoundError

同一文件还定义了生成期使用的三张映射表:

  • groupNames:将服务端分组标识(如server.healthserver.sessionserver.pty)映射为客户端方法名(healthsessionsptys)——共 18 个分组;
  • endpointNames:将特殊端点标识映射为语义化方法名(如session.messageslistintegration.connect.keyconnectKeyquestion.request.listlistRequests);
  • omitEndpoints:明确从通用 HTTP 客户端中剔除的端点集合{"fs.read", "pty.connect", "pty.connectToken"}

生成等价性测试:杜绝传输漂移

README 强调「a generation-equivalence test preventing transport drift」。这一保障落地在 packages/client/test/contract-identity.test.ts:测试分别用compile(Api, …)编译服务端契约、用compile(ClientApi, …)编译客户端投影,然后断言emitPromise(client)emitPromise(server)完全相等——即客户端投影最终生成的 Promise 客户端表面必须与服务端契约生成的表面逐字节一致。

同一测试文件还校验了「权威值复用」:Core 与 Server 复用的是同一份 Schema / Protocol 值,例如AgentV2.ID === Agent.IDCoreLocation.Ref === Location.RefCorePrompt === Prompt,并验证Session.ID.create()ses_前缀、Workspace.ID.create()wrk_前缀、Project.ID.global === "global"Provider.ID.anthropic === "anthropic"等事实(packages/client/test/contract-identity.test.ts)。此外,测试确认共享 DTO 构造与解码得到的是普通对象(原型为Object.prototype),保证跨运行时序列化的纯粹性。

零依赖 Promise 客户端内部实现剖析

生成产物 packages/client/src/generated/client.ts 是 Promise 客户端的完整实现,对外暴露make(options: ClientOptions)工厂函数,返回按 18 个分组组织的扁平方法树。

配置项

export interface ClientOptions { readonly baseUrl: string readonly fetch?: typeof globalThis.fetch readonly headers?: HeadersInit } export interface RequestOptions { readonly signal?: AbortSignal readonly headers?: HeadersInit }

baseUrl必填;fetchheaders可覆盖全局默认(内部为options.fetch ?? globalThis.fetch)。每个端点方法还接受可选的RequestOptions,用于注入AbortSignal与请求级headers

请求流水线

内部以RequestDescriptor描述每个端点:HTTP 方法、路径模板、query、headers、body、成功状态码、声明状态码集合与是否空响应体。make()内部依次实现三层:

  1. prepare:用new URL(descriptor.path, options.baseUrl)拼接 URL,将 query 对象递归展开进searchParams(数组值逐项追加),合并全局 headers 与请求级 headers,并在有 body 且未显式设置时自动写入content-type: application/json,body 统一JSON.stringify
  2. execute:真正调用fetch,任何网络异常都被包装为ClientError("Transport", { cause })
  3. request:按successStatus判定成功;若响应状态落在declaredStatuses(如 400、401、404、503 等)中则抛出生成的服务端错误对象;否则取消响应体并抛出ClientError("UnexpectedStatus", { cause: { status } })empty: true的端点(如switchAgentinterruptcompact)不会解析 JSON。

内建 SSE 客户端

事件流端点(sessions.eventsevents.subscribe)不走普通request,而是由内部sse函数处理:校验text/event-stream内容类型后逐块读取,归一化\r\n/\r换行,按\n\n边界切分事件块,仅提取data:前缀行并JSON.parse后逐条yield。实现还包含多项健壮性保护:缓冲上限 1 MiB(超出抛MalformedResponse)、流中断时reader.cancel()releaseLock()清理、非法 JSON 包装为ClientError("MalformedResponse")。调用方因此可以for await消费事件流:

const client = make({ baseUrl: "https://opencode.example" }) for await (const event of client.sessions.events({ sessionID })) { // 逐条处理会话事件 }

覆盖的完整分组

从 packages/client/src/generated/client.ts 可以看到生成表面覆盖服务端全部标准 HTTP 分组:healthlocationagentssessions(list/create/active/get/switchAgent/switchModel/prompt/compact/wait/stage/clear/commit/context/history/events/interrupt/message 共 17 个端点)、messagesmodelsprovidersintegrations(含 connectKey/connectOauth 与 attempt 三件套)、credentialspermissionsfiles(list/find)、commandsskillseventsptys(list/create/get/update/remove)、questionsreferencesprojectCopies。README 特别提醒:PTY 的 WebSocket 连接(pty.connect/pty.connectToken)属于自定义传输,仍留在通用 HTTP 客户端之外,这也正是omitEndpoints存在的原因。

Effect 客户端实现:从 HttpApiClient 到适配层

packages/client/src/generated-effect/client.ts 展示了 Effect 入口的实现方式。其核心只有一步:

export const make = (options?: { readonly baseUrl?: URL | string }) => HttpApiClient.make(ClientApi, options).pipe(Effect.map(adaptClient))

即直接基于 Effect 的HttpApiClient工厂构造RawClient,再用adaptClient把 Effect 原生返回的「分组 → 端点函数」树适配为与 Promise 客户端一致的扁平命名(adaptGroup0adaptGroup17)。每个端点包装都统一做了两件事:

  • Effect.mapError(mapClientError):把HttpClientError、Schema 解码错误、SSE 重试错误统一映射为ClientError,其余错误原样透传;
  • 对包装在{ data }中的响应(create/prompt/get/active 等)额外Effect.map((value) => value.data)解包,使调用方直接拿到领域对象。

事件流端点(如session.eventsevent.subscribe)在 Effect 侧被映射为Stream:先Stream.unwrap打开底层HttpApiClient流,再对整条流Stream.mapError(mapClientError)归一化错误,调用方可以用 Effect 的Stream.runForEach等算子消费。

规范化输入:使用 Effect 入口的完整示例

README 给出的 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" }) })

要点在于:Location.RefAbsolutePathPrompt这些「规范解码值」来自轻量级@opencode-ai/schema包,通过 packages/client/src/effect.ts 的集中重导出对外暴露。因此调用方构造请求时无需关心 schema 包的内部组织,且当 schema 内部模型重组时,由于客户端表面保持导出不变,调用方代码无需迁移(README 明确这是保留这些导出的设计意图)。

由于make返回的是 Effect,实际调用需要运行环境,典型用法是在Effect.gen中以yield *获取 client,或将OpenCode.make放入依赖注入上下文;effect在 packages/client/package.json 中作为可选 peer dependency(4.0.0-beta.83),使用该入口的项目需自行安装匹配版本。

依赖边界与浏览器打包安全

README 用两条硬约束概括了该包的依赖纪律:

  • Promise 根入口保持结构化(structural),没有任何 Core 或 Effect 运行时依赖——它只依赖标准fetchURLHeadersAbortSignal,因此可以在任意现代运行时直接使用;
  • /effect入口只依赖 Effect、Schema、Protocol,且浏览器打包安全——它不 import Core 或 Server,这正是要用ClientApi本地投影替代服务端Api的根本原因。

这两条 import 图约束由「bundle-boundary tests」在测试中强制校验(README 原文:Bundle-boundary tests enforce both import graphs),确保未来演进中不会有人无意引入破坏性的运行时依赖。结合契约身份测试,客户端包的三大质量支柱可以总结为:契约等价(同一生成表面)、依赖纯净(无 Core/Server 泄漏)、运行时可移植(浏览器与 Node 皆可用)

本地开发与验证命令

在 packages/client 目录下(仓库使用 bun 作为包管理器):

命令作用
bun run generate读取@opencode-ai/server/api契约并重新生成src/generatedsrc/generated-effect两个产物目录
bun run check:generated重新生成后对产物做git diff --exit-code,检测已提交产物是否与契约漂移
bun test运行契约身份、依赖边界等单元测试(超时 5s)
bun run typecheck使用tsgo --noEmit做类型检查

综上,@opencode-ai/client是 Kilo 生态中「契约驱动生成 + 双运行时适配」范式的代表实现:开发者只需维护服务端 EffectHttpApi这一份权威契约,即可同时获得可移植的 Promise 客户端与类型安全、错误归一化的 Effect 客户端,且二者表面形状由测试锁定、永不漂移。

【免费下载链接】kilocodeKilo is the all-in-one agentic engineering platform. Build, ship, and iterate faster with the most popular open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/ki/kilocode

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询