@sema-agent/server 7.130.0 → 7.132.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.
Files changed (294) hide show
  1. package/MIGRATION.md +396 -0
  2. package/README.md +13 -6
  3. package/README.zh-CN.md +9 -5
  4. package/USAGE.md +187 -50
  5. package/dist/approval-card.d.ts +3 -1
  6. package/dist/approval-card.js +2 -3
  7. package/dist/audit.d.ts +7 -10
  8. package/dist/audit.js +4 -1
  9. package/dist/auth-bridge.d.ts +4 -0
  10. package/dist/auth-bridge.js +10 -11
  11. package/dist/bench/s1/live-deps.js +2 -0
  12. package/dist/boot/deployment-mods.d.ts +31 -0
  13. package/dist/boot/deployment-mods.js +26 -0
  14. package/dist/boot/execution-env.d.ts +1 -1
  15. package/dist/boot/execution-env.js +19 -6
  16. package/dist/boot/memory-boundary.d.ts +2 -2
  17. package/dist/boot/read-deny-anchor-audit.d.ts +17 -0
  18. package/dist/boot/read-deny-anchor-audit.js +36 -0
  19. package/dist/boot/resolve-spec.d.ts +1 -5
  20. package/dist/boot/resolve-spec.js +56 -61
  21. package/dist/boot/runner-deps.d.ts +19 -8
  22. package/dist/boot/runner-deps.js +14 -2
  23. package/dist/boot/session-faces.d.ts +1 -7
  24. package/dist/boot/stage-02-web-admin-pull.js +9 -1
  25. package/dist/boot/stage-03-store-backend.d.ts +1 -1
  26. package/dist/boot/stage-03-store-backend.js +2 -2
  27. package/dist/boot/stage-04-stores.d.ts +1 -1
  28. package/dist/boot/stage-05-execution-env.d.ts +4 -4
  29. package/dist/boot/stage-05-execution-env.js +7 -10
  30. package/dist/boot/stage-06-runners.d.ts +2 -2
  31. package/dist/boot/stage-06-runners.js +5 -4
  32. package/dist/boot/stage-07-capability-layer.d.ts +2 -2
  33. package/dist/boot/stage-07-capability-layer.js +3 -3
  34. package/dist/boot/stage-08-reapers.d.ts +2 -2
  35. package/dist/boot/stage-09-leader.d.ts +3 -9
  36. package/dist/boot/stage-10-http-server.d.ts +3 -9
  37. package/dist/boot/stage-10-http-server.js +12 -10
  38. package/dist/boot/stores.d.ts +1 -1
  39. package/dist/boot/stores.js +5 -6
  40. package/dist/boot/web-admin.d.ts +0 -3
  41. package/dist/boot/web-admin.js +35 -35
  42. package/dist/brain.js +17 -22
  43. package/dist/budget.js +5 -4
  44. package/dist/caller-text-echo.d.ts +20 -0
  45. package/dist/caller-text-echo.js +24 -0
  46. package/dist/capabilities/brand-identity.d.ts +26 -0
  47. package/dist/capabilities/brand-identity.js +27 -0
  48. package/dist/capabilities/builtin-tools.js +2 -1
  49. package/dist/capabilities/center-plugins.js +1 -1
  50. package/dist/capabilities/center-prompts.d.ts +32 -59
  51. package/dist/capabilities/center-prompts.js +18 -32
  52. package/dist/capabilities/composite-members.d.ts +1 -0
  53. package/dist/capabilities/composite-members.js +1 -0
  54. package/dist/capabilities/engine-capabilities.js +2 -1
  55. package/dist/capabilities/hands-lane.d.ts +9 -6
  56. package/dist/capabilities/hands-lane.js +3 -3
  57. package/dist/capabilities/host-tool-names.d.ts +17 -0
  58. package/dist/capabilities/host-tool-names.js +15 -0
  59. package/dist/capabilities/mcp-probe.d.ts +14 -1
  60. package/dist/capabilities/mcp-probe.js +22 -133
  61. package/dist/capabilities/repo-tools.js +4 -3
  62. package/dist/capabilities/scenarios.d.ts +42 -47
  63. package/dist/capabilities/scenarios.js +19 -35
  64. package/dist/capabilities/select-environment-tool.js +2 -1
  65. package/dist/capabilities/send-user-file-tool.d.ts +0 -1
  66. package/dist/capabilities/send-user-file-tool.js +2 -2
  67. package/dist/capabilities/skills.d.ts +19 -4
  68. package/dist/capabilities/skills.js +30 -10
  69. package/dist/capabilities/tool-defer.js +2 -1
  70. package/dist/config-catalog.js +22 -11
  71. package/dist/config-invariants.js +2 -2
  72. package/dist/config-lkg.d.ts +9 -9
  73. package/dist/config-lkg.js +20 -4
  74. package/dist/config-provider.d.ts +3 -1
  75. package/dist/config-provider.js +14 -6
  76. package/dist/config-types.d.ts +13 -9
  77. package/dist/config.d.ts +2 -2
  78. package/dist/config.js +34 -20
  79. package/dist/device-ws-protocol.d.ts +1 -1
  80. package/dist/device-ws-protocol.js +1 -0
  81. package/dist/elicitation.d.ts +7 -0
  82. package/dist/elicitation.js +20 -5
  83. package/dist/env-name-allowlist-knobs.d.ts +12 -2
  84. package/dist/env-name-allowlist-knobs.js +5 -4
  85. package/dist/exec-bounds.d.ts +15 -3
  86. package/dist/exec-bounds.js +4 -0
  87. package/dist/execution-lane-caps.d.ts +9 -2
  88. package/dist/execution-lane-caps.js +11 -0
  89. package/dist/hooks/classic-stations.d.ts +86 -0
  90. package/dist/hooks/classic-stations.js +27 -0
  91. package/dist/hooks/hook-llm.d.ts +8 -5
  92. package/dist/hooks/hook-llm.js +80 -37
  93. package/dist/hooks/hook-runner.d.ts +273 -75
  94. package/dist/hooks/hook-runner.js +293 -317
  95. package/dist/http/admission.d.ts +8 -2
  96. package/dist/http/admission.js +103 -59
  97. package/dist/http/approval-sweeps.js +59 -18
  98. package/dist/http/checkpoint-ctx.d.ts +36 -18
  99. package/dist/http/checkpoint-ctx.js +14 -10
  100. package/dist/http/claimed-run.d.ts +13 -0
  101. package/dist/http/claimed-run.js +15 -0
  102. package/dist/http/device-placement.js +5 -10
  103. package/dist/http/dispatch.d.ts +0 -4
  104. package/dist/http/dispatch.js +6 -10
  105. package/dist/http/durable-run-leg.d.ts +2 -1
  106. package/dist/http/durable-run-leg.js +5 -3
  107. package/dist/http/health-self-report.d.ts +7 -5
  108. package/dist/http/health-self-report.js +4 -18
  109. package/dist/http/principal-gate.d.ts +23 -6
  110. package/dist/http/principal-gate.js +24 -5
  111. package/dist/http/refusal-body.d.ts +21 -0
  112. package/dist/http/refusal-body.js +4 -0
  113. package/dist/http/request-guards.d.ts +27 -0
  114. package/dist/http/request-guards.js +24 -3
  115. package/dist/http/resume-legs.d.ts +25 -0
  116. package/dist/http/resume-legs.js +48 -26
  117. package/dist/http/routes/approvals-assistant.js +1 -1
  118. package/dist/http/routes/capabilities.js +7 -17
  119. package/dist/http/routes/diagnostics.js +1 -1
  120. package/dist/http/routes/rules.js +2 -2
  121. package/dist/http/routes/runs.js +13 -17
  122. package/dist/http/routes/session-sync.js +4 -2
  123. package/dist/http/routes/sessions.js +23 -11
  124. package/dist/http/routes/shared-memory.js +2 -13
  125. package/dist/http/routes/side-query.js +2 -3
  126. package/dist/http/routes/tasks.js +34 -19
  127. package/dist/http/scenario-read-face.d.ts +14 -0
  128. package/dist/http/scenario-read-face.js +5 -0
  129. package/dist/http/send.d.ts +13 -1
  130. package/dist/http/send.js +12 -1
  131. package/dist/http/server.d.ts +37 -23
  132. package/dist/http/server.js +30 -4
  133. package/dist/http/stored-body-replay.d.ts +10 -3
  134. package/dist/http/stored-body-replay.js +27 -6
  135. package/dist/http/submit-ctx.d.ts +25 -0
  136. package/dist/http/submit-ctx.js +39 -0
  137. package/dist/http/wire-types.d.ts +18 -5
  138. package/dist/index.d.ts +3 -2
  139. package/dist/index.js +3 -2
  140. package/dist/lane-home-probe.d.ts +43 -0
  141. package/dist/lane-home-probe.js +35 -0
  142. package/dist/limit-refusal.d.ts +47 -0
  143. package/dist/limit-refusal.js +12 -0
  144. package/dist/main.js +1 -1
  145. package/dist/mcp-assertion-header.d.ts +3 -11
  146. package/dist/mcp-assertion-header.js +3 -9
  147. package/dist/mcp-tool-faces.d.ts +5 -0
  148. package/dist/mcp-tool-faces.js +5 -1
  149. package/dist/memory-scope.d.ts +1 -1
  150. package/dist/model-entry-refusal.d.ts +1 -7
  151. package/dist/model-provider.d.ts +39 -12
  152. package/dist/model-provider.js +19 -2
  153. package/dist/model-route-endpoint.d.ts +52 -12
  154. package/dist/model-route-endpoint.js +48 -26
  155. package/dist/model-route-gap.d.ts +5 -4
  156. package/dist/model-route-gap.js +1 -1
  157. package/dist/model-select.d.ts +58 -0
  158. package/dist/model-select.js +31 -0
  159. package/dist/namespaced-rule-name.d.ts +7 -4
  160. package/dist/namespaced-rule-name.js +4 -3
  161. package/dist/observability/cost-taxonomy.d.ts +7 -7
  162. package/dist/observability/fail-open.d.ts +53 -12
  163. package/dist/observability/fail-open.js +47 -7
  164. package/dist/observability/metrics.js +3 -1
  165. package/dist/observability/prompt-manifest.d.ts +47 -82
  166. package/dist/observability/prompt-manifest.js +119 -17
  167. package/dist/observability/run-terminal.js +14 -6
  168. package/dist/observability/secret-env-scrub.d.ts +13 -1
  169. package/dist/observability/secret-env-scrub.js +13 -1
  170. package/dist/observability/tool-trace.d.ts +16 -21
  171. package/dist/observability/tool-trace.js +51 -42
  172. package/dist/per-task-image.d.ts +7 -1
  173. package/dist/per-task-image.js +3 -3
  174. package/dist/plain-string-record.d.ts +25 -0
  175. package/dist/plain-string-record.js +32 -0
  176. package/dist/plugins/blob-backend.d.ts +68 -13
  177. package/dist/plugins/blob-backend.js +53 -10
  178. package/dist/plugins/checkpoint-store-sql.d.ts +14 -4
  179. package/dist/plugins/checkpoint-store-sql.js +39 -26
  180. package/dist/plugins/k8s-bg-scripts.d.ts +11 -0
  181. package/dist/plugins/k8s-bg-scripts.js +22 -0
  182. package/dist/plugins/local-checkpoint-store.js +5 -2
  183. package/dist/plugins/local-session-store.js +3 -3
  184. package/dist/plugins/one-time-migrations.d.ts +147 -91
  185. package/dist/plugins/one-time-migrations.js +372 -208
  186. package/dist/plugins/permission-rule-store-sql.d.ts +13 -7
  187. package/dist/plugins/permission-rule-store-sql.js +4 -18
  188. package/dist/plugins/pg-session-storage.d.ts +4 -5
  189. package/dist/plugins/pg-session-storage.js +7 -17
  190. package/dist/plugins/remote-env-adb.d.ts +6 -0
  191. package/dist/plugins/remote-env-adb.js +9 -4
  192. package/dist/plugins/remote-env-device.js +3 -0
  193. package/dist/plugins/remote-env-e2b.d.ts +1 -1
  194. package/dist/plugins/remote-env-e2b.js +21 -9
  195. package/dist/plugins/remote-env-host.d.ts +9 -9
  196. package/dist/plugins/remote-env-host.js +10 -32
  197. package/dist/plugins/remote-env-k8s.d.ts +95 -26
  198. package/dist/plugins/remote-env-k8s.js +265 -90
  199. package/dist/plugins/remote-env-local-docker.d.ts +20 -1
  200. package/dist/plugins/remote-env-local-docker.js +77 -9
  201. package/dist/plugins/remote-env-ssh.d.ts +15 -0
  202. package/dist/plugins/remote-env-ssh.js +109 -3
  203. package/dist/plugins/remote-shell.d.ts +0 -1
  204. package/dist/plugins/remote-shell.js +1 -2
  205. package/dist/plugins/retention-store-sql.js +6 -2
  206. package/dist/plugins/schema-consistency.d.ts +54 -0
  207. package/dist/plugins/schema-consistency.js +52 -0
  208. package/dist/plugins/session-epoch-anchor.d.ts +13 -0
  209. package/dist/plugins/session-epoch-anchor.js +23 -0
  210. package/dist/plugins/shared-memory-store-sql.d.ts +9 -3
  211. package/dist/plugins/shared-memory-store-sql.js +5 -16
  212. package/dist/plugins/sql-driver.d.ts +141 -0
  213. package/dist/plugins/sql-driver.js +115 -1
  214. package/dist/plugins/sql-errors.d.ts +41 -0
  215. package/dist/plugins/sql-errors.js +36 -0
  216. package/dist/plugins/store-backend.js +2 -1
  217. package/dist/plugins/tidb-session-storage.d.ts +7 -11
  218. package/dist/plugins/tidb-session-storage.js +6 -16
  219. package/dist/plugins/tidb-session-store.js +1 -1
  220. package/dist/prompts-domain-validate.d.ts +17 -61
  221. package/dist/prompts-domain-validate.js +9 -119
  222. package/dist/question.d.ts +18 -9
  223. package/dist/question.js +21 -13
  224. package/dist/request-key-closure.d.ts +4 -4
  225. package/dist/request-key-closure.js +18 -13
  226. package/dist/rule-entry-list.d.ts +134 -24
  227. package/dist/rule-entry-list.js +162 -30
  228. package/dist/run-local.d.ts +3 -3
  229. package/dist/run-local.js +26 -25
  230. package/dist/runs.d.ts +48 -2
  231. package/dist/runs.js +61 -2
  232. package/dist/server-secret-env.d.ts +44 -18
  233. package/dist/server-secret-env.js +49 -8
  234. package/dist/session-incarnation.d.ts +15 -5
  235. package/dist/session-incarnation.js +16 -9
  236. package/dist/session-titler.js +12 -2
  237. package/dist/spec-fields.d.ts +34 -18
  238. package/dist/spec-fields.js +46 -53
  239. package/dist/sql-engine-posture.d.ts +29 -4
  240. package/dist/sql-engine-posture.js +36 -1
  241. package/dist/stored-request-hooks-m20.d.ts +23 -0
  242. package/dist/stored-request-hooks-m20.js +124 -0
  243. package/dist/stored-request-tool-axes-m19.d.ts +20 -5
  244. package/dist/stored-request-tool-axes-m19.js +30 -2
  245. package/dist/task-a2a.d.ts +9 -1
  246. package/dist/task-a2a.js +73 -47
  247. package/dist/task-mcp.d.ts +54 -102
  248. package/dist/task-mcp.js +194 -92
  249. package/dist/task-settings.d.ts +47 -25
  250. package/dist/task-settings.js +46 -61
  251. package/dist/text-bidi.d.ts +3 -46
  252. package/dist/text-bidi.js +3 -3
  253. package/dist/tool-approval.d.ts +1 -1
  254. package/dist/trace/core-keyset-guard.d.ts +24 -4
  255. package/dist/trace/engine-notice-wire.d.ts +11 -3
  256. package/dist/trace/engine-notice-wire.js +8 -1
  257. package/dist/trace/ledger-events.d.ts +3 -1
  258. package/dist/trace/ledger-sink.d.ts +29 -0
  259. package/dist/trace/ledger-sink.js +15 -1
  260. package/dist/trace/model-segment.js +1 -1
  261. package/dist/trace/project.d.ts +25 -6
  262. package/dist/trace/project.js +68 -22
  263. package/dist/trace/wire-projection-faces.d.ts +80 -2
  264. package/dist/trace/wire-projection-faces.js +23 -1
  265. package/dist/user-authored-text.d.ts +7 -0
  266. package/dist/user-authored-text.js +3 -0
  267. package/dist/web-admin-client/apply-effective.js +10 -5
  268. package/dist/web-admin-client/candidate-gate.d.ts +6 -6
  269. package/dist/web-admin-client/candidate-gate.js +3 -3
  270. package/dist/web-admin-client/http-client.js +0 -6
  271. package/dist/web-admin-client/read-warnings.d.ts +13 -0
  272. package/dist/web-admin-client/read-warnings.js +10 -0
  273. package/dist/web-admin-client/skills-mcp.d.ts +3 -0
  274. package/dist/web-admin-client/skills-mcp.js +9 -6
  275. package/dist/web-admin-client/types.d.ts +3 -4
  276. package/dist/workspace-cache.d.ts +35 -0
  277. package/dist/workspace-cache.js +36 -0
  278. package/dist/workspace-space.d.ts +3 -2
  279. package/dist/workspace-space.js +2 -0
  280. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +8 -1
  281. package/package.json +4 -4
  282. package/scripts/drain-check-local.mjs +37 -3
  283. package/scripts/drain-check.mysql.sql +30 -2
  284. package/scripts/drain-check.pg.sql +29 -2
  285. package/dist/capabilities/memory-notice.d.ts +0 -138
  286. package/dist/capabilities/memory-notice.js +0 -64
  287. package/dist/capabilities/prompt.d.ts +0 -17
  288. package/dist/capabilities/prompt.js +0 -6
  289. package/dist/mcp-content-origin.d.ts +0 -34
  290. package/dist/mcp-content-origin.js +0 -10
  291. package/dist/tool-axis-words.d.ts +0 -27
  292. package/dist/tool-axis-words.js +0 -9
  293. package/dist/wire-name-bound.d.ts +0 -14
  294. package/dist/wire-name-bound.js +0 -13
