@kanyun-ai-infra/agenthub 1.14.0 → 1.16.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,51 @@
2
2
 
3
3
  本文件从 1.12.0 开始记录;更早的版本见 git 历史中的 `chore(agenthub-sdk): release X.Y.Z` 提交。
4
4
 
5
+ ## 1.16.0
6
+
7
+ Revision 读面改为宽容读者:服务端给 `workloads.get` / `getRevision` / `getVersion` 的响应新增字段时,已发布的客户端不再报错。这一版同时接受可选的 `sequence`,服务端读面带出后即可读取,无需再次升级 SDK。
8
+
9
+ ### 新增与变化
10
+
11
+ - `PlatformRevision` 新增可选字段 `sequence`(正整数):每个 Agent(workload)内从 1 递增的人类可读版本序号,号可以不连续。当前服务端读面尚未返回该字段;在它上线前该字段恒为 `undefined`。
12
+ - `workloads.get` / `getRevision` / `getVersion` 的响应解析改为宽容读者:schema 未声明的字段被忽略(不报错,也不出现在结果里)。此前 revision 响应是 strict,服务端多一个字段就会让所有已发布客户端抛 `MALFORMED_PLATFORM_RESPONSE`。
13
+ - 请求 schema 不变:`publishRevision` / `applyRevisionManifest` 仍拒绝未知字段,行为与 1.15.0 相同。
14
+
15
+ ### 迁移说明
16
+
17
+ - 无需迁移。升级是可选的,用于获得对服务端后续新增响应字段的兼容性;`sequence` 在服务端读面上线前读不到。
18
+
19
+ ## 1.15.0
20
+
21
+ 浏览器实时语音入口新增播放对齐、页面工具执行与播放输出节点;其余为随共享契约内联的变化。
22
+
23
+ ### 新增(`@kanyun-ai-infra/agenthub/realtime`)
24
+
25
+ - `onTranscriptPlayed(transcript)`:assistant 转写片段在音频实际播放到该片段时逐段触发,载荷带墙钟 `playedAt`。依赖网关在转写帧上带 `audioOffsetMs`;没有偏移的旧网关不触发此回调,被用户打断清空的片段也不触发。
26
+ - `onPlaybackState(state)`:`speaking` / `listening`,按实际可闻的播放状态触发,与按模型生成进度的 `onState` 区分。队列播干、播放被打断或通话停止时回到 `listening`。
27
+ - `onTranscript` 的 assistant 帧新增可选 `audioOffsetMs`:该片段在本轮音频中的起始毫秒数,用于把字幕或画面切到正在播放的位置。
28
+ - `onToolCall(call)`:执行 `voice.realtime.tools` 声明的模型工具调用;`call` 含 `callId`、`name`、`args`,可选 `turnSeq`。返回值作为工具结果回传(JSON,至多 32 KiB)。未提供处理函数回 `tool_unavailable`,处理函数抛错回 `tool_failed`,结果超限回 `tool_result_too_large`;页面不答时网关 10 秒后自行以 `tool_timeout` 答复,不会把模型卡死。
29
+ - `outputNode(context)`:把播放输出接到页面自建的音频节点(如变调等后处理),由页面继续接到 `context.destination`。缺省直连输出。
30
+ - 新导出类型:`RealtimeTranscriptPlayed`、`RealtimePlaybackState`、`RealtimeToolCall`。
31
+
32
+ ### 随共享契约内联的变化
33
+
34
+ - 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` 的本地校验接受该字段;不声明时行为不变。
35
+ - `voice.realtime` 新增可选 `greetOnConnect`:开启时网关在首通建立上游会话后替 agent 发一次初始文本回合,模型按 system prompt 主动开口;缺省或 `false` 关闭,只对首通生效,重连与接手不重发。
36
+
37
+ ### 会话
38
+
39
+ - `sessions.create` 带 `memorySubjects` 的请求现在可以领取预热池:声明的业务主体替换池模板铸出的主体;`idempotencyScope` / `idempotencyKey` 与 `memorySubjects` 不再使请求失去领取资格。本次只有注释与文档变化,领取行为由服务端决定;服务端未升级时请求仍照旧冷启。
40
+
41
+ ### 其他
42
+
43
+ - 语音开关的文档措辞改为 kill switch `voice_turns_disabled`(及同类 `*_DISABLED`),公开方法签名不变。
44
+ - 随包 `SKILL.md` 补充实时语音接入走查。
45
+
46
+ ### 迁移说明
47
+
48
+ - 使用 `onTranscriptPlayed`、`onPlaybackState`、`onToolCall` 或 `outputNode` 的接入方须升级到 `@kanyun-ai-infra/agenthub@1.15.0`。1.14.0 没有这些回调;旧 SDK 在声明工具的 agent 上会忽略 `tool_call`,模型要等网关 10 秒超时答复后才继续。
49
+
5
50
  ## 1.14.0
6
51
 
7
52
  ### 随共享契约内联的语音变化
package/README.md CHANGED
@@ -151,6 +151,11 @@ Agents 和 runtime templates 都是 **project-scoped**。Version 必须在同一
151
151
 
152
152
  > 本文一律用主词汇 **agent / version**;它们是底层 wire 名 `workload` / `revision`
153
153
  > 的别名,SDK 两套都接受,规范名与别名同传会抛 `ALIAS_CONFLICT`,细节见下方「完整参考」。
154
+ >
155
+ > 读面响应是**宽容读者**:`agents.get` / `getVersion` / `getRevision` 的响应出现 SDK
156
+ > 未声明的字段时会被忽略,不会报错;服务端新增字段不需要客户端升级。1.16.0 起
157
+ > `PlatformRevision` 接受可选的 `sequence`(每个 Agent 内从 1 递增的版本序号,号可
158
+ > 不连续);服务端读面返回它之前,该字段为 `undefined`。
154
159
 
155
160
  ## 记忆(agenthub.memories)
156
161
 
package/dist/index.cjs CHANGED
@@ -498,7 +498,8 @@ var EXECUTOR_FORMAT_REGISTRY = {
498
498
  // `declaredMcpTools` allowlist. approvals: none (`bypassPermissions`).
499
499
  capabilities: { resume: true, structuredOutput: false, toolFiltering: true, approvals: false },
500
500
  // Today's behaviour: Claude Code runs every model the platform offers.
501
- allowedModels: { kind: "any" }
501
+ allowedModels: { kind: "any" },
502
+ mcpMode: "native"
502
503
  },
503
504
  pi: {
504
505
  descriptor: { kind: "harness", adapterId: "pi", protocolVersion: "1" },
@@ -519,7 +520,10 @@ var EXECUTOR_FORMAT_REGISTRY = {
519
520
  },
520
521
  // Provisional until #3843 / #3670: no model family decided yet, so none
521
522
  // is offered (fails closed; pi is not publishable before then anyway).
522
- allowedModels: { kind: "idPrefixes", idPrefixes: [] }
523
+ allowedModels: { kind: "idPrefixes", idPrefixes: [] },
524
+ // Pi's MCP goes through managed MCP only (owner 2026-09-28): no rush
525
+ // direct references, no self-loaded MCP client extension.
526
+ mcpMode: "bridge"
523
527
  }
524
528
  };
525
529
  var ReservedExecutorValueSchema = import_zod5.z.never();
@@ -580,6 +584,10 @@ var RecordImageCapabilityDeclarationRequestSchema = import_zod5.z.object({
580
584
  declaration: ImageCapabilityDeclarationSchema,
581
585
  source: import_zod5.z.enum(IMAGE_CAPABILITY_DECLARATION_SOURCES)
582
586
  }).strict();
587
+ var FrozenSessionExecutorSchema = import_zod5.z.object({
588
+ format: import_zod5.z.string().min(1),
589
+ protocolVersion: import_zod5.z.string()
590
+ });
583
591
  var SESSION_ENVIRONMENT_OUTDATED = "SESSION_ENVIRONMENT_OUTDATED";
584
592
  var SessionEnvironmentOutdatedPayloadSchema = import_zod5.z.object({
585
593
  code: import_zod5.z.literal(SESSION_ENVIRONMENT_OUTDATED),
@@ -3576,6 +3584,9 @@ var CreateSessionRequestSchema = import_zod10.z.object({
3576
3584
  *
3577
3585
  * 解析只发生一次、在会话创建事务内,结果冻结进 `sessions.memory_subjects`;写侧
3578
3586
  * (抽取写回)与读侧(召回)之后都只读那一列(I7 写读同源)。
3587
+ *
3588
+ * 带它的请求**照样能领预热池**:领取收尾在同一条 UPDATE 里按这份声明重算冻结列(替换池模板
3589
+ * 铸的业务主体,`[]` = 只剩 agent 主体);绑不进去就退冷启,不会交出没有声明主体的会话。
3579
3590
  */
3580
3591
  memorySubjects: import_zod10.z.array(SessionMemorySubjectDeclarationSchema).max(MAX_SESSION_MEMORY_SUBJECTS).optional(),
3581
3592
  /**
@@ -3596,8 +3607,9 @@ var CreateSessionRequestSchema = import_zod10.z.object({
3596
3607
  * `PILOT_END_USER_ID`、记忆主体(见下)、create 响应 `data.session.user`。
3597
3608
  *
3598
3609
  * **带它不吃池**:请求携带本字段即退出预热领用资格
3599
- * (`canServeFromPrewarmPool`),与 `memorySubjects` 同一条规则——池里暖好的会话
3600
- * 在调用方出现之前就冻结了身份,领用时换不掉。已知访客换 agent 应冷启带既有 id。
3610
+ * (`canServeFromPrewarmPool`)——池里暖好的会话在调用方出现之前就冻结了身份,领用时
3611
+ * 换不掉。已知访客换 agent 应冷启带既有 id。(`memorySubjects` 不受这条限制:它在领取
3612
+ * 收尾时绑定,见该字段。)
3601
3613
  *
3602
3614
  * **不隐含记忆声明**:本字段本身不使这个 id 成为任何记忆主体;要让记忆落到它
3603
3615
  * 名下,冷启路径须同时在 `memorySubjects` 里声明它(池路径由
@@ -4154,13 +4166,32 @@ var WorkloadManifestErrorCodeSchema = import_zod4.z.enum([
4154
4166
  // the `statusForManifestError` default) and additive-widening note as the
4155
4167
  // four codes above.
4156
4168
  "EXECUTOR_NOT_APPLICABLE_TO_LIVE",
4169
+ // HX-T7 (spec `pilot-v2-executor-host-contract.md` §6): a bridge-mode
4170
+ // executor's Revision whose frozen snapshot carries a non-platform MCP
4171
+ // source (rush `enabledMcpServers`). The bridge never connects those
4172
+ // entries, so publishing would silently drop them; the remediation is
4173
+ // managed MCP. Thrown by `applyRevisionManifest` (400, the
4174
+ // `statusForManifestError` default) and by the stage-binding gate (422,
4175
+ // control-plane `WorkloadStageServiceErrorCode`). Additive widening = patch.
4176
+ "EXECUTOR_NON_PLATFORM_MCP_UNSUPPORTED",
4177
+ // EXP-1c (spec `pilot-v2-executor-identity.md` §3, #3668 D6a): the merged,
4178
+ // normalised executor format's registry entry declares a worker contract
4179
+ // kind the Revision does not match (today every entry declares
4180
+ // `managed_agent`, so this fires on a `self_managed` Revision that
4181
+ // inherits a non-default executor — the revision-level snapshot key is
4182
+ // schema-blocked for self_managed, so the reachable channel is workload
4183
+ // `spec.defaults.executor` inheritance). Thrown by `applyRevisionManifest`
4184
+ // (400, the `statusForManifestError` default), judged in
4185
+ // `assertExecutorPublishGate` after EXP-1. Additive widening = patch.
4186
+ "EXECUTOR_NOT_APPLICABLE_TO_SELF_MANAGED",
4157
4187
  // `format: live` publish admission (#4625 PR-1, spec
4158
4188
  // pilot-v2-managed-agent-profile.md §11 MAP-VL3), both thrown by
4159
4189
  // `applyRevisionManifest` and mapped to 400 by the default branch of
4160
4190
  // `statusForManifestError`. Additive widening = patch (see
4161
4191
  // `WORKLOAD_SLUG_TAKEN` above).
4162
4192
  // - `LIVE_AGENTS_DISABLED` — the Revision's snapshot is `format: live` while
4163
- // `live_agents_enabled` is off. Same criterion as before; it used to throw
4193
+ // the kill switch `live_agents_disabled` is on (before #5069: `live_agents_enabled`
4194
+ // off). Same criterion as before; it used to throw
4164
4195
  // the generic `MANIFEST_VALIDATION_ERROR`. Pipeline join reports the same
4165
4196
  // condition earlier as `FILE_AGENT_LIVE_DISABLED`.
4166
4197
  // - `LIVE_REVISION_UNSUPPORTED` — the merged, frozen live snapshot carries a
@@ -4515,7 +4546,79 @@ var RealtimeVoiceNameSchema = import_zod4.z.enum([
4515
4546
  "Sadaltager",
4516
4547
  "Sulafat"
4517
4548
  ]);
4518
- var WorkloadManifestRealtimeVoiceSchema = import_zod4.z.object({
4549
+ var RealtimeToolParameterSchema = import_zod4.z.lazy(
4550
+ () => import_zod4.z.object({
4551
+ type: import_zod4.z.enum(["string", "number", "integer", "boolean", "array", "object"]).optional(),
4552
+ description: import_zod4.z.string().max(1024).optional(),
4553
+ enum: import_zod4.z.array(import_zod4.z.string().max(256)).max(64).optional(),
4554
+ nullable: import_zod4.z.boolean().optional(),
4555
+ items: RealtimeToolParameterSchema.optional(),
4556
+ properties: import_zod4.z.record(import_zod4.z.string().max(64), RealtimeToolParameterSchema).optional(),
4557
+ required: import_zod4.z.array(import_zod4.z.string().max(64)).max(32).optional()
4558
+ }).strict()
4559
+ );
4560
+ function toolParameterProblem(parameters) {
4561
+ if (parameters === void 0) return void 0;
4562
+ let nodes = 0;
4563
+ let problem;
4564
+ const visit = (node, depth) => {
4565
+ var _a, _b;
4566
+ if (problem !== void 0) return;
4567
+ nodes += 1;
4568
+ if (depth > 6) {
4569
+ problem = "tool parameters nest deeper than 6 levels";
4570
+ return;
4571
+ }
4572
+ if (nodes > 128) {
4573
+ problem = "tool parameters contain more than 128 nodes";
4574
+ return;
4575
+ }
4576
+ if (node.type === "array" && node.items === void 0) {
4577
+ problem = "an array parameter must declare items";
4578
+ return;
4579
+ }
4580
+ if (node.required !== void 0) {
4581
+ const declared = new Set(Object.keys((_a = node.properties) != null ? _a : {}));
4582
+ const missing = node.required.find((name) => !declared.has(name));
4583
+ if (missing !== void 0) {
4584
+ problem = `required parameter ${missing} is not declared in properties`;
4585
+ return;
4586
+ }
4587
+ }
4588
+ for (const child of Object.values((_b = node.properties) != null ? _b : {})) visit(child, depth + 1);
4589
+ if (node.items !== void 0) visit(node.items, depth + 1);
4590
+ };
4591
+ visit(parameters, 1);
4592
+ if (problem !== void 0) return problem;
4593
+ if (new TextEncoder().encode(JSON.stringify(parameters)).length > 8192)
4594
+ return "tool parameters exceed 8192 bytes";
4595
+ return void 0;
4596
+ }
4597
+ var WORKLOAD_MANIFEST_REALTIME_TOOLS_MAX_LENGTH = 16;
4598
+ var WorkloadManifestRealtimeToolSchema = import_zod4.z.object({
4599
+ name: import_zod4.z.string().regex(
4600
+ /^[A-Za-z_][A-Za-z0-9_]{0,63}$/,
4601
+ "tool name must be an identifier of at most 64 chars"
4602
+ ),
4603
+ description: import_zod4.z.string().min(1).max(1024),
4604
+ parameters: RealtimeToolParameterSchema.optional()
4605
+ }).strict();
4606
+ var WorkloadManifestRealtimeToolListSchema = import_zod4.z.array(WorkloadManifestRealtimeToolSchema).min(1).max(WORKLOAD_MANIFEST_REALTIME_TOOLS_MAX_LENGTH).superRefine((tools, ctx) => {
4607
+ const names = /* @__PURE__ */ new Set();
4608
+ for (const [index, tool] of tools.entries()) {
4609
+ if (names.has(tool.name))
4610
+ ctx.addIssue({
4611
+ code: "custom",
4612
+ path: [index, "name"],
4613
+ message: "tool names must be unique"
4614
+ });
4615
+ names.add(tool.name);
4616
+ const problem = toolParameterProblem(tool.parameters);
4617
+ if (problem !== void 0)
4618
+ ctx.addIssue({ code: "custom", path: [index, "parameters"], message: problem });
4619
+ }
4620
+ });
4621
+ var RealtimeVoiceWireSchema = import_zod4.z.object({
4519
4622
  provider: import_zod4.z.literal("vertex-gemini-live"),
4520
4623
  model: import_zod4.z.enum(["gemini-3.8-live", "gemini-live-2.5-flash-native-audio"]),
4521
4624
  voice: RealtimeVoiceNameSchema,
@@ -4526,7 +4629,20 @@ var WorkloadManifestRealtimeVoiceSchema = import_zod4.z.object({
4526
4629
  return false;
4527
4630
  }
4528
4631
  }, "language must be a BCP-47 language tag").optional(),
4529
- turnDetection: import_zod4.z.object({ silenceMs: import_zod4.z.number().int().positive() }).strict().optional()
4632
+ turnDetection: import_zod4.z.object({ silenceMs: import_zod4.z.number().int().positive() }).strict().optional(),
4633
+ /**
4634
+ * 接通即开讲(#5221):开启时网关在通话首次建立上游会话后替 agent 发一次初始文本回合,
4635
+ * 模型按 system prompt 主动开口。缺省 / `false` 关闭,行为与声明前一致。只对首通生效:
4636
+ * 上游重连与带原 callId 的接手不重发。
4637
+ */
4638
+ greetOnConnect: import_zod4.z.boolean().optional()
4639
+ }).strict();
4640
+ var WorkloadManifestRealtimeVoiceSchema = RealtimeVoiceWireSchema.extend({
4641
+ /**
4642
+ * Declared client tools. Optional and absent by default: an agent without it behaves exactly
4643
+ * as before, and the claim to an older gateway carries no tools key at all.
4644
+ */
4645
+ tools: WorkloadManifestRealtimeToolListSchema.optional()
4530
4646
  }).strict();
4531
4647
  var WorkloadManifestVoiceSchema = import_zod4.z.object({
4532
4648
  realtime: WorkloadManifestRealtimeVoiceSchema.optional(),
@@ -8884,6 +9000,20 @@ var EnvironmentRepointRecordSchema = import_zod59.z.object({
8884
9000
  actor: import_zod59.z.string().nullable(),
8885
9001
  createdAt: import_zod59.z.coerce.date()
8886
9002
  }).strict();
9003
+ var EnvironmentExecutorWarningRevisionSchema = import_zod59.z.object({
9004
+ revisionId: import_zod59.z.string().uuid(),
9005
+ /** Which pointer of the slot carries the Revision. */
9006
+ arm: import_zod59.z.enum(["stable", "canary"]),
9007
+ format: import_zod59.z.string(),
9008
+ protocolVersion: import_zod59.z.string(),
9009
+ /** `absent` = judged as ENVCAP-3 (no readable declaration for the image). */
9010
+ declaration: import_zod59.z.enum(["absent", "present"]),
9011
+ /** Where the judged image came from; `kind` discriminates the remaining keys. */
9012
+ capabilitySource: import_zod59.z.object({ kind: import_zod59.z.string() }).passthrough()
9013
+ }).strict();
9014
+ var EnvironmentExecutorWarningSchema = import_zod59.z.object({
9015
+ revisions: import_zod59.z.array(EnvironmentExecutorWarningRevisionSchema).min(1)
9016
+ }).strict();
8887
9017
  var EnvironmentReferenceSchema = import_zod59.z.object({
8888
9018
  workloadId: import_zod59.z.string().uuid(),
8889
9019
  workloadSlug: import_zod59.z.string(),
@@ -9691,7 +9821,7 @@ var VOICE_ERROR_CODES = {
9691
9821
  REALTIME_CAPACITY_EXCEEDED: 409,
9692
9822
  /** The platform concurrency store (realtime Redis) is unavailable; retry. */
9693
9823
  REALTIME_ADMISSION_UNAVAILABLE: 503,
9694
- /** `voice_turns_enabled` 关态时任何语音回合派发被拒(紧急停止;VT-1)。 */
9824
+ /** 紧急关闭 `voice_turns_disabled` 打开时任何语音回合派发被拒(VT-1)。 */
9695
9825
  VOICE_TURNS_DISABLED: 400,
9696
9826
  /** 语音附件时长 >60 秒或大小 >2MB(VA-3,上限只在语音路径)。 */
9697
9827
  VOICE_INPUT_TOO_LONG: 400,
@@ -9918,12 +10048,13 @@ var PlatformRevisionSchema = import_zod66.z.object({
9918
10048
  workloadSlug: WorkloadManifestNameSchema.optional(),
9919
10049
  version: WorkloadManifestNameSchema,
9920
10050
  runtimeSnapshotHash: import_zod66.z.string().nullable().optional(),
9921
- createdAt: import_zod66.z.string().optional()
9922
- }).strict();
10051
+ createdAt: import_zod66.z.string().optional(),
10052
+ sequence: import_zod66.z.number().int().positive().optional()
10053
+ }).strip();
9923
10054
  var GetPlatformWorkloadResponseSchema = PlatformWorkloadSchema.extend({
9924
10055
  kind: WorkloadKindSchema,
9925
10056
  defaultRevision: PlatformRevisionSchema.nullable()
9926
- });
10057
+ }).strip();
9927
10058
  var GetPlatformRevisionResponseSchema = PlatformRevisionSchema;
9928
10059
  var GetPlatformSessionResponseSchema = import_zod66.z.object({
9929
10060
  id: import_zod66.z.string().uuid(),
@@ -11617,8 +11748,10 @@ var EvalTriggerSchema = import_zod82.z.enum([
11617
11748
  ]);
11618
11749
  var EvalSeveritySchema = import_zod82.z.enum(["gate", "soft"]);
11619
11750
  var EvalGraderTypeSchema = import_zod82.z.enum(["transcript", "file", "llm", "script", "reply"]);
11751
+ var EvalLlmToolsSchema = import_zod82.z.enum(["full", "calls", "none"]);
11620
11752
  var EvalPassPolicySchema = import_zod82.z.enum(["all", "any"]);
11621
11753
  var EvalSuitePurposeSchema = import_zod82.z.enum(["regression", "capability"]);
11754
+ var EvalSuiteGateDowngradeSchema = import_zod82.z.enum(["allow", "never"]);
11622
11755
  var EVAL_SKIP_REASON_MAX = 2048;
11623
11756
  var EvalSuiteSkipSchema = import_zod82.z.object({ reason: import_zod82.z.string().min(1).max(EVAL_SKIP_REASON_MAX) }).strict();
11624
11757
  var EvalSkippedSuiteSchema = import_zod82.z.object({ path: import_zod82.z.string().min(1), reason: import_zod82.z.string().min(1) }).strict();
@@ -11687,6 +11820,10 @@ var EvalFileShorthandExpectSchema = import_zod82.z.object({
11687
11820
  matches: import_zod82.z.string().optional()
11688
11821
  // 正则字符串
11689
11822
  }).strict();
11823
+ var EvalFileToolSourceSchema = import_zod82.z.object({
11824
+ name: import_zod82.z.string().min(1),
11825
+ part: import_zod82.z.enum(["input", "output", "both"]).optional()
11826
+ }).strict();
11690
11827
  var EvalFileGraderSchema = import_zod82.z.object({
11691
11828
  type: import_zod82.z.literal("file"),
11692
11829
  label: EvalGraderLabelSchema,
@@ -11696,7 +11833,12 @@ var EvalFileGraderSchema = import_zod82.z.object({
11696
11833
  files: import_zod82.z.array(EvalFileEntrySchema).min(1).optional(),
11697
11834
  // 简写形式
11698
11835
  path: EvalRelativePathSchema.optional(),
11699
- expect: EvalFileShorthandExpectSchema.optional()
11836
+ expect: EvalFileShorthandExpectSchema.optional(),
11837
+ /**
11838
+ * #5215:内容来源 = 工具产物而非工作区文件。与 `path` / `files` 恰用其一,
11839
+ * 且必须搭配 `expect`(suite 级 superRefine 校验)。
11840
+ */
11841
+ tool: EvalFileToolSourceSchema.optional()
11700
11842
  }).strict();
11701
11843
  var EvalLlmGraderSchema = import_zod82.z.object({
11702
11844
  type: import_zod82.z.literal("llm"),
@@ -11708,6 +11850,14 @@ var EvalLlmGraderSchema = import_zod82.z.object({
11708
11850
  }),
11709
11851
  dimensions: import_zod82.z.array(import_zod82.z.string().min(1)).optional(),
11710
11852
  threshold: import_zod82.z.number().min(0).max(1),
11853
+ /**
11854
+ * #5215:判官材料是否带工具调用。缺省 `none`(只判回复文本,与既有 suite 的
11855
+ * 判分材料逐字节一致)。optional 字段叠加为增量变更、不升 apiVersion;老
11856
+ * worker 会以 `Unrecognized key` strict 拒(发布次序 = control-worker 先行)。
11857
+ * 工具段改变判分输入,但声明值在 case 对象里、进 `case_definition_digest`,
11858
+ * 因此不升 `engineVersion`(换值即换基线桶,不靠 engineVersion 区分)。
11859
+ */
11860
+ tools: EvalLlmToolsSchema.optional(),
11711
11861
  // 上界对齐 eval_grader_results.grader_version(PG int4,B1 硬列):graderVersion
11712
11862
  // 是**作者可写**的单调整数,时间戳当版本号(Date.now() ≈ 1.7e12)一次就越界——
11713
11863
  // 越界在解析期 400 告知作者,而不是等写明细行时 22003 连累父行回滚、整个 run
@@ -11754,12 +11904,17 @@ var EvalScriptGraderSchema = import_zod82.z.object({
11754
11904
  expect: EvalScriptExpectSchema.optional()
11755
11905
  }).strict();
11756
11906
  var EvalReplyExpectSchema = import_zod82.z.object({
11907
+ /** 字面子串,不是正则。 */
11757
11908
  includes: import_zod82.z.string().min(1).optional(),
11909
+ /** 字面子串,不是正则;「不许出现某段话」的确定写法见 `notMatches`。 */
11758
11910
  excludes: import_zod82.z.string().min(1).optional(),
11759
11911
  equals: import_zod82.z.string().optional(),
11760
11912
  // 正则字符串;无效正则不在 schema 层拒——按 file grader 的 safeRegex 姿势在
11761
11913
  // 判分侧判"不匹配",交给断言侧报 failed(不 errored)。
11762
- matches: import_zod82.z.string().min(1).optional()
11914
+ matches: import_zod82.z.string().min(1).optional(),
11915
+ // 正则字符串,不允许匹配(`excludes` 的正则版)。无效正则**判 failed**而不是
11916
+ // passed:编译不出来时「没有匹配」是假的,绝不能因此绿灯(#5209)。
11917
+ notMatches: import_zod82.z.string().min(1).optional()
11763
11918
  }).strict();
11764
11919
  var EvalReplyGraderSchema = import_zod82.z.object({
11765
11920
  type: import_zod82.z.literal("reply"),
@@ -12003,12 +12158,28 @@ var EvalSuiteSchema = import_zod82.z.object({
12003
12158
  kind: import_zod82.z.literal(EVAL_SUITE_KIND),
12004
12159
  purpose: EvalSuitePurposeSchema.optional(),
12005
12160
  // U2,默认 regression
12161
+ gateDowngrade: EvalSuiteGateDowngradeSchema.optional(),
12162
+ // #5216,默认 allow
12006
12163
  skip: EvalSuiteSkipSchema.optional(),
12007
12164
  // §4 声明跳过(带必填理由)
12165
+ /**
12166
+ * #5215:pipeline 面按 **advisory** 跑——照常建 eval run 并后台执行,但不进
12167
+ * eval step 的等待、不进 conclusion 折叠,**不挡门禁**。结果仍挂在结论的
12168
+ * `evalRunIds` 下,流水线详情可见。缺省 false。与 `skip` 互斥(拒绝组合:
12169
+ * 一个说「不跑」一个说「跑但不判」,语义冲突)。
12170
+ */
12171
+ advisory: import_zod82.z.boolean().optional(),
12008
12172
  defaults: EvalSuiteDefaultsSchema.optional(),
12009
12173
  cases: import_zod82.z.array(EvalCaseSchema).min(1)
12010
12174
  }).strict().superRefine((suite, ctx) => {
12011
12175
  var _a, _b;
12176
+ if (suite.advisory === true && suite.skip != null) {
12177
+ ctx.addIssue({
12178
+ code: import_zod82.z.ZodIssueCode.custom,
12179
+ path: ["advisory"],
12180
+ message: "`advisory: true` and `skip` are mutually exclusive \u2014 skip means the suite does not run in the pipeline at all; advisory means it runs but is excluded from the gate"
12181
+ });
12182
+ }
12012
12183
  const seen = /* @__PURE__ */ new Set();
12013
12184
  suite.cases.forEach((c, ci) => {
12014
12185
  var _a2, _b2, _c, _d, _e;
@@ -12131,11 +12302,15 @@ var EvalSuiteSchema = import_zod82.z.object({
12131
12302
  if (g.type === "file") {
12132
12303
  const hasFiles = g.files != null;
12133
12304
  const hasShorthand = g.path != null && g.expect != null;
12134
- if (hasFiles === hasShorthand) {
12305
+ const hasTool = g.tool != null && g.expect != null;
12306
+ const sourceCount = [hasFiles, hasShorthand, hasTool].filter(Boolean).length;
12307
+ const strayPath = g.path != null && g.expect == null;
12308
+ const strayTool = g.tool != null && g.expect == null;
12309
+ if (sourceCount !== 1 || strayTool || strayPath && !hasFiles) {
12135
12310
  ctx.addIssue({
12136
12311
  code: import_zod82.z.ZodIssueCode.custom,
12137
12312
  path: ["cases", ci, "graders", gi],
12138
- message: "file grader must use either `files[]` or `{path, expect}`, not both/neither"
12313
+ message: "file grader must use exactly one of `files[]`, `{path, expect}` or `{tool, expect}` (tool = assert on a tool call's input/output instead of a workspace file); a bare `path` without `expect` is ignored only when `files[]` is present (legacy tolerance)"
12139
12314
  });
12140
12315
  }
12141
12316
  }
@@ -12173,12 +12348,12 @@ var EvalSuiteSchema = import_zod82.z.object({
12173
12348
  }
12174
12349
  const e = g.expect;
12175
12350
  const hasEquals = e.equals != null;
12176
- const hasOthers = e.includes != null || e.excludes != null || e.matches != null;
12351
+ const hasOthers = e.includes != null || e.excludes != null || e.matches != null || e.notMatches != null;
12177
12352
  if (hasEquals && hasOthers) {
12178
12353
  ctx.addIssue({
12179
12354
  code: import_zod82.z.ZodIssueCode.custom,
12180
12355
  path: ["cases", ci, "graders", gi, "expect"],
12181
- message: "reply grader `expect.equals` is mutually exclusive with includes/excludes/matches"
12356
+ message: "reply grader `expect.equals` is mutually exclusive with includes/excludes/matches/notMatches"
12182
12357
  });
12183
12358
  }
12184
12359
  if (!hasEquals && !hasOthers) {
@@ -12998,15 +13173,13 @@ var OnlineSessionPreconditionSchema = import_zod83.z.union([
12998
13173
  fileExistsPrecondition,
12999
13174
  toolCalledPrecondition
13000
13175
  ]);
13001
- var forbiddenThreshold = import_zod83.z.never({
13002
- error: "threshold is only valid on llm graders \u2014 boolean graders (transcript/reply/file/script) pass or fail, there is no score to threshold"
13003
- }).optional();
13176
+ var optionalBooleanThreshold = import_zod83.z.number().min(0).max(1).optional();
13004
13177
  var OnlineTranscriptGraderSchema = EvalTranscriptGraderSchema.pick({
13005
13178
  type: true,
13006
13179
  label: true
13007
13180
  }).extend({
13008
13181
  precondition: OnlineTurnPreconditionSchema.optional(),
13009
- threshold: forbiddenThreshold,
13182
+ threshold: optionalBooleanThreshold,
13010
13183
  expect: EvalTranscriptExpectSchema.pick({
13011
13184
  completed: true,
13012
13185
  forbiddenTools: true,
@@ -13018,10 +13191,10 @@ var OnlineTranscriptGraderSchema = EvalTranscriptGraderSchema.pick({
13018
13191
  }).strict();
13019
13192
  var OnlineReplyGraderSchema = EvalReplyGraderSchema.pick({ type: true, label: true }).extend({
13020
13193
  precondition: OnlineTurnPreconditionSchema.optional(),
13021
- threshold: forbiddenThreshold,
13194
+ threshold: optionalBooleanThreshold,
13022
13195
  expect: EvalReplyExpectSchema.pick({ excludes: true, matches: true })
13023
13196
  }).strict();
13024
- var OnlineLlmToolsSchema = import_zod83.z.enum(["full", "calls", "none"]);
13197
+ var OnlineLlmToolsSchema = EvalLlmToolsSchema;
13025
13198
  var onlineLlmFields = EvalLlmGraderSchema.pick({
13026
13199
  type: true,
13027
13200
  label: true,
@@ -13042,7 +13215,7 @@ var OnlineFileGraderSchema = import_zod83.z.object({
13042
13215
  type: import_zod83.z.literal("file"),
13043
13216
  label: EvalGraderLabelSchema,
13044
13217
  precondition: OnlineSessionPreconditionSchema,
13045
- threshold: forbiddenThreshold,
13218
+ threshold: optionalBooleanThreshold,
13046
13219
  path: EvalRelativePathSchema,
13047
13220
  expect: EvalReplyExpectSchema.pick({ excludes: true }).required()
13048
13221
  }).strict();
@@ -13053,7 +13226,7 @@ var OnlineScriptGraderSchema = EvalScriptGraderSchema.pick({
13053
13226
  timeout: true
13054
13227
  }).extend({
13055
13228
  precondition: OnlineSessionPreconditionSchema,
13056
- threshold: forbiddenThreshold,
13229
+ threshold: optionalBooleanThreshold,
13057
13230
  expect: EvalScriptExpectSchema.optional()
13058
13231
  }).strict();
13059
13232
  var OnlineTurnGraderSpecSchema = import_zod83.z.discriminatedUnion("type", [
@@ -13727,7 +13900,12 @@ var RealtimeClaimResponseSchema = import_zod87.z.object({
13727
13900
  credentialSource: import_zod87.z.enum(REALTIME_CREDENTIAL_SOURCES).optional(),
13728
13901
  config: import_zod87.z.object({
13729
13902
  systemPrompt: import_zod87.z.string(),
13730
- voice: WorkloadManifestRealtimeVoiceSchema,
13903
+ voice: RealtimeVoiceWireSchema,
13904
+ /**
13905
+ * Client-executed tools frozen with the call. Stripped by readers that do not know it, which
13906
+ * is the intended rolling-deploy story: an older gateway answers the call without tools.
13907
+ */
13908
+ tools: WorkloadManifestRealtimeToolListSchema.optional(),
13731
13909
  recording: import_zod87.z.boolean().default(false),
13732
13910
  upstream: RealtimeUpstreamCredentialSchema.optional()
13733
13911
  })
@@ -15464,13 +15642,13 @@ var SessionsResource = class {
15464
15642
  * The codes and their statuses are the server's voice error-code table.
15465
15643
  *
15466
15644
  * **`VOICE_TURNS_DISABLED` is NOT one of them on this path**, despite being
15467
- * in that table: it answers a NON-empty-text turn while `voice_turns_enabled`
15468
- * is off, and this method always sends `text: ""`. The switch being off is
15645
+ * in that table: it answers a NON-empty-text turn while the kill switch
15646
+ * `voice_turns_disabled` is on, and this method always sends `text: ""`. The switch being on is
15469
15647
  * refused one step earlier as the ordinary empty-text `400`
15470
15648
  * `VALIDATION_ERROR` (issue path `["text"]`).
15471
15649
  *
15472
15650
  * **Four different causes produce that one 400, and the body does not
15473
- * distinguish them**: the switch off; the agent not declaring `voice.input`;
15651
+ * distinguish them**: the switch on; the agent not declaring `voice.input`;
15474
15652
  * an extension outside the voice set; and an attachment that never looks like
15475
15653
  * audio at all — raw bytes passed without `contentType` upload as
15476
15654
  * `application/octet-stream` and fail the empty-text rule in