@sema-agent/settings-schema 1.8.0 → 1.10.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
@@ -6,6 +6,40 @@
6
6
  > `@sema-agent/registry-core`(更早还曾是 `@sema-ai/registry-core`)。本档 `0.19.0` 以下各节里的
7
7
  > 包名是**历史记录**,如实保留不追改。
8
8
 
9
+ ## 1.10.0 — 2026-09-09 — `models.models[].compat` 引擎兼容声明席(镜像 `OpenAICompletionsCompat`;新键=minor)
10
+
11
+ - 引擎 `OpenAICompletionsCompat`(五键 `thinkingFormat` / `reasoningEffortLevels` / `maxTokensField` /
12
+ `requiresReasoningContentOnAssistantMessages` / `supportsReasoningEffort`)早就能按模型发兼容声明,但 `ModelEntry`
13
+ 没有这个键 ⇒ 目录条目写了 `compat` 在 parse 期被 strip 剥掉,声明**根本到不了引擎**;`extraBody` 也穿不过(引擎的
14
+ OpenAI 保留键表拦下 `chat_template_kwargs` / `enable_thinking`)。1.10.0 起 `ModelEntry.compat?`
15
+ (`z.unknown().optional()`)。
16
+ - **三问**:①谁需要 —— vLLM / Qwen 类**缺省开思考**的网关(关思考只能靠 `chat_template_kwargs.enable_thinking=false`),
17
+ 以及任何要按模型钉 `max_tokens` 拼写 / reasoning 档位的部署;②谁受伤 —— 无:additive 可选键,缺席 = 键不铸 = 今日行为,
18
+ 存量配置零迁移;③补偿 —— 不加旋钮(不写就没有这个键)。
19
+ - **解析透明**,与 1.7.2 `source` / 1.9.0 `toolFaces` 同一条规矩:形与词表(`thinkingFormat` 有哪几个词)的校验归
20
+ 消费方摄入侧**单点**,本仓刻意不复述那张表 —— 本域整域 all-or-nothing,一次形/词拒会把整套模型目录抹掉(1.7.1 升级
21
+ 回归的形)。
22
+ - 唯一保留的解析拒 = 与兄弟字段 `extraBody` **同一只**自由形安全判据(secret 形键/值、访问器、循环、非 JSON、过深):
23
+ 1.10.0 把这条流水线从 `extraBody` 里抽成共用的 `vetFreeFormJson(v, field)`(getter-free 描述符走查 → 单次
24
+ `JSON.stringify` → 复扫的 clone 即持久值),两个字段同调一处,不留第二份判据。`extraBody` 的「必须是 plain JSON
25
+ 对象」形门是它自己的(它要被摊进请求体),行为逐字不变(消息文本与判定结果均未变)。深度上限仍是 `MAX_EXTRA_BODY_DEPTH`
26
+ (`./hash` 上的已发布导出,不改名 —— 改名是 BREAKING,收益纯属观感),其 JSDoc 已改写成「所有自由形字段共用的一只上限」。
27
+ - 红先:`test/model-compat-1.10.test.ts` 七格(①原样保留 ①b 五键全量 ②缺席不铸 ③坏形不整域默认 ④安全判据拒
28
+ ⑤`loadLocalConfig` 全链到 `effective.models.models[].compat` ⑤b `readEffectiveWire` wire 腿同样透传且不降级整域)。
29
+ - 消费方点名(能力显式表态制):**worker 服务**(投影/env/读面,随其 7.69.0)、**CLI**(探针写目录条目)、
30
+ **config-center 产品**(models 域透传)。
31
+ - 顺带:`package-lock.json` 的 `packages[""].version` 自 1.6.0 起停在 `1.5.0`(历次发版只改了顶层 `version`),
32
+ 本节一并对齐到 1.10.0。
33
+
34
+ ## 1.9.0 — 2026-09-08 — `mcp.servers[].toolFaces` 覆盖层声明面(core 7.8.0 design/388 片 1 F7;新键=minor)
35
+
36
+ - core 7.8.0 起 `McpServerSpec.toolFaces?: Record<remoteToolName, { family?, pathTarget?: {param, aliases?, access, skillScopeEligible?}, renderHints?, ruleFace? }>`
37
+ 是工具名册的 face 覆盖层。本仓只加**解析透明**的键(`z.unknown().optional()`),与 1.7.2 `source` 同一条规矩:形的校验属 core 摄入侧
38
+ (不合 schema 的工具**不挂**并通告一次 `config.tool_face_invalid`),本仓不复述那张表——这一域 all-or-nothing,任一字段解析拒都会让整套
39
+ mcp 服务器静默消失(S-144 类病)。缺席 = 不铸键。
40
+ - 红先:`test/mcp-tool-faces-1.9.test.ts`(1.8.0 上「带 toolFaces 的 server 键原样到消费方」红)。
41
+ - 消费方:sema-server(MCP 配置面三腿透传,随 7.66.0)、cli(settings 面)。
42
+
9
43
  ## 1.8.0 — 2026-09-07 — `ModelRole` 闭集对齐 core:加 `classifier` / `consolidate`(新词=minor)
10
44
 
11
45
  - cli [6554] 真案:core `ModelRole` 早有 `classifier`(auto 权限分类器,FALLBACK classifier→summarize→default、tier 缺省 flash)
package/dist/hash.d.ts CHANGED
@@ -20,9 +20,13 @@ export declare const MAX_HASH_DEPTH = 200;
20
20
  * getVersion/getEffective (hard-DoS poison pill) — the two caps were each hardened in isolation but never
21
21
  * reconciled (R18). The real max wrapper is ~4–6; 32 is a deliberate ≫ margin. */