@@ -15,7 +15,7 @@
15
15
  * · **fresh 提交腿**:判出问题 ⇒ 同步 400(同设 `request.body_shape`;值不是字符串 `request.field_invalid`)。
16
16
  * · **其余腿(耐久 resume 重放存量 body)**:**不拒、不丢 server** —— 存量 body 里这个键当时是被静默忽略的,server 当时是挂着的;
17
17
  * 此刻把 server 摘掉,一条 park 在该 server 工具审批上的任务批准后会撞 `resume.tool_unavailable` 并重开检查点,而每次重试重放同一份 body ⇒ 永远卡住。
18
- * 所以读器 {@link readMcpIdentityHeaders} 的处置是**降级到升级前的读法**:`principalHeader` 胜(照挂,与升级前逐字相同),`assertionHeader` 不生效;
18
+ * 所以读器(契约包的 `readMcpIdentityHeaders`,`normalizeServer` 的 http 传输读两个身份头用它)的处置是**降级到升级前的读法**:`principalHeader` 胜(照挂,与升级前逐字相同),`assertionHeader` 不生效;
19
19
  * 不静默 —— 真挂上的这类条目由 {@link noteMcpAssertionHeaderIgnored} 说一行 warn(部署声明面没有这一格:配置不是被重放的存量任务,契约包解析期那一刀直接丢这一条)。
