01 SDK 契约中间表示与双发射器


文档摘要

01 SDK 契约中间表示与双发射器 本节摘要:OpenCode 的服务端 API 不是手写客户端的——它用一套契约驱动的代码生成(codegen)。本节讲这套 codegen 的核心:服务端的具体 HTTP API 是权威来源,被编译成一份中间表示(IR),再由两个独立发射器生成两种客户端。理解了这套「单一事实来源 + codegen」,你才知道为什么客户端永远不会和服务端漂移。 一、问题:客户端和服务端为什么会漂移 先看传统做法的痛点。如果手写客户端,经常出现「客户端和服务端漂移」: 服务端加了个新接口,客户端没跟上——客户端调用报「找不到」。 服务端改了某接口的参数,客户端还在用旧参数——调用出错。 服务端改了返回格式,客户端解析失败。

01 SDK 契约中间表示与双发射器

本节摘要:OpenCode 的服务端 API 不是手写客户端的——它用一套契约驱动的代码生成(codegen)。本节讲这套 codegen 的核心:服务端的具体 HTTP API 是权威来源,被编译成一份中间表示(IR),再由两个独立发射器生成两种客户端。理解了这套「单一事实来源 + codegen」,你才知道为什么客户端永远不会和服务端漂移。

一、问题:客户端和服务端为什么会漂移

先看传统做法的痛点。如果手写客户端,经常出现「客户端和服务端漂移」:

  • 服务端加了个新接口,客户端没跟上——客户端调用报「找不到」。
  • 服务端改了某接口的参数,客户端还在用旧参数——调用出错。
  • 服务端改了返回格式,客户端解析失败。

根因是「客户端和服务端是两份独立代码,靠人保持同步」。人总会忘、总会慢一拍。规模越大,漂移越严重。

二、解法:契约驱动的 codegen

OpenCode 的解法是把服务端 API 当单一事实来源,通过 codegen 自动生成客户端:

服务端 HTTP API(权威,手写的) │ ▼ codegen 编译 │ 中间表示(IR) │ ▼ 发射器生成 │ 客户端(自动生成,不是手写)

关键认知:客户端是自动生成的,不是手写的。服务端 API 一改,重新跑 codegen,客户端自动更新。这从根本上消灭了漂移——客户端永远和服务端一致,因为它是从服务端「长出来」的。

三、第一步:编译成中间表示(IR)

codegen 的第一步是编译——从服务端的 HTTP API 反射出一份中间表示(Contract IR):

服务端 API(一堆路由定义) │ ▼ compile(反射) │ 中间表示 IR: { groups: [ { name: "session", endpoints: [...] }, { name: "provider", endpoints: [...] }, ... ] }

IR 是一份「API 的结构化描述」——有哪些分组、每个分组有哪些端点、每个端点的方法/路径/输入/输出是什么。它独立于具体语言,是「API 的纯数据表示」。

为什么要先编成 IR?因为「从 API 直接生成客户端」会让生成器和 API 耦合太紧。先编成 IR,生成器只依赖 IR,不依赖 API——这样生成器可以独立演进,API 改了只要重新编 IR。

四、第二步:双发射器生成两种客户端

有了 IR,用两个独立发射器生成两种客户端:

发射器 产物 特点
emitPromise Promise 客户端 零 Effect 依赖、轻量、返回异步迭代
emitEffect Effect 客户端 富投影、保留全部类型、与效应生态集成
IR ──emitPromise──► Promise 客户端(给不用 Effect 的人) │ └──emitEffect──► Effect 客户端(给用 Effect 的人)

为什么两种?因为用户群体不同:

  • Promise 客户端:给不想引入效应式编程的用户。它用标准 Promise/异步迭代,任何 TS 项目都能用。轻量、零门槛。
  • Effect 客户端:给重度用户。它保留全部类型信息、与效应生态深度集成(错误处理、重试、可观测等都用效应机制)。

💡 同一份 IR 出两种客户端:这是 codegen 的威力——API 定义一次,生成两种风格客户端,覆盖不同用户。加新接口时,两种客户端自动同步更新。

五、生成的产物

两个发射器分别生成到不同目录:

  • Promise 客户端:生成到某目录(如 generated),含所有 API 的 Promise 调用代码。
  • Effect 客户端:生成到另一目录(如 generated-effect),含所有 API 的效应调用代码。

这些代码是自动生成的,不该手改(改了下次重新生成会被覆盖)。用户把它们当库用——import 进来调 API。

六、codegen 的管理细节

codegen 有几个管理细节值得知道:

  • manifest 跟踪:生成时写一个清单文件,记录生成了哪些文件。重新生成时只删旧生成文件(不碰手写文件),避免误删。
  • Prettier 格式化:生成的代码用 Prettier 格式化,保证可读(不是一团乱麻)。
  • 路径扁平唯一:生成文件路径扁平化且唯一,防穿越攻击。

这些细节让 codegen 既安全(不误删手写)又干净(代码可读)。

七、边界:codegen 生成什么,不生成什么

codegen 不是什么都生成。它只生成 HTTP API 派生的客户端——也就是「调用服务端 API」的客户端代码。它不生成:

  • 业务逻辑(那在服务端)
  • 网络传输层(那用标准 fetch)
  • 内部实现

所以 codegen 的边界很清楚:它生成「怎么调 API」,不生成「API 怎么实现」。实现是服务端的事,调用是客户端的事,codegen 自动化后者。

八、本节要点回顾

  1. 问题:手写客户端会和服务端漂移(忘同步、慢一拍)。
  2. 解法:契约驱动 codegen——服务端 API 是单一事实来源,客户端自动生成。
  3. 第一步编 IR:从 API 反射出中间表示(结构化的 API 描述),语言无关。
  4. 第二步双发射器:emitPromise(轻量,给不用 Effect 的人)+ emitEffect(富投影,给用 Effect 的人)。
  5. 同一 IR 出两种客户端:API 定义一次,生成两种风格,覆盖不同用户。
  6. 生成代码不该手改:改了会被重新生成覆盖;manifest 防误删手写。
  7. 边界:生成「怎么调 API」,不生成「API 怎么实现」。

IR 和双发射器讲清了,下一节详讲两种客户端的取舍。


发布者: 作者: 灏天文库 转发
评论区 (0)
U