@kanyun-ai-infra/agenthub 1.16.0 → 1.18.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,76 @@
2
2
 
3
3
  本文件从 1.12.0 开始记录;更早的版本见 git 历史中的 `chore(agenthub-sdk): release X.Y.Z` 提交。
4
4
 
5
+ ## 1.18.0
6
+
7
+ 浏览器实时语音入口新增级联通话的按键说话;主入口新增会话预览、回合摘要与按需读正文、项目仓库读写,以及客户端回合计时上报。
8
+
9
+ ### 新增(`@kanyun-ai-infra/agenthub/realtime`)
10
+
11
+ - 级联通话的按键说话(#5494):
12
+ - `engine()`:返回本通话的引擎,`"cascade"` 或 `"gemini"`;`ready` 之前、通话外、`stop()` 之后返回 `undefined`。网关的 `ready` 帧不带引擎字段时按 `"gemini"` 处理。
13
+ - `beginInput()`:开始一段说话。只在级联通话上开采集;按住期间重复调用(如键盘自动重复)不起作用;`ready` 之前按下,`ready` 时开始上行。
14
+ - `endInput(): boolean`:结束这段说话。只在级联通话、`ready` 之后、且这段按下过时发 `{ type: "input_end" }` 并返回 `true`,其余情况返回 `false`、不发帧。松开后不足 20 ms 的尾帧不上传。
15
+ - 新选项 `onTurn({ turnSeq, turnId? })`:每个被接受的 `turn_start` 一次。
16
+ - 新选项 `onTurnAudible({ turnSeq, firstAudibleAt })`:每个回合第一次播出非静音样本时一次,时刻与发给网关的 final 回执的 `firstPlayedAt` 相同。
17
+ - 新导出类型 `RealtimeTurnStart`、`RealtimeTurnAudible`、`RealtimeVoiceEngine`。
18
+
19
+ ### 新增(主入口)
20
+
21
+ - `sessions.getPreview(sessionId)`:会话的实时预览 URL 与当下是否可访问(scope `runs:stream`)。返回 `null` 表示没有声明预览、会话失败或沙箱已不在;`{ url: null, ready: false }` 表示沙箱还没起来。服务端每次请求探测(缓存 5 s),轮询间隔不小于 2 s。`SessionSnapshotResponse` 新增可选 `agentPublicEndpoints`:运行时模板声明的入口在本沙箱上的公开地址。
22
+ - `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`。
23
+ - `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`。
24
+ - `projects.repo`(`ProjectRepoResource`):用项目自己的 git 凭据读写绑定仓库,调用方不持有凭据。运营方逐个打开前,各 API 返回 403 `REPO_API_DISABLED`(读、写分开开关)。
25
+ - 读(scope `project:repo-read`):`getTree`、`getFile`。先把 `ref` 解析到一个 commit 并只读这个 commit,结果带上该 commit;ref 或路径不存在报 404 `REF_NOT_FOUND` / `PATH_NOT_FOUND`。
26
+ - 写(scope `project:repo-write`,按 `idempotencyKey` 幂等):`createBranch` 建运行分支,`openMergeRequest` 从运行分支的某个 commit 开发布 MR 到默认分支(平台不合并)。
27
+ - `setPushBackPolicy`(scope `project:settings`):设置 `push_back` 会话可推回的分支模式;空列表关闭本项目的 `push_back`。
28
+ - 新导出 `CreateRepoBranchRequestSchema`、`OpenRepoMergeRequestRequestSchema`、`SetPushBackPolicyRequestSchema`、`PushBackPolicySchema`、`RepoBranchSchema`、`RepoMergeRequestSchema`、`RepoTreeSchema`、`REPO_READ_COMMIT_HEADER`、`REPO_READ_BLOB_SHA_HEADER` 及对应类型,`RepoFile`、`RepoFileQuery`、`RepoTreeEntry`。
29
+
30
+ ### 随共享契约内联的变化
31
+
32
+ - 建会话请求 `deliveryMode` 新增 `"push_back"`,并新增可选 `commitAuthor: { ldap }`。
33
+ - Workload 清单 `voice.output` 新增可选 `delivery: "segments" | "gateway"`,用于声明级联语音。
34
+ - 若干枚举(Pipeline trigger、Turn status、记忆写入结果)在声明文件里只调整了成员顺序,取值不变。
35
+
36
+ ### 随包文档
37
+
38
+ - `SKILL.md` 补充按键说话、会话预览、回合有界读取、项目仓库 API 与回合计时上报,版本 0.20.9 → 0.24.0;README 补充按键说话与回合有界读取。
39
+
40
+ ### 迁移说明
41
+
42
+ - **类型破坏**:`RealtimeVoiceClient` 新增必选方法 `engine()`、`beginInput()`、`endInput()`。自己实现或 mock 这个接口的代码升级后会报类型错误,要补上这三个方法;通过 `createRealtimeVoiceClient` 工厂拿到的客户端不受影响。
43
+ - **级联通话的上行变化**:级联通话(`engine() === "cascade"`)在 `beginInput()` 之前不再上传麦克风 PCM,松手后也不上传;没有按键的页面在级联通话上不会发出任何回合(此前同样发不出,只是会持续上传音频)。Gemini 通话的采集、播放、打断与重连行为不变。
44
+ - **`firstPlayedAt` 定义变化**:发给网关的播放回执中,`firstPlayedAt` 改为该回合第一个绝对值不低于 0.02 的输出样本的时刻(此前是第一个输出样本,含静音前导)。回合以静音开头时,首条非 final 回执的 `firstPlayedAt` 可能为 `null`,真值随 final 回执到达。`onPlaybackState("speaking")`、回执发送时机与 `onTranscriptPlayed` 不变。
45
+
46
+ ## 1.17.0
47
+
48
+ 浏览器实时语音入口 `@kanyun-ai-infra/agenthub/realtime`:终止错误带上网关终止码,新增默认关闭的自动重开,以及拉取式的音量电平。主入口的方法没有变化。
49
+
50
+ ### 新增(`@kanyun-ai-infra/agenthub/realtime`)
51
+
52
+ - `onError` 的 `RealtimeVoiceError` 新增可选 `gatewayCode`:网关终止通话时给出的码,只在 `GATEWAY_ERROR` 出口与 `REOPENING` 通知上出现。取值为 11 个终止码的闭集:`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` 且已有通话 ID 的错误仍走自动接手,不落到这里;`no_response` 的可重试提示仍以 `NO_RESPONSE` 上报,不带 `gatewayCode`。
53
+ - 新选项 `reopenOnce`(默认 `false`):开启后,SDK 对「新通话还有机会」的终止错误自动重开一次——新票、新 WebSocket、新通话 ID,复用麦克风与 `AudioContext`,每次 `start()` 最多一次。重开前发一次 `onError({ code: "REOPENING", retryable: true, gatewayCode })`;重开后的通话再次终态按普通终态处理,不再重开。
54
+ - 可重开:`no_response`、`renew_abandoned`、`realtime_turn_lost`,以及 `ready` 之前到达的可重试 `upstream_unavailable`、`resume_store_unavailable`、`write_queue_full`、`control_timeout`、`control_rejected`。
55
+ - 不重开:`generation_expired`、`renew_rejected`、`execution_expired`(通话已被接管或执行许可到期)、`call_ended`、本地 `stop()`、未知码。
56
+ - 新方法 `audioLevels()`:返回 `RealtimeVoiceLevels { input, output }`,两侧都是最近 2048 个采样(48 kHz 下约 42.7 ms,采样率不同时时长随之变化)的线性 RMS,范围 [0, 1],不做平滑。`input` 是麦克风信号,`output` 是 SDK 播放出的信号,在 `outputNode` 之前取样:不传 `outputNode` 时即用户听到的声音;页面经 `outputNode` 再做增益或处理时,读数不含这部分处理。拉取式接口,页面在自己的 `requestAnimationFrame` 里调用,SDK 不新增定时器或回调。通话外与 `stop()` 之后返回 `undefined`;`ready` 之前与断线接手期间采集暂停,`input` 为 0。
57
+ - 新导出类型 `RealtimeVoiceLevels`。
58
+ - 电平分析节点串联在音频链路上:`source → 输入分析器 → worklet → 输出分析器 → outputNode/destination`。两个分析节点都是直通,送往网关的 PCM 与播放音频不变。
59
+
60
+ ### 随共享契约内联的变化
61
+
62
+ - 工作台与发布板读模型新增可选的 Revision 序号与版本串:`WorkbenchRecentChange` 的 `revisionVersion` / `revisionSequence`,槽位摘要(含灰度摘要)的 `revisionSequence`,以及 `ReleaseBoardRow.defaultRevision` / `latestRevision` 的 `sequence`。旧服务端的响应缺这些字段,解析行为不变。
63
+ - `Environment` 的 `envVars` 新增拒绝 `PILOT_WORKLOAD_SLUG` 的大小写变体(如 `pilot_workload_slug`),在 SDK 客户端校验时就报错;大写形式此前已被 `PILOT_` 前缀规则拒绝。会话配置值与 secret `envKey` 不受影响。
64
+
65
+ ### 随包文档
66
+
67
+ - `SKILL.md` 实时段补充音量电平、网关终止码与 `reopenOnce`。
68
+
69
+ ### 迁移说明
70
+
71
+ - **类型破坏**:`RealtimeVoiceClient` 新增了必选方法 `audioLevels()`。自己实现或 mock 这个接口的代码升级后会报类型错误,要补上该方法;通过 `createRealtimeVoiceClient` 工厂拿到的客户端不受影响。
72
+ - **音频接线变化**:麦克风源不再直接接 worklet,中间多了一个直通的分析节点。页面如果通过拦截 `AudioNode.prototype.connect`、按 `source → worklet` 这条连线识别上行的分接写法,升级后上行(麦克风)拿不到,下行照旧。正路是改用 `audioLevels()`。
73
+ - `reopenOnce` 默认关闭时,终止行为与 1.16.0 一致,只多一个 `gatewayCode` 字段。
74
+
5
75
  ## 1.16.0
6
76
 
7
77
  Revision 读面改为宽容读者:服务端给 `workloads.get` / `getRevision` / `getVersion` 的响应新增字段时,已发布的客户端不再报错。这一版同时接受可选的 `sequence`,服务端读面带出后即可读取,无需再次升级 SDK。
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 使用。