20
20
  *
21
21
  * ## 发送归引擎座(本版只承载,不发送)
@@ -56,18 +56,10 @@ export type McpIdentityHeadersIssue = "both_set" | "not_a_string";
56
56
  * 两类同时成立时报 `both_set`(先名字后值)。stdio 传输不判(两键在 stdio 形上本就不存在,契约包同判)。
57
57
  */
58
58
  export declare function mcpIdentityHeadersIssue(transport: Record<string, unknown>): McpIdentityHeadersIssue | undefined;
59
- /**
60
- * 读器的这一格(`normalizeServer` 的 http 传输读两个身份头):字符串才收;**`principalHeader` 胜**——它在场(不论值形)时 `assertionHeader` 不收。
61
- * 于是 normalize 后的形**结构上不可能同带两键**(`mcpIdentityHeadersIssue` 的 `both_set` 在读器之后不存在),重放存量 body 的处置因此就是升级前的读法。
62
- */
63
- export declare function readMcpIdentityHeaders(transport: Record<string, unknown>): {
64
- principalHeader?: string;
65
- assertionHeader?: string;
66
- };
67
59
  /** 同设的拒句 —— 对契约包解析期那一句**逐字**(`server` = 条目名,调用方负责清洗后传入)。 */
68
60
  export declare function describeMcpIdentityHeadersConflict(server: string): string;
69
- /** `assertionHeader` 值不是字符串的拒句(值的形状只说种类,不回显值本身:它是一个调用方写的头名位置,可能是任何东西)。 */
70
- export declare function describeMcpAssertionHeaderNotAString(server: string, value: unknown): string;
61
+ /** `assertionHeader` 值不是字符串的**原因句**(不带条目名与键路径:点名由请求面统一的拒形 `entryFieldRefusal` 负责;值的形状只说种类,不回显值本身:它是一个调用方写的头名位置,可能是任何东西)。 */
62
+ export declare function describeMcpAssertionHeaderNotAString(value: unknown): string;
71
63
  /** 这一条请求面条目是否声明了 `assertionHeader`(= 铸 core 规格时会被剥掉、需要说一行的那一类)。 */
72
64
  export declare function declaresMcpAssertionHeader(spec: RequestMcpServerSpec): boolean;
73
65
  /** 请求面条目 → 交给引擎的规格:剥掉 `assertionHeader`(引擎无座);其余逐字不动。声明过它的条目由调用方另说一行 info。 */
@@ -7,18 +7,12 @@ export function mcpIdentityHeadersIssue(transport) {
7
7
  return "not_a_string";
8
8
  return undefined;
9
9
  }
10
- export function readMcpIdentityHeaders(transport) {
11
- return {
12
- ...(typeof transport.principalHeader === "string" ? { principalHeader: transport.principalHeader } : {}),
13
- ...(transport.principalHeader === undefined && typeof transport.assertionHeader === "string" ? { assertionHeader: transport.assertionHeader } : {}),
14
- };
15
- }
16
10
  export function describeMcpIdentityHeadersConflict(server) {
17
11
  return `mcp server "${server}" sets both principalHeader and assertionHeader — an http entry names ONE header for the identity it sends: keep principalHeader (the end-user principal) or assertionHeader (the connector assertion), not both`;
18
12
  }
