第 7 章 · 02 OpenAPI 契约与 typescript-client


第 7 章 · 02 OpenAPI 契约与 typescript-client

本节摘要:本节把视角拉到多仓库全景,讲清 @openhands/typescript-client(1.39.0)——本仓库唯一合法访问 Agent Server 的方式——是怎么来的、长什么样、怎么保持同步。生成链路是一条单向河:Python 侧 software-agent-sdk 用 FastAPI 定义并导出 OpenAPI(Swagger)契约 → typescript-client 仓库据此生成/维护 TS 方法与类型 → Agent Canvas 引用。契约一变,client 跟着重新生成,前端还在用旧方法的地方 TS 编译期直接报错——这就是上一节"防漂移"承诺的兑现机制。我们再看 client 的接口面(conversations/events/git/runtime/agents 等)、config/defaults.json 的版本钉扎(agent-server 1.44.1、typescript-client 1.39.0、最低兼容 1.28.0)与 sdk-version-sync.yml 的自动校验,最后回答对开发者最有用的问题:要接一个新接口,改动该落在哪个仓库。

内容来源:原项目 package.jsonconfig/defaults.jsonsrc/api/agent-server-client-options.tsAGENTS.md 的"Repository Map"与"Cross-Repository Boundaries"章节、.github/workflows/sdk-version-sync.yml,交叉精读。

⚠️ 注意:typescript-client 是独立仓库(OpenHands/typescript-client),不是本仓库里的目录。它的定位是"生成/维护兼备"——大部分代码由契约生成,个别浏览器适配层手工维护。本仓库只消费它的 npm 包,升级 = 改 package.json 里那一行版本号。

学习目标

阅读完本节,你应当能够:

  1. 画出"SDK 契约 → typescript-client → Canvas"的单向依赖链,并说明为什么方向不能反过来。
  2. 说清契约变更如何传导成前端编译错误,而不是运行时 404。
  3. 列举 client 暴露的主要 client 类与方法面(conversations CRUD、events 订阅、git、runtime、agents 等)。
  4. 解释 config/defaults.json 里三组版本号(agentServer/agentCanvas/automation)与 compatibility.minimumAgentServer 各自的作用。
  5. 掌握新接口的正确贡献路径:先改 SDK 契约,再生成 client,最后前端才写调用。

一、多仓库全景:client 在体系中的位置

AGENTS.md 开篇的"Repository Map"用一张表界定三个仓库的分工,这张表是理解整个体系的钥匙:

仓库 角色 什么改动落在这里
OpenHands/OpenHands(本仓库) React/TypeScript 前端(agent-canvas):UI、路由、src/api/ 里的前端服务,消费后端 API 改 UI、前端状态,或前端调用既有端点的方式
OpenHands/software-agent-sdk Python SDK + agent-server:agents、tools、conversations、events,以及 REST/WebSocket API 面(openhands-sdkopenhands-toolsopenhands-agent-serveropenhands-workspace) 新增/修改后端端点、agent/tool 行为、服务端逻辑
OpenHands/typescript-client 生成/维护的 TS client,镜像 agent-server API。前端触达 agent-server 的唯一受认可方式 为 agent-server 端点新增客户端访问能力(类型化方法、请求/响应类型)

表下紧跟一句结论:API endpoint access belongs in typescript-client, then consumed here——端点访问代码属于 client 仓库,然后才被本仓库消费,不许在本仓库重造。

依赖方向在"Cross-Repository Boundaries"一节有明确表述:

The usual dependency direction is software-agent-sdk / Agent Server → OpenAPI contract → typescript-client → Agent Canvas.

这是一条单向河:Python 侧是源头,OpenAPI 契约是河面,client 是河上的桥,Canvas 是对岸的消费者。方向不能反——前端永远不会"定义"接口让后端实现,这保证了 API 面的唯一权威在服务端。

02-OpenAPItypescript-client-mmd15-0aced7

💡 驾驶舱要点:把"前后端协作"建模成"契约 + 双端生成物",是大规模多仓库项目止痒的根本办法。前端仓库里看不到一行后端代码,但它 import 的每一个类型都来自后端契约的机器转译——两边永远不会"各说各话"。

二、生成链路:契约变更如何变成编译错误

2.1 从 FastAPI 到 OpenAPI

agent-server 用 FastAI 构建(Python 生态的惯例),FastAPI 天生把每个路由的路径参数、请求体、响应模型导出为 OpenAPI 3 规范(Swagger JSON)——Docker 镜像里对外暴露的 /docs/redoc/openapi.json 三个端点(第 9 章会再见到它们被 ingress 逐条转发)就是这份契约的可视化与机器可读形态。契约描述了:GET /api/conversations/{id} 返回什么 schema、POST /api/conversations 接受什么 body、WebSocket /sockets 怎么升级。