22
22
  export declare const MAX_HASH_WRAPPER_RESERVE = 32;
23
- /** Depth cap for a standalone `extraBody` value, reconciled with the hash cap MINUS the wrapper reserve so that
24
- * ANYTHING the validator accepts is guaranteed hashable in every context it's later hashed in. 168 ≫ any real
25
- * extraBody (OpenAI-style request params nest <10 deep). */
23
+ /** Depth cap for a standalone FREE-FORM config value, reconciled with the hash cap MINUS the wrapper reserve so
24
+ * that ANYTHING the validator accepts is guaranteed hashable in every context it's later hashed in. 168 ≫ any
25
+ * real free-form value (OpenAI-style request params nest <10 deep).
26
+ * Scope: the ONE cap for EVERY free-form field vetted by `vetFreeFormJson` (`ModelEntry.extraBody` and, since
27
+ * 1.10.0, `ModelEntry.compat`) — they sit at the SAME wrapper depth (`{models:{models:[{…}]}}`), so one cap
28
+ * serves both. The `EXTRA_BODY` spelling is kept because this is a published export on the `./hash` subpath
29
+ * (renaming it would be a BREAKING change for a purely cosmetic gain). */
26
30
  export declare const MAX_EXTRA_BODY_DEPTH: number;
27
31
  /** Deterministic JSON with sorted object keys (so key order never affects the hash). */
28
32
  export declare function stableStringify(v: unknown, depth?: number): string;
package/dist/hash.js CHANGED
@@ -20,9 +20,13 @@ export const MAX_HASH_DEPTH = 200;
20
20
  * getVersion/getEffective (hard-DoS poison pill) — the two caps were each hardened in isolation but never
21
21
  * reconciled (R18). The real max wrapper is ~4–6; 32 is a deliberate ≫ margin. */
22
22
  export const MAX_HASH_WRAPPER_RESERVE = 32;
23
- /** Depth cap for a standalone `extraBody` value, reconciled with the hash cap MINUS the wrapper reserve so that
24
- * ANYTHING the validator accepts is guaranteed hashable in every context it's later hashed in. 168 ≫ any real
25
- * extraBody (OpenAI-style request params nest <10 deep). */
23
+ /** Depth cap for a standalone FREE-FORM config value, reconciled with the hash cap MINUS the wrapper reserve so
24
+ * that ANYTHING the validator accepts is guaranteed hashable in every context it's later hashed in. 168 ≫ any
25
+ * real free-form value (OpenAI-style request params nest <10 deep).
26
+ * Scope: the ONE cap for EVERY free-form field vetted by `vetFreeFormJson` (`ModelEntry.extraBody` and, since
27
+ * 1.10.0, `ModelEntry.compat`) — they sit at the SAME wrapper depth (`{models:{models:[{…}]}}`), so one cap
28
+ * serves both. The `EXTRA_BODY` spelling is kept because this is a published export on the `./hash` subpath
29
+ * (renaming it would be a BREAKING change for a purely cosmetic gain). */
26
30
  export const MAX_EXTRA_BODY_DEPTH = MAX_HASH_DEPTH - MAX_HASH_WRAPPER_RESERVE;
27
31
  /** Deterministic JSON with sorted object keys (so key order never affects the hash). */
