统一 API 合同(/api)总览
/api 是 @tansr/serve 对外的唯一 HTTP 调用面:会话、执行、档案、缓存、终端等所有领域都在这一个前缀下,共用同一套请求头、响应头、错误信封与事件包络。合同名为 unified-v1,自 @tansr/serve 0.13.0(2026-10-02 发布)起正式可用。此前按领域分散的 /v2、/v3/sdk2、/v3/terminal* 仍在进程内,但只作为内部适配入口;/v1 在装配了 /api 的部署上缺省关闭。
如果你是第一次接触会话服务,先读会话服务 5 分钟跑通把服务跑起来,再回到这里;如果你已经在用 /v2 协议,直接看迁移与退役时间表。
| 层 | 内容 | 由谁约束 |
|---|---|---|
| 公共信封(Common Envelope) | 请求头、响应头、统一错误信封、统一事件包络、幂等与前置条件语义 | 本章各页;wire 形以 doc/rfc/unified-v1.schema.json 与 165 条金向量为准 |
| 能力清单(Capability Manifest) | 路径模板、方法、查询键、读 / 写 / 流分类、能力项与限值 | packages/server/contract/api-manifest.json(当前 revision 7) |
| 领域子合同(Domain Subcontracts) | 各领域的请求 / 响应体、事件载荷、域内错误码 | agent-session-v1、sdk2-ext-v1、terminal-services-v1 等 11 个族 |
三层的分工是固定的:公共信封决定怎么调,能力清单决定能调什么,领域子合同决定体里有什么。/api 不改写任何领域子合同的成功响应体——同一个操作经 /api 与经旧前缀得到的 2xx 体逐字节相同;改变的只有头、错误形与(显式协商后的)事件帧外形。
一次调用长什么样
Section titled “一次调用长什么样”POST /api/sessions/0a1b2c3d/messages HTTP/1.1Authorization: Bearer <short-lived token>Content-Type: application/jsontansr-closure-id: 9f3c…e1
{"prompt":"hi"}HTTP/1.1 202 Acceptedtansr-contract: unified-v1tansr-manifest-revision: 7tansr-domain: sessiontansr-schema-hash: sha256:6e1876ed…content-type: application/json
{"sessionId":"0a1b2c3d","accepted":true}每个 /api 响应(含 2xx 与 SSE 打开)都带 tansr-contract / tansr-manifest-revision / tansr-domain / tansr-schema-hash 四个头;缺任何一个,客户端都应判定「合同不可用」而不是继续解析。详见统一请求头、响应头与错误信封。
版本落地时间
Section titled “版本落地时间”| 发行物 | 日期 | 与 /api 有关的变化 |
|---|---|---|
@tansr/serve 0.13.0 |
2026-10-02 | /api 正式可用;统一错误信封(19 个统一码、6 种 retryAction);显式协商的 7 键事件包络;发现端点与会话级能力围栏;装配了 /api 的部署上 /v1 缺省关闭(库选项 disableV1,CLI tansr serve --legacy-v1 或 TANSR_SERVE_LEGACY_V1=1 过渡保留) |
@tansr/protocol 0.2.0 |
2026-10-02 | 首次公开发布,@tansr/protocol/canonical 子入口(严格 JSON 的 canonical 编解码与域摘要) |
@tansr/serve 0.14.0 |
2026-10-04 | manifest revision 7:81 个操作,新增 approval.credential.submit(POST /api/approvals/:id/credential),权限 / 提问应答改用 :ticketId 占位;三个请求头 Idempotency-Key、If-Match(配 ETag)、deadline 接线;--legacy-v1 / TANSR_SERVE_LEGACY_V1 标废弃并在启动时告警 |
@tansr/cli 0.10.1 |
2026-10-04 | tansr serve --legacy-v1 标废弃(仍生效) |
@tansr/api-client 0.3.0 |
2026-10-04 | 正式版:@tansr/api-client/api 子入口(createApiClient),操作目录对齐 revision 7,三个请求头以调用选项暴露;./sdk2、./sdk2/node、./terminal、./terminal/node 旧入口冻结不删 |
@tansr/serve 0.15.0 |
2026-10-06 | 旧前缀退役窗口旋钮 legacyPrefixes(TANSR_SERVE_LEGACY_PREFIX=readwrite | readonly | off,缺省 readwrite)与 legacyPrefixSunset(TANSR_SERVE_LEGACY_SUNSET);门面在场时 /v2、/v3/* 响应附 Deprecation / Link(设置后再附 Sunset);同一会话跨前缀混用记 api.legacy_prefix_mixed;/api 面与 manifest 零变化 |
@tansr/cli 0.10.2 |
2026-10-06 | tansr serve --v2 可用上述两个环境变量;缺省行为不变 |
@tansr/api-client 0.4.0 |
2026-10-06 | 冻结入口 ./sdk2、./terminal 不传 routePrefixes 时缺省走 /api;经门面时的错误体按统一信封还原;./api 子入口零变化 |
@tansr/serve 0.15.1 |
2026-10-10 | 统一错误信封映射修正:矩阵未登记的域码一律 500 internal_error(原状态只留 detail.domainStatus),result_unknown 的 retryAction 只会是 query-status 或 rebind;会话级围栏对 execution.binding.create 不再要求已有可用的设备工具;旧前缀窗口缺省仍是 readwrite |
@tansr/cli 0.10.3 |
2026-10-10 | 内置 @tansr/serve 0.15.1;无新命令、旗标 |
本章所有页面按上表的已发布版本写事实;写到的能力都已在 @tansr/serve 0.15.1(当前 npm latest)上可用,没有「候选」项。
最短可运行示例(Node)
Section titled “最短可运行示例(Node)”import { createApiClient, ApiError } from '@tansr/api-client/api';
const api = createApiClient({ baseUrl: 'http://127.0.0.1:8787', token: () => loadEndUserToken(), // your own loader for the short-lived bearer token eventEnvelope: 'unified-v1',});
const created = await api.call('session.create', { body: {} });const sessionId = (created.body as { sessionId: string }).sessionId;
const { closureId } = await api.sessionCapabilities(sessionId);await api.call('session.message.send', { params: { id: sessionId }, body: { prompt: 'hi' }, closureId });
for await (const frame of api.events('session.events.observe', { params: { id: sessionId } })) { if (frame.envelope.type === 'msg.text.delta') process.stdout.write(String(frame.envelope.raw['text'] ?? '')); if (frame.envelope.terminalStatus !== null) break;}这段代码里没有一处手写 /api/... 路径:操作名 session.create、session.message.send、session.events.observe 来自随包生成的操作目录,路径由 SDK 按 manifest 模板本地实例化。错误以 ApiError(统一信封)抛出,error.code 是 19 个统一码之一,error.retryAction 告诉你下一步该做什么。其它语言的现状见各语言客户端。
开发者七条纪律
Section titled “开发者七条纪律”这七条来自合同文本,所有官方客户端都按它们实现;自己写客户端时请逐条对照。
- 不静默降级。 响应缺
tansr-contract: unified-v1头即视为合同不可用,上报而不是改走/v2或/v3;请求了事件包络而服务端未回响同名头,也按未协商处理。 - 不从响应里取 URL。 发现端点不返回任何可导航字段;路径只由 manifest 模板加路径参数本地实例化。
- 不重放未知副作用。 只有服务端明示
retryAction: same-request且沿用同一Idempotency-Key时才重放一次;result_unknown只能查询状态。 - 身份不进体。 鉴权只在
Authorization头;体内出现身份字段且与鉴权产物不符即被拒。 - SSE 结束与 202 都不等于完成。 轮次 / 操作的终态只看事件包络的
terminalStatus。 - 五个游标互不替代。
eventCursor、archiveCoverage、outputWatermark、materialConsumed、ackReceipt各推进各的,不能拿一个去续另一个。 - 不手写旧前缀。 不在源码里出现
/v2、/v3/sdk2、/v3/terminal字面量;同一会话族只经一种前缀访问。
| 页 | 回答的问题 |
|---|---|
| 调用面、领域与操作目录 | /api 下有哪些领域与操作,路径怎么拼,查询串里的修订标识怎么带 |
| 发现与能力围栏 | 启动时先问什么,closureId 是什么,围栏外的操作会得到什么应答 |
| 统一请求头、响应头与错误信封 | 每个请求能带哪些头,每个响应必带哪些头,19 个统一码与 6 种 retryAction 怎么判 |
| 事件流与统一事件包络 | 四条 SSE 路由、显式协商、7 键包络、terminalStatus 判定与续流 |
| 迁移与退役时间表 | 从 /v1、/v2、/v3 怎么切到 /api,旧前缀什么时候退役,怎样成对回滚 |
| 各语言客户端 | Node / .NET / Go / Rust / Python / C++ 与 Android / iOS / 鸿蒙各自对 unified-v1 的对齐状态 |
本文是否有帮助?
感谢反馈,我们会持续改进这篇文章。