2.2 契约 → TS client

typescript-client 仓库以这份 OpenAPI 契约为输入,产出浏览器可用的 TS 包:每个资源一组类型化 client 类,方法签名直接从 schema 生成。AGENTS.md 的 Rule 1 列出前端可用的 client 与其子路径导入(subpath imports):

ConversationClient -- @openhands/typescript-client/clients FileClient -- @openhands/typescript-client/clients VSCodeClient -- @openhands/typescript-client/clients ServerClient -- @openhands/typescript-client/clients RemoteWorkspace -- @openhands/typescript-client/workspace/remote-workspace RemoteEventsList -- @openhands/typescript-client/events/remote-events-list

这个清单本身就是接口面的地图:

  • ConversationClient——conversations 的 CRUD:创建(POST /api/conversations)、查询、暂停/恢复、删除,以及 agent 配置的随会话传递;
  • RemoteEventsList——事件流的分页拉取,配合第 2 章讲过的 WebSocket 订阅构成"断线补拉"双通道;
  • RemoteWorkspace——runtime/工作区管理:沙箱会话的建立与存活探测;
  • FileClient——工作区文件读写(下载文本文件、列目录),支撑文件树与编辑器;
  • VSCodeClient——内置编辑器的 URL 协商(/api/vscode/url);
  • ServerClient——服务器级信息(/server_info)与设置(settings API,AGENTS.md 提到 settings-service 用它做持久化)。

git 变更、agents/profiles 等较新的能力同样以类型化方法随包分发——凡是 agent-server OpenAPI 契约里的资源,client 都有对应镜像。

2.3 漂移如何在编译期暴露

现在把上一节的承诺兑现。假设后端把 Conversation.description 字段改名 title:

  1. SDK 契约更新,typescript-client 重新生成,Conversation 类型里 description 消失、出现 title;
  2. client 发版,本仓库升级依赖;
  3. 前端所有 conversation.description 的访问点 TS 编译报错,IDE 红波浪线当场标出。

对比直连方案:字符串拼 URL + any 响应,字段改名后前端编译毫无波澜,直到运行时页面出现一片 undefined。契约的价值不是"文档好看",而是把破坏性变更的发现时机从"用户撞上"提前到"按下保存键"

守卫测试(上一节)则保证这条链路不被短路:只要有人绕过 client 手拼 /api/ 请求,编译器就帮不上忙了,所以 CI 先把这条路堵死。两道防线,一道管"必须走桥",一道管"桥面随河改"。

⚠️ 注意:client 的低层入口 client/http-client 虽然存在于包内,但应用代码禁止导入(守卫正则第 3、4 条)。类型化包装类如 RemoteEventsList 需要底层选项时,必须经 getAgentServerHttpClientOptions(overrides?) 组装——AGENTS.md 原文:"application code must not import or construct the low-level HttpClient"。给"必须用底层"的场景留一个收口 helper,是比一刀切禁止更成熟的设计。

三、版本同步:defaults.json 钉扎与 CI 校验

3.1 版本从哪读

本仓库对 agent-server 的版本关系不是靠 README 口头声明,而是钉在 config/defaults.json——AGENTS.md 称之为"版本钉扎、端口、路径与默认值的单一事实来源"(single source of truth),npm 启动器、Docker 构建、CI 工作流全都读它:

{ "versions": { "agentServer": "1.44.1", "agentCanvas": "1.16.0", "automation": "1.10.0" }, "compatibility": { "minimumAgentServer": "1.28.0" }, "images": { "agentServer": "ghcr.io/openhands/agent-server", "agentCanvas": "ghcr.io/openhands/agent-canvas" } }

四组数字各司其职:

字段 含义
versions.agentServer 1.44.1 npm/Docker 启动器实际拉起并钉住的 agent-server 版本(uvx 安装、基础镜像均用此版本)
versions.agentCanvas 1.16.0 Canvas 自身版本,与 package.json.release-please-manifest.json 三处同步
versions.automation 1.10.0 automation 后端的钉扎版本
compatibility.minimumAgentServer 1.28.0 Canvas 能正常对话的最低 agent-server 版本,bin/agent-canvas.mjs --info 会打印它,连接旧后端时给出明确诊断

而 typescript-client 的版本在 package.json 里钉扎为精确的 "1.39.0"(非 ^ 区间)——client 与 agent-server 1.44.x 这对组合是经过测试矩阵验证过的配对,浮动版本会让"契约同步"退化成"碰运气"。

3.2 CI 替你盯版本

.github/workflows/sdk-version-sync.yml 专门校验这些数字的协调性,它的触发条件暴露了设计意图:

on: pull_request: paths: - "config/defaults.json" - "scripts/dev-safe.mjs" - "scripts/dev-with-automation.mjs" - "scripts/check-sdk-version-sync.mjs" - ".github/workflows/sdk-version-sync.yml" push: branches: [main] paths: ["config/defaults.json", ...] # Triggered by external repos (e.g., OpenHands/automation) when SDK deps change repository_dispatch: types: [sdk-version-check, sdk-release] workflow_dispatch: inputs: sdk_version: description: "New SDK version to check against (optional)"

三个触发通道:本仓库改了版本相关文件(Pull Request/push)、其他仓库通过 repository_dispatch 远程触发(SDK 或 automation 发新版时主动通知这里跑校验)、手动 dispatch 传入指定 SDK 版本。也就是说,版本一致性不是"每次发版前想起来了才查",而是组成了跨仓库的事件联动——上游一动,下游自动对表。

bin/agent-canvas.mjs--info 输出把这组关系直接暴露给用户:agent-server 钉扎版本、automation 版本、最低兼容版本、默认端口一览,排查"前端太新后端太旧"类问题时第一眼就看这里。

四、对开发者的意义:新接口的正确贡献路径

把前两节合起来,回答一个实操问题:"我想给 Canvas 加一个后端还没暴露的功能,PR 该怎么写?" AGENTS.md 的分工表给出明确答案,按顺序:

  1. 第一步,software-agent-sdk:在 Python 侧实现端点。FastAPI 路由 + pydantic 模型,自动进 OpenAPI 契约。新 API endpoint 永远落在 SDK 仓库,"not in the frontend"。
  2. 第二步,typescript-client:契约更新后重新生成(或维护)client,新增类型化方法与请求/响应类型,发版。
  3. 第三步,本仓库:升级 client 依赖,在 src/api/ 的相应 service 里用 new XxxClient(getAgentServerClientOptions()) 调用,写 UI,补测试。

三步三个 PR、三个仓库。看起来繁琐,但每一步都有独立评审与独立测试——服务端逻辑不被 UI 代码污染,契约变更被单独审视,前端 PR 只剩纯前端问题。反过来,如果允许前端在组件里手拼新端点的 URL,三个关注点就挤进了一个 diff,评审质量与回滚粒度同时劣化。

同理,遇到"client 里没有我要的方法"时,正确动作是去 client 仓库补方法,而不是在 src/ 里写一处 fetch 例外——白名单之所以只有三个文件,就是为了逼新需求走正门。

💡 驾驶舱要点:契约驱动的协作把"前后端联调"里最耗人的人肉对协议环节,替换成了三段各自机械化的一公里:FastAPI 自动出契约、生成器自动出类型、编译器自动查出不同步。人只负责三段之间的"业务判断"(接口该怎么设计、UI 该怎么用),机械的部分一行不落地交给机器。这就是第 7 站"协议管线"名字的由来——它不是某个具体功能的代码,而是让前后端之间始终保持"同一份真相"的传输带。

本节要点回顾

  1. 单向依赖链:software-agent-sdk(FastAPI 定义 API 面)→ OpenAPI 契约 → typescript-client(生成/维护的 TS 镜像)→ Agent Canvas;方向不可逆,API 权威在服务端。
  2. 接口面地图:ConversationClient(会话 CRUD)、RemoteEventsList(事件分页)、RemoteWorkspace(runtime/工作区)、FileClient(文件读写)、VSCodeClient(编辑器协商)、ServerClient(server_info/settings),经子路径导入;底层 HttpClient 禁止应用代码直接构造,须走 getAgentServerHttpClientOptions()
  3. 防漂移机制:后端改契约 → client 重新生成 → 前端旧用法编译报错;配合上一节的 CI 守卫(禁止绕过 client),破坏性变更的暴露点从运行时提前到编译期。
  4. 版本钉扎:config/defaults.json 是单一事实来源——agentServer 1.44.1、agentCanvas 1.16.0、automation 1.10.0、minimumAgentServer 1.28.0;typescript-client 在 package.json 精确钉 1.39.0,与 agent-server 组成验证过的配对。
  5. 跨仓库联动:sdk-version-sync.yml 由本仓库 PR、上游 repository_dispatch(sdk-release/sdk-version-check)与手动 dispatch 三通道触发,自动对表版本一致性。
  6. 贡献路径:新接口三步走——SDK 加端点 → client 加类型化方法 → 本仓库消费;client 缺方法时去 client 仓库补,不在前端开例外。

下一节:第 8 章 · 01 测试金字塔:单测/MSW/变异 ★——离开协议层,进入全书高潮之一的工程保障舱,先看 602 个单测文件、3.6K 行 MSW mock 与 Stryker 变异测试怎么托住这套契约。


作者与出处
原作者: 灏天文库
整理: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U