28
32
  export function stableStringify(v, depth = 0) {
package/dist/types.d.ts CHANGED
@@ -184,6 +184,24 @@ export declare const ModelEntry: z.ZodEffects<z.ZodObject<{
184
184
  * through), e.g. {frequency_penalty:0.5, presence_penalty:0.3}. Non-secret model metadata — same class
185
185
  * as cost/tier. Shipped on /effective.models.models[].extraBody; the service merges it into the request. */
186
186
  extraBody: z.ZodEffects<z.ZodOptional<z.ZodUnknown>, Record<string, unknown> | undefined, unknown>;
187
+ /** Per-model wire-compat declaration for the OpenAI-completions lane — mirrors the engine's
188
+ * `OpenAICompletionsCompat` (five keys: `thinkingFormat` / `reasoningEffortLevels` / `maxTokensField` /
189
+ * `requiresReasoningContentOnAssistantMessages` / `supportsReasoningEffort`). Real need: a vLLM/Qwen-class
190
+ * gateway that thinks by DEFAULT can only be turned off with `chat_template_kwargs.enable_thinking=false`,
191
+ * which `extraBody` cannot carry (the engine reserves those request keys) — and without this key a catalog
192
+ * entry that declares it is stripped at parse, so the declaration never reaches the engine at all.
193
+ *
194
+ * **Parse-transparent** (`z.unknown()`), same rule as `McpServerSpec.source` (1.7.2) and `.toolFaces` (1.9.0):
195
+ * the SHAPE and the VOCABULARY (which `thinkingFormat` words exist) are the consuming side's single point —
196
+ * this package deliberately does NOT restate that table. This domain parses all-or-nothing, so a shape/word
197
+ * rejection HERE would blank the entire model catalog over one bad key (the 1.7.1 upgrade regression). ABSENT
198
+ * = key not minted = today's behaviour, zero migration.
199
+ *
200
+ * The ONE rejection that remains is the free-form SAFETY judgment shared with `extraBody`
201
+ * ({@link vetFreeFormJson}): a secret-shaped key/value, an accessor, a cycle, non-JSON, or too deep to hash
202
+ * later. That is not shape validation — it is the boundary every free-form value must clear to enter the
203
+ * persisted config and `stableHash` (R18). */
204
+ compat: z.ZodEffects<z.ZodOptional<z.ZodUnknown>, unknown, unknown>;
187
205
  /** 配额倍率(EXPERT-REDESIGN §4-1):该模型烧 token 双窗额度的倍率,默认 1。折算时点=weight-at-burn
188
206
  * (记账时固化到 UsageEntry.quotaWeightAtUse/weightedTokens):改倍率只影响此后记账,不回算窗口内历史。 */
189
207
  quotaWeight: z.ZodDefault<z.ZodNumber>;
@@ -219,6 +237,7 @@ export declare const ModelEntry: z.ZodEffects<z.ZodObject<{
219
237
  cacheWrite: number;
220
238
  } | undefined;
221
239
  extraBody?: Record<string, unknown> | undefined;
240
+ compat?: unknown;
222
241
  notes?: string | undefined;
223
242
  }, {
224
243
  id: string;
@@ -248,6 +267,7 @@ export declare const ModelEntry: z.ZodEffects<z.ZodObject<{
248
267
  cacheWrite?: number | undefined;
249
268
  } | undefined;
250
269
  extraBody?: unknown;
270
+ compat?: unknown;
251
271
  quotaWeight?: number | undefined;
252
272
  enabled?: boolean | undefined;
253
273
  notes?: string | undefined;
@@ -281,6 +301,7 @@ export declare const ModelEntry: z.ZodEffects<z.ZodObject<{
281
301
  cacheWrite: number;
282
302
  } | undefined;
283
303
  extraBody?: Record<string, unknown> | undefined;
304
+ compat?: unknown;
284
305
  notes?: string | undefined;
285
306
  }, {
286
307
  id: string;
@@ -310,6 +331,7 @@ export declare const ModelEntry: z.ZodEffects<z.ZodObject<{
310
331
  cacheWrite?: number | undefined;
311
332
  } | undefined;
312
333
  extraBody?: unknown;
334
+ compat?: unknown;
313
335
  quotaWeight?: number | undefined;
314
336
  enabled?: boolean | undefined;
315
337
  notes?: string | undefined;
@@ -504,6 +526,24 @@ export declare const ModelsConfig: z.ZodEffects<z.ZodObject<{
504
526
  * through), e.g. {frequency_penalty:0.5, presence_penalty:0.3}. Non-secret model metadata — same class
505
527
  * as cost/tier. Shipped on /effective.models.models[].extraBody; the service merges it into the request. */
506
528
  extraBody: z.ZodEffects<z.ZodOptional<z.ZodUnknown>, Record<string, unknown> | undefined, unknown>;
529
+ /** Per-model wire-compat declaration for the OpenAI-completions lane — mirrors the engine's
530
+ * `OpenAICompletionsCompat` (five keys: `thinkingFormat` / `reasoningEffortLevels` / `maxTokensField` /
531
+ * `requiresReasoningContentOnAssistantMessages` / `supportsReasoningEffort`). Real need: a vLLM/Qwen-class
532
+ * gateway that thinks by DEFAULT can only be turned off with `chat_template_kwargs.enable_thinking=false`,
533
+ * which `extraBody` cannot carry (the engine reserves those request keys) — and without this key a catalog
534
+ * entry that declares it is stripped at parse, so the declaration never reaches the engine at all.
535
+ *
536
+ * **Parse-transparent** (`z.unknown()`), same rule as `McpServerSpec.source` (1.7.2) and `.toolFaces` (1.9.0):
537
+ * the SHAPE and the VOCABULARY (which `thinkingFormat` words exist) are the consuming side's single point —
538
+ * this package deliberately does NOT restate that table. This domain parses all-or-nothing, so a shape/word
539
+ * rejection HERE would blank the entire model catalog over one bad key (the 1.7.1 upgrade regression). ABSENT
540
+ * = key not minted = today's behaviour, zero migration.
541
+ *
542
+ * The ONE rejection that remains is the free-form SAFETY judgment shared with `extraBody`
543
+ * ({@link vetFreeFormJson}): a secret-shaped key/value, an accessor, a cycle, non-JSON, or too deep to hash
544
+ * later. That is not shape validation — it is the boundary every free-form value must clear to enter the
545
+ * persisted config and `stableHash` (R18). */
546
+ compat: z.ZodEffects<z.ZodOptional<z.ZodUnknown>, unknown, unknown>;
507
547
  /** 配额倍率(EXPERT-REDESIGN §4-1):该模型烧 token 双窗额度的倍率,默认 1。折算时点=weight-at-burn
508
548
  * (记账时固化到 UsageEntry.quotaWeightAtUse/weightedTokens):改倍率只影响此后记账,不回算窗口内历史。 */
509
549
  quotaWeight: z.ZodDefault<z.ZodNumber>;
@@ -539,6 +579,7 @@ export declare const ModelsConfig: z.ZodEffects<z.ZodObject<{
539
579
  cacheWrite: number;
540
580
  } | undefined;
541
581
  extraBody?: Record<string, unknown> | undefined;
582
+ compat?: unknown;
542
583
  notes?: string | undefined;
543
584
  }, {
544
585
  id: string;
@@ -568,6 +609,7 @@ export declare const ModelsConfig: z.ZodEffects<z.ZodObject<{
568
609
  cacheWrite?: number | undefined;
569
610
  } | undefined;
570
611
  extraBody?: unknown;
612
+ compat?: unknown;
571
613
  quotaWeight?: number | undefined;
572
614
  enabled?: boolean | undefined;
573
615
  notes?: string | undefined;
@@ -601,6 +643,7 @@ export declare const ModelsConfig: z.ZodEffects<z.ZodObject<{
601
643
  cacheWrite: number;
602
644
  } | undefined;
603
645
  extraBody?: Record<string, unknown> | undefined;
646
+ compat?: unknown;
604
647
  notes?: string | undefined;
605
648
  }, {
606
649
  id: string;
@@ -630,6 +673,7 @@ export declare const ModelsConfig: z.ZodEffects<z.ZodObject<{
630
673
  cacheWrite?: number | undefined;
631
674
  } | undefined;
632
675
  extraBody?: unknown;
676
+ compat?: unknown;
633
677
  quotaWeight?: number | undefined;
634
678
  enabled?: boolean | undefined;
635
679
  notes?: string | undefined;
@@ -753,6 +797,7 @@ export declare const ModelsConfig: z.ZodEffects<z.ZodObject<{
753
797
  cacheWrite: number;
754
798
  } | undefined;
755
799
  extraBody?: Record<string, unknown> | undefined;
800
+ compat?: unknown;
756
801
  notes?: string | undefined;
757
802
  }[];
