
opencode-ai/httpapi-codegen从 HttpApi 契约生成 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/kilocodeKilo 仓库中的packages/httpapi-codegen是一个构建期build-time源码生成包它以effect/unstable/httpapi的HttpApi与 Effect Schema 契约为唯一权威输入直接生成领域导向domain-oriented的 Promise API 与 Effect API。本文以该包的 README 为主线结合 核心实现 与 生成器测试、写入测试系统讲解其设计规则、编译/发射/写入三段式流水线、输入扁平化与成功/错误映射语义以及保证可复现性的清单机制。读完本文你将掌握如何理解与使用这套契约驱动的双形态客户端生成方案。包定位与设计背景opencode-ai/httpapi-codegen是仓库内一个private包见 package.json 中private: true当前版本为 7.6.0使用 Bun 作为测试运行器bun test运行时代码仅依赖effect来自 catalog 版本与prettier用于输出格式化。它的使命非常聚焦Build-time source generation for domain-oriented Promise and Effect APIs derived directly fromHttpApiand Effect Schema contracts.也就是说你只需要维护一份HttpApi声明包含端点、Schema 化的参数/查询/头/载荷/成功/错误即可在构建期自动获得两套风格各异的客户端源码。由于 API 尚在探索期包保持 private且必须独立于 OpenCode Core测试中使用合成synthetic的HttpApifixtures 作为可执行规范executable specification。为什么需要双形态客户端Effect 客户端面向使用 Effect 生态的宿主如 OpenCode 嵌入式场景返回解码后的 Effect-native 值携带运行时 schema 与保留的转换transformation底层走HttpApiClientPromise 客户端零 Effect 依赖面向普通异步调用方返回结构化、面向 wire 的值直接使用fetch与语法解析不做运行时结构校验。二者的公共基础是compile(Api)产出的共享契约contract而不是某个生成的类型包——这一设计避免了生成类型包成为二次权威源的坏味道。已定规则Settled Rules全解README 用一组已定规则明确了生成器的行为契约以下按主题分组展开并给出源码中的实现佐证。三段式架构与双发射器用一个权威HttpApi反射出共享契约compile(Api)客户端独立发射emitPromise(contract)与emitEffect(contract)每个发射器拥有自己的公共类型投影共享契约而非生成的类型包是唯一共同来源。对应到 src/index.ts 中即为三个导出函数compile返回Contract、emitEffect返回Output、emitPromise返回Output外加面向导入权威 API场景的emitEffectImported与负责落盘的write、一步到位的generate。Output结构包含operations供元数据消费与files路径 内容对例如 Promise 发射固定产出types.ts、client-error.ts、client.ts、index.ts四个文件由测试 generate.test.ts 断言。客户端语义规则领域导向而非 Hey API 兼容Promise 方法返回解包后的值并以带标签tagged的声明错误或ClientErrorreject流式返回Promise 流返回惰性AsyncIterableEffect 流返回Stream二者运行时都不会自动重连。在源码中Promise 客户端通过sseA(descriptor, requestOptions)返回AsyncIterableAindex.tsEffect 端则用Stream.unwrap(...)把EffectStream拍平成直接Streamindex.ts。测试 generate.test.ts 验证了惰性语义调用方法时请求数为 0只有开始for await迭代后才发出 1 次请求且循环结束后不会自动重连。输入通道的扁平化把 path、query、header、payload 四类字段拍平进一个输入对象跨输入通道出现重复字段名直接拒绝零字段不发方法参数全可选时发可选对象存在必选字段时发必选对象。源码在compile中把params/query/headers/payload的 schema 字段统一收集为inputs用Set检测碰撞并抛Input field collisionindex.tsinputMode的取值逻辑在 index.ts。测试覆盖了碰撞拒绝generate.test.ts、none/optional/required三种模式同文件 L666-L711以及每个字段都可选则参数可选的生成形态。成功响应映射精确的{ data: A }成功信封会被解包无内容NoContent成功映射为void其余单一成功值原样保留多成功契约拒绝流式成功暴露为Stream而非EffectStream。isDataEnvelope判断是否恰为单字段data对象index.ts测试验证{ data: A }解包为Effect.map((value) value.data)generate.test.ts、NoContent 映射 void 与 204 状态L756-L763、多成功拒绝L777-L787、SSE 直接建模为 streamL789-L799。错误与失败建模拒绝无法精确生成 wire/domain 转换的 schema传输、意外状态码、响应解码三类失败统一映射为一个稳定的生成ClientError。ClientError在 Effect 端是基于Schema.TaggedErrorClass的 tagged error携带cause: Schema.Defect()Promise 端是带reason: Transport | UnexpectedStatus | UnsupportedContentType | MalformedResponse的普通 Error 类。测试 generate.test.ts 断言操作错误列表只含ClientError不含HttpClientError/SchemaError。可复现与文件所有权生成的源码必须提交供审查CI 重新生成并在工作区发生变更时失败生成文件记录在.httpapi-codegen.json清单中重新生成时只删除此前由生成器拥有、如今已过期的文件。write函数index.ts读取旧清单删除以前拥有但本次不再生成的文件再写入新文件并更新清单。测试 write.test.ts 用FileSystem.makeNoop模拟文件系统断言只移除old.ts。此外write还会校验输出路径安全拒绝绝对路径、./..、含斜杠路径、大小写不敏感重复路径、保留清单名、已存在的符号链接目标相关测试见 write.test.ts。边界Boundary只生成客户端不越界README 明确划定了包的职责边界这对理解整个方案的架构定位至关重要只生成由HttpApi派生的客户端 API不生成仅嵌入式embedded-only的能力。网络化与嵌入式 OpenCode 使用同一份 Effect 客户端分别对接网络与内存HttpClient传输嵌入式宿主在该客户端之上以结构扩展方式追加同进程能力。端点筛选是产品决策生成器会生成它收到的HttpApi中的每一个端点没有任何端点过滤策略。由 OpenCode 在调用生成器前组合出精确的远程 API 来行使产品决策权。API 分阶段演进既有的公开generate(Api, { directory })操作写出富 Effect 输出是需要FileSystem的 Effect而分阶段 API 则是纯函数compile(Api)→emitEffect(contract)/emitPromise(contract)→write(output, directory)。编译器测试直接检查虚拟文件写入测试使用FileSystem.makeNoop见 write.test.ts。命名透明compile可以显式映射面向消费者的组名groupNames与端点操作 IDendpointNames或默认取点分段的最后一段除此之外生成器不做任何隐式的产品化命名或公共名注解映射。从源码结构看HttpApi.reflect回调中options?.groupNames?.[group.identifier] ?? group.identifier与options?.endpointNames?.[endpoint.name] ?? clientEndpointName(endpoint.name)即体现这一约定且topLevel组会被展开到客户端根命名空间扁平化非 topLevel 组保留嵌套对象形态。输出形态与模块布局可移植PortableEffect 输出每个HttpApiGroup生成一个自包含模块如session.ts、tool.ts外加根client.ts与index.ts。组模块内通过HttpApiGroup.make重建组、HttpApiEndpoint.make重建端点并用SchemaRepresentation把 schema 重新物化为可运行的 schema 声明随后定义EndpointN适配器函数与adaptGroupN见 index.ts。schema 依赖可能在多个可移植组模块间重复README 明示跨组 schema 分区被推迟到确有产出或 bundle 成本时再处理——这是当前版本刻意接受的取舍。零 Effect 的 Promise 输出共享types.ts与client.ts模块。类型侧使用SchemaRepresentation生成结构化 wire 类型去除 Brand、内联非递归引用、Schema.Json替换为JsonValueindex.ts并为每个声明错误导出类型守卫isError。客户端侧则生成基于fetch的make(options)构造 URL、序列化 query含嵌套对象key[child]展开与数组重复追加、设置 headers、JSON 序列化 body、校验 success 状态与声明的状态集合、解析 JSON 与 SSE 流index.ts。Promise 输出还可通过emitPromise的outputTypes选项让某个操作的输出类型改用权威导入的 wire 类型测试见 generate.test.ts。导入权威 API 的 Effect 输出emitEffectImported当 schema 无法被精确重建时例如自定义转换藏在标准 HttpApi codec 之下会抛Effect schema requires authoritative importindex.ts。此时应改用emitEffectImported提供三种模式{ module, api }直接import { Api } from module用HttpApiClient.ForApitypeof Api作 raw client{ module, group }导入权威的HttpApiGroup不再重建{ module, endpoints }把端点常量投影进一个生成的HttpApi。三种模式的测试分别见 generate.test.ts。这种输出把适配器全部收拢在根client.ts中不再为每个组生成独立模块。可移植性Portability校验宁拒绝不错生成这是本包最体现工程质量的部分拒绝生成语义不精确的 schema。compile阶段会对每个输入/成功/错误 schema 执行assertPortable/metadataPortable/checksPortable/generationPortable/annotationsPortable等多层检查index.ts凡是出现以下情况都会抛出带明确reason的GenerationError自定义声明类型如Schema.declare的URL守卫——Unportable schema自定义转换隐藏在标准 HttpApi codec 之下——Effect schema requires authoritative import无便携元数据的自定义校验如Schema.Number.check(...)的 filter 没有meta/arbitrary注解——Unportable schema被伪造spoofed或中止aborted的校验、被篡改的 wire 侧 schema、引用本地词法lexical的generation/annotation 值客户端 middleware 声明requiredForClient: true却没有提供 adapter——Client middleware requires adapterindex.tsPromise 端不支持的表单/文本/二进制编码、路径通配符/file/*、非data模式的 SSE 等assertPromiseEndpointindex.ts。这些拒绝路径在测试中都有对应断言例如 generate.test.ts 中的OpaqueUrl、QueryBoolean、Positive、Spoofed、Aborted、Altered、Generated、Annotated等用例。这一策略的价值在于生成的源码一旦交付就应当正确无法保证正确时宁可编译失败也不输出看起来能用的错误代码。确定性输出稳定排序与唯一命名生成器保证确定性deterministic输出端点错误按状态码 schema 标识符稳定排序errorSchemas.sortindex.ts测试验证[Alpha, Beta]与[Beta, Alpha]两种声明顺序产出相同结果generate.test.ts组模块文件名唯一且安全保留client、client-error、index为保留名大小写不敏感非法标识符退化为group-N冲突时追加-N后缀uniqueModuleindex.ts测试 L937-L966公共命名空间扁平化的 topLevel 端点名与非 topLevel 组名冲突会被拒绝index.ts测试 L968-L980。此外write落盘前会用 Prettier 以semi: false, printWidth: 120的 TypeScript 配置格式化每个文件index.ts保证生成物风格统一、diff 干净。仓库中的可运行样例本包以测试即规范自证仓库内提供了一套可直接对照的合成样例权威 fixture APItest/fixture.ts 声明了sessionhealth/list/get/interrupt含{ data }信封、404 的Missingtagged error、NoContent、eventSSE 202 状态与topLevel的system三组已提交的生成产物test/generated 目录下的client-error.ts、client.ts、event.ts、session.ts、system.ts、index.ts供直接审查index.ts的形态是export { ClientError } ... export * as OpenCode from ./client严格消费者 fixturetest/generated-consumer.ts 与测试keeps the strict generated-consumer fixture currentgenerate.test.ts共同保证fixture 重新编译的结果与已提交的生成文件逐字一致——这正是 README 所述提交生成源码供审查CI 重新生成并在工作区变化时失败的落地形式。运行方式见 package.json在packages/httpapi-codegen目录执行bun test本机测试或bun run test:ciJUnit 报告输出到.artifacts/unit类型检查用bun run typecheck基于tsgo。小结opencode-ai/httpapi-codegen的设计可以概括为三句话一份HttpApi契约两个独立发射器一套严格的可移植性校验。它通过纯函数的三段式流水线compile→emit→write把契约到客户端源码变成可测试、可提交、可复现的构建期产物通过拒绝语义无法精确生成的 schema 来守住正确性底线通过.httpapi-codegen.json清单机制让代码生成器像文件的所有者一样管理自己的输出。对于任何需要从单一 API 契约同时服务 Effect 生态与普通 Promise 调用方的项目这套共享契约 双类型投影 权威导入逃生舱的组合都值得借鉴。【免费下载链接】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),仅供参考