@kanyun-ai-infra/agenthub 1.13.0 → 1.15.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,49 @@
2
2
 
3
3
  本文件从 1.12.0 开始记录;更早的版本见 git 历史中的 `chore(agenthub-sdk): release X.Y.Z` 提交。
4
4
 
5
+ ## 1.15.0
6
+
7
+ 浏览器实时语音入口新增播放对齐、页面工具执行与播放输出节点;其余为随共享契约内联的变化。
8
+
9
+ ### 新增(`@kanyun-ai-infra/agenthub/realtime`)
10
+
11
+ - `onTranscriptPlayed(transcript)`:assistant 转写片段在音频实际播放到该片段时逐段触发,载荷带墙钟 `playedAt`。依赖网关在转写帧上带 `audioOffsetMs`;没有偏移的旧网关不触发此回调,被用户打断清空的片段也不触发。
12
+ - `onPlaybackState(state)`:`speaking` / `listening`,按实际可闻的播放状态触发,与按模型生成进度的 `onState` 区分。队列播干、播放被打断或通话停止时回到 `listening`。
13
+ - `onTranscript` 的 assistant 帧新增可选 `audioOffsetMs`:该片段在本轮音频中的起始毫秒数,用于把字幕或画面切到正在播放的位置。
14
+ - `onToolCall(call)`:执行 `voice.realtime.tools` 声明的模型工具调用;`call` 含 `callId`、`name`、`args`,可选 `turnSeq`。返回值作为工具结果回传(JSON,至多 32 KiB)。未提供处理函数回 `tool_unavailable`,处理函数抛错回 `tool_failed`,结果超限回 `tool_result_too_large`;页面不答时网关 10 秒后自行以 `tool_timeout` 答复,不会把模型卡死。
15
+ - `outputNode(context)`:把播放输出接到页面自建的音频节点(如变调等后处理),由页面继续接到 `context.destination`。缺省直连输出。
16
+ - 新导出类型:`RealtimeTranscriptPlayed`、`RealtimePlaybackState`、`RealtimeToolCall`。
17
+
18
+ ### 随共享契约内联的变化
19
+
20
+ - agent 定义的 `voice.realtime` 新增可选 `tools`(1–16 个):每项含 `name`(匹配 `^[A-Za-z_][A-Za-z0-9_]{0,63}$`,重名拒绝)、`description`(1–1024 字符)与可选 `parameters`(JSON Schema 子集,嵌套至多 6 层、节点至多 128、序列化至多 8 KiB)。`publishVersion` / `applyManifest` 的本地校验接受该字段;不声明时行为不变。
21
+ - `voice.realtime` 新增可选 `greetOnConnect`:开启时网关在首通建立上游会话后替 agent 发一次初始文本回合,模型按 system prompt 主动开口;缺省或 `false` 关闭,只对首通生效,重连与接手不重发。
22
+
23
+ ### 会话
24
+
25
+ - `sessions.create` 带 `memorySubjects` 的请求现在可以领取预热池:声明的业务主体替换池模板铸出的主体;`idempotencyScope` / `idempotencyKey` 与 `memorySubjects` 不再使请求失去领取资格。本次只有注释与文档变化,领取行为由服务端决定;服务端未升级时请求仍照旧冷启。
26
+
27
+ ### 其他
28
+
29
+ - 语音开关的文档措辞改为 kill switch `voice_turns_disabled`(及同类 `*_DISABLED`),公开方法签名不变。
30
+ - 随包 `SKILL.md` 补充实时语音接入走查。
31
+
32
+ ### 迁移说明
33
+
34
+ - 使用 `onTranscriptPlayed`、`onPlaybackState`、`onToolCall` 或 `outputNode` 的接入方须升级到 `@kanyun-ai-infra/agenthub@1.15.0`。1.14.0 没有这些回调;旧 SDK 在声明工具的 agent 上会忽略 `tool_call`,模型要等网关 10 秒超时答复后才继续。
35
+
36
+ ## 1.14.0
37
+
38
+ ### 随共享契约内联的语音变化
39
+
40
+ - 回合式语音的 `voice.input.provider` 和 `voice.output.provider` 支持 `dashscope`、`modelgate-chat`,并保留 `modelgate`。`publishVersion` / `publishRevision` 的本地请求校验接受这些值。
41
+ - `voice.input` 和 `voice.output` 各自支持可选的 `modelService`,用于指定该方向使用的项目模型服务名;不填写时沿用主模型服务。显式指定的模型服务无法解析或与 provider 不匹配时拒绝使用,不回落到主模型服务。
42
+ - 随包语音错误码包含 `VOICE_INPUT_FORMAT_UNSUPPORTED`(HTTP 400):所选语音服务不支持输入音频格式。模型服务不满足语音要求时仍使用 `VOICE_MODEL_SERVICE_UNSUPPORTED`(HTTP 400),模型未获授权时使用 `VOICE_MODEL_NOT_AUTHORIZED`(HTTP 502)。这些是服务端错误,不是本地 schema 校验的错误码。
43
+
44
+ ### 迁移说明
45
+
46
+ - 使用 `dashscope`、`modelgate-chat` 或语音子段 `modelService` 的接入方须升级到 `@kanyun-ai-infra/agenthub@1.14.0`。1.13.0 的内联请求 schema 会在发送请求前拒绝这些配置并抛出 `ZodError`;仅升级服务端不能更新已安装 SDK 的 schema。
47
+
5
48
  ## 1.13.0