758
803
  roles: Partial<Record<"default" | "summarize" | "subagent" | "team" | "synthesize" | "verifier" | "classifier" | "consolidate", {
@@ -808,6 +853,7 @@ export declare const ModelsConfig: z.ZodEffects<z.ZodObject<{
808
853
  cacheWrite?: number | undefined;
809
854
  } | undefined;
810
855
  extraBody?: unknown;
856
+ compat?: unknown;
811
857
  quotaWeight?: number | undefined;
812
858
  enabled?: boolean | undefined;
813
859
  notes?: string | undefined;
@@ -865,6 +911,7 @@ export declare const ModelsConfig: z.ZodEffects<z.ZodObject<{
865
911
  cacheWrite: number;
866
912
  } | undefined;
867
913
  extraBody?: Record<string, unknown> | undefined;
914
+ compat?: unknown;
868
915
  notes?: string | undefined;
869
916
  }[];
870
917
  roles: Partial<Record<"default" | "summarize" | "subagent" | "team" | "synthesize" | "verifier" | "classifier" | "consolidate", {
@@ -920,6 +967,7 @@ export declare const ModelsConfig: z.ZodEffects<z.ZodObject<{
920
967
  cacheWrite?: number | undefined;
921
968
  } | undefined;
922
969
  extraBody?: unknown;
970
+ compat?: unknown;
923
971
  quotaWeight?: number | undefined;
924
972
  enabled?: boolean | undefined;
925
973
  notes?: string | undefined;
@@ -7334,6 +7382,12 @@ export declare const McpServerSpec: z.ZodObject<{
7334
7382
  * both legs stripped it (default-strip object); this is the declaration half of the consumer-side fix
7335
7383
  * (ticket B-036 / S-143), the projection half lives in the consumer. */
7336
7384
  source: z.ZodOptional<z.ZodUnknown>;
7385
+ /** 1.9.0 —— core 7.8.0(design/388 片 1 F7)`toolFaces` 覆盖层:按远端工具名 key 的 face 声明
7386
+ * (`{ family?, pathTarget?: {param, aliases?, access, skillScopeEligible?}, renderHints?, ruleFace? }`)。
7387
+ * 与 `source` 同一条规矩:**解析透明**(`z.unknown()`),形的校验属 core 摄入侧(不合 schema 的工具**不挂**并
7388
+ * 通告一次 `config.tool_face_invalid`);本仓不复述那张表 —— 这一域 all-or-nothing,任一字段解析拒都会
7389
+ * 让整套 mcp 服务器静默消失。缺席 = 不铸键。 */
7390
+ toolFaces: z.ZodOptional<z.ZodUnknown>;
7337
7391
  notes: z.ZodOptional<z.ZodString>;
7338
7392
  }, "strip", z.ZodTypeAny, {
7339
7393
  name: string;
@@ -7356,6 +7410,7 @@ export declare const McpServerSpec: z.ZodObject<{
7356
7410
  allowTools?: string[] | undefined;
7357
7411
  contentOrigin?: "local" | "execution" | "external" | undefined;
7358
7412
  source?: unknown;
7413
+ toolFaces?: unknown;
7359
7414
  }, {
7360
7415
  name: string;
7361
7416
  transport: {
@@ -7377,6 +7432,7 @@ export declare const McpServerSpec: z.ZodObject<{
7377
7432
  elicitation?: boolean | undefined;
7378
7433
  contentOrigin?: "local" | "execution" | "external" | undefined;
7379
7434
  source?: unknown;
7435
+ toolFaces?: unknown;
7380
7436
  }>;
7381
7437
  export type McpServerSpec = z.infer<typeof McpServerSpec>;
7382
7438
  export declare const McpConfig: z.ZodEffects<z.ZodObject<{
@@ -7465,6 +7521,12 @@ export declare const McpConfig: z.ZodEffects<z.ZodObject<{
7465
7521
  * both legs stripped it (default-strip object); this is the declaration half of the consumer-side fix
7466
7522
  * (ticket B-036 / S-143), the projection half lives in the consumer. */
7467
7523
  source: z.ZodOptional<z.ZodUnknown>;
7524
+ /** 1.9.0 —— core 7.8.0(design/388 片 1 F7)`toolFaces` 覆盖层:按远端工具名 key 的 face 声明
7525
+ * (`{ family?, pathTarget?: {param, aliases?, access, skillScopeEligible?}, renderHints?, ruleFace? }`)。
7526
+ * 与 `source` 同一条规矩:**解析透明**(`z.unknown()`),形的校验属 core 摄入侧(不合 schema 的工具**不挂**并
7527
+ * 通告一次 `config.tool_face_invalid`);本仓不复述那张表 —— 这一域 all-or-nothing,任一字段解析拒都会
7528
+ * 让整套 mcp 服务器静默消失。缺席 = 不铸键。 */
7529
+ toolFaces: z.ZodOptional<z.ZodUnknown>;
7468
7530
  notes: z.ZodOptional<z.ZodString>;
7469
7531
  }, "strip", z.ZodTypeAny, {
7470
7532
  name: string;
@@ -7487,6 +7549,7 @@ export declare const McpConfig: z.ZodEffects<z.ZodObject<{
7487
7549
  allowTools?: string[] | undefined;
7488
7550
  contentOrigin?: "local" | "execution" | "external" | undefined;
7489
7551
  source?: unknown;
7552
+ toolFaces?: unknown;
7490
7553
  }, {
7491
7554
  name: string;
7492
7555
  transport: {
@@ -7508,6 +7571,7 @@ export declare const McpConfig: z.ZodEffects<z.ZodObject<{
7508
7571
  elicitation?: boolean | undefined;
7509
7572
  contentOrigin?: "local" | "execution" | "external" | undefined;
7510
7573
  source?: unknown;
7574
+ toolFaces?: unknown;
7511
7575
  }>, "many">>;
7512
7576
  }, "strip", z.ZodTypeAny, {
7513
7577
  servers: {
@@ -7531,6 +7595,7 @@ export declare const McpConfig: z.ZodEffects<z.ZodObject<{
7531
7595
  allowTools?: string[] | undefined;
7532
7596
  contentOrigin?: "local" | "execution" | "external" | undefined;
7533
7597
  source?: unknown;
7598
+ toolFaces?: unknown;
7534
7599
  }[];
7535
7600
  }, {
7536
7601
  servers?: {
@@ -7554,6 +7619,7 @@ export declare const McpConfig: z.ZodEffects<z.ZodObject<{
7554
7619
  elicitation?: boolean | undefined;
7555
7620
  contentOrigin?: "local" | "execution" | "external" | undefined;
7556
7621
  source?: unknown;
7622
+ toolFaces?: unknown;
7557
7623
  }[] | undefined;
7558
7624
  }>, {
7559
7625
  servers: {
@@ -7577,6 +7643,7 @@ export declare const McpConfig: z.ZodEffects<z.ZodObject<{
7577
7643
  allowTools?: string[] | undefined;
7578
7644
  contentOrigin?: "local" | "execution" | "external" | undefined;
7579
7645
  source?: unknown;
7646
+ toolFaces?: unknown;
7580
7647
  }[];
7581
7648
  }, {
7582
7649
  servers?: {
@@ -7600,6 +7667,7 @@ export declare const McpConfig: z.ZodEffects<z.ZodObject<{
7600
7667
  elicitation?: boolean | undefined;
7601
7668
  contentOrigin?: "local" | "execution" | "external" | undefined;
7602
7669
  source?: unknown;
7670
+ toolFaces?: unknown;
7603
7671
  }[] | undefined;
7604
7672
  }>;
7605
7673
  export type McpConfig = z.infer<typeof McpConfig>;
@@ -9379,6 +9447,24 @@ export declare const DOMAIN_SCHEMAS: {
9379
9447
  * through), e.g. {frequency_penalty:0.5, presence_penalty:0.3}. Non-secret model metadata — same class
9380
9448
  * as cost/tier. Shipped on /effective.models.models[].extraBody; the service merges it into the request. */
9381
9449
  extraBody: z.ZodEffects<z.ZodOptional<z.ZodUnknown>, Record<string, unknown> | undefined, unknown>;
9450
+ /** Per-model wire-compat declaration for the OpenAI-completions lane — mirrors the engine's
9451
+ * `OpenAICompletionsCompat` (five keys: `thinkingFormat` / `reasoningEffortLevels` / `maxTokensField` /
9452
+ * `requiresReasoningContentOnAssistantMessages` / `supportsReasoningEffort`). Real need: a vLLM/Qwen-class
9453
+ * gateway that thinks by DEFAULT can only be turned off with `chat_template_kwargs.enable_thinking=false`,
9454
+ * which `extraBody` cannot carry (the engine reserves those request keys) — and without this key a catalog
9455
+ * entry that declares it is stripped at parse, so the declaration never reaches the engine at all.
9456
+ *
9457
+ * **Parse-transparent** (`z.unknown()`), same rule as `McpServerSpec.source` (1.7.2) and `.toolFaces` (1.9.0):
9458
+ * the SHAPE and the VOCABULARY (which `thinkingFormat` words exist) are the consuming side's single point —
9459
+ * this package deliberately does NOT restate that table. This domain parses all-or-nothing, so a shape/word
9460
+ * rejection HERE would blank the entire model catalog over one bad key (the 1.7.1 upgrade regression). ABSENT
9461
+ * = key not minted = today's behaviour, zero migration.
9462
+ *
9463
+ * The ONE rejection that remains is the free-form SAFETY judgment shared with `extraBody`
9464
+ * ({@link vetFreeFormJson}): a secret-shaped key/value, an accessor, a cycle, non-JSON, or too deep to hash
9465
+ * later. That is not shape validation — it is the boundary every free-form value must clear to enter the
9466
+ * persisted config and `stableHash` (R18). */
9467
+ compat: z.ZodEffects<z.ZodOptional<z.ZodUnknown>, unknown, unknown>;
9382
9468
  /** 配额倍率(EXPERT-REDESIGN §4-1):该模型烧 token 双窗额度的倍率,默认 1。折算时点=weight-at-burn
9383
9469
  * (记账时固化到 UsageEntry.quotaWeightAtUse/weightedTokens):改倍率只影响此后记账,不回算窗口内历史。 */
9384
9470
  quotaWeight: z.ZodDefault<z.ZodNumber>;
@@ -9414,6 +9500,7 @@ export declare const DOMAIN_SCHEMAS: {
9414
9500
  cacheWrite: number;
9415
9501
  } | undefined;
9416
9502
  extraBody?: Record<string, unknown> | undefined;
9503
+ compat?: unknown;
9417
9504
  notes?: string | undefined;
9418
9505
  }, {
9419
9506
  id: string;
@@ -9443,6 +9530,7 @@ export declare const DOMAIN_SCHEMAS: {
9443
9530
  cacheWrite?: number | undefined;
9444
9531
  } | undefined;
9445
9532
  extraBody?: unknown;
9533
+ compat?: unknown;
9446
9534
  quotaWeight?: number | undefined;
9447
9535
  enabled?: boolean | undefined;
9448
9536
  notes?: string | undefined;
@@ -9476,6 +9564,7 @@ export declare const DOMAIN_SCHEMAS: {
9476
9564
  cacheWrite: number;
9477
9565
  } | undefined;
9478
9566
  extraBody?: Record<string, unknown> | undefined;
9567
+ compat?: unknown;
9479
9568
  notes?: string | undefined;
9480
9569
  }, {
9481
9570
  id: string;
@@ -9505,6 +9594,7 @@ export declare const DOMAIN_SCHEMAS: {
9505
9594
  cacheWrite?: number | undefined;
9506
9595
  } | undefined;
9507
9596
  extraBody?: unknown;
9597
+ compat?: unknown;
9508
9598
  quotaWeight?: number | undefined;
9509
9599
  enabled?: boolean | undefined;
9510
9600
  notes?: string | undefined;
@@ -9628,6 +9718,7 @@ export declare const DOMAIN_SCHEMAS: {
9628
9718
  cacheWrite: number;
9629
9719
  } | undefined;
9630
9720
  extraBody?: Record<string, unknown> | undefined;
9721
+ compat?: unknown;
9631
9722
  notes?: string | undefined;
9632
9723
  }[];
9633
9724
  roles: Partial<Record<"default" | "summarize" | "subagent" | "team" | "synthesize" | "verifier" | "classifier" | "consolidate", {
@@ -9683,6 +9774,7 @@ export declare const DOMAIN_SCHEMAS: {
9683
9774
  cacheWrite?: number | undefined;
9684
9775
  } | undefined;
9685
9776
  extraBody?: unknown;
9777
+ compat?: unknown;
9686
9778
  quotaWeight?: number | undefined;
9687
9779
  enabled?: boolean | undefined;
9688
9780
  notes?: string | undefined;
@@ -9740,6 +9832,7 @@ export declare const DOMAIN_SCHEMAS: {
9740
9832
  cacheWrite: number;
9741
9833
  } | undefined;
9742
9834
  extraBody?: Record<string, unknown> | undefined;
9835
+ compat?: unknown;
9743
9836
  notes?: string | undefined;
9744
9837
  }[];
9745
9838
  roles: Partial<Record<"default" | "summarize" | "subagent" | "team" | "synthesize" | "verifier" | "classifier" | "consolidate", {
@@ -9795,6 +9888,7 @@ export declare const DOMAIN_SCHEMAS: {
9795
9888
  cacheWrite?: number | undefined;
9796
9889
  } | undefined;
9797
9890
  extraBody?: unknown;
9891
+ compat?: unknown;
9798
9892
  quotaWeight?: number | undefined;
9799
9893
  enabled?: boolean | undefined;
9800
9894
  notes?: string | undefined;
@@ -10138,6 +10232,12 @@ export declare const DOMAIN_SCHEMAS: {
10138
10232
  * both legs stripped it (default-strip object); this is the declaration half of the consumer-side fix
10139
10233
  * (ticket B-036 / S-143), the projection half lives in the consumer. */
10140
10234
  source: z.ZodOptional<z.ZodUnknown>;
10235
+ /** 1.9.0 —— core 7.8.0(design/388 片 1 F7)`toolFaces` 覆盖层:按远端工具名 key 的 face 声明
10236
+ * (`{ family?, pathTarget?: {param, aliases?, access, skillScopeEligible?}, renderHints?, ruleFace? }`)。
10237
+ * 与 `source` 同一条规矩:**解析透明**(`z.unknown()`),形的校验属 core 摄入侧(不合 schema 的工具**不挂**并
10238
+ * 通告一次 `config.tool_face_invalid`);本仓不复述那张表 —— 这一域 all-or-nothing,任一字段解析拒都会
10239
+ * 让整套 mcp 服务器静默消失。缺席 = 不铸键。 */
10240
+ toolFaces: z.ZodOptional<z.ZodUnknown>;
10141
10241
  notes: z.ZodOptional<z.ZodString>;
10142
10242
  }, "strip", z.ZodTypeAny, {
10143
10243
  name: string;
@@ -10160,6 +10260,7 @@ export declare const DOMAIN_SCHEMAS: {
10160
10260
  allowTools?: string[] | undefined;
10161
10261
  contentOrigin?: "local" | "execution" | "external" | undefined;
10162
10262
  source?: unknown;
10263
+ toolFaces?: unknown;
10163
10264
  }, {
10164
10265
  name: string;
10165
10266
  transport: {
@@ -10181,6 +10282,7 @@ export declare const DOMAIN_SCHEMAS: {
10181
10282
  elicitation?: boolean | undefined;
10182
10283
  contentOrigin?: "local" | "execution" | "external" | undefined;
10183
10284
  source?: unknown;
10285
+ toolFaces?: unknown;
10184
10286
  }>, "many">>;
10185
10287
  }, "strip", z.ZodTypeAny, {
10186
10288
  servers: {
@@ -10204,6 +10306,7 @@ export declare const DOMAIN_SCHEMAS: {
10204
10306
  allowTools?: string[] | undefined;
10205
10307
  contentOrigin?: "local" | "execution" | "external" | undefined;
10206
10308
  source?: unknown;
10309
+ toolFaces?: unknown;
10207
10310
  }[];
10208
10311
  }, {
10209
10312
  servers?: {
@@ -10227,6 +10330,7 @@ export declare const DOMAIN_SCHEMAS: {
10227
10330
  elicitation?: boolean | undefined;
10228
10331
  contentOrigin?: "local" | "execution" | "external" | undefined;
10229
10332
  source?: unknown;
10333
+ toolFaces?: unknown;
10230
10334
  }[] | undefined;
10231
10335
  }>, {
10232
10336
  servers: {
@@ -10250,6 +10354,7 @@ export declare const DOMAIN_SCHEMAS: {
10250
10354
  allowTools?: string[] | undefined;
10251
10355
  contentOrigin?: "local" | "execution" | "external" | undefined;
10252
10356
  source?: unknown;
10357
+ toolFaces?: unknown;
10253
10358
  }[];
10254
10359
  }, {
10255
10360
  servers?: {
@@ -10273,6 +10378,7 @@ export declare const DOMAIN_SCHEMAS: {
10273
10378
  elicitation?: boolean | undefined;
10274
10379
  contentOrigin?: "local" | "execution" | "external" | undefined;
10275
10380
  source?: unknown;
10381
+ toolFaces?: unknown;
10276
10382
  }[] | undefined;
10277
10383
  }>;
10278
10384
  readonly plugins: z.ZodEffects<z.ZodObject<{
package/dist/types.js CHANGED
@@ -209,6 +209,40 @@ export function extraBodyUnsafe(v) {
209
209
  }
210
210
  return false;
211
211
  }
212
+ /** THE vetting pipeline for every FREE-FORM JSON value that lands in the persisted config — `ModelEntry.extraBody`
213
+ * and `ModelEntry.compat` share this ONE function (1.10.0; before that it was inline in `extraBody` and a second
214
+ * free-form field would have copied it). The judgment is identical for every such field, so it lives in one place:
215
+ *
216
+ * 1. getter-free descriptor walk ({@link extraBodyUnsafe}) over the RAW value — never invokes a getter, and any
217
+ * reflective throw (a hostile Proxy trap could throw a secret-bearing message) collapses into a FIXED message;
218
+ * 2. a SINGLE `JSON.stringify` — its clone is re-scanned, so a Proxy that fakes its descriptors still can't ship
219
+ * a secret, and that same clone IS what we return to persist, so a non-idempotent `toJSON` cannot validate one
220
+ * serialization and persist another (the pre-R10 superRefine+transform stringified TWICE).
221
+ *
222
+ * `field` only NAMES the field in the message — it never changes the judgment. What this does NOT do is validate
223
+ * SHAPE: a field's shape/vocabulary is the consumer's single point (1.7.2 / 1.9.0 rule), and this domain parses
224
+ * all-or-nothing, so a shape rejection here would blank the whole catalog. */
225
+ function vetFreeFormJson(v, field) {
226
+ let rawUnsafe;
227
+ try {
228
+ rawUnsafe = extraBodyUnsafe(v);
229
+ }
230
+ catch {
231
+ return { ok: false, message: `${field} could not be validated (reflective access threw)` }; // R11
232
+ }
233
+ if (rawUnsafe)
234
+ return { ok: false, message: `${field}: a key/value is a secret shape, non-JSON (Infinity/function/Map), an accessor, cyclic, or too deep` };
235
+ let clone;
236
+ try {
237
+ clone = JSON.parse(JSON.stringify(v)); // the ONLY serialization — its result is what we scan AND persist
238
+ }
239
+ catch {
240
+ return { ok: false, message: `${field} is not JSON-serializable` };
241
+ }
242
+ if (extraBodyUnsafe(clone))
243
+ return { ok: false, message: `${field}: its serialized form carries a secret` };
244
+ return { ok: true, value: clone };
245
+ }
212
246
  /** A k8s resource quantity ("250m", "1", "0.5", "512Mi", "1Gi", "128M") — catches garbage at config-submit
213
247
  * rather than surfacing as a reconcile ApplyError at the apiserver (consistent with ENV_NAME/DURATION guards). */
214
248
  export const K8S_QUANTITY = z.string().regex(/^\d+(\.\d+)?(m|[kKMGTPE]i?)?$/, 'must be a k8s quantity like "250m", "512Mi", "1Gi"');
@@ -313,10 +347,9 @@ export const ModelEntry = z.object({
313
347
  extraBody: z
314
348
  .unknown()
315
349
  .optional()
316
- // ONE transform that validates AND canonicalizes with a SINGLE JSON.stringify — the scanned clone IS the
317
- // persisted value, so a non-idempotent getter/Proxy can't validate one serialization and persist another
318
- // (the prior superRefine+transform stringified TWICE — review R10). getter-free descriptor walk bounds size
319
- // BEFORE the stringify; the clone is re-scanned so a Proxy that fakes its descriptors still can't ship a secret.
350
+ // The SHAPE gate (plain JSON object) is extraBody's OWN — it is spread into a request body, so a non-object
351
+ // is meaningless here; the SAFETY judgment is the shared vetFreeFormJson pipeline (see its doc: getter-free
352
+ // walk → single stringify → re-scanned clone IS the persisted value).
320
353
  .transform((v, ctx) => {
321
354
  if (v === undefined)
322
355
  return undefined;
@@ -325,33 +358,42 @@ export const ModelEntry = z.object({
325
358
  ctx.addIssue({ code: z.ZodIssueCode.custom, message: "extraBody must be a plain JSON object" });
326
359
  return z.NEVER;
327
360
  }
328
- // wrap in try/catch: a hostile Proxy's getOwnPropertyDescriptor/ownKeys trap could THROW a secret-bearing
329
- // message — never surface it; treat any reflective failure as invalid with a FIXED message. R11.
330
- let rawUnsafe;
331
- try {
332
- rawUnsafe = extraBodyUnsafe(v);
333
- }
334
- catch {
335
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: "extraBody could not be validated (reflective access threw)" });
361
+ const vetted = vetFreeFormJson(v, "extraBody");
362
+ if (!vetted.ok) {
363
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: vetted.message });
336
364
  return z.NEVER;
337
365
  }
338
- if (rawUnsafe) {
339
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: "extraBody: a key/value is a secret shape, non-JSON (Infinity/function/Map), an accessor, cyclic, or too deep" });
340
- return z.NEVER;
341
- }
342
- let clone;
343
- try {
344
- clone = JSON.parse(JSON.stringify(v)); // the ONLY serialization — its result is what we scan AND persist
345
- }
346
- catch {
347
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: "extraBody is not JSON-serializable" });
348
- return z.NEVER;
349
- }
350
- if (extraBodyUnsafe(clone)) {
351
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: "extraBody: its serialized form carries a secret" });
366
+ return vetted.value;
367
+ }),
368
+ /** Per-model wire-compat declaration for the OpenAI-completions lane — mirrors the engine's
369
+ * `OpenAICompletionsCompat` (five keys: `thinkingFormat` / `reasoningEffortLevels` / `maxTokensField` /
370
+ * `requiresReasoningContentOnAssistantMessages` / `supportsReasoningEffort`). Real need: a vLLM/Qwen-class
371
+ * gateway that thinks by DEFAULT can only be turned off with `chat_template_kwargs.enable_thinking=false`,
372
+ * which `extraBody` cannot carry (the engine reserves those request keys) — and without this key a catalog
373
+ * entry that declares it is stripped at parse, so the declaration never reaches the engine at all.
374
+ *
375
+ * **Parse-transparent** (`z.unknown()`), same rule as `McpServerSpec.source` (1.7.2) and `.toolFaces` (1.9.0):
376
+ * the SHAPE and the VOCABULARY (which `thinkingFormat` words exist) are the consuming side's single point —
377
+ * this package deliberately does NOT restate that table. This domain parses all-or-nothing, so a shape/word
378
+ * rejection HERE would blank the entire model catalog over one bad key (the 1.7.1 upgrade regression). ABSENT
379
+ * = key not minted = today's behaviour, zero migration.
380
+ *
381
+ * The ONE rejection that remains is the free-form SAFETY judgment shared with `extraBody`
382
+ * ({@link vetFreeFormJson}): a secret-shaped key/value, an accessor, a cycle, non-JSON, or too deep to hash
383
+ * later. That is not shape validation — it is the boundary every free-form value must clear to enter the
384
+ * persisted config and `stableHash` (R18). */
385
+ compat: z
386
+ .unknown()
387
+ .optional()
388
+ .transform((v, ctx) => {
389
+ if (v === undefined)
390
+ return undefined;
391
+ const vetted = vetFreeFormJson(v, "compat");
392
+ if (!vetted.ok) {
393
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: vetted.message });
352
394
  return z.NEVER;
353
395
  }
354
- return clone;
396
+ return vetted.value;
355
397
  }),
356
398
  /** 配额倍率(EXPERT-REDESIGN §4-1):该模型烧 token 双窗额度的倍率,默认 1。折算时点=weight-at-burn
357
399
  * (记账时固化到 UsageEntry.quotaWeightAtUse/weightedTokens):改倍率只影响此后记账,不回算窗口内历史。 */
@@ -1912,6 +1954,12 @@ export const McpServerSpec = z.object({
1912
1954
  * both legs stripped it (default-strip object); this is the declaration half of the consumer-side fix
1913
1955
  * (ticket B-036 / S-143), the projection half lives in the consumer. */
1914
1956
  source: z.unknown().optional(),
1957
+ /** 1.9.0 —— core 7.8.0(design/388 片 1 F7)`toolFaces` 覆盖层:按远端工具名 key 的 face 声明
1958
+ * (`{ family?, pathTarget?: {param, aliases?, access, skillScopeEligible?}, renderHints?, ruleFace? }`)。
1959
+ * 与 `source` 同一条规矩:**解析透明**(`z.unknown()`),形的校验属 core 摄入侧(不合 schema 的工具**不挂**并
1960
+ * 通告一次 `config.tool_face_invalid`);本仓不复述那张表 —— 这一域 all-or-nothing,任一字段解析拒都会
1961
+ * 让整套 mcp 服务器静默消失。缺席 = 不铸键。 */
1962
+ toolFaces: z.unknown().optional(),
1915
1963
  notes: z.string().optional(),
1916
1964
  });
1917
1965
  export const McpConfig = z
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/settings-schema",
3
- "version": "1.8.0",
3
+ "version": "1.10.0",
4
4
  "description": "Sema settings schema — pure config contract (zod domains + effective-config + roster resolution + ref-integrity + remoteExec) shared by sema-registry, sema-server, and the TOC desktop/CLI. One source of truth. (Renamed at 1.0.0 from @sema-agent/registry-core — name and docs only, no behavior change; note the range moves 0.19.0 -> 1.0.0, so a dependency must become ^1.0.0. The old names @sema-agent/registry-core <=0.19.0 and @sema-ai/registry-core remain published, frozen, for migration.)",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",