@kanyun-ai-infra/agenthub 1.17.0 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,68 @@
2
2
 
3
3
  本文件从 1.12.0 开始记录;更早的版本见 git 历史中的 `chore(agenthub-sdk): release X.Y.Z` 提交。
4
4
 
5
+ ## 1.19.0
6
+
7
+ 宿主接入线(#5492)R0 的 SDK 面:新增 webhook 订阅、vault 与凭据、项目模型服务、项目级 agent 列表四个入口,`agents.create` 可以带初版定义一步出可跑版本,客户端可以声明代终端用户调用的归因用户。对导出面是纯增量,已有调用方不用改代码。
8
+
9
+ ### 新增(主入口)
10
+
11
+ - `webhookSubscriptions`(`WebhookSubscriptionsResource`,scope `project:settings`):出站 webhook 订阅的 `create`、`list`、`update`、`delete`、`rotateSecret`。平台在 run / session / turn 结束时向订阅地址 POST 一条只带 id 的签名事件(`turn.ended` 等),后端不必停在 `waitForTurn` 上等结果。签名密钥只在 `create` 与 `rotateSecret` 的返回里出现一次,之后读不回来;`rotateSecret` 立即切换,没有双密钥窗口;删除订阅会级联删除投递历史。同项目订阅数有上限,超出 409 `SUBSCRIPTION_LIMIT_EXCEEDED`;URL 过不了 SSRF 策略 400 `WEBHOOK_URL_FORBIDDEN`。新导出 `WebhookEventTypeSchema`、`WebhookEventPayloadSchema`、`WebhookSubscriptionSchema`、`WebhookSubscriptionWithSecretSchema`、`WebhookSubscriptionStatusSchema`、`WebhookSubscriptionErrorCodeSchema`、`CreateWebhookSubscriptionRequestSchema`、`UpdateWebhookSubscriptionRequestSchema`、`WEBHOOK_EVENT_TYPES`、`MAX_WEBHOOK_SUBSCRIPTIONS_PER_PROJECT`、`MAX_WEBHOOK_EVENT_ID_LENGTH`、`OUTBOUND_WEBHOOK_SIGNATURE_HEADER`、`WEBHOOK_SECRET_PREFIX` 及对应类型。
12
+ - `vaults`(`VaultsResource`)与 `vaults.credentials`(`VaultCredentialsResource`):vault 的 `create`、`list`、`get`、`archive`,以及凭据的 `create`、`update`、`validateMcpOAuth`。返回的是公开 metadata,不含凭据值。需要显式签发的 `vaults:read` / `vaults:write`:没有任何预设 token 带这两个 scope,`vaults:admin` 仍不可签发。token 能访问多个工作区时 `workspaceId` 必填。新导出 `VaultRequestOptions`、`ListVaultsOptions`、`ListVaultsResult`,以及 contracts 的 `CreateVaultRequest`、`CreateCredentialRequest`、`UpdateCredentialRequest`、`VaultRecord`、`VaultCredentialRecord`、`VaultCredentialValidation`。
13
+ - `modelServices.list(projectId, { signal })`(`ModelServicesResource`):列出一个项目的模型服务身份与状态(`{ id, displayName, status }`,`active` 与 `archived` 都给),宿主不必再用显示名反查 `llmp_` ID。需要 `model-services:read`;连接配置、鉴权类型、凭据、环境变量和内部 UUID 都不返回。项目不存在、不在 token allowlist 内或不在 ACL 内通常报 403 `PROJECT_NOT_IN_TOKEN_SCOPE`;ACL 通过后项目被删的竞态报 404 `PROJECT_NOT_FOUND`。新导出 `ModelServiceSummary`、`ListModelServicesResponse`。
14
+ - `workloads.list({ projectId, limit?, cursor? })`(与 `agents.list` 是同一个方法):列出一个项目内的 agent 元数据,按 ID 稳定排序;`limit` 默认 50、上限 200,翻页把上一页返回的 `nextCursor` 原样作为 `cursor` 传入。需要 `agents:read`;不可访问与不存在的项目都报 404 `PROJECT_NOT_FOUND`。新导出 `ListPlatformWorkloadsQuerySchema`、`ListPlatformWorkloadsResponseSchema` 及对应类型。
15
+ - `agents.create({ version })`:一步出可跑版本(R0-4,#5492 宿主接入)。可选初版定义的形状与 `publishVersion` 的 body 一致(去掉 `projectId` / `workloadSlug` / `setDefault`;`workerContract` 可省,省略时继承 create 的 `kind`);服务端在同一事务里建 workload + 首版 Revision + 默认指针 + 首发 `test` 槽位。初版省略 `llmProviderRef`(且未钉 `defaults.model` / 快照 `model`)时按「项目恰有一个 active 文本模型服务」自动选,0 个或多个 → 400 `MODEL_SERVICE_REQUIRED` 并列出候选。带初版的 create 是 create-only:同名 slug 409 `WORKLOAD_SLUG_TAKEN`。返回值新增 `revision`(含 `versionId` 镜像);新导出类型 `WorkloadInitialVersionApplyResult`。
16
+ - 客户端选项 `onBehalfOf`:宿主代终端用户调 API 时声明归因用户。设置后每个请求(JSON、multipart、SSE 首连与 `Last-Event-ID` 重连)都带 `X-AgentHub-On-Behalf-Of`,调用方自己带的同名头(任意大小写)不能覆盖。它只进审计与 `createdBy` 展示,不参与鉴权,不扩大 token 能做的事。空白值、或与 `actAs` 同时配置,构造时即抛 `CONFIG_INVALID_ON_BEHALF_OF`;头值形状由服务端判,非法值 400 `ON_BEHALF_OF_INVALID`。
17
+
18
+ ### 随包文档
19
+
20
+ - `SKILL.md` 补充 webhook 订阅、vault 与凭据、模型服务、项目级 agent 列表与 `agents.create` 的 `version`,版本 0.24.0 → 0.29.0。
21
+
22
+ ### 迁移说明
23
+
24
+ - 相对 npm 1.18.0 的声明文件,本版新增 41 个导出(315 → 356)、删除 0 个,`dist/realtime.d.ts` 与 1.18.0 逐字节相同。五个原有声明有变化,全部是可选字段或新成员:`PilotPlatformClientConfig.onBehalfOf?`、`PilotPlatformClient` 新增 `modelServices` / `webhookSubscriptions` / `vaults` 三个 `readonly` 资源属性、`CreatePlatformWorkloadRequestSchema` 的可选 `version`、`WorkloadApplyResult.revision?`、`WorkloadsResource`(即 `AgentsResource`)新增 `list()`。另有一个声明只改了 JSDoc(`CreateSessionRequestInput` 的 `stage` 说明),不涉及签名。没有不兼容的签名变化。
25
+
26
+ ## 1.18.0
27
+
28
+ 浏览器实时语音入口新增级联通话的按键说话;主入口新增会话预览、回合摘要与按需读正文、项目仓库读写,以及客户端回合计时上报。
29
+
30
+ ### 新增(`@kanyun-ai-infra/agenthub/realtime`)
31
+
32
+ - 级联通话的按键说话(#5494):
33
+ - `engine()`:返回本通话的引擎,`"cascade"` 或 `"gemini"`;`ready` 之前、通话外、`stop()` 之后返回 `undefined`。网关的 `ready` 帧不带引擎字段时按 `"gemini"` 处理。
34
+ - `beginInput()`:开始一段说话。只在级联通话上开采集;按住期间重复调用(如键盘自动重复)不起作用;`ready` 之前按下,`ready` 时开始上行。
35
+ - `endInput(): boolean`:结束这段说话。只在级联通话、`ready` 之后、且这段按下过时发 `{ type: "input_end" }` 并返回 `true`,其余情况返回 `false`、不发帧。松开后不足 20 ms 的尾帧不上传。
36
+ - 新选项 `onTurn({ turnSeq, turnId? })`:每个被接受的 `turn_start` 一次。
37
+ - 新选项 `onTurnAudible({ turnSeq, firstAudibleAt })`:每个回合第一次播出非静音样本时一次,时刻与发给网关的 final 回执的 `firstPlayedAt` 相同。
38
+ - 新导出类型 `RealtimeTurnStart`、`RealtimeTurnAudible`、`RealtimeVoiceEngine`。
39
+
40
+ ### 新增(主入口)
41
+
42
+ - `sessions.getPreview(sessionId)`:会话的实时预览 URL 与当下是否可访问(scope `runs:stream`)。返回 `null` 表示没有声明预览、会话失败或沙箱已不在;`{ url: null, ready: false }` 表示沙箱还没起来。服务端每次请求探测(缓存 5 s),轮询间隔不小于 2 s。`SessionSnapshotResponse` 新增可选 `agentPublicEndpoints`:运行时模板声明的入口在本沙箱上的公开地址。
43
+ - `sessions.turns` 新增有界读取:`listSummaries`、`getMetadata`、`listParts`(`fromOrdinal` 直接定位到尾部)、`getPartContent`(按字节区间读单个 part),以及显式全量拼装 `getComplete`(正文变化时报 `TURN_CONTENT_CHANGED`;旧回合或 metadata 截断时需 `allowLegacy: true`)。这些方法要求服务端启用 Turn wire 版本 2,服务端默认关闭。原 `list` / `get` 保持完整 Turn 语义。新导出类型 `TurnSummary`、`TurnSummaryPage`、`TurnMetadata`、`TurnPartsPage`、`TurnPartSection`、`ListTurnPartsOptions`、`GetTurnPartContentOptions`。
44
+ - `sessions.reportTurnTiming(sessionId, turnId, report)`:上报一个语音回合在客户端从说完到首个可听片段的耗时(spec VO-5)。平台只写一条 `voice.client.timing` 日志,不落库、不发事件、不改回合。每个时长为同一客户端时钟下的整数毫秒,范围 `[0, 120000]`,越界整条拒收(400 `VALIDATION_ERROR`);公开项目的访客报 404 `SESSION_NOT_FOUND`。新导出 `TurnClientTimingReportSchema`、`TURN_CLIENT_TIMING_MAX_MS` 与类型 `TurnClientTimingReport`、`TurnClientTimingMode`、`TurnClientTimingOutcome`。
45
+ - `projects.repo`(`ProjectRepoResource`):用项目自己的 git 凭据读写绑定仓库,调用方不持有凭据。运营方逐个打开前,各 API 返回 403 `REPO_API_DISABLED`(读、写分开开关)。
46
+ - 读(scope `project:repo-read`):`getTree`、`getFile`。先把 `ref` 解析到一个 commit 并只读这个 commit,结果带上该 commit;ref 或路径不存在报 404 `REF_NOT_FOUND` / `PATH_NOT_FOUND`。
47
+ - 写(scope `project:repo-write`,按 `idempotencyKey` 幂等):`createBranch` 建运行分支,`openMergeRequest` 从运行分支的某个 commit 开发布 MR 到默认分支(平台不合并)。
48
+ - `setPushBackPolicy`(scope `project:settings`):设置 `push_back` 会话可推回的分支模式;空列表关闭本项目的 `push_back`。
49
+ - 新导出 `CreateRepoBranchRequestSchema`、`OpenRepoMergeRequestRequestSchema`、`SetPushBackPolicyRequestSchema`、`PushBackPolicySchema`、`RepoBranchSchema`、`RepoMergeRequestSchema`、`RepoTreeSchema`、`REPO_READ_COMMIT_HEADER`、`REPO_READ_BLOB_SHA_HEADER` 及对应类型,`RepoFile`、`RepoFileQuery`、`RepoTreeEntry`。
50
+
51
+ ### 随共享契约内联的变化
52
+
53
+ - 建会话请求 `deliveryMode` 新增 `"push_back"`,并新增可选 `commitAuthor: { ldap }`。
54
+ - Workload 清单 `voice.output` 新增可选 `delivery: "segments" | "gateway"`,用于声明级联语音。
55
+ - 若干枚举(Pipeline trigger、Turn status、记忆写入结果)在声明文件里只调整了成员顺序,取值不变。
56
+
57
+ ### 随包文档
58
+
59
+ - `SKILL.md` 补充按键说话、会话预览、回合有界读取、项目仓库 API 与回合计时上报,版本 0.20.9 → 0.24.0;README 补充按键说话与回合有界读取。
60
+
61
+ ### 迁移说明
62
+
63
+ - **类型破坏**:`RealtimeVoiceClient` 新增必选方法 `engine()`、`beginInput()`、`endInput()`。自己实现或 mock 这个接口的代码升级后会报类型错误,要补上这三个方法;通过 `createRealtimeVoiceClient` 工厂拿到的客户端不受影响。
64
+ - **级联通话的上行变化**:级联通话(`engine() === "cascade"`)在 `beginInput()` 之前不再上传麦克风 PCM,松手后也不上传;没有按键的页面在级联通话上不会发出任何回合(此前同样发不出,只是会持续上传音频)。Gemini 通话的采集、播放、打断与重连行为不变。
65
+ - **`firstPlayedAt` 定义变化**:发给网关的播放回执中,`firstPlayedAt` 改为该回合第一个绝对值不低于 0.02 的输出样本的时刻(此前是第一个输出样本,含静音前导)。回合以静音开头时,首条非 final 回执的 `firstPlayedAt` 可能为 `null`,真值随 final 回执到达。`onPlaybackState("speaking")`、回执发送时机与 `onTranscriptPlayed` 不变。
66
+
5
67
  ## 1.17.0
6
68
 
7
69
  浏览器实时语音入口 `@kanyun-ai-infra/agenthub/realtime`:终止错误带上网关终止码,新增默认关闭的自动重开,以及拉取式的音量电平。主入口的方法没有变化。
package/README.md CHANGED
@@ -322,7 +322,19 @@ stopButton.onclick = () => { void voice.stop(); };
322
322
 
323
323
  采集需要 HTTPS 安全上下文(本地 localhost 可用)与麦克风权限。SDK 使用 AudioWorklet 将音频转为 16 kHz 单声道 PCM16,每帧 20 ms;播放接收 24 kHz PCM16,按轮排队,收到打断即清空。页面 CSP 需允许 `blob:` AudioWorklet 模块。支持目标为桌面 Chrome / Edge / Safari、iOS Safari 和微信内置浏览器;需要宿主提供 `AudioContext`、`AudioWorklet` 与 `getUserMedia`,缺失时通过 `onError` 报错。小程序和 App 内 WebView 不在本期范围,具体设备仍需接入方验收。
324
324
 
325
- `stop()` 立即停止麦克风,发送本轮最终播放回执,再发 `end` 并关闭连接;Worklet 无响应时最多等待 200 ms,使用最后确认的播放统计。页面卸载同步清理。播放回执按 `turnSeq` 归属,`playedMs` 计实际渲染样本,不计排队音频;`firstPlayedAt` 是 Unix 毫秒,尚未播放时为 `null`。`turn_end` 表示业务回合收尾,不是音频 EOF。SDK 记住提前到达的回合终态,在播放排空时发送 final;迟到尾块沿用首次播放时刻并累加 playedMs,再补累计 final。记录保留最近 128 轮,尚在播放队列中的轮不会被淘汰。网关的文本截止不受晚回执影响,晚回执用于录音统计。
325
+ **按键说话(级联通话,SDK 1.18.0 起):`voice.engine()`、`voice.beginInput()`、`voice.endInput()`。** Agent 声明级联语音时,网关在 `ready` 里标明该通话走级联,`start()` 完成后 `voice.engine()` 返回 `"cascade"`;其他通话返回 `"gemini"`(含不标明的旧网关)。首次 `ready` 之前、`stop()` 之后和 `reopenOnce` 重开期间返回 `undefined`。级联通话由页面决定一段话何时说完:`ready` 后麦克风不上行,按下时调 `beginInput()` 开始上行,松开时调 `endInput()` 停止上行并通知网关这段话说完,网关随后开一个回合。`endInput()` 把通知交给连接时返回 `true`;没有按住、不是级联通话或连接正断开时返回 `false`,连接断开时这段话作废。按住期间重复调用 `beginInput()`(例如键盘自动重复)不起作用,`stop()` 之后两个方法都不做事。松开后立刻再按时,上一段排队未送出的音频不会进入新的一段。断线接手期间仍按住的,接手后继续上行;`reopenOnce` 重开的新通话从松开状态开始。松开时最后不足约 20 ms 的音频可能不上行。级联通话未按住时 `audioLevels()` 的 `input` 为 0。非级联通话上这两个方法不发帧、不改采集。
326
+
327
+ ```ts
328
+ await voice.start();
329
+ if (voice.engine() === "cascade") {
330
+ talkButton.onpointerdown = () => voice.beginInput();
331
+ talkButton.onpointerup = () => voice.endInput();
332
+ }
333
+ ```
334
+
335
+ `onTurn({ turnSeq, turnId })` 在网关开出新回合时按序触发一次,级联回合带 `turnId`,其他通话可能不带。`onTurnAudible({ turnSeq, firstAudibleAt })` 在本回合回答第一次可听时触发一次:`firstAudibleAt` 是 Unix 毫秒,取第一个幅度不低于满幅 0.02 的播放样本,开头的静音不算;整轮低于该幅度或在可听之前被打断的回合不触发。`onPlaybackState("speaking")` 仍在本回合音频开始输出时触发,不等可听样本。
336
+
337
+ `stop()` 立即停止麦克风,发送本轮最终播放回执,再发 `end` 并关闭连接;Worklet 无响应时最多等待 200 ms,使用最后确认的播放统计。页面卸载同步清理。播放回执按 `turnSeq` 归属,`playedMs` 计实际渲染样本,不计排队音频;`firstPlayedAt` 是本轮第一个可听样本(幅度不低于满幅 0.02)的 Unix 毫秒,与 `onTurnAudible` 的 `firstAudibleAt` 相同,还没播到可听样本时为 `null`;1.17.0 及以前取第一个输出样本。`turn_end` 表示业务回合收尾,不是音频 EOF。SDK 记住提前到达的回合终态,在播放排空时发送 final;迟到尾块沿用首次播放时刻并累加 playedMs,再补累计 final。记录保留最近 128 轮,尚在播放队列中的轮不会被淘汰。网关的文本截止不受晚回执影响,晚回执用于录音统计。
326
338
 
327
339
  收到当前通话的 `call_ended` 或专用关闭码 `4000` 时,SDK 停止自动接手、清理计时器、停止麦克风并清空播放,随后调用一次可选的 `onEnded({ callId, reason })`。首期原因是 `duration_limit`,未知原因映射为 `unknown`;关闭码判定不依赖 `wasClean`。只有关闭帧先于 `ready` 到达且没有通话 ID 时,`callId` 缺省;尚未完成的 `start()` 以既有 `REALTIME_STOPPED` 拒绝,不触发 `onError`。本地 `stop()` 不触发 `onEnded`,不等服务端回声。结束后可手动 `start()` 开新通话。`turn_end` 仍只代表一轮结束。
328
340
 
@@ -331,6 +343,10 @@ SDK 1.13.0 起提供此行为。旧版 1.11.0 / 1.12.0 / 1.12.1 忽略 `call_end
331
343
  断线或收到 `reconnecting` 时保留播放队列、暂停采集,换新票并带原 `callId` 重连。每通话最多自动接手 5 次,退避从 250 ms 增至 4 s;取票与 WebSocket upgrade 最多等待 10 s,open 后另等 ready 最多 30 s,音频初始化最多 30 s。WebSocket 握手已验票,SDK 立即处理下行消息;转写、状态回调与播放可能早于 start() 完成。ready 只控制麦克风上行和 start() 完成,interrupted 始终立即清空播放。`no_response` 仅以 `NO_RESPONSE` 可重试通知上报,不主动接手通话。无法恢复时释放资源并报不可重试错误,可由用户重新开始。主动 stop 后再次 start 是新通话。
332
344
  浏览器子入口同时导出只读 `REALTIME_RECOVERY_REASONS` 和 `RealtimeRecoveryReason` 类型。实时 `NO_RESPONSE` 可重试通知可附带 `reason`:`input_not_received`(本句未获得回答)、`response_incomplete`(回答未完成)、`connection_interrupted`(空闲连接中断)。调用方应显示对应提示并允许重试;旧网关不带 reason 时沿用通用提示,未知 reason 被忽略。reason 不改变 SDK 的 stop/自动接手策略,也不表示丢失输入已被重放。已发布 1.11.0 忽略该可选字段,但仍正常通知 NO_RESPONSE。网关在 GoAway 截止时换新模型会话、无法保留此前对话上下文时,同一通知另带 `contextReset: true`:通话继续,平台回合历史不变,调用方可提示用户模型已不记得之前的对话;已发布 1.11.0 / 1.12.0 忽略该字段。
333
345
 
346
+ 网关给出的终止错误(`error` 帧 `retryable: false`,或 `ready` 之前到达的可重试错误)以 `GATEWAY_ERROR` 上报,并带可选 `gatewayCode`,取值为 contracts 的 `REALTIME_TERMINAL_ERROR_CODES` 闭集:`write_queue_full`、`control_timeout`、`control_rejected`、`generation_expired`、`upstream_unavailable`、`resume_store_unavailable`、`renew_rejected`、`execution_expired`、`no_response`、`renew_abandoned`、`realtime_turn_lost`。未知码不带该字段;`code` 仍是 `GATEWAY_ERROR`,按旧码分支的页面不用改。`retryable: true` 且已有 `callId` 的错误仍走自动接手,`no_response` 的可重试提示仍以 `NO_RESPONSE` 上报。
347
+
348
+ `reopenOnce`(默认 `false`)开启后,SDK 对「新通话还有机会」的终止错误自动重开一次:新票、新 WebSocket、新 `callId`,复用麦克风与 `AudioContext`,每次 `start()` 最多一次。可重开:`no_response`、`renew_abandoned`、`realtime_turn_lost`,以及 `ready` 之前到达的可重试 `upstream_unavailable`、`resume_store_unavailable`、`write_queue_full`、`control_timeout`、`control_rejected`;不重开:`generation_expired`、`renew_rejected`、`execution_expired`、`call_ended`、本地 `stop()` 与未知码。重开前 SDK 发一次 `onError({ code: "REOPENING", retryable: true, gatewayCode })`;重开后的通话再次终态按普通终态处理。`ready` 之前重开时 `start()` 等第二通 `ready` 才完成,两通都在 `ready` 前失败则以 `GATEWAY_ERROR` 拒绝。选项关闭时不会出现 `REOPENING`。
349
+
334
350
  首次建连(第一次收到 ready 之前)的 WebSocket 若在 open 之前关闭,SDK 依次等待 500 ms、1 s、2 s,每次重新取票再连,最多重试 3 次,每次以可重试通知 `CONNECT_RETRYING` 上报;第 3 次重试仍被拒时报 `CONNECTION_FAILED`。浏览器不暴露握手的 HTTP 状态码,网关排空或满额时的 503 与 401 等其他拒绝无法区分,因此都按此重试;票是一次性的,网关可能已经兑票,所以每次重试都重新取票。以下情况不重试:`getTicket()` 抛错、票格式错误、open 之后的协议不符或网关的不可重试错误、open 之后 ready 之前断开、10 s 内未完成取票与 upgrade。首次建连重试与通话接手的 5 次额度分开计数。`CONNECT_RETRYING` 与 `TICKET_GATEWAY_URL_*` 都是 `retryable: true` 的通知,与 `RECONNECTING` 一样不表示通话失败。
335
351
 
336
352
  `adaptiveConnect` 缺省为 `true`;设为 `false` 时只连初始化地址,首次建连被拒立即报 `CONNECTION_FAILED`,与 1.11.0 一致。
@@ -383,3 +399,7 @@ try {
383
399
  会话不是 ready(`SESSION_NOT_READY`)、Revision 未声明 `voice.realtime`(`REALTIME_NOT_DECLARED`)、会话绑定了渠道(`REALTIME_CHANNEL_SESSION`)也是 409,不带 `message`。会话不存在或不属于调用方项目回 404 `SESSION_NOT_FOUND`;平台没配好网关地址回 503 `REALTIME_NOT_CONFIGURED`,票存储不可用回 503 `REALTIME_TICKET_STORE_UNAVAILABLE`,两者都不带 `message`,项目侧无法处理。会话上已有在线通话时,这张票只用于接手那通通话,不做上表里模型服务、凭据、平台上限和租户这几项预检,也不受项目上限限制。
384
400
 
385
401
  **握手被拒(站点来源)。** 站点来源不在取票时检查。请求没有 `Origin` 头(或超过 2048 个字符)时,网关在兑票前就回 401,票没有用掉。带了 `Origin` 的握手,网关兑票之后交平台判来源:既不是平台门户、也不在项目名单里时回 403 `REALTIME_ORIGIN_FORBIDDEN`,这时票已经用掉。另一个来源码 `REALTIME_ORIGIN_REQUIRED`(claim 没带来源)经平台网关碰不到:网关在没有 `Origin` 时已先回 401。浏览器读不到握手的 HTTP 状态码,SDK 按首次建连被拒处理:报 `CONNECT_RETRYING`,每次重新取票再连,3 次后报 `CONNECTION_FAILED`(`adaptiveConnect: false` 时直接报)。取票一直成功、`start()` 却以 `CONNECTION_FAILED` 结束时,先核对项目的站点来源;网关满额、排空等其他握手拒绝也是同样的表现。断线接手时同样检查来源,通话中途从名单里删掉的来源,接手会失败。不用 SDK、自己建 WebSocket 的接入方,被拒后要重新取票再连,同一张票不能再用。
402
+
403
+ ### 摘要与按需详情
404
+
405
+ 服务启用 Turn wire 版本 2 后,用 `sessions.turns.listSummaries` 轮询轻量摘要,按需调用 `getMetadata`、`listParts`、`getPartContent`。`listParts({ fromOrdinal })` 支持直接定位尾部;正文变化返回 `TURN_CONTENT_CHANGED`,应丢弃未完成拼装后重新打开。`getComplete` 是显式全量拼装,只有 `allowLegacy: true` 才允许旧回合或截断 metadata 的完整读取。原 `list/get` 保持完整 Turn,不能把新摘要当旧 Turn 使用。