6
49
 
7
50
  - 浏览器实时语音支持可选 `onEnded({ callId, reason })`:识别通话终态帧 `call_ended` 或关闭码 `4000`,清理采集与播放并停止自动接手;时长上限映射为 `duration_limit`,未知原因映射为 `unknown`。回调每通至多一次,本地 `stop()` 不触发;终态到达后可以手动开始新通话。
package/README.md CHANGED
@@ -36,6 +36,8 @@ pnpm add @kanyun-ai-infra/agenthub
36
36
 
37
37
  需要 Node 18+,因为 SDK 使用全局 `fetch`。
38
38
 
39
+ 回合式语音使用 `dashscope`、`modelgate-chat` provider 或 `voice.input.modelService` / `voice.output.modelService` 时,需要 SDK ≥ 1.14.0。
40
+
39
41
  如果调用方是 Java、Python 或 Shell(curl),请优先看
40
42
  <https://agenthub.zhenguanyu.com/v1/docs/api-and-sdk#multi-language>(Python / Java)
41
43
  或 <https://agenthub.zhenguanyu.com/v1/docs/api-and-sdk#rest-flow>(curl)。
@@ -276,7 +278,7 @@ try {
276
278
 
277
279
  ## 实时语音(浏览器)
278
280
 
279
- **开发中,暂不支持线上用户接入。**
281
+ 通话前,项目要先在门户配好实时语音模型服务和站点来源,见下文「开通前提」。
280
282
 
281
283
  从 `@kanyun-ai-infra/agenthub/realtime` 导入 `createRealtimeVoiceClient`。浏览器只持有短期通话票;接入方后端调用 `client.sessions.createVoiceTicket(sessionId, options?)` 换票,`options` 可传 `signal: AbortSignal`,返回 `{ ticket, gatewayUrl, expiresAt }`。`getTicket()` 推荐返回整个响应对象,SDK 按票里的 `gatewayUrl` 建连;也可以只返回 `ticket` 字符串,此时连初始化时传入的 `gatewayUrl`。不能把平台 token 放进前端。主入口不加载浏览器代码。转写 `role` 为 `user` 或 `assistant`。
282
284
 
@@ -329,3 +331,50 @@ SDK 1.13.0 起提供此行为。旧版 1.11.0 / 1.12.0 / 1.12.1 忽略 `call_end
329
331
  `adaptiveConnect` 缺省为 `true`;设为 `false` 时只连初始化地址,首次建连被拒立即报 `CONNECTION_FAILED`,与 1.11.0 一致。
330
332
 
331
333
  仓库内运行本地浏览器冒烟:`pnpm --filter @kanyun-ai-infra/agenthub run smoke:realtime`。该入口先按依赖顺序构建 SDK 及 contracts,再用本地假网关和无头 Chrome 验证采集、播放、打断、回执与停止;运行前需安装仓库依赖及 Chrome,不需要通话凭据。
334
+
335
+ ### 开通前提
336
+
337
+ 项目要先在门户配好下面两项。SDK 和 API 都没有这两项的入口。
338
+
339
+ 1. **实时语音模型服务。** 项目里要有且只有一个启用中的 GCP 服务账号模型服务(鉴权方式 `gcp_service_account`)。在门户「凭证与集成」→「模型服务」→「新建模型服务」里把用途选为「GCP 实时语音」,粘贴服务账号 JSON,填区域(默认 `us-central1`)。Vertex AI 的费用记在服务账号 JSON 里 `project_id` 所指的 GCP 项目上。`agent.yaml` 不引用这一项:每通通话首次接通时,平台在项目里找到这一行并冻结到这通通话上。live Agent 声明的 `modelService` 是文本模型服务,照常必填,两者互不替代。
340
+ - 项目里已有一个启用中的 GCP 模型服务时,再建第二个会被拒。更换服务账号或私钥时编辑原来那一行,粘贴完整的新 JSON。
341
+ - 归档这一行后,项目不能再开新通话;已经接通的通话不受影响,断线接手照常。
342
+ - 取票时的预检只验证 Google 能为这个服务账号换发访问令牌,不验证它在该 GCP 项目里有没有调用 Vertex AI 的权限。缺权限时取票照常成功,到网关连 Vertex AI 时才失败;网关连上游失败发生在 `ready` 之前时,SDK 报 `GATEWAY_ERROR`。
343
+ 2. **站点来源。** 浏览器从接入方自己的站点发起通话时,在门户「配置」→「项目」页的「允许发起实时通话的站点来源」里加上这个站点的来源,即发起连接的页面的 `location.origin`:协议、主机名和可选端口,例如 `https://app.example.com`,不带路径和通配符。比较前只统一大小写、去掉默认端口和末尾斜杠,`https://www.example.com` 与 `https://example.com` 是两个来源。HTTP 只允许 `localhost`、`127.0.0.1` 和 `[::1]`,最多 20 个,只有项目 owner 或管理员能修改。从 AgentHub 门户(`https://agenthub.zhenguanyu.com`、`https://agenthub-test.zhenguanyu.com`)发起的通话不受这份名单限制。
344
+
345
+ ### 拒绝码与处理
346
+
347
+ 通话可能在两处被拒:接入方后端取票时,和浏览器握手时。
348
+
349
+ **取票被拒。** 发票接口的错误体是 `{ success: false, error: { code, message?, scope? } }`。SDK 只从响应顶层取码,所以 `createVoiceTicket` 抛出的 `PilotPlatformApiError` 上,`err.code` 只是按 HTTP 状态给的通用值(409 为 `REQUEST_FAILED`,503 为 `SERVER_ERROR`)。实际的码在 `err.body.error.code`,可读原因在 `err.body.error.message`,下表注明哪些码带 `message`。
350
+
351
+ ```ts
352
+ import { PilotPlatformApiError } from "@kanyun-ai-infra/agenthub";
353
+
354
+ try {
355
+ return await client.sessions.createVoiceTicket(sessionId);
356
+ } catch (err) {
357
+ if (err instanceof PilotPlatformApiError) {
358
+ const detail = (err.body?.error ?? {}) as { code?: string; message?: string; scope?: string };
359
+ // detail.code 例如 "REALTIME_UPSTREAM_NOT_CONFIGURED";detail.message 可以直接展示给用户
360
+ }
361
+ throw err;
362
+ }
363
+ ```
364
+
365
+ 浏览器里 `getTicket()` 抛错时,SDK 只报 `CONNECTION_FAILED`,不转交错误内容。要让用户看到原因,由接入方的取票接口把 `code` 和 `message` 返回给前端,在 `getTicket()` 里自行展示。
366
+
367
+ | 码 | HTTP | 带 `message` | 含义 | 处理 |
368
+ | --- | --- | --- | --- | --- |
369
+ | `REALTIME_UPSTREAM_NOT_CONFIGURED` | 409 | 是 | 项目没有启用中的 GCP 实时语音模型服务 | 按「开通前提」第 1 项添加 |
370
+ | `REALTIME_UPSTREAM_AMBIGUOUS` | 409 | 是 | 项目里有多个启用中的 GCP 实时语音模型服务 | 归档多余的,只留一个 |
371
+ | `REALTIME_UPSTREAM_CREDENTIAL_INVALID` | 409 | 是 | Google 拒绝为这个服务账号换发访问令牌,或私钥无法使用,例如服务账号被删除或停用、密钥被撤销、JSON 填错 | 在模型服务里重新粘贴服务账号 JSON;重试不会恢复 |
372
+ | `REALTIME_UPSTREAM_CREDENTIAL_UNAVAILABLE` | 503 | 是 | 暂时换不到访问令牌:Google 令牌端点不可达,或返回 5xx、429 | 稍后重试;响应不带 `Retry-After` |
373
+ | `REALTIME_CAPACITY_EXCEEDED` | 409 | 是 | `scope` 为 `"project"`:项目的在线通话数到上限,上限值写在 `message` 里;`scope` 为 `"global"`:平台的在线通话总数到上限 | 项目上限:结束本项目的其他通话后重试;平台上限:稍后重试 |
374
+ | `REALTIME_ADMISSION_UNAVAILABLE` | 503 | 否 | 平台的通话计数服务暂时不可用 | 稍后重试;响应不带 `Retry-After` |
375
+ | `REALTIME_UPSTREAM_TENANT_UNSUPPORTED` | 409 | 是 | 会话所在租户使用独立数据库,这种部署形态还不支持项目自带的实时语音凭据 | 项目侧无法处理,联系平台 |
376
+ | `REALTIME_DISABLED` | 409 | 否 | 平台紧急停用了实时语音 | 项目侧无法处理,等平台恢复 |
377
+
378
+ 会话不是 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`,项目侧无法处理。会话上已有在线通话时,这张票只用于接手那通通话,不做上表里模型服务、凭据、平台上限和租户这几项预检,也不受项目上限限制。
379
+
380
+ **握手被拒(站点来源)。** 站点来源不在取票时检查。请求没有 `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 的接入方,被拒后要重新取票再连,同一张票不能再用。