19
- export function describeMcpAssertionHeaderNotAString(server, value) {
20
- const kind = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
21
- return `mcp server "${server}" sets assertionHeader to a ${kind} — it must be a string (the HTTP header NAME the connector assertion travels in); omit the key to send no assertion header`;
13
+ export function describeMcpAssertionHeaderNotAString(value) {
14
+ const kind = value === null ? "null" : Array.isArray(value) ? "an array" : `a ${typeof value}`;
15
+ return `must be a string (the HTTP header NAME the connector assertion travels in) — got ${kind}; omit the key to send no assertion header`;
22
16
  }
23
17
  export function declaresMcpAssertionHeader(spec) {
24
18
  return spec.transport.kind === "http" && spec.transport.assertionHeader !== undefined;
@@ -58,6 +58,11 @@ export type McpToolFacesRead = {
58
58
  /**
59
59
  * 从任一腿的原始条目里读出 `toolFaces` —— **三条腿共用的唯一判据点**。
60
60
  * 缺席 ⇒ 合形无覆盖层;整只不是对象 / **任一只**工具的面判不出形 ⇒ `ok:false`(调用方整条 server 不收)。
61
+ *
62
+ * 🔴 工具名是 `__proto__` ⇒ 同样判不出形,**响亮拒**:按名存面的记录装不下它 —— JSON 的自有键 `"__proto__"` 写进记录那一步走的是原型的设置器,
63
+ * 键从结果里消失而判官答 `ok`,那条 server 照挂、收窄面叠层被无声丢掉。方向与本模块头注同(收窄声明在场就必须原样可用)。
64
+ * 只拒这一个名字:`constructor` / `toString` 等其余根原型成员名写进记录是普通自有键、不丢,引擎按名查面也取得到 —— 拒它们是给重放存量 body 的腿
65
+ * 新添一条「整条 server 不进」,不是修这个病。
61
66
  */
62
67
  export declare function readMcpToolFaces(v: unknown): McpToolFacesRead;
63
68
  export {};
@@ -1,4 +1,5 @@
1
1
  import { TOOL_APPROVAL_CARDS, TOOL_FAMILIES, TOOL_PATH_ABSENCES, TOOL_PATH_ACCESSES, TOOL_PATH_BASES } from "@sema-agent/core";
2
+ import { quotedCallerText } from "./caller-text-echo.js";
2
3
  function isRecord(v) {
3
4
  return typeof v === "object" && v !== null && !Array.isArray(v);
4
5
  }
@@ -92,9 +93,12 @@ export function readMcpToolFaces(v) {
92
93
  return { ok: false, problem: `toolFaces is ${Array.isArray(v) ? "an array" : typeof v}, not an object of per-tool faces` };
93
94
  const faces = {};
94
95
  for (const [tool, raw] of Object.entries(v)) {
96
+ if (tool === "__proto__") {
97
+ return { ok: false, problem: `toolFaces names tool "__proto__" — a per-tool record cannot hold a face under that name (writing it sets the record's prototype instead of a key), so the declaration would silently vanish` };
98
+ }
95
99
  const face = readOneFace(raw);
96
100
  if (face === undefined)
97
- return { ok: false, problem: `the declared face for tool ${JSON.stringify(tool)} does not fit the tool-face shape` };
101
+ return { ok: false, problem: `the declared face for tool ${quotedCallerText(tool)} does not fit the tool-face shape` };
98
102
  faces[tool] = face;
99
103
  }
100
104
  return Object.keys(faces).length > 0 ? { ok: true, faces } : { ok: true };
@@ -286,7 +286,7 @@ declarations?: {
286
286
  /**
287
287
  * 「这一腿挂不挂记忆面」的**唯一属主**(合并复审 车HF F1,2026-09-18):与 core `prepareMemory` 的判据同源 ——
288
288
  * `normalizeMemorySpec(spec)?.enabled === true`。S-405 之后 `memory:"off"` 铸的是「键在场 + `enabled:false`」(保留事实),
289
- * 任何按「键在不在场」判挂没挂的读者从那一天起都读错(改前 `memory-notice` 正是那样,把无记忆面的披露一起吞掉)。
289
+ * 任何按「键在不在场」判挂没挂的读者从那一天起都读错(改前本仓的记忆披露判别式正是那样,把无记忆面的披露一起吞掉;那只判别式已随引擎接管披露退役)。
290
290
  * 消费方拿本函数的布尔,不拿 `spec.memory` 自己判。
291
291
  *
292
292
  * ⚠️ **本函数会抛,而「抛」本身就是一条判据**(S-424 补注)。`normalizeMemorySpec` 对两形直接 throw
@@ -1,5 +1,6 @@
1
1
  import { type ModelEntryRefusalCode, type ModelPlaneRefusalCode, type ModelRefusalsSignal } from "@sema-agent/settings-schema";
2
2
  import { type DroppedCatalogEntry } from "./web-admin-client/read-warnings.js";
3
+ import { type ModelTableLeg } from "./model-select.js";
3
4
  /**
4
5
  * 条目拒因码 → 本仓铸它的成因 —— **以码为键**(S-768):码闭集的属主是 settings-schema(`MODEL_ENTRY_REFUSAL_CODES`,6.2.0 起;此前本仓
5
6
  * 自持一份同名码表,撞名已消灭、零别名)。那边加一个条目码 ⇒ 本表缺键 ⇒ 本仓 tsc 红(只拿 `satisfies` 约束码值证明的是「子集」,
@@ -126,13 +127,6 @@ export interface DefaultModelView {
126
127
  readonly models: Readonly<Record<string, unknown>>;
127
128
  readonly modelRefusals: Readonly<Record<string, ModelEntryRefusal>>;
128
129
  }
129
- /**
130
- * 这张表是从哪条腿来的(闭集 —— 拒因句的出路按腿分:加腿不补 {@link defaultModelRefusal} 的臂 = 编译红)。
131
- * · `env` —— 进程此刻服务的表**没有目录在管**(没配目录 / 目录迟到且无 LKG / 目录的模型面这一组被拒):`MODEL_ID` 是出路之一;
132
- * · `catalog` —— 判的是一份**目录候选**(模型面 stage):目录一旦生效就整张换掉 env 表,`MODEL_ID` 救不了这份候选 ——
133
- * 出路只在目录里(修条目 / 换绑)。opus 腿 ④(7.101.0 合并复审):此前两腿同一句,目录腿教「设 MODEL_ID 即可」是假话。
134
- */
135
- export type ModelTableLeg = "env" | "catalog";
136
130
  /**
137
131
  * 🔴 S-705 —— **这张模型表有没有一只缺省模型**(`models.default`)。`undefined` = 有。
138
132
  *
@@ -77,20 +77,47 @@ export declare function modelBrainLane(provider: ModelProvider): ModelBrainLane;
77
77
  */
78
78
  export declare function catalogBrainLane(provider: string | undefined): ModelBrainLane;
79
79
  /**
80
- * 🔴 S-623 —— 一条目录条目在**本部署**上真正落哪条 brain 腿(= `createBrain` 的组装事实,不是词面)。
80
+ * 🔴 **本部署装上的路由表的唯一铸法**(core 7.53.0 #1842;S-623 的「词 ∧ 路由装上」合取由此收成「问表」)。
81
81
  *
82
- * `createBrain` 只在 anthropic 路由**装上**(`ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` 在场 ⇒
83
- * `config.anthropic` 在场)时才铸 `createRoutingBrain({anthropic}, {fallback: gatewayStack})`;没装时整台机器
84
- * 只有网关栈这一只 brain,**一切**条目(包括 `provider:"anthropic"` 的)都落 openai 兼容腿 ——
85
- * `apply-effective.ts` 那条 `web_admin_model_no_brain` warn 说的「mis-routes to the gateway brain」即此。
82
+ * 键 = `createRoutingBrain` 选线读的那个词(`model.provider`),值 = 那条路由上的东西 —— `createBrain` 铸 brain 交给路由器,判词侧(启动摘要、端点判官、
83
+ * hook 选线)铸路由自己的配置交给 {@link brainLaneFor}(core 的判官从不读值,只问 `Object.hasOwn(routes, word)`)。两侧走同一只函数 ⇒ 同一组键,
84
+ * 「哪条路由装上了」只有这里一处写法。
86
85
  *
87
- * 所以「这条路由要不要一个 openai 端点」的判据必须同时看词与装配:词判 {@link catalogBrainLane},
88
- * 装配判路由在不在。本函数是这一合取的**唯一**写法 —— 端点判官(`model-route-endpoint.ts`)、降级腿与
89
- * 启动摘要(`brain.ts`)都调它,不再各写一个 `=== "anthropic" && config.anthropic`。
86
+ * `anthropic` 键在场 ⇔ anthropic 路由装上(`ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` 在场 ⇒ `config.anthropic` 在场);没装时**键缺席**,
87
+ * 绝不写成 `{ anthropic: undefined }` —— core 7.53.0 起「装上的键而值不是 brain」是一条坏线路(该请求 error terminal),不再回落网关
88
+ * (core CHANGELOG #1842「the key must be absent, not `undefined`」)。视图上 `anthropic` 的自有键带 `undefined`(配置对象的形)照样读成没装。
89
+ */
90
+ export declare function mountRoutes<R extends object, T>(view: {
91
+ readonly anthropic?: R | undefined;
92
+ }, mount: (route: R) => T): Readonly<Record<string, T>>;
93
+ /**
94
+ * 🔴 **anthropic 路由的请求根** —— 唯一读法(S-1655 / core 7.53.0 #1905)。声明了 `ANTHROPIC_BASE_URL` ⇒ 它;没声明 ⇒ core 包根导出的 `ANTHROPIC_DEFAULT_BASE_URL`
95
+ * (引擎的 Anthropic brain 的请求根与配对律的车道缺省、`fable5Model` 预设的 `baseUrl` 读的同一只常量)。「没声明」按引擎的读法:缺席、空串、纯空白都是没声明
96
+ * (core 原话:「nothing, an empty string and whitespace read as undeclared」)。修前本仓两处手抄字面量:主路由的声明根(`brain.ts`,`??`)与 hook 的 anthropic 腿根
97
+ * (`hooks/hook-llm.ts`,`||`)—— 一个根、两份拷贝、两种合取。
98
+ */
99
+ export declare function anthropicRouteRoot(route: {
100
+ readonly baseUrl?: string | undefined;
101
+ }): string;
102
+ /** 判词侧的路由表:值 = 路由自己的配置({@link mountRoutes} 的恒等铸法 —— 判官不读值,只问键)。 */
103
+ export declare function routeTableOf<R extends object>(view: {
104
+ readonly anthropic?: R | undefined;
105
+ }): Readonly<Record<string, R>>;
106
+ /**
107
+ * 🔴 **一条目录条目在本部署上真正落哪条 brain 腿**(= 引擎路由器对同一只表的答案,不是词面)—— 唯一判词。
108
+ *
109
+ * 判据是 core 包根导出的 `brainLaneOf({ provider }, routes)`(core 7.53.0 #1842):`createRoutingBrain` 对每个请求问的正是它,所以引擎主路由与本仓的
110
+ * 四处调用点(主路由降级腿与启动摘要在 `brain.ts`、端点判官 `model-route-endpoint.ts`、hook 选线 `hooks/hook-llm.ts`)读**一个**答案。修前本仓自写一只
111
+ * 合取 `dispatchBrainLane(provider, anthropicRouteMounted)`(`catalogBrainLane(provider) === "anthropic" ∧ 路由装上`)—— 两份写法,S-1630 那次真病
112
+ * (hook 面按词面判线、主路由按装配判线)就是两份写法分叉的样子。
113
+ *
114
+ * core 的答案是路由名或 `undefined`(网关 fallback 服务它:词下没装路由,或没有 string 词)。本部署只装 `anthropic` 一条路由(表的唯一铸法 {@link mountRoutes}),
115
+ * 所以答案映到本仓两词闭集:`"anthropic"` ⇒ anthropic 腿,`undefined` ⇒ 网关腿;别的名字结构上不可能(表里没有那个键),真撞上 ⇒ **响亮抛**([ref]:运行期
116
+ * miss 臂必须说出来,不静默折网关)。
90
117
  *
91
- * 🔴 S-704(7.101.0):**目录条目**已到不了「anthropic 词、路由没装」这一格 —— 模型面 stage 的逐条判据把它判成不可用
92
- * (`config.model_no_brain`,不进 `config.models`)。这一格今天只剩 **env 腿**:`MODEL_PROVIDER=anthropic` 而没配 anthropic
93
- * 凭据的部署,env 模型落网关腿、用的是网关自己的 `MODEL_API_KEY`(凭据与端点同属网关,不越界;是否也该拒启另车)。
118
+ * S-704(7.101.0):**目录条目**到不了「anthropic 词、路由没装」这一格 —— 模型面 stage 的逐条判据把它判成不可用(`config.model_no_brain`,那一判问的是
119
+ * 「词想要的腿装没装」,{@link catalogBrainLane},与本判词是两个问题)。这一格今天只剩 **env 腿**:`MODEL_PROVIDER=anthropic` 而没配 anthropic 凭据的部署,
120
+ * env 模型落网关腿、用的是网关自己的 `MODEL_API_KEY`(凭据与端点同属网关,不越界)。
94
121
  */
95
- export declare function dispatchBrainLane(provider: string | undefined, anthropicRouteMounted: boolean): ModelBrainLane;
122
+ export declare function brainLaneFor(provider: string | undefined, routes: Readonly<Record<string, unknown>>): ModelBrainLane;
96
123
  //# sourceMappingURL=model-provider.d.ts.map
@@ -1,3 +1,4 @@
1
+ import { ANTHROPIC_DEFAULT_BASE_URL, brainLaneOf } from "@sema-agent/core";
1
2
  export const MODEL_PROVIDER_WORDS = ["gateway", "vllm", "anthropic"];
2
3
  export function isModelProvider(word) {
3
4
  return word !== undefined && MODEL_PROVIDER_WORDS.includes(word);
@@ -23,7 +24,23 @@ export function modelBrainLane(provider) {
23
24
  export function catalogBrainLane(provider) {
24
25
  return isModelProvider(provider) ? modelBrainLane(provider) : "gateway";
25
26
  }
26
- export function dispatchBrainLane(provider, anthropicRouteMounted) {
27
- return anthropicRouteMounted && catalogBrainLane(provider) === "anthropic" ? "anthropic" : "gateway";
27
+ export function mountRoutes(view, mount) {
28
+ const route = view.anthropic;
29
+ return route !== undefined ? { anthropic: mount(route) } : {};
30
+ }
31
+ export function anthropicRouteRoot(route) {
32
+ const declared = route.baseUrl;
33
+ return declared !== undefined && declared.trim() !== "" ? declared : ANTHROPIC_DEFAULT_BASE_URL;
34
+ }
35
+ export function routeTableOf(view) {
36
+ return mountRoutes(view, (route) => route);
37
+ }
38
+ export function brainLaneFor(provider, routes) {
39
+ const line = brainLaneOf({ provider }, routes);
40
+ if (line === undefined)
41
+ return "gateway";
42
+ if (line === "anthropic")
43
+ return "anthropic";
44
+ throw new Error(`brainLaneFor: the engine's route judge answered route "${line}", which this deployment never mounts (the route table holds only the anthropic route) — the table was built outside mountRoutes`);
28
45
  }
29
46
  //# sourceMappingURL=model-provider.js.map
@@ -15,12 +15,15 @@
15
15
  * (`MODEL_GATEWAY_FALLBACK_URLS`)是主网关的镜像,没有主就没有镜像 ⇒ 拒。
16
16
  *
17
17
  * ## 执法点(判据只在这里,执法点在调用处 —— 与 S-475 `gateway-url-userinfo` 同形)
18
- * · boot:`config.ts` 九域解析之后的跨域不变量区(锚 `CROSS-INVARIANT:model-route-endpoint`)⇒ 拒启;经 `model-route-gap.ts`
19
- * 的 {@link judgeModelRouteGap}(env 读成本文件判据要的切面,与包根导出的纯判官是**同一只**);严格名册
20
- * 布防(`isRosterGateArmed`)时 env 腿是占位、从不被派发 ⇒ 不判它(备用网关那一条照判);
21
- * · 热路径:`web-admin-client/apply-effective.ts` 的模型面 stage ⇒ 模型面组拒(opus 腿 O3:同候选其余组照落)、上一代模型面继续服务;
22
- * · run-local:配置源解析完之后按真要派发的表再判一次(`run-local.ts`,7.98.0 合并复审 F2)—— 本入口没有名册门,
23
- * loadConfig 对严格名册布防的占位豁免在这里兑现不了;没布防时恒惰性;
18
+ * **判官只判「将要服务的那张表」,拒因句按视图说真话**(视图词 = {@link ModelTableLeg} 两词,进判据入参,不靠调用方拼句):
19
+ * · boot:装配第 2 段**目录应用之后**的跨域不变量区(`boot/stage-02-web-admin-pull.ts`,锚 `CROSS-INVARIANT:model-route-endpoint`)⇒ 拒启 ——
20
+ * 经 {@link judgeServedModelRoute}:目录被采纳 ⇒ 判目录表(目录视图),目录缺席 / 被整拒 / 没有启用条目 ⇒ 判 env 表(env 视图);
21
+ * 严格名册布防(`isRosterGateArmed`)时 env 表是占位、从不被派发 ⇒ 不判它(备用网关那一条与表无关,照判)。
22
+ * 不在 `loadConfig()`:那是目录应用**之前**,env 表还不知道会不会服务(目录一旦采纳就整张换掉它);
23
+ * · 包根导出的 `judgeModelRouteGap(env)`(`model-route-gap.ts`)问的是**env 腿**:「这份 env 交给引擎、没有目录时起不起得来」—— 同一只判据的 env 视图,
24
+ * 目录在场时起服以目录为准;
25
+ * · 热路径:`web-admin-client/apply-effective.ts` 的模型面 stage ⇒ 模型面组拒(opus 腿 O3:同候选其余组照落)、上一代模型面继续服务(目录视图);
26
+ * · run-local:配置源解析完之后按真要派发的表判(`run-local.ts`)—— 本入口没有名册门,不豁免占位;
24
27
  * · 登记:`config-invariants.ts` 的 `model-route-endpoint` 条(表的「不许有表外不变量」职责);
25
28
  * · 派发面:titler / hook 选模型(`hooks/hook-llm.ts`)与降级腿 / 启动摘要(`brain.ts`)求有效端点时调
26
29
  * {@link openaiRouteBaseUrl},不再各写一个 `model.baseUrl || gatewayBaseUrl`。
@@ -34,6 +37,7 @@
34
37
  * `brain.ts` / `hooks/hook-llm.ts` 都可以安全引它。
35
38
  */
36
39
  import type { Model } from "@sema-agent/core";
40
+ import { type ModelTableLeg } from "./model-select.js";
37
41
  /** 机读码(拼法随 `config.*` 家族,按**规则**命名不按键名):一条 openai 路由解析不到端点。 */
38
42
  export declare const MODEL_ROUTE_ENDPOINT_MISSING_CODE = "config.model_route_endpoint_missing";
39
43
  /** 同族第二码:备用网关设了、主网关没设。与上一码分开是因为出路不同(补主网关 / 撤备用 vs 补主网关 / 给条目 baseUrl)。 */
@@ -43,7 +47,7 @@ export declare const GATEWAY_FALLBACK_WITHOUT_PRIMARY_CODE = "config.gateway_fal
43
47
  *
44
48
  * 与上面的**码**是两根轴:码是组拒的机读词(`ModelPlaneRefusalCode`,settings-schema 属主),这里是**出路**:
45
49
  * · `endpoint_missing` —— 缺的是网关端点:openai 兼容腿上的条目解析不到 base URL(或备用网关设了、主网关没设);
46
- * 出路 = 设 `MODEL_GATEWAY_BASEURL`,或给条目自己的 `baseUrl`;
50
+ * 出路 = 设 `MODEL_GATEWAY_BASEURL`,或(目录视图)给条目自己的 `baseUrl` / (env 视图)挂 anthropic 路由、或不设 `MODEL_ID` 让目录缺省服务;
47
51
  * · `lane_credential_missing` —— 缺的是**车道凭据**:条目的 provider 词是 `anthropic`(用户选的是 anthropic 车道),
48
52
  * 却因 `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` 都没有而没装上 anthropic 路由、落进了 openai 腿,那条腿又没有端点;
49
53
  * 出路 = 补那两把钥匙之一(设网关是另一条出路,拒因句一并给);
@@ -75,8 +79,11 @@ export type OpenaiRouteEndpoint = {
75
79
  readonly ok: false;
76
80
  readonly refusal: string;
77
81
  };
78
- /** 单条 openai 路由的有效端点 / 拒因(拒因与部署级判据是**同一只**铸点 {@link routeEndpointMissingRefusal})。 */
79
- export declare function resolveOpenaiRouteEndpoint(routeName: string, routeBaseUrl: string | undefined, gatewayBaseUrl: string | undefined): OpenaiRouteEndpoint;
82
+ /**
83
+ * 单条 openai 路由的有效端点 / 拒因(拒因与部署级判据是**同一只**铸点 {@link routeEndpointMissingRefusal};`leg` = 这条路由所在的表的视图词)。
84
+ * `route` = 这条路由的条目(只读 `baseUrl` 与 `provider`);条目都没有(模型名解不出)⇒ 传 `undefined`,按「无自有 baseUrl、非 anthropic 词」判。
85
+ */
86
+ export declare function resolveOpenaiRouteEndpoint(routeName: string, route: Pick<Model, "baseUrl" | "provider"> | undefined, gatewayBaseUrl: string | undefined, leg: ModelTableLeg): OpenaiRouteEndpoint;
80
87
  /** 部署级判据读的切面(`ServiceConfig` 与 `CrossInvariantView` 都结构满足)。 */
81
88
  export interface ModelRouteEndpointView {
82
89
  readonly gatewayBaseUrl?: string;
@@ -90,13 +97,13 @@ export interface ModelRouteEndpointView {
90
97
  *
91
98
  * 判序即语义:
92
99
  * ① 备用网关设了、主网关没设 ⇒ `config.gateway_fallback_without_primary`(备用是主的镜像,这一条与条目无关,先说);
93
- * ② 逐条目:按 {@link dispatchBrainLane} 落 openai 腿、且 {@link openaiRouteBaseUrl} 解析不到 ⇒ 收集名字,
100
+ * ② 逐条目:按 {@link brainLaneFor}(引擎路由器对本部署路由表的答案)落 openai 腿、且 {@link openaiRouteBaseUrl} 解析不到 ⇒ 收集名字,
94
101
  * 一句话**全部点名**(运维一次改完,而不是改一个撞下一个)。
95
102
  *
96
103
  * `default` 键是目录里另一条的别名(env 腿 `{default: m, [m.id]: m}`、center 腿 `models.default = models[picked]`),
97
104
  * 与别的键指同一只对象时不重复点名;只有它一个键时(手搓夹具)照样点名 `default`。
98
105
  */
99
- export declare function modelRouteEndpointRefusal(view: ModelRouteEndpointView): string | undefined;
106
+ export declare function modelRouteEndpointRefusal(view: ModelRouteEndpointView, leg: ModelTableLeg): string | undefined;
100
107
  /** S-768:部署级判决的结构化形 —— 码与拒因句由**同一次**判决给出(一个不变量 id 两个码,组拒码在判决那刻当数据带出,不从句子里抠)。 */
101
108
  export interface ModelRouteEndpointVerdict {
102
109
  readonly code: typeof GATEWAY_FALLBACK_WITHOUT_PRIMARY_CODE | typeof MODEL_ROUTE_ENDPOINT_MISSING_CODE;
@@ -107,6 +114,39 @@ export interface ModelRouteEndpointVerdict {
107
114
  /**
108
115
  * **判据属主**(S-768 起的结构化形;{@link modelRouteEndpointRefusal} 是它的句子投影):`undefined` = 通过。判序即 {@link modelRouteEndpointRefusal}
109
116
  * 头注所列 —— ① 备用无主先说,② 逐条目解析不到端点全部点名。加一枝 = 在这里同时给出码与句(返回型强制两者同在)。
117
+ * `leg` = `view.models` 是哪一张表({@link ModelTableLeg}):判据本身两视图同一把,**句子**按视图铸(出路在 env 表与目录表上不是同一组)。
118
+ */
119
+ export declare function judgeModelRouteEndpoint(view: ModelRouteEndpointView, leg: ModelTableLeg): ModelRouteEndpointVerdict | undefined;
120
+ /** {@link judgeServedModelRoute} 读的切面:路由判据的切面 + 「服务的是哪张表」的事实(`ServiceConfig` 结构满足)。 */
121
+ export interface ServedModelRouteView extends ModelRouteEndpointView {
122
+ readonly model: object;
123
+ readonly envModel: object;
124
+ }
125
+ /** {@link judgeServedModelRoute} 的旁路入参。 */
126
+ export interface ServedModelRouteOptions {
127
+ /**
128
+ * env 表是不是**占位**(只在服务的是 env 表时有意义)。严格名册布防(`isRosterGateArmed`:远程 registry ∧ 非 dryRun ∧ `CONFIG_REQUIRE_ROSTER=true`)=
129
+ * 运维声明 env `MODEL_ID` 是占位:名册落地前计费提交 503、落地后整表换装 ⇒ env 表从不被派发,不在「能派发到的」射程里(备用网关那一条与表无关,照判)。
130
+ * 布防是中心接入的事实,配置读不出 ⇒ 由调用方给。没有名册门的入口(run-local)传 `false`。
131
+ */
132
+ readonly envTableIsPlaceholder: boolean;
133
+ }
134
+ /**
135
+ * **起服段的路由判官:只判「将要服务的那张表」**。`undefined` = 通过。
136
+ *
137
+ * 服务的是哪张表由 {@link servedModelTableLeg} 读出来,判据与句子随它走(同一只属主判据 {@link judgeModelRouteEndpoint},零第二判官):
138
+ * · 目录被采纳 ⇒ 判目录表(目录视图;模型面 stage 已对同一张表判过同一只判据,这里是装配终点上的复核);
139
+ * · 目录缺席 / 被整拒 / 没有启用条目 ⇒ 判 env 表(env 视图);占位豁免见 {@link ServedModelRouteOptions.envTableIsPlaceholder}。
140
+ *
141
+ * 必须在**目录应用之后**调(装配第 2 段 / run-local 配置源解析完):在那之前没有「将要服务的表」可判 ——
142
+ * 对 env 表提前拒启,会把「条目写进目录、再设 `MODEL_ID`」的部署无谓拒掉(目录一旦采纳,引擎从不读 env `MODEL_ID`)。
143
+ */
144
+ export declare function judgeServedModelRoute(config: ServedModelRouteView, opts: ServedModelRouteOptions): ModelRouteEndpointVerdict | undefined;
145
+ /**
146
+ * **没有目录要应用的直接装配**(开发脚本 / 基准 / 嵌入方的纯 env 形:`loadConfig()` → `createBrain` → `Runner`)的拒启口:服务的表就是 `loadConfig()` 铸的 env 表,
147
+ * 判决 ⇒ 抛(`Error.message` = 判决的 `reason` 原样)。`loadConfig()` 不判路由 —— 没有这一步,缺端点的 env 行会一路跑到第一次模型调用才失败(基准里是十次失败调用、
148
+ * 全零统计、退出 0)。装配第 2 段与 `run-local` 要锚、要在目录应用之后判,调的是 {@link judgeServedModelRoute};本函数只是它的抛出形。
149
+ * 源码类钉(`test/served-model-route.test.ts` ⑨):`src/` 与 `scripts/` 里每个调 `loadConfig()` 的入口,要么在同文件里判过路由,要么是登记在册的例外。
110
150
  */
111
- export declare function judgeModelRouteEndpoint(view: ModelRouteEndpointView): ModelRouteEndpointVerdict | undefined;
151
+ export declare function assertServedModelRoute(config: ServedModelRouteView, opts: ServedModelRouteOptions): void;
112
152
  //# sourceMappingURL=model-route-endpoint.d.ts.map
@@ -1,5 +1,5 @@
1
- import { dispatchBrainLane } from "./model-provider.js";
2
- import { DEFAULT_MODEL_REF } from "./model-select.js";
1
+ import { brainLaneFor, catalogBrainLane, routeTableOf } from "./model-provider.js";
2
+ import { DEFAULT_MODEL_REF, servedModelTableLeg } from "./model-select.js";
3
3
  export const MODEL_ROUTE_ENDPOINT_MISSING_CODE = "config.model_route_endpoint_missing";
4
4
  export const GATEWAY_FALLBACK_WITHOUT_PRIMARY_CODE = "config.gateway_fallback_without_primary";
5
5
  export const MODEL_ROUTE_GAP_WORDS = ["endpoint_missing", "lane_credential_missing", "none"];
@@ -10,14 +10,19 @@ function declared(url) {
10
10
  export function openaiRouteBaseUrl(routeBaseUrl, gatewayBaseUrl) {
11
11
  return declared(routeBaseUrl) ?? declared(gatewayBaseUrl);
12
12
  }
13
- export function resolveOpenaiRouteEndpoint(routeName, routeBaseUrl, gatewayBaseUrl) {
14
- const baseUrl = openaiRouteBaseUrl(routeBaseUrl, gatewayBaseUrl);
15
- return baseUrl !== undefined ? { ok: true, baseUrl } : { ok: false, refusal: routeEndpointMissingRefusal([`"${routeName}"`]) };
13
+ function unresolvedRouteLabel(name, provider) {
14
+ return catalogBrainLane(provider) === "anthropic"
15
+ ? `"${name}" (provider "anthropic", but no Anthropic route is mounted — ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN are unset — so it falls to the openai-compatible lane)`
16
+ : `"${name}"`;
16
17
  }
17
- export function modelRouteEndpointRefusal(view) {
18
- return judgeModelRouteEndpoint(view)?.reason;
18
+ export function resolveOpenaiRouteEndpoint(routeName, route, gatewayBaseUrl, leg) {
19
+ const baseUrl = openaiRouteBaseUrl(route?.baseUrl, gatewayBaseUrl);
20
+ return baseUrl !== undefined ? { ok: true, baseUrl } : { ok: false, refusal: routeEndpointMissingRefusal([unresolvedRouteLabel(routeName, route?.provider)], leg) };
19
21
  }
20
- export function judgeModelRouteEndpoint(view) {
22
+ export function modelRouteEndpointRefusal(view, leg) {
23
+ return judgeModelRouteEndpoint(view, leg)?.reason;
24
+ }
25
+ export function judgeModelRouteEndpoint(view, leg) {
21
26
  const gateway = declared(view.gatewayBaseUrl);
22
27
  if (gateway === undefined && view.gatewayFallbackUrls.length > 0) {
23
28
  const reason = (`env MODEL_GATEWAY_FALLBACK_URLS is set but MODEL_GATEWAY_BASEURL is not [${GATEWAY_FALLBACK_WITHOUT_PRIMARY_CODE}] — ` +
@@ -27,39 +32,56 @@ export function judgeModelRouteEndpoint(view) {
27
32
  "or unset MODEL_GATEWAY_FALLBACK_URLS.");
28
33
  return { code: GATEWAY_FALLBACK_WITHOUT_PRIMARY_CODE, gap: "endpoint_missing", reason };
29
34
  }
30
- const anthropicMounted = view.anthropic !== undefined;
35
+ const routes = routeTableOf(view);
31
36
  const entries = Object.entries(view.models ?? {});
32
37
  const unresolved = [];
33
38
  let laneCredentialMissing = false;
34
39
  for (const [name, m] of entries) {
35
40
  if (name === DEFAULT_MODEL_REF && entries.some(([k, other]) => k !== name && other === m))
36
41
  continue;
37
- if (dispatchBrainLane(m.provider, anthropicMounted) !== "gateway")
42
+ if (brainLaneFor(m.provider, routes) !== "gateway")
38
43
  continue;
39
44
  if (openaiRouteBaseUrl(m.baseUrl, gateway) !== undefined)
40
45
  continue;
41
- const anthropicWordWithoutRoute = m.provider === "anthropic";
42
- laneCredentialMissing ||= anthropicWordWithoutRoute;
43
- unresolved.push(anthropicWordWithoutRoute
44
- ? `"${name}" (provider "anthropic", but no Anthropic route is mounted — ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN are unset — so it falls to the openai-compatible lane)`
45
- : `"${name}"`);
46
+ laneCredentialMissing ||= catalogBrainLane(m.provider) === "anthropic";
47
+ unresolved.push(unresolvedRouteLabel(name, m.provider));
46
48
  }
47
49
  if (unresolved.length === 0)
48
50
  return undefined;
49
- return { code: MODEL_ROUTE_ENDPOINT_MISSING_CODE, gap: laneCredentialMissing ? "lane_credential_missing" : "endpoint_missing", reason: routeEndpointMissingRefusal(unresolved) };
51
+ return { code: MODEL_ROUTE_ENDPOINT_MISSING_CODE, gap: laneCredentialMissing ? "lane_credential_missing" : "endpoint_missing", reason: routeEndpointMissingRefusal(unresolved, leg) };
50
52
  }
51
- function routeEndpointMissingRefusal(quotedNames) {
53
+ function routeEndpointMissingRefusal(quotedNames, leg) {
52
54
  const one = quotedNames.length === 1;
53
55
  const subject = one ? `model ${quotedNames[0]} rides` : `models ${quotedNames.join(", ")} ride`;
54
- return (`${subject} the openai-compatible lane but ${one ? "resolves" : "resolve"} to no endpoint ` +
55
- `[${MODEL_ROUTE_ENDPOINT_MISSING_CODE}] — ${one ? "it has no" : "none has a"} baseUrl of its own, and MODEL_GATEWAY_BASEURL is unset. ` +
56
- "MODEL_GATEWAY_BASEURL has no factory default: the former silent placeholder (this machine's port 8000) let such a " +
56
+ const head = `${subject} the openai-compatible lane but ${one ? "resolves" : "resolve"} to no endpoint [${MODEL_ROUTE_ENDPOINT_MISSING_CODE}] — `;
57
+ const noDefault = "MODEL_GATEWAY_BASEURL has no factory default: the former silent placeholder (this machine's port 8000) let such a " +
57
58
  "deployment start fine and then fail on the first model call, far from the cause (\"fetch failed … ECONNREFUSED\", or " +
58
- "a container dialling its own loopback when compose passes an empty value). " +
59
- `Set it explicitly — for a gateway on this machine: ${LOCAL_GATEWAY_HINT} — or give ${one ? "that model" : "each named model"} its own baseUrl in the catalog. ` +
60
- "(A model defined by MODEL_ID / MODEL_CHEAP_ID has no baseUrl of its own: it needs the gateway, or MODEL_PROVIDER=anthropic " +
61
- "with ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN. It is judged at boot even when a catalog will replace it later — it is what this " +
62
- "process serves whenever no catalog lands: the config source unreachable, a refused candidate, or zero enabled models — unless " +
63
- "CONFIG_REQUIRE_ROSTER=true on a remote registry declares it a placeholder that is never served.)");
59
+ "a container dialling its own loopback when compose passes an empty value). ";
60
+ switch (leg) {
61
+ case "catalog":
62
+ return (head +
63
+ `${one ? "it has no" : "none has a"} baseUrl of its own, and MODEL_GATEWAY_BASEURL is unset. ` +
64
+ noDefault +
65
+ `Set it explicitly — for a gateway on this machine: ${LOCAL_GATEWAY_HINT} — or give ${one ? "that model" : "each named model"} its own baseUrl in the catalog.`);
66
+ case "env":
67
+ return (head +
68
+ `${one ? "it is" : "they are"} defined by env (MODEL_ID / MODEL_CHEAP_ID), which gives ${one ? "it" : "them"} no baseUrl of its own, and MODEL_GATEWAY_BASEURL is unset. ` +
69
+ noDefault +
70
+ `This process serves the env model table because no catalog is in force (none configured, the config source unreachable, the candidate refused, or no enabled model in it). Pick one way out: ` +
71
+ `(1) set MODEL_GATEWAY_BASEURL explicitly — for a gateway on this machine: ${LOCAL_GATEWAY_HINT}; ` +
72
+ "(2) mount the Anthropic route — MODEL_PROVIDER=anthropic with ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN (plus ANTHROPIC_BASE_URL for a non-default endpoint); " +
73
+ "(3) leave MODEL_ID unset and let the default entry of a catalog (config.d/models.json, or the web-admin) serve — a catalog in force replaces the env model table. " +
74
+ "(With CONFIG_REQUIRE_ROSTER=true on a remote registry the env model is declared a placeholder that is never served, and is not judged here.)");
75
+ }
76
+ }
77
+ export function judgeServedModelRoute(config, opts) {
78
+ const leg = servedModelTableLeg(config);
79
+ const view = leg === "env" && opts.envTableIsPlaceholder ? { ...config, models: {} } : config;
80
+ return judgeModelRouteEndpoint(view, leg);
81
+ }
82
+ export function assertServedModelRoute(config, opts) {
83
+ const verdict = judgeServedModelRoute(config, opts);
84
+ if (verdict !== undefined)
85
+ throw new Error(verdict.reason);
64
86
  }
65
87
  //# sourceMappingURL=model-route-endpoint.js.map
@@ -81,8 +81,8 @@ export interface ModelRouteGapOptions {
81
81
  /**
82
82
  * 判官的读数(闭集三词,穷举 switch 消费):
83
83
  * · `none` —— 无缺口,仅此一键;
84
- * · 缺口两词 —— `code` = 起服拒启的机读码(与 `reason` 里的方括号码同一个词;`lane_credential_missing` 也带 `config.model_route_endpoint_missing`),
85
- * `reason` = 起服拒启句原文(点名条目与出路)。
84
+ * · 缺口两词 —— `code` = 机读码(与 `reason` 里的方括号码同一个词;`lane_credential_missing` 也带 `config.model_route_endpoint_missing`),
85
+ * `reason` = 起服判官对 env 表拒启时的句子原文(env 视图:点名条目与三条 env 出路)。
86
86
  */
87
87
  export type ModelRouteGapReading = {
88
88
  readonly gap: "none";
@@ -94,9 +94,10 @@ export type ModelRouteGapReading = {
94
94
  };
95
95
  }[ModelRouteGapBreach];
96
96
  /**
97
- * **起服前可调的模型路由缺口纯判官**(与起服判官同一实现;包根导出)。
97
+ * **起服前可调的模型路由缺口纯判官**(与起服判官判 env 表时同一实现、同一句拒因;包根导出)。答的是 **env 腿**:没有目录在管时,这份 env 起不起得来
98
+ * (目录在场时起服以目录为准,见文件头注)。
98
99
  *
99
- * @param env 一份 env 快照(只读 {@link MODEL_ROUTE_ENV_KEYS} 那八把键;`process.env` 也行 —— 起服段就是这样调的)。
100
+ * @param env 一份 env 快照(只读 {@link MODEL_ROUTE_ENV_KEYS} 那八把键;`process.env` 也行)。
100
101
  * @returns 三词之一;有缺口时带 `code` 与 `reason`(见 {@link ModelRouteGapReading})。
101
102
  */
102
103
  export declare function judgeModelRouteGap(env: ModelRouteEnv, opts?: ModelRouteGapOptions): ModelRouteGapReading;
@@ -72,7 +72,7 @@ export function judgeModelRouteGap(env, opts = {}) {
72
72
  gatewayFallbackUrls: r.gatewayFallbackUrls,
73
73
  anthropic: r.anthropicRouteMounted ? {} : undefined,
74
74
  models,
75
- });
75
+ }, "env");
76
76
  if (verdict === undefined)
77
77
  return { gap: "none" };
78
78
  return { gap: verdict.gap, code: verdict.code, reason: verdict.reason };
@@ -34,6 +34,41 @@ export declare function readAtMention(raw: unknown): AtMention | undefined;
34
34
  * `resolveModel("default", catalog)` 解它)。**单点**:读面要说「这台部署的主模型是谁」时读同一个词
35
35
  * (`auto-mode-face.ts` 的席解析器入参),两处各写一个 `"default"` 字面串正是漂移的成因。 */
36
36
  export declare const DEFAULT_MODEL_REF = "default";
37
+ /**
38
+ * **进程服务的是哪一张模型表**(闭集两词,消费方用穷举 switch —— 加一词不补臂 ⇒ 编译红)。路由端点判官与缺省模型判官的「视图词」:
39
+ * 拒因句的出路按视图说真话,不靠调用方拼句。
40
+ * · `env` —— env 表(`MODEL_ID` / `MODEL_CHEAP_ID` 铸的 `{default, [id]}`):**没有目录在管** —— 没配目录、配置源不可达且无 LKG、
41
+ * 候选被整拒、或候选没有一条启用条目。env 行永远没有自己的 `baseUrl`;
42
+ * · `catalog` —— 目录表:一份目录的模型面落地之后**整张换掉** env 表(`config.model = config.models.default`),引擎从不读 env `MODEL_ID`。
43
+ */
44
+ export declare const MODEL_TABLE_LEGS: readonly ["env", "catalog"];
45
+ export type ModelTableLeg = (typeof MODEL_TABLE_LEGS)[number];
46
+ /**
47
+ * 这个进程**此刻**服务的是哪一张表。读法只有一个事实:`config.model` 与 `config.envModel` 是不是同一个对象 ——
48
+ * `envModel` 在 `loadConfig` 铸一次、此后永不换,并与 `model` 在 boot 那一刻是同一只引用;`model` 的唯一写口是目录的模型面落地
49
+ * (`web-admin-client/apply-effective.ts` 的 commit 段,`config.model = config.models.default`)。所以「`model` 还是那只 env 模型」⇔
50
+ * 没有任何一份目录落过模型面 ⇔ 服务的是 env 表。不另记一个会与它漂开的「目录采纳了吗」旗子。
51
+ */
52
+ export declare function servedModelTableLeg(config: {
53
+ readonly model: object;
54
+ readonly envModel: object;
55
+ }): ModelTableLeg;
56
+ /**
57
+ * 目录服务、而 env 里还设着 `MODEL_ID` ⇒ 启动时说一行(沿 `config_env_set_but_ineffective` 通告族:值合法、却不参与服务)。
58
+ * 这不是拒:目录一旦被采纳就整张换掉 env 表,`MODEL_ID` 在这一形下本来就不是缺省来源(缺省来自目录)—— 运维该知道自己的那一行
59
+ * 没在服务,否则会以为改它有效。env 表服务(或没设 `MODEL_ID`)⇒ `undefined`。
60
+ *
61
+ * 不读 `process.env`:env 的读数在 `loadConfig` 铸 `envModel` 时已定(`envModel.id` 就是 `MODEL_ID`,空串 = 没设)。
62
+ * 返回 plain data(事件名 + 字段)。**唯一发声点** = `web-admin-client/apply-effective.ts` 的 `applyEffective` commit 之后、服务表由 env 换成目录的那一拍(进程一生至多一次):
63
+ * 启动(含 LKG / 按组回落)、`run-local`、迟到 boot、refresh 都经那一个函数,「恰好一次」是构造出来的,没有去重表。
64
+ */
65
+ export declare function buildEnvModelIdNotServedNotice(config: {
66
+ readonly model: Pick<Model, "id" | "name">;
67
+ readonly envModel: Pick<Model, "id">;
68
+ }): {
69
+ readonly event: string;
70
+ readonly fields: Record<string, unknown>;
71
+ } | undefined;
37
72
  /**
38
73
  * 本仓给一条**没选模型**的 task 铸的主模型 —— {@link DEFAULT_MODEL_REF} 在**展开**目录上(与引擎构造期 `expandTiers(models, tiers)`
39
74
  * 之后再解逐字同源)解到的那只**目录条目**(对象引用)。`boot/resolve-spec.ts` 每条 spec 都盖 `spec.model`,所以引擎眼里的 main
@@ -153,6 +188,29 @@ export type ModelImageInput = (typeof MODEL_IMAGE_INPUTS)[number];
153
188
  export declare function modelImageInput(model: {
154
189
  input?: unknown;
155
190
  } | undefined): ModelImageInput;
191
+ /**
192
+ * 「这个目录条目的 `reasoning` 位声明了什么」的**唯一读法**:`true` / `false` 原样,**缺席 = 未声明 = `undefined`**(不替没声明的模型答「是」也不答「否」)。
193
+ * `GET /v1/models` 的 `reasoning` 列与 `GET /v1/config/catalog` 的 `MODEL_REASONING` 行都投影它。非布尔(手写错形)与条目不在,同归未声明:没有可信的声明。
194
+ */
195
+ export declare function modelReasoningDecl(model: {
196
+ reasoning?: unknown;
197
+ } | undefined): boolean | undefined;
198
+ /** 两个能力位的**声明**(三态:`true` / `false` / 缺席 = 未声明)—— 铸 `Model` 上对应键的输入,见 {@link mintCapabilityKeys}。 */
199
+ export interface ModelCapabilityDecl {
200
+ readonly reasoning?: boolean;
201
+ readonly vision?: boolean;
202
+ }
203
+ /**
204
+ * 能力位声明 ⇒ `Model` 上的两个键,**缺席不铸键**:`reasoning` 原样;`vision` 翻成 `input`(`true` ⇒ `["text","image"]`,`false` ⇒ `["text"]`)。
205
+ * env 腿(`MODEL_REASONING` / `MODEL_VISION`)、cheap 槽(`MODEL_CHEAP_*`)与中心腿(`models[].reasoning` / `vision`)都经它铸 —— 三条腿一个铸法、一个「缺席」的意思。
206
+ * 读回的唯一读法是 {@link modelReasoningDecl} 与 {@link modelImageInput}(带图预检与 `GET /v1/models` 同读后者)。
207
+ *
208
+ * 🔴 **本仓唯一的类型接缝**:引擎 7.51.2 的 `Model` 型面把 `reasoning` / `input` 写成必填,而它自己的读法对缺席有定义 ——
209
+ * `input` 缺席 ⇒ 支持图(`modelSupportsVision`:`model?.input === undefined ? true : …`),`reasoning` 缺席 ⇒ 真值判假(`!!model.reasoning`)。所以交给引擎的目录里缺席的键是安全的,
210
+ * 只是型面还没跟上;返回型因此对 `Pick<Model, …>` 说了一句「可能为空」的谎,把断言收在这一处 —— 调用点的 `Model` 字面量照旧带 `Model` 注解(其余每个键仍被 tsc 逐个核,
211
+ * 读表门按注解收字面量的 core 键)。型面归引擎:引擎把这两位放成可选之日,本接缝(返回型 + 这一处断言)整件删除。
212
+ */
213
+ export declare function mintCapabilityKeys(decl: ModelCapabilityDecl): Pick<Model, "reasoning" | "input">;
156
214
  /**
157
215
  * 带图预检:只有目录条目**明说**不收图({@link modelImageInput} 为 `"no"`)才为假 —— 提交时据此计数并告警(引擎会把这些图换成给模型的
158
216
  * 占位句);没声明的条目为真(不出信号,图原样交给模型)。
@@ -7,6 +7,25 @@ export function readAtMention(raw) {
7
7
  return r.success ? r.data : undefined;
8
8
  }
9
9
  export const DEFAULT_MODEL_REF = "default";
10
+ export const MODEL_TABLE_LEGS = ["env", "catalog"];
11
+ export function servedModelTableLeg(config) {
12
+ return config.model === config.envModel ? "env" : "catalog";
13
+ }
14
+ export function buildEnvModelIdNotServedNotice(config) {
15
+ if (servedModelTableLeg(config) !== "catalog" || config.envModel.id === "")
16
+ return undefined;
17
+ const served = config.model.name || config.model.id;
18
+ return {
19
+ event: "config_env_set_but_ineffective",
20
+ fields: {
21
+ env: "MODEL_ID",
22
+ raw: config.envModel.id,
23
+ problem: `a catalog is in force and replaces the env model table — the catalog default "${served}" serves this process, and MODEL_ID (with MODEL_CHEAP_ID) takes no part in it; remove the env line or point the catalog's default at the model you mean`,
24
+ servedBy: "catalog",
25
+ catalogDefault: served,
26
+ },
27
+ };
28
+ }
10
29
  export function resolveMainModel(plane) {
11
30
  const expanded = expandTiers(plane.models, plane.tiers ?? {}) ?? plane.models;
12
31
  return coreResolveTaskModel({ model: DEFAULT_MODEL_REF }, { ...(expanded !== undefined ? { models: expanded } : {}) }).model;
@@ -101,6 +120,18 @@ export function modelImageInput(model) {
101
120
  return "undeclared";
102
121
  return model.input.includes("image") ? "yes" : "no";
103
122
  }
123
+ export function modelReasoningDecl(model) {
124
+ return typeof model?.reasoning === "boolean" ? model.reasoning : undefined;
125
+ }
126
+ const CAPABILITY_MODEL_KEYS = { reasoning: "reasoning", input: "input" };
127
+ export function mintCapabilityKeys(decl) {
128
+ const keys = {};
129
+ if (decl.reasoning !== undefined)
130
+ keys[CAPABILITY_MODEL_KEYS.reasoning] = decl.reasoning;
131
+ if (decl.vision !== undefined)
132
+ keys[CAPABILITY_MODEL_KEYS.input] = decl.vision ? ["text", "image"] : ["text"];
133
+ return keys;
134
+ }
104
135
  export function modelSupportsImages(model) {
105
136
  return modelImageInput(model) !== "no";
106
137
  }