@theoremjs/agents 0.3.0 → 0.3.1

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 (568) hide show
  1. package/README.md +276 -995
  2. package/esm/mod.d.ts +33 -19
  3. package/esm/mod.js +18 -11
  4. package/esm/src/cli/commands/bench.js +36 -14
  5. package/esm/src/cli/commands/detect-compile.d.ts +17 -0
  6. package/esm/src/cli/commands/detect-compile.js +22 -0
  7. package/esm/src/cli/commands/eval.d.ts +3 -3
  8. package/esm/src/cli/commands/fuzz-canary.js +55 -16
  9. package/esm/src/cli/commands/profile.js +4 -4
  10. package/esm/src/cli/commands/run.d.ts +2 -2
  11. package/esm/src/cli/commands/run.js +1 -1
  12. package/esm/src/cli/commands/test.d.ts +4 -3
  13. package/esm/src/cli/commands/test.js +2 -2
  14. package/esm/src/cli/index.js +9 -9
  15. package/esm/src/cli/matrix/fixtures.js +25 -30
  16. package/esm/src/evals/attachments.d.ts +1 -0
  17. package/esm/src/evals/attachments.js +4 -2
  18. package/esm/src/evals/breakdown.d.ts +2 -0
  19. package/esm/src/evals/breakdown.js +1 -0
  20. package/esm/src/evals/graders/answer.d.ts +3 -0
  21. package/esm/src/evals/graders/answer.js +1 -0
  22. package/esm/src/evals/graders/code.d.ts +7 -31
  23. package/esm/src/evals/graders/code.js +4 -25
  24. package/esm/src/evals/graders/judge.d.ts +4 -0
  25. package/esm/src/evals/graders/judge.js +7 -5
  26. package/esm/src/evals/graders/latency.d.ts +1 -0
  27. package/esm/src/evals/graders/latency.js +3 -2
  28. package/esm/src/evals/graders/live.d.ts +3 -0
  29. package/esm/src/evals/graders/live.js +2 -0
  30. package/esm/src/evals/graders/media.js +1 -2
  31. package/esm/src/evals/graders/shared.js +1 -1
  32. package/esm/src/evals/graders/transcript.d.ts +2 -0
  33. package/esm/src/evals/graders/transcript.js +5 -3
  34. package/esm/src/evals/mod.d.ts +1 -9
  35. package/esm/src/evals/mod.js +1 -9
  36. package/esm/src/evals/record.d.ts +6 -0
  37. package/esm/src/evals/record.js +4 -2
  38. package/esm/src/evals/rubrics/mod.d.ts +1 -1
  39. package/esm/src/evals/rubrics/mod.js +1 -1
  40. package/esm/src/evals/rubrics/mustache.js +1 -1
  41. package/esm/src/evals/rubrics/types.js +1 -1
  42. package/esm/src/evals/run.d.ts +9 -52
  43. package/esm/src/evals/run.js +12 -46
  44. package/esm/src/evals/suite.d.ts +12 -3
  45. package/esm/src/evals/suite.js +9 -4
  46. package/esm/src/evals/summary.d.ts +3 -0
  47. package/esm/src/evals/summary.js +1 -0
  48. package/esm/src/evals/trial.d.ts +3 -16
  49. package/esm/src/evals/trial.js +7 -30
  50. package/esm/src/evals/types.d.ts +30 -121
  51. package/esm/src/evals/types.js +3 -16
  52. package/esm/src/evals/verdict.d.ts +5 -0
  53. package/esm/src/evals/verdict.js +4 -1
  54. package/esm/src/guardrails/boundaries.d.ts +41 -0
  55. package/esm/src/guardrails/boundaries.js +103 -0
  56. package/esm/src/guardrails/canary-gate.d.ts +1 -0
  57. package/esm/src/guardrails/canary.d.ts +49 -15
  58. package/esm/src/guardrails/canary.js +103 -30
  59. package/esm/src/guardrails/compile-egress.d.ts +6 -5
  60. package/esm/src/guardrails/compile-egress.js +6 -5
  61. package/esm/src/guardrails/corpus/canary-egress-attacks.d.ts +3 -1
  62. package/esm/src/guardrails/corpus/canary-egress-attacks.js +4 -2
  63. package/esm/src/guardrails/corpus/fuzz-inbound.js +14 -17
  64. package/esm/src/guardrails/corpus/inbound-payloads.d.ts +2 -0
  65. package/esm/src/guardrails/corpus/inbound-payloads.js +4 -2
  66. package/esm/src/guardrails/corpus/live-attacks.d.ts +2 -0
  67. package/esm/src/guardrails/corpus/live-attacks.js +6 -1
  68. package/esm/src/guardrails/corpus/mod.d.ts +1 -1
  69. package/esm/src/guardrails/corpus/secrets.d.ts +3 -3
  70. package/esm/src/guardrails/corpus/secrets.js +2 -2
  71. package/esm/src/guardrails/corpus/strings.d.ts +1 -1
  72. package/esm/src/guardrails/corpus/strings.js +1 -1
  73. package/esm/src/guardrails/corpus/types.d.ts +5 -1
  74. package/esm/src/guardrails/credential-rules.d.ts +36 -0
  75. package/esm/src/guardrails/credential-rules.js +3001 -0
  76. package/esm/src/guardrails/credential-scan.d.ts +45 -0
  77. package/esm/src/guardrails/credential-scan.js +172 -0
  78. package/esm/src/guardrails/destinations.d.ts +45 -0
  79. package/esm/src/guardrails/destinations.js +234 -0
  80. package/esm/src/guardrails/detect-at.d.ts +107 -0
  81. package/esm/src/guardrails/detect-at.js +434 -0
  82. package/esm/src/guardrails/detect-reply.d.ts +61 -0
  83. package/esm/src/guardrails/detect-reply.js +95 -0
  84. package/esm/src/guardrails/detect-stream.d.ts +33 -0
  85. package/esm/src/guardrails/detect-stream.js +77 -0
  86. package/esm/src/guardrails/detectors.d.ts +216 -0
  87. package/esm/src/guardrails/detectors.js +503 -0
  88. package/esm/src/guardrails/egress-automata.d.ts +4 -6
  89. package/esm/src/guardrails/egress-automata.js +24095 -4572
  90. package/esm/src/guardrails/egress-compiler.d.ts +38 -16
  91. package/esm/src/guardrails/egress-compiler.js +254 -60
  92. package/esm/src/guardrails/egress-patterns.d.ts +4 -2
  93. package/esm/src/guardrails/egress-patterns.js +6 -2
  94. package/esm/src/guardrails/egress-stream.d.ts +54 -36
  95. package/esm/src/guardrails/egress-stream.js +253 -124
  96. package/esm/src/guardrails/egress-urls.d.ts +6 -3
  97. package/esm/src/guardrails/egress-urls.js +14 -22
  98. package/esm/src/guardrails/egress.d.ts +26 -86
  99. package/esm/src/guardrails/egress.js +59 -309
  100. package/esm/src/guardrails/error.d.ts +13 -8
  101. package/esm/src/guardrails/error.js +23 -18
  102. package/esm/src/guardrails/event-schemas.d.ts +46 -3
  103. package/esm/src/guardrails/event-schemas.js +12 -5
  104. package/esm/src/guardrails/events.d.ts +5 -1
  105. package/esm/src/guardrails/events.js +4 -1
  106. package/esm/src/guardrails/hints.d.ts +14 -0
  107. package/esm/src/guardrails/hints.js +57 -0
  108. package/esm/src/guardrails/hits.d.ts +7 -1
  109. package/esm/src/guardrails/hits.js +12 -1
  110. package/esm/src/guardrails/host-patterns.d.ts +85 -0
  111. package/esm/src/guardrails/host-patterns.js +173 -0
  112. package/esm/src/guardrails/injection-patterns.d.ts +6 -1
  113. package/esm/src/guardrails/injection-patterns.js +33 -1
  114. package/esm/src/guardrails/injection-reversed.d.ts +9 -0
  115. package/esm/src/guardrails/injection-reversed.js +49 -0
  116. package/esm/src/guardrails/injection.d.ts +17 -1
  117. package/esm/src/guardrails/injection.js +259 -36
  118. package/esm/src/guardrails/labels.d.ts +40 -0
  119. package/esm/src/guardrails/labels.js +874 -0
  120. package/esm/src/guardrails/live-outbound-gate.d.ts +9 -8
  121. package/esm/src/guardrails/live-outbound-gate.js +100 -107
  122. package/esm/src/guardrails/mod.d.ts +29 -21
  123. package/esm/src/guardrails/mod.js +18 -13
  124. package/esm/src/guardrails/network.d.ts +4 -0
  125. package/esm/src/guardrails/network.js +21 -19
  126. package/esm/src/guardrails/normalize.d.ts +15 -1
  127. package/esm/src/guardrails/normalize.js +34 -1
  128. package/esm/src/guardrails/policy.d.ts +2 -17
  129. package/esm/src/guardrails/policy.js +12 -41
  130. package/esm/src/guardrails/progressive-yield.d.ts +65 -24
  131. package/esm/src/guardrails/progressive-yield.js +191 -147
  132. package/esm/src/guardrails/prompt-echo.d.ts +4 -2
  133. package/esm/src/guardrails/prompt-echo.js +87 -6
  134. package/esm/src/guardrails/quota.d.ts +1 -0
  135. package/esm/src/guardrails/quota.js +1 -1
  136. package/esm/src/guardrails/rules.d.ts +20 -29
  137. package/esm/src/guardrails/rules.js +23 -30
  138. package/esm/src/guardrails/sanitize.d.ts +32 -15
  139. package/esm/src/guardrails/sanitize.js +100 -124
  140. package/esm/src/guardrails/sensitive-groups.d.ts +21 -0
  141. package/esm/src/guardrails/sensitive-groups.js +12 -0
  142. package/esm/src/guardrails/sensitive.d.ts +16 -23
  143. package/esm/src/guardrails/sensitive.js +168 -47
  144. package/esm/src/guardrails/serialize.d.ts +1 -0
  145. package/esm/src/guardrails/testing.d.ts +4 -2
  146. package/esm/src/guardrails/testing.js +4 -2
  147. package/esm/src/guardrails/theorem-error.d.ts +4 -1
  148. package/esm/src/guardrails/theorem-error.js +3 -2
  149. package/esm/src/guardrails/thought-guard.d.ts +34 -13
  150. package/esm/src/guardrails/thought-guard.js +264 -64
  151. package/esm/src/guardrails/tool-directives.d.ts +29 -10
  152. package/esm/src/guardrails/tool-directives.js +133 -47
  153. package/esm/src/guardrails/tool-leak.d.ts +26 -0
  154. package/esm/src/guardrails/tool-leak.js +45 -0
  155. package/esm/src/guardrails/tool-result.d.ts +45 -14
  156. package/esm/src/guardrails/tool-result.js +94 -70
  157. package/esm/src/guardrails/types.d.ts +79 -71
  158. package/esm/src/guardrails/types.js +11 -7
  159. package/esm/src/host/client-turn.d.ts +29 -2
  160. package/esm/src/host/client-turn.js +46 -4
  161. package/esm/src/host/mint-trace.d.ts +1 -0
  162. package/esm/src/host/mint-trace.js +3 -2
  163. package/esm/src/host/mod.d.ts +2 -2
  164. package/esm/src/host/mod.js +1 -1
  165. package/esm/src/host/readStreamingJsonStringField.js +2 -43
  166. package/esm/src/host/reply.d.ts +6 -0
  167. package/esm/src/host/reply.js +7 -1
  168. package/esm/src/interface/blocks.d.ts +3 -0
  169. package/esm/src/interface/blocks.js +84 -14
  170. package/esm/src/interface/composer-actions.d.ts +4 -0
  171. package/esm/src/interface/composer-actions.js +5 -3
  172. package/esm/src/interface/draft.d.ts +6 -0
  173. package/esm/src/interface/draft.js +22 -11
  174. package/esm/src/interface/from-profile.d.ts +13 -1
  175. package/esm/src/interface/from-profile.js +66 -31
  176. package/esm/src/interface/history.d.ts +9 -3
  177. package/esm/src/interface/history.js +33 -24
  178. package/esm/src/interface/inputs.d.ts +1 -0
  179. package/esm/src/interface/inputs.js +3 -0
  180. package/esm/src/interface/mod.d.ts +4 -2
  181. package/esm/src/interface/mod.js +3 -2
  182. package/esm/src/interface/models.d.ts +7 -0
  183. package/esm/src/interface/models.js +5 -0
  184. package/esm/src/interface/pending.d.ts +9 -0
  185. package/esm/src/interface/pending.js +9 -3
  186. package/esm/src/interface/profile-interface.js +54 -40
  187. package/esm/src/interface/session.d.ts +12 -5
  188. package/esm/src/interface/session.js +17 -10
  189. package/esm/src/interface/stop.d.ts +13 -0
  190. package/esm/src/interface/stop.js +28 -0
  191. package/esm/src/interface/tool-calls.d.ts +14 -2
  192. package/esm/src/interface/tool-calls.js +64 -10
  193. package/esm/src/interface/tool-invoke.d.ts +2 -0
  194. package/esm/src/interface/tool-invoke.js +3 -1
  195. package/esm/src/interface/tool-media.d.ts +2 -0
  196. package/esm/src/interface/tool-media.js +2 -1
  197. package/esm/src/interface/types.d.ts +105 -40
  198. package/esm/src/interface/types.js +1 -0
  199. package/esm/src/kernel/api-catalog.d.ts +85 -0
  200. package/esm/src/kernel/api-catalog.js +229 -0
  201. package/esm/src/kernel/auth/crypto.d.ts +7 -2
  202. package/esm/src/kernel/auth/crypto.js +12 -10
  203. package/esm/src/kernel/auth/oauth.d.ts +2 -1
  204. package/esm/src/kernel/auth/oauth.js +23 -22
  205. package/esm/src/kernel/auth/typed-secret.js +2 -2
  206. package/esm/src/kernel/auth/types.d.ts +13 -0
  207. package/esm/src/kernel/checkpoint.d.ts +43 -0
  208. package/esm/src/kernel/checkpoint.js +58 -0
  209. package/esm/src/kernel/default-scope.d.ts +43 -5
  210. package/esm/src/kernel/default-scope.js +54 -5
  211. package/esm/src/kernel/engine/call-trace.d.ts +19 -0
  212. package/esm/src/kernel/engine/call-trace.js +29 -0
  213. package/esm/src/kernel/engine/compaction.d.ts +6 -5
  214. package/esm/src/kernel/engine/compaction.js +12 -11
  215. package/esm/src/kernel/engine/decision.d.ts +10 -8
  216. package/esm/src/kernel/engine/decision.js +173 -146
  217. package/esm/src/kernel/engine/embedding.d.ts +8 -0
  218. package/esm/src/kernel/engine/embedding.js +118 -0
  219. package/esm/src/kernel/engine/live-inbound.d.ts +14 -4
  220. package/esm/src/kernel/engine/live-inbound.js +36 -11
  221. package/esm/src/kernel/engine/live-ingress.d.ts +6 -0
  222. package/esm/src/kernel/engine/live-ingress.js +6 -1
  223. package/esm/src/kernel/engine/repair.js +12 -12
  224. package/esm/src/kernel/engine/runner/gates.d.ts +2 -2
  225. package/esm/src/kernel/engine/runner/gates.js +287 -101
  226. package/esm/src/kernel/engine/runner/mod.d.ts +19 -5
  227. package/esm/src/kernel/engine/runner/mod.js +107 -56
  228. package/esm/src/kernel/engine/runner/nested.d.ts +32 -0
  229. package/esm/src/kernel/engine/runner/nested.js +134 -0
  230. package/esm/src/kernel/engine/runner/schema-validation.d.ts +24 -3
  231. package/esm/src/kernel/engine/runner/schema-validation.js +78 -4
  232. package/esm/src/kernel/engine/runner/stages.d.ts +1 -1
  233. package/esm/src/kernel/engine/runner/stages.js +1 -8
  234. package/esm/src/kernel/engine/runner/state.d.ts +33 -12
  235. package/esm/src/kernel/engine/runner/state.js +11 -1
  236. package/esm/src/kernel/engine/runner/step-order.d.ts +27 -0
  237. package/esm/src/kernel/engine/runner/step-order.js +165 -0
  238. package/esm/src/kernel/engine/runner/steps.d.ts +2 -2
  239. package/esm/src/kernel/engine/runner/steps.js +328 -169
  240. package/esm/src/kernel/engine/runner/stream.d.ts +42 -11
  241. package/esm/src/kernel/engine/runner/stream.js +197 -46
  242. package/esm/src/kernel/engine/runner/usage.d.ts +5 -5
  243. package/esm/src/kernel/engine/runner/usage.js +12 -14
  244. package/esm/src/kernel/engine/session/mod.d.ts +8 -6
  245. package/esm/src/kernel/engine/session/mod.js +360 -129
  246. package/esm/src/kernel/engine/session/session-trace.d.ts +10 -10
  247. package/esm/src/kernel/engine/session/session-trace.js +41 -30
  248. package/esm/src/kernel/engine/token-estimate.d.ts +10 -64
  249. package/esm/src/kernel/engine/token-estimate.js +14 -194
  250. package/esm/src/kernel/engine/tool-trace.d.ts +3 -3
  251. package/esm/src/kernel/engine/tool-trace.js +2 -1
  252. package/esm/src/kernel/engine/turn-trace.d.ts +8 -7
  253. package/esm/src/kernel/engine/turn-trace.js +28 -36
  254. package/esm/src/kernel/engine/usage.d.ts +6 -0
  255. package/esm/src/kernel/engine/usage.js +17 -0
  256. package/esm/src/kernel/interaction-parts.d.ts +3 -0
  257. package/esm/src/kernel/interaction-parts.js +18 -0
  258. package/esm/src/kernel/json.d.ts +10 -0
  259. package/esm/src/kernel/json.js +3 -0
  260. package/esm/src/kernel/mod.d.ts +38 -11
  261. package/esm/src/kernel/mod.js +22 -7
  262. package/esm/src/kernel/profile-graph.d.ts +11 -6
  263. package/esm/src/kernel/profile-graph.js +14 -7
  264. package/esm/src/kernel/profile-presence.d.ts +10 -1
  265. package/esm/src/kernel/profile-presence.js +90 -75
  266. package/esm/src/kernel/profile-scope.d.ts +7 -3
  267. package/esm/src/kernel/profile-scope.js +67 -55
  268. package/esm/src/kernel/protocols/binding.d.ts +65 -0
  269. package/esm/src/kernel/protocols/binding.js +125 -0
  270. package/esm/src/kernel/protocols/limits.d.ts +15 -0
  271. package/esm/src/kernel/protocols/limits.js +28 -0
  272. package/esm/src/kernel/protocols/mod.d.ts +5 -0
  273. package/esm/src/kernel/protocols/mod.js +5 -0
  274. package/esm/src/kernel/protocols/prices.d.ts +36 -0
  275. package/esm/src/kernel/protocols/prices.js +37 -0
  276. package/esm/src/kernel/protocols/protocol.d.ts +253 -0
  277. package/esm/src/kernel/protocols/protocol.js +253 -0
  278. package/esm/src/kernel/protocols/provider.d.ts +108 -0
  279. package/esm/src/kernel/protocols/provider.js +143 -0
  280. package/esm/src/kernel/protocols/runtime.d.ts +55 -0
  281. package/esm/src/kernel/protocols/runtime.js +534 -0
  282. package/esm/src/kernel/provider-budget.d.ts +32 -0
  283. package/esm/src/kernel/provider-budget.js +262 -0
  284. package/esm/src/kernel/provider-dispatch.d.ts +13 -0
  285. package/esm/src/kernel/provider-dispatch.js +36 -0
  286. package/esm/src/kernel/provider-live.d.ts +7 -0
  287. package/esm/src/kernel/provider-live.js +189 -0
  288. package/esm/src/kernel/provider-runtime.d.ts +57 -0
  289. package/esm/src/kernel/provider-runtime.js +524 -0
  290. package/esm/src/kernel/registry/attachments.d.ts +15 -4
  291. package/esm/src/kernel/registry/attachments.js +31 -22
  292. package/esm/src/kernel/registry/catalog.d.ts +6 -0
  293. package/esm/src/kernel/registry/catalog.js +9 -2
  294. package/esm/src/kernel/registry/embedding-rules.d.ts +28 -0
  295. package/esm/src/kernel/registry/embedding-rules.js +66 -0
  296. package/esm/src/kernel/registry/ingress.d.ts +6 -2
  297. package/esm/src/kernel/registry/ingress.js +55 -10
  298. package/esm/src/kernel/registry/kernel-registry.d.ts +3 -0
  299. package/esm/src/kernel/registry/kernel-registry.js +9 -2
  300. package/esm/src/kernel/registry/profile-outputs.js +4 -1
  301. package/esm/src/kernel/registry/profiles.d.ts +38 -14
  302. package/esm/src/kernel/registry/profiles.js +280 -205
  303. package/esm/src/kernel/registry/provider-request.d.ts +3 -3
  304. package/esm/src/kernel/registry/provider-request.js +5 -13
  305. package/esm/src/kernel/registry/resolve.d.ts +4 -1
  306. package/esm/src/kernel/registry/resolve.js +59 -58
  307. package/esm/src/kernel/registry/schemas.d.ts +3 -0
  308. package/esm/src/kernel/registry/schemas.js +3 -1
  309. package/esm/src/kernel/registry/slot-fill.d.ts +7 -0
  310. package/esm/src/kernel/registry/slot-fill.js +22 -0
  311. package/esm/src/kernel/registry/system-prompt.js +7 -8
  312. package/esm/src/kernel/registry/vault.d.ts +14 -13
  313. package/esm/src/kernel/registry/vault.js +1 -14
  314. package/esm/src/kernel/schema.d.ts +100 -46
  315. package/esm/src/kernel/schema.js +499 -257
  316. package/esm/src/kernel/scope.d.ts +12 -5
  317. package/esm/src/kernel/scope.js +23 -3
  318. package/esm/src/kernel/stages.d.ts +38 -3
  319. package/esm/src/kernel/stages.js +82 -39
  320. package/esm/src/kernel/stop.d.ts +29 -6
  321. package/esm/src/kernel/stop.js +16 -4
  322. package/esm/src/kernel/system-parts.js +6 -6
  323. package/esm/src/kernel/tools/activity-label.d.ts +12 -6
  324. package/esm/src/kernel/tools/activity-label.js +258 -32
  325. package/esm/src/kernel/tools/agent.d.ts +66 -0
  326. package/esm/src/kernel/tools/agent.js +212 -0
  327. package/esm/src/kernel/tools/coordinate.d.ts +4 -0
  328. package/esm/src/kernel/tools/coordinate.js +165 -0
  329. package/esm/src/kernel/tools/events.d.ts +21 -6
  330. package/esm/src/kernel/tools/events.js +53 -18
  331. package/esm/src/kernel/tools/execute.d.ts +47 -11
  332. package/esm/src/kernel/tools/execute.js +508 -136
  333. package/esm/src/kernel/tools/gate-answer.d.ts +12 -2
  334. package/esm/src/kernel/tools/gate-answer.js +17 -6
  335. package/esm/src/kernel/tools/handler-events.d.ts +45 -0
  336. package/esm/src/kernel/tools/handler-events.js +106 -0
  337. package/esm/src/kernel/tools/harness.d.ts +1 -0
  338. package/esm/src/kernel/tools/harness.js +2 -1
  339. package/esm/src/kernel/tools/http-limit.d.ts +27 -0
  340. package/esm/src/kernel/tools/http-limit.js +85 -0
  341. package/esm/src/kernel/tools/invoke.d.ts +27 -5
  342. package/esm/src/kernel/tools/invoke.js +223 -70
  343. package/esm/src/kernel/tools/mcp-sessions.d.ts +4 -1
  344. package/esm/src/kernel/tools/mcp-sessions.js +13 -8
  345. package/esm/src/kernel/tools/mod.d.ts +4 -3
  346. package/esm/src/kernel/tools/mod.js +3 -2
  347. package/esm/src/kernel/tools/model-text.d.ts +10 -5
  348. package/esm/src/kernel/tools/model-text.js +18 -12
  349. package/esm/src/kernel/tools/operation.d.ts +76 -0
  350. package/esm/src/kernel/tools/operation.js +100 -0
  351. package/esm/src/kernel/tools/page-answer.d.ts +8 -0
  352. package/esm/src/kernel/tools/page-answer.js +19 -0
  353. package/esm/src/kernel/tools/permission.d.ts +11 -2
  354. package/esm/src/kernel/tools/permission.js +12 -12
  355. package/esm/src/kernel/tools/project.d.ts +4 -1
  356. package/esm/src/kernel/tools/project.js +32 -2
  357. package/esm/src/kernel/tools/registry.d.ts +9 -2
  358. package/esm/src/kernel/tools/registry.js +69 -10
  359. package/esm/src/kernel/tools/remote.d.ts +40 -6
  360. package/esm/src/kernel/tools/remote.js +219 -92
  361. package/esm/src/kernel/tools/resolve.d.ts +3 -0
  362. package/esm/src/kernel/tools/resolve.js +12 -4
  363. package/esm/src/kernel/tools/run.d.ts +28 -0
  364. package/esm/src/kernel/tools/run.js +86 -0
  365. package/esm/src/kernel/tools/schema.d.ts +3 -3
  366. package/esm/src/kernel/tools/schema.js +21 -100
  367. package/esm/src/kernel/tools/signed-in-fetch.d.ts +2 -1
  368. package/esm/src/kernel/tools/signed-in-fetch.js +3 -3
  369. package/esm/src/kernel/tools/stage-run.d.ts +27 -4
  370. package/esm/src/kernel/tools/stage-run.js +75 -17
  371. package/esm/src/kernel/tools/types.d.ts +235 -25
  372. package/esm/src/kernel/tools/unchecked-output.d.ts +14 -0
  373. package/esm/src/kernel/tools/unchecked-output.js +18 -0
  374. package/esm/src/kernel/turn-events.d.ts +208 -31
  375. package/esm/src/kernel/turn-events.js +127 -9
  376. package/esm/src/kernel/types.d.ts +302 -161
  377. package/esm/src/kernel/util/base64.d.ts +2 -0
  378. package/esm/src/kernel/util/base64.js +2 -0
  379. package/esm/src/kernel/util/partial-json.d.ts +23 -0
  380. package/esm/src/kernel/util/partial-json.js +128 -0
  381. package/esm/src/kernel/util/record.d.ts +1 -0
  382. package/esm/src/kernel/util/record.js +1 -0
  383. package/esm/src/observability/destinations.d.ts +5 -0
  384. package/esm/src/observability/destinations.js +7 -2
  385. package/esm/src/observability/jsonl.d.ts +2 -2
  386. package/esm/src/observability/jsonl.js +15 -22
  387. package/esm/src/observability/mod.d.ts +3 -3
  388. package/esm/src/observability/mod.js +2 -2
  389. package/esm/src/observability/openinference.d.ts +1 -1
  390. package/esm/src/observability/openinference.js +3 -3
  391. package/esm/src/observability/otlp.d.ts +3 -0
  392. package/esm/src/observability/otlp.js +3 -3
  393. package/esm/src/observability/phoenix.d.ts +1 -0
  394. package/esm/src/observability/policy.d.ts +3 -0
  395. package/esm/src/observability/policy.js +5 -1
  396. package/esm/src/observability/resolve-policy.js +11 -5
  397. package/esm/src/observability/spans.d.ts +8 -2
  398. package/esm/src/observability/spans.js +21 -5
  399. package/esm/src/observability/trace-catalog.d.ts +19 -2
  400. package/esm/src/observability/trace-catalog.js +128 -87
  401. package/esm/src/observability/trace-record.js +22 -17
  402. package/esm/src/observability/trace-schema.d.ts +3 -0
  403. package/esm/src/observability/trace-schema.js +1 -1
  404. package/esm/src/observability/trace-sink.d.ts +1 -0
  405. package/esm/src/observability/trace-sink.js +1 -1
  406. package/esm/src/observability/trace-span.d.ts +7 -1
  407. package/esm/src/observability/trace-span.js +3 -1
  408. package/esm/src/observability/trace.d.ts +2 -0
  409. package/esm/src/observability/trace.js +4 -2
  410. package/esm/src/observability/types.d.ts +35 -11
  411. package/esm/src/observability/types.js +1 -1
  412. package/esm/src/presets/google/speech-voices.d.ts +1 -0
  413. package/esm/src/presets/google-limits.d.ts +1 -0
  414. package/esm/src/presets/google.d.ts +27 -12
  415. package/esm/src/presets/google.js +34 -25
  416. package/esm/src/presets/local.d.ts +26 -0
  417. package/esm/src/presets/local.js +30 -0
  418. package/esm/src/presets/mod.d.ts +5 -4
  419. package/esm/src/presets/mod.js +4 -4
  420. package/esm/src/presets/openrouter.d.ts +14 -1
  421. package/esm/src/presets/openrouter.js +19 -1
  422. package/esm/src/providers/embedding-limits.d.ts +7 -0
  423. package/esm/src/providers/embedding-limits.js +12 -0
  424. package/esm/src/providers/gemini/embedding-protocol.d.ts +8 -0
  425. package/esm/src/providers/gemini/embedding-protocol.js +125 -0
  426. package/esm/src/providers/gemini/interactions-protocol.d.ts +8 -0
  427. package/esm/src/providers/gemini/interactions-protocol.js +105 -0
  428. package/esm/src/providers/gemini/live-protocol.d.ts +3 -0
  429. package/esm/src/providers/gemini/live-protocol.js +191 -0
  430. package/esm/src/providers/gemini/prices.d.ts +3 -0
  431. package/esm/src/providers/gemini/prices.js +63 -0
  432. package/esm/src/providers/gemini/provider.d.ts +7 -0
  433. package/esm/src/providers/gemini/provider.js +39 -0
  434. package/esm/src/providers/google/builtins.d.ts +14 -0
  435. package/esm/src/providers/google/builtins.js +57 -0
  436. package/esm/src/providers/google/grounding.d.ts +4 -4
  437. package/esm/src/providers/google/interactions/framing.d.ts +20 -8
  438. package/esm/src/providers/google/interactions/framing.js +85 -44
  439. package/esm/src/providers/google/interactions/steps.d.ts +5 -5
  440. package/esm/src/providers/google/interactions/steps.js +4 -4
  441. package/esm/src/providers/google/interactions/stream.d.ts +10 -11
  442. package/esm/src/providers/google/interactions/stream.js +33 -100
  443. package/esm/src/providers/google/live/framing.d.ts +15 -5
  444. package/esm/src/providers/google/live/framing.js +25 -25
  445. package/esm/src/providers/google/live/stream.d.ts +6 -44
  446. package/esm/src/providers/google/live/stream.js +3 -209
  447. package/esm/src/providers/google/urls.d.ts +1 -3
  448. package/esm/src/providers/google/urls.js +1 -3
  449. package/esm/src/providers/google/usage.js +2 -2
  450. package/esm/src/providers/live.d.ts +6 -0
  451. package/esm/src/providers/live.js +6 -0
  452. package/esm/src/providers/local/embedding-protocol.d.ts +7 -0
  453. package/esm/src/providers/local/embedding-protocol.js +60 -0
  454. package/esm/src/providers/local/provider.d.ts +7 -0
  455. package/esm/src/providers/local/provider.js +28 -0
  456. package/esm/src/providers/mod.d.ts +28 -7
  457. package/esm/src/providers/mod.js +19 -3
  458. package/esm/src/providers/open-ai/protocol.d.ts +3 -0
  459. package/esm/src/providers/open-ai/protocol.js +189 -0
  460. package/esm/src/providers/openrouter/embedding-protocol.d.ts +6 -0
  461. package/esm/src/providers/openrouter/embedding-protocol.js +86 -0
  462. package/esm/src/providers/openrouter/image.d.ts +0 -9
  463. package/esm/src/providers/openrouter/image.js +0 -153
  464. package/esm/src/providers/openrouter/images-protocol.d.ts +3 -0
  465. package/esm/src/providers/openrouter/images-protocol.js +82 -0
  466. package/esm/src/providers/openrouter/openai/compat.d.ts +2 -2
  467. package/esm/src/providers/openrouter/openai/compat.js +6 -6
  468. package/esm/src/providers/openrouter/openai/image-payload.d.ts +2 -2
  469. package/esm/src/providers/openrouter/openai/reasoning-state.d.ts +2 -0
  470. package/esm/src/providers/openrouter/openai/reasoning-state.js +31 -0
  471. package/esm/src/providers/openrouter/protocol.d.ts +8 -0
  472. package/esm/src/providers/openrouter/protocol.js +171 -0
  473. package/esm/src/providers/openrouter/provider.d.ts +10 -0
  474. package/esm/src/providers/openrouter/provider.js +50 -0
  475. package/esm/src/providers/shared/carried-tool-files.d.ts +7 -0
  476. package/esm/src/providers/shared/carried-tool-files.js +43 -0
  477. package/esm/src/providers/shared/pcm.js +2 -2
  478. package/esm/src/providers/shared/protocol-events.d.ts +8 -0
  479. package/esm/src/providers/shared/protocol-events.js +20 -0
  480. package/esm/src/providers/shared/response-identity.d.ts +2 -2
  481. package/esm/src/providers/shared/retry.d.ts +17 -0
  482. package/esm/src/providers/shared/retry.js +90 -0
  483. package/esm/src/providers/shared/sse.d.ts +0 -4
  484. package/esm/src/providers/shared/sse.js +50 -36
  485. package/esm/src/providers/shared/tool-args.d.ts +6 -7
  486. package/esm/src/providers/shared/tool-args.js +10 -6
  487. package/esm/src/providers/shared/tool-draft.d.ts +16 -0
  488. package/esm/src/providers/shared/tool-draft.js +14 -0
  489. package/esm/src/providers/shared/upstream-tap.d.ts +2 -2
  490. package/esm/src/providers/shared/upstream-tape.js +1 -1
  491. package/esm/src/providers/system-one/protocol.d.ts +8 -0
  492. package/esm/src/providers/system-one/protocol.js +93 -0
  493. package/esm/src/providers/types.d.ts +48 -16
  494. package/esm/src/providers/types.js +2 -1
  495. package/esm/src/providers/typesafe/provider.d.ts +7 -0
  496. package/esm/src/providers/typesafe/provider.js +24 -0
  497. package/esm/src/surface/formats.d.ts +19 -1
  498. package/esm/src/surface/formats.js +68 -29
  499. package/esm/src/surface/mod.d.ts +4 -2
  500. package/esm/src/surface/mod.js +4 -2
  501. package/esm/src/surface/runtime.d.ts +4 -0
  502. package/esm/src/surface/runtime.js +2 -0
  503. package/esm/src/surface/tools.d.ts +2 -8
  504. package/esm/src/surface/tools.js +4 -22
  505. package/esm/src/surface/types.d.ts +6 -0
  506. package/package.json +5 -13
  507. package/esm/src/cli/commands/egress-compile.d.ts +0 -17
  508. package/esm/src/cli/commands/egress-compile.js +0 -25
  509. package/esm/src/guardrails/egress-policy.d.ts +0 -27
  510. package/esm/src/guardrails/egress-policy.js +0 -76
  511. package/esm/src/guardrails/egress-rules.d.ts +0 -54
  512. package/esm/src/guardrails/egress-rules.js +0 -49
  513. package/esm/src/guardrails/lexicon.d.ts +0 -30
  514. package/esm/src/guardrails/lexicon.js +0 -478
  515. package/esm/src/kernel/engine/media-probe/audio.d.ts +0 -7
  516. package/esm/src/kernel/engine/media-probe/audio.js +0 -342
  517. package/esm/src/kernel/engine/media-probe/bytes.d.ts +0 -6
  518. package/esm/src/kernel/engine/media-probe/bytes.js +0 -26
  519. package/esm/src/kernel/engine/media-probe/image.d.ts +0 -5
  520. package/esm/src/kernel/engine/media-probe/image.js +0 -171
  521. package/esm/src/kernel/engine/media-probe/iso-bmff.d.ts +0 -19
  522. package/esm/src/kernel/engine/media-probe/iso-bmff.js +0 -48
  523. package/esm/src/kernel/engine/media-probe/matroska.d.ts +0 -35
  524. package/esm/src/kernel/engine/media-probe/matroska.js +0 -226
  525. package/esm/src/kernel/engine/media-probe/mp4.d.ts +0 -22
  526. package/esm/src/kernel/engine/media-probe/mp4.js +0 -279
  527. package/esm/src/kernel/engine/media-probe/opus.d.ts +0 -4
  528. package/esm/src/kernel/engine/media-probe/opus.js +0 -23
  529. package/esm/src/kernel/engine/media-probe/pdf.d.ts +0 -5
  530. package/esm/src/kernel/engine/media-probe/pdf.js +0 -117
  531. package/esm/src/kernel/engine/media-probe/video.d.ts +0 -15
  532. package/esm/src/kernel/engine/media-probe/video.js +0 -49
  533. package/esm/src/presets/typesafe.d.ts +0 -2
  534. package/esm/src/presets/typesafe.js +0 -2
  535. package/esm/src/providers/create-provider.d.ts +0 -21
  536. package/esm/src/providers/create-provider.js +0 -90
  537. package/esm/src/providers/decision/usage.d.ts +0 -3
  538. package/esm/src/providers/decision/usage.js +0 -25
  539. package/esm/src/providers/google/interactions/mod.d.ts +0 -2
  540. package/esm/src/providers/google/interactions/mod.js +0 -2
  541. package/esm/src/providers/google/keys.d.ts +0 -20
  542. package/esm/src/providers/google/keys.js +0 -87
  543. package/esm/src/providers/google/live/mod.d.ts +0 -7
  544. package/esm/src/providers/google/live/mod.js +0 -7
  545. package/esm/src/providers/google/live/openapi-schema.d.ts +0 -1
  546. package/esm/src/providers/google/live/openapi-schema.js +0 -42
  547. package/esm/src/providers/google/live/session.d.ts +0 -14
  548. package/esm/src/providers/google/live/session.js +0 -150
  549. package/esm/src/providers/local/local.d.ts +0 -12
  550. package/esm/src/providers/local/local.js +0 -186
  551. package/esm/src/providers/local/mod.d.ts +0 -8
  552. package/esm/src/providers/local/mod.js +0 -8
  553. package/esm/src/providers/openrouter/cache-control.d.ts +0 -5
  554. package/esm/src/providers/openrouter/cache-control.js +0 -4
  555. package/esm/src/providers/openrouter/chat.d.ts +0 -64
  556. package/esm/src/providers/openrouter/chat.js +0 -490
  557. package/esm/src/providers/openrouter/openai/sdk-messages.d.ts +0 -10
  558. package/esm/src/providers/openrouter/openai/sdk-messages.js +0 -99
  559. package/esm/src/providers/openrouter/resolve-api-key.d.ts +0 -3
  560. package/esm/src/providers/openrouter/resolve-api-key.js +0 -8
  561. package/esm/src/providers/openrouter/speech.d.ts +0 -18
  562. package/esm/src/providers/openrouter/speech.js +0 -109
  563. package/esm/src/providers/probe.d.ts +0 -5
  564. package/esm/src/providers/probe.js +0 -15
  565. package/esm/src/providers/shared/builtin-wire.d.ts +0 -4
  566. package/esm/src/providers/shared/builtin-wire.js +0 -9
  567. package/esm/src/providers/shared/vault.d.ts +0 -13
  568. package/esm/src/providers/shared/vault.js +0 -41
@@ -3,15 +3,27 @@
3
3
  *
4
4
  * @module
5
5
  */
6
- /** lexicon-exempt-file: authoring field-meta / closed unions — not runtime user or model copy (P2) */
6
+ /** labels-exempt-file: authoring field-meta / closed unions — not runtime user or model copy (P2) */
7
7
  import "../../_dnt.polyfills.js";
8
- import { LEXICON_NOTES } from '../guardrails/lexicon.js';
9
- import { EGRESS_ON_BLOCK, TAINT_GATES } from '../guardrails/types.js';
8
+ import { BOUNDARIES, BOUNDARY_META, recordOf } from '../guardrails/boundaries.js';
9
+ import { DETECT_ACTION_META, DETECT_ACTIONS, DETECT_DEFAULTS, DETECTOR_BOUNDARIES, DETECTOR_META, DETECTORS, } from '../guardrails/detectors.js';
10
+ import { LABEL_NOTES } from '../guardrails/labels.js';
11
+ import { BLOCKED_REPLY_ON_BLOCK, DESTINATION_GATES, TAINT_GATES, } from '../guardrails/types.js';
10
12
  import { GOOGLE_SPEECH_VOICES } from '../presets/google/speech-voices.js';
11
13
  import { PROFILE_FIELD_PRESENCE } from './profile-presence.js';
12
- import { profileFieldScope } from './profile-scope.js';
13
- export { EGRESS_ON_BLOCK };
14
- export const PROFILE_TYPES = ['text', 'image', 'speech', 'live', 'decision', 'host'];
14
+ import { isProfileRootField, profileFieldScope } from './profile-scope.js';
15
+ export { BLOCKED_REPLY_ON_BLOCK };
16
+ /** The kinds of profile: each fixes which fields and models a profile may use. */
17
+ export const PROFILE_TYPES = [
18
+ 'text',
19
+ 'image',
20
+ 'speech',
21
+ 'live',
22
+ 'decision',
23
+ 'embedding',
24
+ 'host',
25
+ ];
26
+ /** Reasoning effort levels, lowest to highest; a model accepts only the levels its catalog row lists. */
15
27
  export const THINKING_LEVELS = [
16
28
  'none',
17
29
  'minimal',
@@ -21,45 +33,40 @@ export const THINKING_LEVELS = [
21
33
  'xhigh',
22
34
  'max',
23
35
  ];
24
- export const PROTOCOLS = ['geminiInteractions', 'geminiLive', 'openAi', 'decision'];
25
- export const PROVIDERS = ['google', 'openrouter', 'local', 'typesafe'];
26
- /** Turn pairs are handled by `createProvider`; decision pairs by `runDecision`. */
27
- export const PROTOCOL_PROVIDERS = {
28
- geminiInteractions: ['google'],
29
- geminiLive: ['google'],
30
- openAi: ['openrouter', 'local'],
31
- decision: ['typesafe', 'openrouter'],
32
- };
33
- export const PROFILE_TYPE_PROTOCOLS = {
34
- text: ['geminiInteractions', 'openAi'],
35
- image: ['geminiInteractions', 'openAi'],
36
- speech: ['geminiInteractions', 'openAi'],
37
- live: ['geminiLive'],
38
- decision: ['decision'],
39
- host: [],
40
- };
41
- export function protocolsForProfileType(type) {
42
- return PROFILE_TYPE_PROTOCOLS[type];
43
- }
44
- export function isValidProfileProtocol(type, protocol) {
45
- return PROFILE_TYPE_PROTOCOLS[type].includes(protocol);
46
- }
47
- /** Letters, digits, `-` and `_`, up to 32 characters, starting with a letter or digit. */
36
+ /** The longest a profile's `id` may be, in characters: room for a dotted or generated one. */
37
+ export const PROFILE_ID_MAX_CHARS = 64;
38
+ /** The longest a profile's `identity.handle` may be, in characters: it is a name people read. */
39
+ export const PROFILE_HANDLE_MAX_CHARS = 32;
40
+ /** Letters, digits, `-`, and `_`, up to 32 characters, starting with a letter or digit. */
48
41
  export const KEY_SLOT_NAME = /^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$/;
42
+ /** True when the value is a legal key slot name. */
49
43
  export function isKeySlotName(value) {
50
44
  return typeof value === 'string' && KEY_SLOT_NAME.test(value);
51
45
  }
46
+ /** The kinds of media a model can take as input. */
52
47
  export const MEDIA_INPUT_KIND_VALUES = ['image', 'audio', 'video', 'document'];
48
+ /** Whether thought summaries are asked for (`auto`) or not (`none`). */
53
49
  export const SUMMARY_MODES = ['auto', 'none'];
50
+ /** Whether the provider call streams (`sse`) or is one buffered call. */
54
51
  export const STREAM_MODES = ['sse', 'buffered'];
52
+ /** Audio encodings a speech profile can return. */
55
53
  export const SPEECH_AUDIO_FORMATS = ['pcm', 'mp3'];
54
+ /** Who can send a turn its context: the browser, or the host's own code. */
55
+ /** How long a caller is away, in milliseconds, before `live.resumed.prompt` is sent, when the profile sets no `afterMs`. */
56
+ export const DEFAULT_RESUMED_AFTER_MS = 3000;
57
+ export const CONTEXT_SENDERS = ['client', 'server'];
58
+ /** What the user starting to speak does to a live model that is mid-reply. */
56
59
  export const LIVE_ACTIVITY_HANDLINGS = ['START_OF_ACTIVITY_INTERRUPTS', 'NO_INTERRUPTION'];
60
+ /** How readily live voice detection decides the user has started speaking. */
57
61
  export const LIVE_START_SENSITIVITIES = [
58
62
  'START_SENSITIVITY_LOW',
59
63
  'START_SENSITIVITY_HIGH',
60
64
  ];
65
+ /** How readily live voice detection decides the user has stopped speaking. */
61
66
  export const LIVE_END_SENSITIVITIES = ['END_SENSITIVITY_LOW', 'END_SENSITIVITY_HIGH'];
67
+ /** What compaction counts against its token limit: the stored history or the next request's input. */
62
68
  export const COMPACTION_METERS = ['history', 'input'];
69
+ /** Whether compaction runs before or after a turn. */
63
70
  export const COMPACTION_TIMINGS = ['before', 'after'];
64
71
  /**
65
72
  * - `compacted`: a summary replaced the compacted messages.
@@ -67,8 +74,11 @@ export const COMPACTION_TIMINGS = ['before', 'after'];
67
74
  * - `dropped`: the compactor failed over `maxTokens`, so the compacted messages were dropped.
68
75
  */
69
76
  export const COMPACTION_OUTCOMES = ['compacted', 'deferred', 'dropped'];
77
+ /** Which part of the prompt is cached: the whole request (`automatic`) or the system instruction only (`system`). */
70
78
  export const CACHE_MODES = ['automatic', 'system'];
79
+ /** How long a provider keeps a cached prompt. */
71
80
  export const CACHE_TTLS = ['5m', '1h'];
81
+ /** Why a turn stopped, as `done.stop.kind` reports it. */
72
82
  export const TURN_STOP_KINDS = [
73
83
  'completed',
74
84
  'length',
@@ -80,12 +90,19 @@ export const TURN_STOP_KINDS = [
80
90
  'provider_error',
81
91
  'cancelled',
82
92
  'stream_incomplete',
93
+ /** The turn made its last allowed model call and that call asked for tools: there is no answer. */
94
+ 'step_limit',
83
95
  'interrupted',
84
96
  /** Live: model finished generating audio/text for this utterance; turn may still be open. */
85
97
  'generation_complete',
86
98
  ];
99
+ /** What a text turn's last allowed model call may do, as `turnBehaviour.atStepLimit` sets it. */
100
+ export const STEP_LIMIT_MODES = ['stop', 'answer'];
101
+ /** What happens to a list item its check refuses, as `outputs.validation.items.*.onFail` sets it. */
102
+ export const ITEM_FAIL_ACTIONS = ['drop', 'fix'];
87
103
  /** Every other stop resumes by another host path, or not at all. */
88
104
  export const CONTINUE_STOP_KINDS = ['length', 'stream_incomplete', 'provider_error'];
105
+ /** The points in a turn where a stage handler runs. */
89
106
  export const TURN_STAGES = [
90
107
  'pre_turn',
91
108
  'pre_tool',
@@ -94,24 +111,29 @@ export const TURN_STAGES = [
94
111
  'post_turn',
95
112
  ];
96
113
  const TURN_STAGE_SET = new Set(TURN_STAGES);
114
+ /** True when the value names a turn stage. */
97
115
  export function isTurnStage(value) {
98
116
  return typeof value === 'string' && TURN_STAGE_SET.has(value);
99
117
  }
100
118
  /** Stages where an inject can land; the inject gate still applies. */
101
119
  export const TURN_INJECT_STAGES = ['pre_turn', 'post_tool', 'before_end'];
102
120
  const TURN_INJECT_STAGE_SET = new Set(TURN_INJECT_STAGES);
121
+ /** True when the value names a stage an inject can land in. */
103
122
  export function isTurnInjectStage(value) {
104
123
  return typeof value === 'string' && TURN_INJECT_STAGE_SET.has(value);
105
124
  }
106
- /** `pre_tool` gates; not `awaiting_user_input`. */
107
- export const TOOL_GATE_KINDS = ['confirmation', 'permission', 'auth'];
125
+ /** `pre_tool` gates; not `awaiting_user_input`. `page` holds a call for the page that answers it. */
126
+ export const TOOL_GATE_KINDS = ['confirmation', 'permission', 'auth', 'page'];
108
127
  /** Why a refused gate settles: the user said no, walked away, or let it run out. */
109
128
  export const TOOL_RESUME_CAUSES = ['declined', 'abandoned', 'expired'];
110
129
  const TOOL_GATE_KIND_SET = new Set(TOOL_GATE_KINDS);
130
+ /** True when the value names a tool gate kind. */
111
131
  export function isToolGateKind(value) {
112
132
  return typeof value === 'string' && TOOL_GATE_KIND_SET.has(value);
113
133
  }
134
+ /** The questions a turn can pause to ask the user. */
114
135
  export const AWAITING_USER_INPUT_KINDS = ['confirm', 'choice', 'text'];
136
+ /** Why a stage handler's result was partly or wholly ignored. */
115
137
  export const STAGE_APPLY_WARNING_CODES = [
116
138
  'affordance_not_allowed',
117
139
  'inject_not_allowed',
@@ -121,22 +143,30 @@ export const STAGE_APPLY_WARNING_CODES = [
121
143
  'deny_invalid',
122
144
  'confirm_invalid',
123
145
  'mutate_invalid',
146
+ 'label_failed_invalid',
124
147
  'abort_invalid',
148
+ 'wait_for_invalid',
125
149
  'unknown_field',
126
150
  'result_invalid',
127
151
  ];
152
+ /** The `status` of a tool output that asks the user a question. */
128
153
  export const AWAITING_USER_INPUT_STATUS = 'awaiting_user_input';
129
154
  /** Enforced by the kernel at resolve time. */
130
- export const TOOL_LOAD_TIERS = ['T0', 'T1', 'T2'];
155
+ export const TOOL_LOAD_TIERS = ['T0', 'T2'];
156
+ /** The HTTP methods an `http` tool may use. */
131
157
  export const HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
132
158
  /** When remote tool auth is missing or expired. */
133
- export const AUTH_UNAUTHENTICATED_POLICIES = ['gate', 'report_to_model'];
134
- export const TOOL_TYPES = ['builtin', 'function', 'http', 'mcp'];
159
+ export const AUTH_UNAUTHENTICATED_POLICIES = ['gate', 'report_to_model', 'fail'];
160
+ /** The kinds of tool a profile can list. */
161
+ export const TOOL_TYPES = ['builtin', 'function', 'http', 'mcp', 'agent'];
162
+ /** How much a tool can change: nothing, data, or irreversibly. */
135
163
  export const TOOL_ACCESS = ['read-only', 'read-write', 'destructive'];
164
+ /** When the user must approve a tool call: never, once per session, or every call. */
136
165
  export const TOOL_PERMISSION = ['auto', 'session_consent', 'always_confirm'];
166
+ /** The credential shapes a remote tool can authenticate with. */
137
167
  export const TOOL_AUTH_TYPES = ['bearer', 'api_key', 'oauth2'];
138
168
  /** Adds UI-only `none`, which omits auth at compile time. */
139
- export const PLAYGROUND_AUTH_TYPES = ['none', ...TOOL_AUTH_TYPES];
169
+ export const STUDIO_AUTH_TYPES = ['none', ...TOOL_AUTH_TYPES];
140
170
  /**
141
171
  * The package's complete media-input vocabulary: `assertMediaMime` refuses any MIME not
142
172
  * here, so hosts keep no second table. Rows follow Google Interactions / Live's documented
@@ -200,6 +230,7 @@ export const MEDIA_WILDCARDS = ['image/*', 'audio/*', 'video/*'];
200
230
  function mimesOf(kind) {
201
231
  return Object.keys(MEDIA_INPUT_KINDS).filter((mime) => MEDIA_INPUT_KINDS[mime] === kind);
202
232
  }
233
+ /** The MIME types a text profile's `accept` entries may list. */
203
234
  export const ATTACHMENT_ACCEPT_MIMES = [
204
235
  'image/*',
205
236
  'video/*',
@@ -219,35 +250,8 @@ export const IMAGE_ATTACHMENT_ACCEPT_MIMES = [
219
250
  ...mimesOf('video'),
220
251
  'application/pdf',
221
252
  ];
253
+ /** The MIME types a voice profile's `accept` entries may list. */
222
254
  export const VOICE_ACCEPT_MIMES = ['audio/*', ...mimesOf('audio')];
223
- export function providersFor(protocol) {
224
- return PROTOCOL_PROVIDERS[protocol];
225
- }
226
- export function protocolsFor(provider) {
227
- const found = [];
228
- for (const protocol of PROTOCOLS) {
229
- if (PROTOCOL_PROVIDERS[protocol].includes(provider)) {
230
- found.push(protocol);
231
- }
232
- }
233
- return found;
234
- }
235
- /** True when `createProvider` will accept this pair. */
236
- export function isValidPair(protocol, provider) {
237
- return PROTOCOL_PROVIDERS[protocol].includes(provider);
238
- }
239
- /** When protocol changes, snap provider to a valid partner. */
240
- export function coerceProvider(protocol, provider) {
241
- const allowed = providersFor(protocol);
242
- const [first] = allowed;
243
- return allowed.includes(provider) ? provider : first;
244
- }
245
- /** When provider changes, snap protocol to a valid partner. */
246
- export function coerceProtocol(protocol, provider) {
247
- const allowed = protocolsFor(provider);
248
- const [first] = allowed;
249
- return allowed.includes(protocol) ? protocol : first;
250
- }
251
255
  function unionType(values) {
252
256
  return values.map((value) => `'${value}'`).join(' | ');
253
257
  }
@@ -273,12 +277,17 @@ export const DYNAMIC_FIELD_PARENTS = new Set([
273
277
  'models',
274
278
  'models.*.efforts',
275
279
  'identity.systemByRole',
276
- 'lexicon',
280
+ 'labels',
277
281
  'inputs.slots',
278
282
  'inputs.limitsByMime',
279
283
  'outputs.validation.fields',
284
+ 'outputs.validation.items',
280
285
  ]);
286
+ /** Turns a key path into its catalog path, replacing host-chosen map keys with `*`. */
281
287
  export function catalogPathFor(keys) {
288
+ const [first] = keys;
289
+ if (keys.length === 1 && first !== undefined && isProfileRootField(first))
290
+ return `profile.${first}`;
282
291
  const resolved = [];
283
292
  for (const key of keys) {
284
293
  const prefix = resolved.join('.');
@@ -291,6 +300,181 @@ export function catalogPathFor(keys) {
291
300
  }
292
301
  return resolved.join('.');
293
302
  }
303
+ const DETECT_ACTION_DOCS = recordOf(DETECT_ACTIONS, (action) => DETECT_ACTION_META[action].doc);
304
+ /** The `allow` rows of a detector: the URLs it lets through, or for `tool_leak` the names. */
305
+ function allowFields(path, detector) {
306
+ if (DETECTOR_META[detector].allow === 'names') {
307
+ return [
308
+ [`${path}.allow`, field('NameAllow', 'The names that are innocent in a reply.')],
309
+ [
310
+ `${path}.allow.names`,
311
+ {
312
+ ...field('string[]', 'Names of tools or parameters that are not read for, such as a tool called search.'),
313
+ unset: 'None',
314
+ },
315
+ ],
316
+ ];
317
+ }
318
+ const hosts = detector === 'ungiven_links'
319
+ ? 'Hostnames whose links pass whatever their URL; the hosts ungiven_images allows pass too.'
320
+ : 'Hostnames whose images load whatever their URL, such as your own CDN.';
321
+ return [
322
+ [
323
+ `${path}.allow`,
324
+ field('UrlAllow', 'What passes besides the URLs the model was given this turn.'),
325
+ ],
326
+ [`${path}.allow.hosts`, { ...field('string[]', hosts), unset: 'None' }],
327
+ [
328
+ `${path}.allow.fromTools`,
329
+ {
330
+ ...field('boolean', 'Whether a URL a tool returned counts as given. Off keeps only what the system prompt, the user, and history gave.'),
331
+ unset: 'On',
332
+ },
333
+ ],
334
+ ];
335
+ }
336
+ const COMPILED_DOC = 'The table your patterns compile to, from compileDetect or `agents detect-compile`. Needed whenever patterns is set.';
337
+ /** The rows of one pattern in a detector's `patterns`. */
338
+ function patternRows(path) {
339
+ const each = `${path}.patterns.*`;
340
+ return [
341
+ [`${each}.name`, field('string', 'What a match is called in the trace.')],
342
+ [
343
+ `${each}.pattern`,
344
+ field('string', 'The source of a regular expression. A pattern takes this or words.'),
345
+ ],
346
+ [
347
+ `${each}.flags`,
348
+ {
349
+ ...field('string', 'Regular-expression flags, each a letter: i ignores case, m matches ^ and $ at each line, s lets . match a line break, u reads Unicode. Not g or y: every match is always found.'),
350
+ unset: 'None',
351
+ },
352
+ ],
353
+ [
354
+ `${each}.words`,
355
+ field('string[]', 'Words to match whole, whatever their case. A space in one matches any run of whitespace.'),
356
+ ],
357
+ ];
358
+ }
359
+ /** The rows of a detector of the host's own, under a key with a dot. */
360
+ function hostDetectorFields(path) {
361
+ const action = (doc) => field('DetectAction', doc, DETECT_ACTIONS, DETECT_ACTION_DOCS);
362
+ return [
363
+ [
364
+ path,
365
+ field('HostDetectorConfig', 'Your own detector, keyed with a dot: acme.codenames. No default action.'),
366
+ ],
367
+ [`${path}.label`, field('string', 'What an editor and the trace call it.')],
368
+ [
369
+ `${path}.action`,
370
+ {
371
+ ...action('What a match does, at every boundary.'),
372
+ unset: 'Only the boundaries at names are read',
373
+ },
374
+ ],
375
+ [`${path}.at`, field('{ [boundary]: DetectAction }', 'An action per boundary, over action.')],
376
+ ...BOUNDARIES.map((boundary) => [
377
+ `${path}.at.${boundary}`,
378
+ { ...action(BOUNDARY_META[boundary].doc), unset: DETECT_ACTION_META.ignore.label },
379
+ ]),
380
+ [
381
+ `${path}.patterns`,
382
+ {
383
+ ...field('HostPattern[]', 'Its patterns, each a name and a regular expression or a list of words, read on the text as written.'),
384
+ unset: 'None',
385
+ },
386
+ ],
387
+ ...patternRows(path),
388
+ [`${path}.compiled`, field('CompiledPatterns', COMPILED_DOC)],
389
+ [
390
+ `${path}.hint`,
391
+ {
392
+ ...field('string', 'One line telling the model what to leave out, sent when this detector blocks a reply that is then retried.'),
393
+ unset: 'The labels’s detect.hint.own, which names the label',
394
+ },
395
+ ],
396
+ [
397
+ `${path}.find`,
398
+ field('HostFind', 'Its own reading, for what patterns cannot say: a function that returns where the matches start and end. It runs on every text crossing a boundary the detector reads, so it returns at once.'),
399
+ ],
400
+ ];
401
+ }
402
+ /** The rows of a detector that reads with patterns: whose it reads with. */
403
+ function patternFields(path) {
404
+ return [
405
+ [
406
+ `${path}.theorem`,
407
+ {
408
+ ...field('boolean', 'Whether Theorem’s own patterns run. Off with no patterns of your own turns the detector off.'),
409
+ unset: 'On',
410
+ },
411
+ ],
412
+ [
413
+ `${path}.patterns`,
414
+ {
415
+ ...field('HostPattern[]', 'Your own patterns, each a name and a regular expression or a list of words. A match takes this detector’s action.'),
416
+ unset: 'None',
417
+ },
418
+ ],
419
+ ...patternRows(path),
420
+ [`${path}.compiled`, field('CompiledPatterns', COMPILED_DOC)],
421
+ [
422
+ `${path}.hint`,
423
+ {
424
+ ...field('string', 'One line telling the model what to leave out, sent when this detector blocks a reply that is then retried. Set it beside patterns of your own.'),
425
+ unset: `The labels’s detect.hint line for this detector`,
426
+ },
427
+ ],
428
+ ];
429
+ }
430
+ /** The settings a detector's full form takes, as a sentence lists them. */
431
+ function settingsOf(detector) {
432
+ const { allow, patterns } = DETECTOR_META[detector];
433
+ if (allow && patterns)
434
+ return 'action, at, allow, theorem, patterns, and hint';
435
+ if (allow)
436
+ return 'action, at, and allow';
437
+ return patterns ? 'action, at, theorem, patterns, and hint' : 'action and at';
438
+ }
439
+ /** The `guardrails.detect` rows: one for the setting, one per detector, and one per detector and boundary. */
440
+ function detectFields() {
441
+ const action = (type, doc) => field(type, doc, DETECT_ACTIONS, DETECT_ACTION_DOCS);
442
+ const rows = [
443
+ [
444
+ 'guardrails.detect',
445
+ action('DetectAction | { [detector]: DetectorRule }', 'What a guardrail does when a detector matches. One action covers every detector; an object sets each detector (or one of your own) apart. Absent: each detector keeps its default action.'),
446
+ ],
447
+ ];
448
+ for (const detector of DETECTORS) {
449
+ const path = `guardrails.detect.${detector}`;
450
+ rows.push([
451
+ path,
452
+ action('DetectAction | DetectorConfig', `${DETECTOR_META[detector].doc} Set one action, or ${settingsOf(detector)}.`),
453
+ ]);
454
+ if (DETECTOR_META[detector].allow)
455
+ rows.push(...allowFields(path, detector));
456
+ if (DETECTOR_META[detector].patterns)
457
+ rows.push(...patternFields(path));
458
+ rows.push([
459
+ `${path}.action`,
460
+ {
461
+ ...action('DetectAction', 'What a match of this detector does at every boundary it reads, unless `at` names that boundary.'),
462
+ unset: 'Each boundary keeps its default',
463
+ },
464
+ ]);
465
+ rows.push([
466
+ `${path}.at`,
467
+ field('{ [boundary]: DetectAction }', 'An action per boundary, over action or the default.'),
468
+ ]);
469
+ for (const boundary of DETECTOR_BOUNDARIES[detector]) {
470
+ const unset = DETECT_ACTION_META[DETECT_DEFAULTS[detector][boundary]].label;
471
+ const meta = action('DetectAction', BOUNDARY_META[boundary].doc);
472
+ rows.push([`${path}.at.${boundary}`, { ...meta, unset }]);
473
+ }
474
+ }
475
+ rows.push(...hostDetectorFields('guardrails.detect.*'));
476
+ return Object.fromEntries(rows);
477
+ }
294
478
  function withScopeAndPresence(fields) {
295
479
  return Object.fromEntries(Object.entries(fields).map(([path, meta]) => {
296
480
  const scope = profileFieldScope(path);
@@ -309,61 +493,59 @@ function withScopeAndPresence(fields) {
309
493
  * take it, and record it in `PROFILE_FIELD_PRESENCE` if it is required or its absence matters.
310
494
  */
311
495
  export const PROFILE_FIELDS = withScopeAndPresence({
312
- id: field('string', 'The name this profile is registered and looked up by; registering the same id again replaces it.'),
313
- type: field("'text' | 'image' | 'speech' | 'live' | 'decision' | 'host'", 'What kind of agent this is, and so what each call gives back: text, image, speech, live, decision or host.', PROFILE_TYPES, {
496
+ 'profile.id': field('string', 'The name this profile is registered and looked up by, at most 64 characters; registering the same id again replaces it.'),
497
+ 'profile.type': field("'text' | 'image' | 'speech' | 'live' | 'decision' | 'embedding' | 'host'", 'What kind of agent this is, and so what each call gives back: text, image, speech, live, decision, embedding, or host.', PROFILE_TYPES, {
314
498
  text: 'A chat or task agent. Each turn the model answers in text, or in JSON when outputs ask for a structured reply, and can call tools on the way.',
315
- image: 'Makes pictures from a prompt, in the shape, size and format the image block sets.',
499
+ image: 'Makes pictures from a prompt, in the shape, size, and format the image block sets.',
316
500
  speech: 'Gives your app a voice. Each turn reads text aloud in the voice the speech block names.',
317
- live: 'For talking in real time. One continuous voice and video session over Gemini Live, rather than separate turns.',
318
- decision: 'For when your app needs a judgement, not a reply. It answers questions about a JSON state, each with a choice, a score or a number.',
319
- host: 'A governed passthrough to your tool registry, with no model. It calls MCP, HTTP and in-app function tools under the same permissions, guardrails and traces as any agent.',
501
+ live: 'For talking in real time. One continuous voice and video session, rather than separate turns.',
502
+ decision: 'For when your app needs a judgement, not a reply. It answers questions about a JSON state, each with a choice, a score, or a number.',
503
+ embedding: 'For when your app needs vectors from text or provider-supported media.',
504
+ host: 'A governed passthrough to your tool registry, with no model. It calls MCP, HTTP, and in-app function tools under the same permissions and traces as any agent, guarded by detect and network.',
320
505
  }),
321
506
  identity: field('{ handle, system?, systemByRole? }', "The agent's display name and the system instruction it is given."),
322
- 'identity.handle': field('string', "The agent's display name for hosts and users (the model never sees it), and the systemByRole entry a turn gets when it names no known role."),
323
- 'identity.system': field('string | Array<string | { private: string }>', 'The instruction the model gets on every turn unless a systemByRole entry replaces it; OpenRouter image profiles send it only with includeText on. Mark what must not leak as { private: text }; only that is guarded. With none, the whole prompt is.'),
507
+ 'identity.handle': field('string', 'The name hosts and users see for this agent, at most 32 characters; the model never sees it. It is also the role a turn gets when it names none, so identity.systemByRole may hold an entry under it.'),
508
+ 'identity.system': field('string | Array<string | { private: string }>', 'The instruction the model gets on every turn unless a systemByRole entry replaces it; some providers send it for an image profile only with includeText on. Mark what must not leak as { private: text }; only that is guarded. With none, the whole prompt is.'),
324
509
  'identity.systemByRole': field('Record<string, string | Array<string | { private: string }>>', 'Other system instructions, picked by the role a turn names.'),
325
510
  'identity.systemByRole.*': field('string | Array<string | { private: string }>', 'The instruction that replaces identity.system when a turn names this role.'),
326
- models: field('Record<ModelId, ModelBinding | DecisionModelBinding>', 'The models this profile can use, each under a name you choose.'),
511
+ models: field('Record<ModelId, ModelBinding | DecisionModelBinding>', 'The trained AI systems that turn input into results. Each entry binds a local name to an upstream model and its call settings.'),
327
512
  'models.*': field('ModelBinding | DecisionModelBinding', 'One model: how to reach it and the settings sent with it.'),
328
- 'models.*.protocol': field(unionType(PROTOCOLS), 'The API this model is called through: Gemini, OpenAI-style, or the Decisions API.', PROTOCOLS, {
329
- geminiInteractions: "Google's Gemini Interactions API.",
330
- geminiLive: "Google's Gemini Live streaming API.",
331
- openAi: 'The OpenAI-style API, served by OpenRouter or a local server.',
332
- decision: 'The typed Decisions API, served by TypeSafe or OpenRouter.',
333
- }),
334
- 'models.*.provider': field(unionType(PROVIDERS), 'Who serves the model: Google, OpenRouter, a local server, or TypeSafe.', PROVIDERS, {
335
- google: "Google's Gemini API.",
336
- openrouter: 'OpenRouter, which routes to many model vendors.',
337
- local: "A server you run (Ollama, llama.cpp, vLLM); it can't serve image profiles.",
338
- typesafe: 'TypeSafe, serving its native decision models.',
339
- }),
340
- 'models.*.apiId': field('string', "The model's name at the provider."),
513
+ 'models.*.protocol': field('string', 'The API shape this model is spoken to in.'),
514
+ 'models.*.provider': field('string', 'The service that runs the model or routes requests to one, such as Gemini or OpenRouter. Its registration supplies the endpoint and authentication.'),
515
+ 'models.*.keySlot': field('KeySlot', "The key slot this model's calls are signed with, instead of the provider's. Use it to bill one model to a different key. Absent: the provider's primary slot."),
516
+ 'models.*.fallbackKeySlot': field('KeySlot', "The key slot tried once after a rate limit on the primary slot, instead of the provider's fallback. Must differ from the primary slot. Absent: the provider's fallback slot, if it has one."),
517
+ 'models.*.options': field('JsonObject', "Settings only this model's protocol has, such as a task type or vector size. Each is checked when the profile loads. Any left out keep the provider's default."),
518
+ providerContinuation: field('ProviderContinuationPolicy', 'Who holds the conversation a saved provider state continues.'),
519
+ 'providerContinuation.history': field("'host' | 'stateless' | 'provider'", 'Who holds the conversation a checkpoint continues: the host passes history back, the provider keeps it and the checkpoint travels alone, or the profile is stateless and every request stands alone.', ['host', 'stateless', 'provider']),
520
+ 'models.*.apiId': field('string', "The model's id as the provider lists it, such as gemini-2.5-flash; sent on every call. The name this model has in the profile is its key in models."),
341
521
  'models.*.timeoutMs': field('number', 'How long a decision waits for its provider, in milliseconds. Omit for no timeout.'),
342
522
  decision: field('{ contract: DecisionContractId }', 'Names the host decision this profile makes.'),
343
- 'decision.contract': field('string', "Your name for this decision, recorded on its traces; it isn't sent to the model or checked against the questions."),
344
- 'models.*.efforts': field('Record<string, ThinkingLevel>', 'Named thinking levels for this model, which a turn can pick between when allowEffortSelect is on; local models ignore them.'),
523
+ 'decision.contract': field('string', 'Your name for this decision, on its traces. Not sent to the model. Not checked against the questions.'),
524
+ 'models.*.efforts': field('Record<string, ThinkingLevel>', 'Named thinking levels. A turn picks one when allowEffortSelect is on. A provider refuses a level its model does not take. Speech ignores them, and so may an image profile without includeText.'),
345
525
  'models.*.efforts.*': field(unionType(THINKING_LEVELS), 'The thinking level sent to the provider for this name.', THINKING_LEVELS, {
346
- none: 'No thinking; OpenRouter only.',
526
+ none: 'No thinking, where the provider has that level.',
347
527
  minimal: 'The least thinking.',
348
528
  low: 'Light thinking.',
349
529
  medium: 'Moderate thinking.',
350
530
  high: 'Heavy thinking.',
351
- xhigh: 'Heavier thinking; OpenRouter only.',
352
- max: 'The most thinking the model supports; OpenRouter only.',
531
+ xhigh: 'Heavier thinking, where the provider has that level.',
532
+ max: 'The most thinking the model supports, where the provider has that level.',
353
533
  }),
354
534
  'models.*.defaultEffort': field('string', "The effort a turn gets when it doesn't pick one."),
355
535
  'models.*.allowEffortSelect': field('boolean', 'Lets a turn pick an effort; needs two or more efforts.'),
356
- 'models.*.summaries': field('boolean', 'Asks Gemini Interactions for summaries of its thinking; on OpenRouter, off only hides thoughts, and other providers ignore it.'),
357
- 'models.*.maxOutputTokens': field('number', 'The most tokens the model may write in one reply.'),
358
- 'models.*.temperature': field('number', "How varied the model's wording is; higher is more random."),
359
- 'models.*.builtInTools': field('BuiltinToolId[]', "The provider's own tools, such as Google Search, this model may use; each must be registered as a builtin tool."),
360
- 'models.*.key': field('KeySlot', "The key slot this model's calls use, ahead of the profile's key."),
361
- 'models.*.fallbackKey': field('KeySlot', "The key slot this model's calls retry on when its key is refused for quota, ahead of the profile's fallbackKey."),
362
- 'models.*.compaction': field('CompactionSpec', 'Summarises older history once it grows past a threshold; the agent writes the summary unless profile names another.'),
536
+ 'models.*.summaries': field('boolean', 'Asks the model for summaries of its thinking, which not every model sends; with some providers, off only hides thoughts, and others ignore it.'),
537
+ 'models.*.maxOutputTokens': field('number', 'The token limit for one reply. Some providers never send it for speech, or for image without includeText.'),
538
+ 'models.*.temperature': field('number', "How varied the model's wording is; higher is more random. Some providers never send it for speech, or for image without includeText."),
539
+ 'models.*.builtInTools': field('BuiltinToolId[]', "The provider's own tools, such as Google Search, this model may use; each must be registered as a builtin tool and declared by the provider's adapter."),
540
+ 'models.*.compaction': field('CompactionSpec', 'Summarises history past a threshold. A text agent can write the summary. Other types name a compaction profile.'),
363
541
  'models.*.compaction.maxTokens': field('number', 'The token budget compactAt is a fraction of.'),
364
- 'models.*.compaction.compactAt': field('number', 'The fraction of maxTokens, between 0 and 1, at which compaction starts.'),
365
- 'models.*.compaction.previousExchanges': field('number', 'How much recent history compaction keeps: a number of exchanges (1 or more), a fraction of maxTokens (between 0 and 1), or 0 to compact it all.'),
366
- 'models.*.compaction.profile': field('ProfileId', 'The profile that writes the summary; register it before this one. Leave it out and the agent summarises its own history, with its own instructions and model and no tools.'),
542
+ 'models.*.compaction.trigger': field('(ctx: CompactionTriggerContext) => boolean | Promise<boolean>', 'Your function that decides when to compact, in place of compactAt and maxTokens.'),
543
+ 'models.*.compaction.compactAt': field('number', 'The fraction of maxTokens, between 0 and 1, at which compaction starts, unless trigger decides.'),
544
+ 'models.*.compaction.previousExchanges': field('number', 'How much recent history compaction keeps: a number of exchanges (1 or more), a fraction of maxTokens below compactAt, or 0 to compact it all.'),
545
+ 'models.*.compaction.profile': {
546
+ ...field('ProfileId', 'The text profile that writes the summary; register it before this one. Leave it out and a text agent summarises its own history, with its own instructions and model and no tools. Its registered model binding selects the provider independently of the calling agent.'),
547
+ unset: 'The agent itself',
548
+ },
367
549
  'models.*.compaction.timing': field(unionType(COMPACTION_TIMINGS), 'Whether the kernel compacts before the turn or tells the host to compact after it.', COMPACTION_TIMINGS, {
368
550
  before: 'The kernel compacts before the turn runs.',
369
551
  after: "The turn's done event says compaction is due, and the host runs it.",
@@ -372,67 +554,62 @@ export const PROFILE_FIELDS = withScopeAndPresence({
372
554
  history: 'Tokens in the earlier conversation.',
373
555
  input: 'The prompt tokens the provider reported for the last call, or the count the host passes in.',
374
556
  }),
375
- 'models.*.cache': field('CacheSpec', 'Prompt caching on OpenRouter: reuses the start of a prompt it has already seen.'),
376
- 'models.*.cache.mode': field(unionType(CACHE_MODES), 'Which part of the prompt is cached.', CACHE_MODES, {
377
- automatic: 'The whole request, so the cached part grows with the conversation.',
378
- system: 'The system instruction only; nothing is cached without one.',
379
- }),
380
- 'models.*.cache.ttl': field(unionType(CACHE_TTLS), 'How long a cached prompt lasts.', CACHE_TTLS, {
381
- '5m': 'Five minutes.',
382
- '1h': 'One hour; writing the cache costs more, which pays off in long sessions.',
383
- }),
384
- 'models.*.store': field('boolean', 'Whether Google stores the interaction (Gemini Interactions only); a turn can override it.'),
385
- 'models.*.persistViaInteractionId': field('boolean', 'Whether Google builds the context from its stored interaction (true) or every call sends the history the host passes plus the steps of this turn (false); Gemini Interactions only, and true needs storing on.'),
386
- 'models.*.server': field('string', "Which local server runs the model (ollama, vllm, …), recorded on traces; it doesn't change where requests go."),
387
- defaultModel: field('ModelId', "The model a turn gets when it doesn't pick one."),
388
- allowModelSelect: field('boolean', 'Lets a turn pick a model; needs two or more models.'),
389
- maxSteps: field('number', 'The most model calls one turn may make while using tools, where 1 runs the tools asked for but never sends their results back; live sessions ignore it.'),
390
- key: field('KeySlot', 'The key slot used when a model has no key of its own.'),
391
- fallbackKey: field('KeySlot', 'The key slot a call retries on once when its key is refused for quota. Off unless set.'),
392
- tools: field('{ allow: ToolId[]; t1Policy?; t2Loader? }', 'Which of your registered tools this profile may use, and how they load; built-in tools are turned on per model.'),
557
+ 'profile.defaultModel': field('ModelId', "The model a turn gets when it doesn't pick one."),
558
+ 'profile.allowModelSelect': field('boolean', 'Lets a turn name the model it runs on; a name the profile does not declare is refused.'),
559
+ 'profile.maxSteps': field('number', 'The most model calls one turn may make while using tools, counted afresh for each rewrite; 1 runs the tools asked for but never sends their results back. Live sessions ignore it.'),
560
+ tools: field('{ allow: ToolId[]; t1Policy?; t2Loader?; parallel? }', 'The registered tools this profile may use, and how they load. Built-in tools are set per model.'),
393
561
  'tools.allow': field('ToolId[]', "Your registered tools this agent may use; the provider's own tools go on the model's builtInTools instead."),
394
- 'tools.t1Policy': field('(ctx) => ToolId[] | Promise<ToolId[]>', 'Your function that picks, at the start of each turn, which T1 tools to load.'),
395
- 'tools.t2Loader': field('ToolId', 'The function tool the model calls to load T2 tools; it must be in allow and return the ids to load.'),
562
+ 'tools.t1Policy': field('(ctx) => ToolId[] | Promise<ToolId[]>', 'Your function that picks, at the start of each turn, which T2 tools to load up front; the rest wait for tools.t2Loader.'),
563
+ 'tools.t2Loader': field('ToolId', "The function tool that loads T2 tools. It must be in allow and return { loaded: ToolId[] }. One forbidden id fails the load. Ids off the turn path are skipped. The tools it loads can be called from the next step: a step's calls see the tools as they stood when the step began."),
564
+ 'tools.parallel': field('boolean', 'true: the calls of one step run side by side, and a call whose pre_tool answers waitFor starts its body once those calls settle. Left out or false: they run one at a time, in the order the model made them. A step is the calls the model makes together in a turn, or the calls a host runs through invokeToolStep.'),
396
565
  inputs: field('ProfileInputsSpec | DecisionInputsSpec', 'What a turn may send (text, files, voice, choices), or on a decision, the state it reads.'),
397
566
  'inputs.state': field("'json'", 'A decision reads JSON state, passed on each call.', ['json']),
398
567
  'inputs.maxStateBytes': field('number', 'The largest state, in bytes, a decision call may send; larger state is refused before it leaves your process.'),
399
568
  'inputs.text': field('boolean', 'Whether a turn may send text.'),
400
569
  'inputs.attachments': field('{ accept: string[] }', 'Lets turns send files.'),
401
- 'inputs.attachments.accept': field('string[]', 'The file types a turn may send, where type/* matches a whole family; image profiles take only images, video and PDF.', ATTACHMENT_ACCEPT_MIMES, "Types the kernel knows; a listed type it doesn't know is refused when a turn sends it."),
570
+ 'inputs.attachments.accept': field('string[]', 'The file types a turn may send, where type/* matches a whole family; image profiles take only images, video, and PDF.', ATTACHMENT_ACCEPT_MIMES, "Types the kernel knows; a listed type it doesn't know is refused when a turn sends it."),
402
571
  'inputs.voice': field('{ accept: string[] }', 'Lets turns send voice clips.'),
403
572
  'inputs.voice.accept': field('string[]', 'The audio types a voice clip may be; audio/* matches them all.', VOICE_ACCEPT_MIMES, 'Audio types the kernel knows.'),
404
- 'inputs.maxFiles': field('number', 'The most files and voice clips one turn may send, counted together.'),
405
- 'inputs.maxBytes': field('number', 'The largest file or clip, in bytes, a turn may send inline.'),
406
- 'inputs.maxTurnBytes': field('number', 'The most bytes one turn may send inline across all its files and clips.'),
573
+ 'inputs.maxFiles': field('number', 'The most files and voice clips one turn or embedding request may send, counted together.'),
574
+ 'inputs.maxBytes': field('number', 'The largest file or clip, in bytes, a turn or embedding request may send inline.'),
575
+ 'inputs.maxTurnBytes': field('number', 'The most bytes one turn or embedding request may send inline across all its files and clips.'),
407
576
  'inputs.limitsByMime': field('Record<string, number>', 'Size limits by file type that replace maxBytes for those types.'),
408
- 'inputs.limitsByMime.*': field('number', 'The largest file of this type (or type/* family), in bytes, a turn may send inline.'),
409
- 'inputs.slots': field('Record<string, string[]>', 'Named choices a turn can make, such as language, each with its allowed values.'),
577
+ 'inputs.limitsByMime.*': field('number', 'The largest file of this type (or type/* family), in bytes, a turn or embedding request may send inline.'),
578
+ 'inputs.slots': field('Record<string, string[]>', 'Named choices a turn or a call can make, such as language, each with its allowed values. {name} in a prompt becomes the chosen value.'),
410
579
  'inputs.slots.*': field('string[]', 'The values a turn may choose for this slot; any other is refused.'),
580
+ 'inputs.context': field('{ from: ContextSender[]; maxChars: number }', 'Lets the page or the host send the agent one package of things to know, read at the context boundary.'),
581
+ 'inputs.context.from': field("Array<'client' | 'server'>", 'Who may send context; context from anyone else is refused.', CONTEXT_SENDERS, {
582
+ client: 'The browser. A visitor can change it, so it is untrusted.',
583
+ server: "The host's own code. It is assembled, like turn system text.",
584
+ }),
585
+ 'inputs.context.maxChars': field('number', 'The longest package, in characters of its JSON, each sender may send.'),
411
586
  outputs: field('ProfileOutputsSpec', 'The shape of the reply, how it is checked, and how it streams.'),
412
- 'outputs.structured': field('StructuredSchemaId | StructuredBySlot | null', 'The registered JSON schema the reply must follow, which can vary by slot.'),
413
- image: field('ProfileImageSpec', 'Settings for the images this profile makes.'),
587
+ 'outputs.structured': field('StructuredSchemaId | StructuredBySlot | null', 'The registered JSON schema the reply must follow, which can vary by slot; register it before the profile.'),
588
+ image: field('ProfileImageSpec', 'How the images this profile generates are shaped and made: size, format, quality and references. Each setting is sent only where the provider has it; one it lacks is refused or ignored, as that setting says.'),
414
589
  'image.aspectRatio': field('string', 'The shape of generated images, such as 16:9.'),
415
590
  'image.resolution': field('string', 'How detailed generated images are, such as 1K, 2K or 4K.'),
416
- 'image.mimeType': field('string', 'The file type of generated images; OpenRouter takes png, jpeg or webp and uses png for anything else.'),
417
- 'image.quality': field('string', 'How much effort the model spends on each image; OpenRouter takes auto, low, medium or high.'),
418
- 'image.background': field('string', 'The background of generated images; OpenRouter takes auto, transparent or opaque.'),
419
- 'image.n': field('number', 'How many images one request makes; only OpenRouter `/images` takes it, up to 10.'),
420
- 'image.seed': field('number', 'A seed for repeatable images; OpenRouter `/images` and Google take it, where the model honours it.'),
421
- 'image.outputCompression': field('number', 'Compression from 0 to 100 for jpeg and webp images; OpenRouter only.'),
422
- 'image.references': field('Array<TurnBlob | TurnMediaRef>', 'Reference images sent with every turn, ahead of the ones the user attaches; each is bytes (`data`) or a link (`uri`).'),
423
- 'image.includeText': field('boolean', 'Whether the model may write text alongside its images; on OpenRouter this also sends the system instruction and history.'),
424
- speech: field('ProfileSpeechSpec', 'Settings for the audio this profile speaks.'),
425
- 'speech.voice': field('string', 'The voice the speech is read in.'),
426
- 'speech.format': field(unionType(SPEECH_AUDIO_FORMATS), 'The audio format asked of the provider.', SPEECH_AUDIO_FORMATS, {
591
+ 'image.mimeType': field('string', 'The file type of generated images; a provider that takes a fixed set of types may use png for any other.'),
592
+ 'image.quality': field('string', 'How much effort the model spends on each image: auto, low, medium, or high. A provider that has no such setting refuses it.'),
593
+ 'image.background': field('string', 'The background of generated images: auto, transparent, or opaque. A provider that has no such setting refuses it.'),
594
+ 'image.n': field('number', 'How many images one request makes, where the provider takes a count.'),
595
+ 'image.seed': field('number', 'A seed for repeatable images, where the provider and the model honour it.'),
596
+ 'image.outputCompression': field('number', 'Compression from 0 to 100 for jpeg and webp images, where the provider takes it.'),
597
+ 'image.references': field('Array<TurnBlob | TurnMediaRef>', "Images sent with every turn, before the user's. Each is bytes (`data`) or a link (`uri`). Some providers take http(s) links only, and none with includeText."),
598
+ 'image.includeText': field('boolean', 'Whether the model may write text alongside its images; with some providers this also sends the system instruction and history.'),
599
+ speech: field('ProfileSpeechSpec', "How the profile's speech sounds and is delivered: voice, style, speed and audio format. A turn may replace voice, style and speed for that turn."),
600
+ 'speech.voice': field('string', "The voice the speech is read in, by the provider's own name for it; a turn's speech.voice replaces it."),
601
+ 'speech.style': field('string', "How the script is delivered, in plain words (pace, mood, accent); it is never read aloud, and a turn's speech.style replaces it. A model that takes no direction ignores it."),
602
+ 'speech.speed': field('number', "The speaking rate, where 1 is the voice's own pace; a turn's speech.speed replaces it. A provider that has no rate refuses it or ignores it."),
603
+ 'speech.format': field(unionType(SPEECH_AUDIO_FORMATS), "The audio encoding each turn's speech comes back in, and so what the host plays or stores.", SPEECH_AUDIO_FORMATS, {
427
604
  pcm: 'Uncompressed audio, delivered as WAV.',
428
- mp3: 'Compressed audio; OpenRouter only.',
605
+ mp3: 'Compressed audio; a provider that cannot make it refuses the turn.',
429
606
  }),
430
- live: field('ProfileLiveSpec', 'Settings for the realtime voice and video session.'),
607
+ live: field('ProfileLiveSpec', 'How a realtime voice and video session runs: what it accepts from the user, the voice it answers in, when it hears the user start and stop, and how it resumes and trims its context.'),
431
608
  'live.ingress': field('LiveIngressSpec', 'Which channels the session accepts from the user.'),
432
609
  'live.ingress.audio': field('boolean', 'Whether the session accepts microphone audio.'),
433
610
  'live.ingress.video': field('boolean', 'Whether the session accepts camera frames.'),
434
611
  'live.ingress.text': field('boolean', 'Whether the session accepts typed text.'),
435
- 'live.voice': field('string', 'The voice the model speaks in.', GOOGLE_SPEECH_VOICES, "Google's voice names; any other name is sent as written."),
612
+ 'live.voice': field('string', 'The voice the model speaks in.', GOOGLE_SPEECH_VOICES, 'Voice names to pick from; any other name is sent as written.'),
436
613
  'live.vad': field('LiveVadSpec', 'How the session detects when the user starts and stops speaking.'),
437
614
  'live.vad.activityHandling': field(unionType(LIVE_ACTIVITY_HANDLINGS), "Whether the user speaking interrupts the model's reply.", LIVE_ACTIVITY_HANDLINGS, {
438
615
  START_OF_ACTIVITY_INTERRUPTS: 'The user speaking cuts the reply off.',
@@ -448,22 +625,30 @@ export const PROFILE_FIELDS = withScopeAndPresence({
448
625
  }),
449
626
  'live.vad.prefixPaddingMs': field('number', 'How long speech must last, in milliseconds, before it counts as the user starting to speak.'),
450
627
  'live.vad.silenceDurationMs': field('number', "How long a silence, in milliseconds, ends the user's speech."),
451
- 'live.sessionResumption': field('boolean', 'Whether Google sends resume handles, which the host can pass to a new session to carry this one on.'),
628
+ 'live.sessionResumption': field('boolean', 'Whether the provider sends resume handles, which the host can pass to a new session to carry this one on.'),
629
+ 'live.greeting': field('string', 'A prompt the call sends itself when it opens new, so the agent speaks first. The visitor does not see it. It may use {slot}.'),
630
+ 'live.resumed': field('{ prompt: string; afterMs?: number }', 'What happens when a call comes back after a drop. It needs live.sessionResumption.'),
631
+ 'live.resumed.prompt': field('string', 'A prompt the call sends itself when it resumes, so the agent says it is back. The visitor does not see it. It may use {slot}.'),
632
+ 'live.resumed.afterMs': field('number', 'The shortest time away, in milliseconds, that earns the prompt; a shorter drop resumes silently.'),
452
633
  'live.contextCompression': field('LiveContextCompressionSpec', "Trims the session's context once it grows past the trigger."),
453
634
  'live.contextCompression.triggerTokens': field('number', 'The context size, in tokens, that starts trimming.'),
454
635
  'live.contextCompression.slidingWindow': field('LiveSlidingWindowSpec', 'Trims by dropping the oldest turns; the system instruction stays.'),
455
636
  'live.contextCompression.slidingWindow.targetTokens': field('number', 'The context size, in tokens, to trim down to; below the trigger.'),
456
637
  'live.transcription': field('LiveTranscriptionSpec', "Text transcripts of the session's speech."),
457
638
  'live.transcription.input': field('boolean', "Whether the user's speech is transcribed."),
458
- 'live.transcription.output': field('boolean', "Whether the model's speech is transcribed."),
639
+ 'live.transcription.output': field('boolean', "Whether the model's speech is transcribed; always on while a detector reads live_reply, since it reads the transcript."),
459
640
  'outputs.validation': field('ProfileValidationSpec', 'Your checks on a structured reply, and how many rewrites a failure gets.'),
460
- 'outputs.validation.fields': field('Record<string, ProfileValidator>', 'Your checks, each keyed by a dotted path into the structured reply, such as diagram.mermaid.'),
461
- 'outputs.validation.fields.*': field('(candidate: unknown, slots?: Record<string, string>) => ValidationResult | Promise<ValidationResult>', 'Your check on this part of the reply; a failure sends its error back to the model to rewrite.'),
462
- 'outputs.validation.maxRetries': field('number', 'How many times the model may rewrite a reply that fails your checks before it goes out as it is; the larger of this and egress.maxRetries applies to both.'),
641
+ 'outputs.validation.fields': field('Record<string, ProfileValidator>', 'Your checks, each keyed by a dotted path into the structured reply, such as diagram.mermaid; a path no structured schema reaches is refused when the profile registers.'),
642
+ 'outputs.validation.fields.*': field('(candidate: unknown, slots?: Record<string, string>) => ValidationResult | Promise<ValidationResult>', 'Your check on this part of the reply, reached through object properties only; a failure sends its error back to the model to rewrite while retries remain.'),
643
+ 'outputs.validation.items': field('Record<string, ProfileItemRule>', 'Your checks on the items of a list, each keyed by the dotted path of that list in the structured reply, such as claims; a path no structured schema reaches as a list is refused when the profile registers.'),
644
+ 'outputs.validation.items.*': field('ProfileItemRule', 'Your check on each item of this list. A refused item never fails the reply and the rest of the reply is kept.'),
645
+ 'outputs.validation.items.*.check': field('(item: unknown, turn: ItemCheckTurn) => ValidationResult | Promise<ValidationResult>', 'Your check on one item, given what the tools of the turn returned (turn.toolResults); its error is the reason the model reads on a rewrite and the host reads on the removed item.'),
646
+ 'outputs.validation.items.*.onFail': field("'drop' | 'fix'", 'drop removes a refused item. fix first asks the model to rewrite the refused items alone, once and while retries remain, then removes any refused again. Removed items leave on the structured event as removed.', ITEM_FAIL_ACTIONS),
647
+ 'outputs.validation.maxRetries': field('number', "How many times the model may rewrite a reply that fails your checks or the schema's required keys before it goes out as it is; under sse, an attempt that gets rewritten has already streamed. A rewrite of a blocked reply (guardrails.blockedReply) counts against this too."),
463
648
  'outputs.streaming': field('ProfileStreamingSpec', 'How the reply reaches the host, and whether its thinking does.'),
464
- 'outputs.streaming.mode': field(unionType(STREAM_MODES), 'Whether the provider call streams.', STREAM_MODES, {
465
- sse: 'Events arrive as the model writes.',
466
- buffered: 'One non-streaming call; its events arrive together when it answers.',
649
+ 'outputs.streaming.mode': field(unionType(STREAM_MODES), 'Whether the reply reaches the host as the model writes it, or whole.', STREAM_MODES, {
650
+ sse: 'Events arrive as the model writes, so a reply that fails outputs.validation has already streamed when it is rewritten; some providers always answer image and speech in one piece.',
651
+ buffered: 'One non-streaming call, whose events arrive together; under outputs.validation, only once the reply passes or retries run out. Thinking still streams.',
467
652
  }),
468
653
  'outputs.streaming.streamThoughts': field('boolean', "Whether the model's thinking reaches the host."),
469
654
  turnBehaviour: field('ProfileTurnBehaviourSpec', 'Whether a reply that stopped early can be continued, and whether a running turn can be steered.'),
@@ -473,181 +658,238 @@ export const PROFILE_FIELDS = withScopeAndPresence({
473
658
  stream_incomplete: 'The connection dropped mid-reply.',
474
659
  provider_error: 'The provider returned an error or timed out.',
475
660
  }),
476
- 'turnBehaviour.resumption.autoContinue': field('ContinueStopKind[]', 'Which early stops the host continues once without asking; each must also be in allowContinue.', CONTINUE_STOP_KINDS, {
661
+ 'turnBehaviour.resumption.autoContinue': field('ContinueStopKind[]', 'Which early stops are continued without asking, whether or not allowContinue offers a Continue for them. When set, a text turn sends a listed failed model call that gave no reply again itself; the host continues the rest.', CONTINUE_STOP_KINDS, {
477
662
  length: 'The reply hit the output token limit.',
478
663
  stream_incomplete: 'The connection dropped mid-reply.',
479
664
  provider_error: 'The provider returned an error or timed out.',
480
665
  }),
481
- 'turnBehaviour.resumption.maxContinues': field('number', 'How many times one reply may be continued; once set, each continue must say which one it is.'),
482
- 'turnBehaviour.allowSteering': field('boolean', 'Whether your onStage hook may add messages to a running turn; text profiles also offer the user a way to steer.'),
483
- guardrails: field('ProfileGuardrailsSpec', "Protections for this profile's turns."),
484
- 'guardrails.quota': field('QuotaGuardrailSpec', "A daily turn limit your server middleware enforces; runTurn itself doesn't count turns."),
485
- 'guardrails.quota.perDay': field('number', 'Turns each client IP may run on this profile per UTC day.'),
486
- 'guardrails.canary': field('boolean', 'Plants a secret token in the system instruction and stops the reply if the model repeats it.'),
487
- 'guardrails.promptEcho': field('boolean', 'With the canary on, also stops a reply that repeats 12 words in a row of the private system instruction. On by default; mark the lines meant to be quoted as shareable parts, or turn it off when the whole instruction is meant to be quoted.'),
488
- 'guardrails.sanitizeInput': field('boolean', 'Replaces prompt-injection text in user input, history and tool results before the model sees it.'),
489
- 'guardrails.redactSensitive': field('boolean | SensitiveSwitches', 'Replaces credentials and personal data in user input, history and tool results before the model sees them. true (the default) covers every group, false none; an object turns groups off one by one.'),
490
- 'guardrails.redactSensitive.ids': field('boolean', 'US SSN, ITIN and EIN numbers.'),
491
- 'guardrails.redactSensitive.financial': field('boolean', 'IBANs and card numbers.'),
492
- 'guardrails.redactSensitive.network': field('boolean', 'IPv4 and IPv6 addresses.'),
493
- 'guardrails.redactSensitive.credentials': field('boolean', 'API keys, access tokens, bearer tokens and private keys.'),
494
- 'guardrails.egress': field('ProfileEgressSpec', 'Your check on the reply before the user sees it.'),
495
- 'guardrails.egress.enforce': field('EgressEnforcer', 'Your own check on the reply, run as it streams and when it ends. Return allow, flag (log only), redact (swap in your text) or block (see onBlock); a block or redact holds the rest of the stream, and a throw counts as a block. Set this or checks, not both.'),
496
- 'guardrails.egress.checks': field('boolean | EgressChecks', 'The bundled checks on the reply, blocking on what they find. true runs each at its default, false none but the system-prompt leak checks, and an object switches the ones it names. Set this or enforce, not both.'),
497
- 'guardrails.egress.checks.sensitive': field('boolean | SensitiveSwitches', 'Credentials and personal data in the reply, by group. Every group but network by default: an address in a reply is not a secret.'),
498
- 'guardrails.egress.checks.sensitive.ids': field('boolean', 'US SSN, ITIN and EIN numbers.'),
499
- 'guardrails.egress.checks.sensitive.financial': field('boolean', 'IBANs and card numbers.'),
500
- 'guardrails.egress.checks.sensitive.network': field('boolean', 'IPv4 and IPv6 addresses.'),
501
- 'guardrails.egress.checks.sensitive.credentials': field('boolean', 'API keys, access tokens, bearer tokens and private keys.'),
502
- 'guardrails.egress.checks.boundary': field('boolean', 'The markers the kernel puts around user data, and the canary note, repeated in the reply.'),
503
- 'guardrails.egress.checks.injection': field('boolean', 'Prompt-injection phrasing in the reply, as written or disguised.'),
504
- 'guardrails.egress.checks.images': field('boolean | UrlCheck', 'Images in the reply that load a URL the model was not given, which would send data off the device with no click.'),
505
- 'guardrails.egress.checks.images.hosts': field('string[]', 'Hostnames whose images load whatever their URL, such as your own CDN.'),
506
- 'guardrails.egress.checks.images.fromTools': field('boolean', 'Whether a URL a tool returned counts as given. Off keeps only what the system prompt, the user and history gave.'),
507
- 'guardrails.egress.checks.links': field('boolean | UrlCheck', 'Links in the reply to a URL the model was not given. Turn it on when your app unfurls links into previews.'),
508
- 'guardrails.egress.checks.links.hosts': field('string[]', 'Hostnames whose links pass whatever their URL; the images hosts pass too.'),
509
- 'guardrails.egress.checks.links.fromTools': field('boolean', 'Whether a URL a tool returned counts as given. Off keeps only what the system prompt, the user and history gave.'),
510
- 'guardrails.egress.onBlock': field(unionType(EGRESS_ON_BLOCK), 'What happens when enforce blocks the reply.', EGRESS_ON_BLOCK, {
511
- reject_to_agent: 'The model reads why and rewrites the reply, up to maxRetries times; once retries run out, the reply is withheld and the turn ends with a safety error.',
512
- refuse_to_user: 'The user sees the egress.refusal wording in place of the reply, and the turn ends.',
666
+ 'turnBehaviour.resumption.maxContinues': field('number', 'How many times one reply may be continued; once set, each continue must say which one it is. Also how many failed model calls one text turn sends again itself (one when unset).'),
667
+ 'turnBehaviour.allowSteering': field('boolean', 'Lets your onStage hook add messages to a running turn. Text profiles also let the user steer.'),
668
+ 'turnBehaviour.atStepLimit': field("'stop' | 'answer'", 'What happens when a turn reaches maxSteps while the model still wants tools: the turn stops with no answer, or the last call is made without tools so it answers.', STEP_LIMIT_MODES, {
669
+ stop: 'It may still call tools; if it does, they run and the turn stops as step_limit with no answer.',
670
+ answer: 'It is sent without tools, so it writes the answer from what the turn has gathered.',
671
+ }),
672
+ guardrails: field('ProfileGuardrailsSpec', 'Theorem checks what enters or leaves an agent’s context and generation against built-in or custom rules at each configured boundary.'),
673
+ 'guardrails.quota': field('QuotaGuardrailSpec', 'A daily turn limit. Your server enforces it with takeSlot.'),
674
+ 'guardrails.quota.perDay': field('number', 'Turns each client IP may run on this profile per UTC day, one at a time; counts live in the process, and a loopback caller is not counted.'),
675
+ ...detectFields(),
676
+ 'guardrails.blockedReply': field('BlockedReplySpec', 'How a reply a guardrail blocked is handled: the model rewrites it, or the user gets a refusal, and how many rewrites it gets.'),
677
+ 'guardrails.blockedReply.onBlock': field(unionType(BLOCKED_REPLY_ON_BLOCK), 'Whether the model gets to rewrite a reply a guardrail blocked, or the user gets a refusal in its place.', BLOCKED_REPLY_ON_BLOCK, {
678
+ retry: 'The model reads why and rewrites the reply, up to maxRetries times; once retries run out, the reply is withheld and the turn ends with a safety error. Live never rewrites: it withholds at once.',
679
+ refuse: 'The user sees the egress.refusal wording in place of the reply, and the turn ends.',
513
680
  }),
514
- 'guardrails.egress.maxRetries': field('number', 'How many times the model may rewrite a blocked reply; the larger of this and outputs.validation.maxRetries applies to both.'),
515
- 'guardrails.egress.holdback': field('number', 'How many characters the stream holds back so your own enforce can catch text split across chunks (default 256; 96 on Live). The bundled policy holds exactly what it needs and ignores this.'),
516
- 'guardrails.network': field('NetworkGuardrailSpec', 'Which addresses your HTTP and MCP tools may reach.'),
681
+ 'guardrails.blockedReply.maxRetries': field('number', 'How many times the model may rewrite a blocked reply. Live ignores it.'),
682
+ 'guardrails.network': field('NetworkGuardrailSpec', 'Addresses HTTP and MCP tools may reach.'),
517
683
  'guardrails.network.allowPrivateNetworks': field('boolean', 'Lets tools reach localhost and private network addresses, for local development.'),
518
- 'guardrails.network.allowedHosts': field('string[]', "Hostnames tools may reach even when they resolve to a private address; public hosts don't need listing."),
684
+ 'guardrails.network.allowedHosts': field('string[]', "Hostnames tools may reach even when they resolve to a private address; public hosts don't need listing. Names are resolved only when the host passes resolveHost."),
519
685
  'guardrails.network.allowedSchemes': field('string[]', 'The URL schemes tools may use.'),
520
- 'guardrails.taint': field('TaintGuardrailSpec', "What tools may still do after the turn reads a remote tool's result."),
686
+ 'guardrails.taint': field('TaintGuardrailSpec', 'Tool limits after a remote read.'),
521
687
  'guardrails.taint.afterRemoteRead': field(unionType(TAINT_GATES), "Which tool calls are refused, by each tool's access, once the turn has read a remote tool's result.", TAINT_GATES, {
522
688
  off: 'None; risky calls are only logged.',
523
689
  destructive: 'Destructive calls.',
524
690
  write: 'Read-write and destructive calls.',
525
691
  }),
692
+ 'guardrails.taint.remoteDestination': field(unionType(DESTINATION_GATES), "What happens to a tool call, read-only ones too, that sends to an email address, a link's host or a bank account number (IBAN) that only the running text of a remote tool's result named. One the user, the system prompt, or a local tool gave passes, and so does one that is a whole field of the result.", DESTINATION_GATES, {
693
+ off: 'The call runs; it is only logged.',
694
+ confirm: 'The call waits for the user to approve it; an approval runs it.',
695
+ block: 'The call is refused, and the model is told why.',
696
+ }),
526
697
  'guardrails.disclosure': field('{ enforce: DecisionDisclosureEnforcer }', 'Your check on the state before a decision sends it to the model.'),
527
698
  'guardrails.disclosure.enforce': field('(state, context) => DecisionDisclosureVerdict | Promise<DecisionDisclosureVerdict>', 'Your function that allows or blocks the state before it is sent.'),
528
- lexicon: field('LexiconOverrides', "This profile's own wording for error lines, notices and notes to the model, by lexicon key."),
529
- 'lexicon.*': field('string', 'The wording for this lexicon key; canary.bind_note must keep its {canary} placeholder.'),
699
+ labels: field('LabelOverrides', "This profile's own wording for error lines, notices, and notes to the model, by labels key."),
700
+ 'labels.*': field('string', 'The wording for this labels key; canary.bind_note must keep its {canary} placeholder.'),
530
701
  observability: field('ProfileObservabilitySpec', "Where this profile's traces go and what they keep."),
531
- 'observability.writeTo': field('false | string | TraceSink', 'Where traces are written: a destination id you registered, your own writer, or false for none; a sink passed straight to runTurn replaces it.'),
532
- 'observability.sampleRate': field('number', 'The share of traces kept, from 0 to 1, each kept or dropped whole.'),
702
+ 'observability.writeTo': field('false | string | TraceSink', 'Where traces go: a registered destination id, your own writer, or false. An unregistered id fails the turn at the start. A sink passed to runTurn replaces it.'),
703
+ 'observability.sampleRate': field('number', 'The share of traces kept, from 0 to 1, each kept or dropped whole; a sink passed straight to runTurn keeps every trace.'),
533
704
  'observability.include': field('TraceIncludeSpec', 'Which optional parts of a trace are kept.'),
534
- 'observability.include.upstreamLog': field('boolean', 'Keeps each row the provider sent back, scrubbed, as it arrived.'),
705
+ 'observability.include.upstreamLog': field('boolean', 'Keeps each row the provider sent back, scrubbed, as it arrived, with media bytes replaced by their sha256.'),
535
706
  'observability.include.outboundWire': field('boolean', 'Keeps each request body sent to the provider, scrubbed.'),
536
- 'observability.include.evidenceRaw': field('boolean', "Keeps the provider's raw payload on grounding results such as search citations."),
537
- 'observability.include.usage': field('boolean', 'Keeps token counts and other usage figures.'),
707
+ 'observability.include.evidenceRaw': field('boolean', "Keeps the provider's raw payload on grounding results such as search citations; upstreamLog rows keep their own copy."),
708
+ 'observability.include.usage': field('boolean', 'Keeps token counts and other usage figures on spans; upstreamLog rows keep their own.'),
538
709
  'observability.include.guardrailDecisions': field('boolean', 'Keeps each guardrail decision.'),
539
- 'observability.include.guardrailMatchPreview': field('boolean', "Keeps the exact text each guardrail matched, on the trace and the host's event stream; for debugging only."),
710
+ 'observability.include.guardrailMatchPreview': field('boolean', "Keeps the exact text each guardrail matched, unscrubbed, on the trace and the host's event stream; for debugging only."),
540
711
  'observability.resource': field('Record<string, TraceAttributeValue>', 'Attributes stamped on every trace, such as service.name.'),
541
712
  'observability.scrub': field('TraceScrubSpec', "What is removed from stored traces, separately from the turn's guardrails."),
542
- 'observability.scrub.sensitive': field('boolean', 'Removes credentials and personal data from stored traces.'),
543
- 'observability.scrub.injection': field('boolean', 'Removes prompt-injection text from stored traces.'),
544
- 'observability.scrub.canary': field('boolean', 'Removes the canary token from stored traces.'),
545
- 'observability.retainForDays': field('number', 'How many days each trace is kept; 0 or less keeps them forever.'),
713
+ 'observability.scrub.sensitive': field('ScrubSwitch', "Removes what the data detectors (IDs, Financial, Network, Credentials) and your own detectors find from stored traces. On, it cleans with the patterns each reads the turn with; { theorem, host } picks Theorem's patterns, yours, or both for the trace alone."),
714
+ 'observability.scrub.injection': field('ScrubSwitch', "Removes what Injection and Tool instructions find from stored traces. On, it cleans with the patterns each reads the turn with; { theorem, host } picks Theorem's patterns, yours, or both for the trace alone."),
715
+ 'observability.scrub.canary': field('ScrubSwitch', "Removes the canary token from stored traces. The canary is Theorem's, so { theorem: false } keeps it."),
716
+ 'observability.retainForDays': field('number', 'Days a trace file is kept, by its UTC day. The next write removes old files. 0 or less keeps all.'),
546
717
  'observability.rotateAfterMiB': field('number', 'File size in MiB at which a file-based destination starts a new file, handed to every destination with the record. Default 32.'),
547
- 'observability.onWriteError': field('(err: unknown) => void', "Your function called when a trace can't be built or written; the turn carries on, and a destination's own error handler takes priority."),
718
+ 'observability.onWriteError': field('(err: unknown) => void', "Your function called when a trace can't be built or written; the turn carries on, and a destination's own error handler takes priority. An unregistered writeTo id is a config error instead."),
548
719
  });
549
720
  const TOOL_TYPE_FIELD = field(unionType(TOOL_TYPES), 'How the tool runs.', TOOL_TYPES, {
550
721
  builtin: "The provider's own tool, such as Google Search, run by the provider.",
551
722
  function: 'Your handler runs it.',
552
723
  http: 'The kernel calls your HTTP endpoint.',
553
724
  mcp: 'The kernel calls a tool on a remote MCP server.',
725
+ agent: 'The kernel runs one turn of another registered agent and returns its reply.',
554
726
  });
555
727
  const CREDENTIAL_KINDS = {
556
728
  bearer: 'A token; a secret typed in at the prompt becomes a bearer token.',
557
729
  ['api_' + 'key']: 'An API key; a secret typed in at the prompt becomes an API key.',
558
730
  oauth2: 'An OAuth sign-in; the host supplies the token.',
559
731
  };
732
+ /**
733
+ * The catalog of the fields `registerTool` takes, keyed `registerTool.<field>`. The `studio.` and
734
+ * `local.` rows are the studio's own; a builder never sets them.
735
+ */
560
736
  export const EXTRA_FIELDS = {
561
- /** Playground / UI path — avoids collision with profile `type` in fieldMeta(). */
737
+ 'done.stop.kind': field('TurnStopKind', 'Why the turn ended, on the done event: its stop.kind.', TURN_STOP_KINDS, {
738
+ completed: 'The model finished its reply.',
739
+ length: 'The model reached its token limit.',
740
+ tool: 'The model called tools and the turn hands them to the host, in done.tools.',
741
+ gate: 'A pre_tool stage held a tool call. The host resumes it with invokeTool, not continueFrom.',
742
+ filtered: 'The provider filtered the reply, or the egress guardrail withheld it.',
743
+ provider_error: 'The provider failed or reported an error.',
744
+ cancelled: 'The turn was cancelled, by the user or by a stage that aborted it.',
745
+ stream_incomplete: 'The stream ended without the provider reporting a final status.',
746
+ step_limit: 'The last model call maxSteps allows asked for tools, so the turn ended with no answer.',
747
+ interrupted: 'A live utterance was cut off by an interruption.',
748
+ generation_complete: 'Live: the model finished generating audio or text for this utterance. The turn may still be open.',
749
+ }),
562
750
  'registerTool.type': TOOL_TYPE_FIELD,
563
- name: field('string', "The tool's id, which profiles list in tools.allow (or a model's builtInTools) and the model calls a custom tool by."),
564
- description: field('string', 'Tells the model what the tool does (custom tools only).'),
565
- input: field('ZodSchema', "The Zod schema for the tool's arguments, sent to the model and checked on every call."),
566
- output: field('ZodSchema', 'The Zod schema every result must match.'),
567
- handler: field('ToolHandler', 'Your function that runs the tool; it can also yield progress as it goes.'),
568
- endpoint: field('string', 'The URL the tool calls, with {name} placeholders for mapping.pathParams; the scheme and host must be written out.'),
569
- method: field(unionType(HTTP_METHODS), 'The HTTP method; every method but GET sends a JSON body.', HTTP_METHODS),
570
- headers: field('Record<string, string>', "Headers sent with every request to the tool's own host, never to a host it redirects to."),
571
- mapping: field('{ pathParams?, queryParams?, bodyParam? }', 'Where each argument goes in the request.'),
572
- 'mapping.pathParams': field('string[]', 'Arguments that fill the {name} placeholders in the endpoint.'),
573
- 'mapping.queryParams': field('string[]', 'Arguments added to the query string.'),
574
- 'mapping.bodyParam': field('string', 'The one argument sent as the whole JSON body; without it, a method other than GET sends every argument the path and query leave unused.'),
575
- serverUrl: field('string', "The MCP server's URL."),
576
- mcpToolName: field('string', "The tool's name on the MCP server."),
577
- auth: {
751
+ /** Studio-only rows: the editor's local-model connection, and a decision's questions and images. */
752
+ 'local.baseUrl': field('string', 'Where your local model server listens, such as Ollama, llama.cpp or vLLM. The browser calls it directly.'),
753
+ 'studio.remoteTools': {
754
+ ...field('boolean', 'Whether tools that reach other hosts (HTTP and MCP) may run while a local model is in use. Off, a run with a local model reaches no host.'),
755
+ unset: 'Off',
756
+ },
757
+ 'decision.questions.id': field('string', "The question's name. Its answer comes back under it."),
758
+ 'decision.questions.type': field("'choice' | 'score' | 'noul' | 'box'", 'The kind of answer the question takes.', ['choice', 'score', 'noul', 'box'], {
759
+ choice: 'One of the named options, with a confidence and a probability for each option.',
760
+ score: 'A level on an ordered scale, with a confidence.',
761
+ noul: 'One number, which may weigh named criteria.',
762
+ box: 'Where something is in an image: its top, bottom, left and right edges, with a confidence.',
763
+ }),
764
+ 'decision.questions.instructions': field('DecisionEntry', "What to ask about the state. Name a state field in backticks, such as `photo`, and one of the decision's images by its reference, such as <ginger>. A box looks in its In image, else in the image of the first field it names."),
765
+ 'decision.questions.image': {
766
+ ...field('string', "The image a box is in: one of the decision's images, such as <ghs02>, or a state field its instructions name, such as `photo`. The model is told it in the instructions."),
767
+ unset: 'The image the instructions name',
768
+ },
769
+ 'decision.images': field('Record<string, DecisionAsset>', 'Pictures sent with every decision, beside the state. A question names one by its reference, such as <ghs02>. A PNG or JPEG.'),
770
+ 'decision.images.name': field('string', 'The reference a question writes, without its angle brackets: ghs02 is written <ghs02>. Lowercase letters, digits and _.'),
771
+ 'registerTool.name': field('string', "The tool's id, which profiles list in tools.allow (or a model's builtInTools) and the model calls a custom tool by."),
772
+ 'registerTool.description': field('string', 'Tells the model what the tool does (custom tools only).'),
773
+ 'registerTool.input': field('ZodSchema', "The Zod schema for the tool's arguments, sent to the model and checked on every call."),
774
+ 'registerTool.output': field('ZodSchema', "The Zod schema every result the tool returns must match; the kernel's own not-signed-in note is not checked."),
775
+ 'registerTool.handler': field('ToolHandler', 'Your function that runs the tool; it can also yield progress as it goes.'),
776
+ 'registerTool.endpoint': field('string', 'The URL the tool calls, with {name} placeholders for mapping.pathParams; the scheme and host must be written out.'),
777
+ 'registerTool.method': field(unionType(HTTP_METHODS), 'The HTTP method; every method but GET sends a JSON body.', HTTP_METHODS),
778
+ 'registerTool.headers': field('Record<string, string>', "Headers sent with every request to the tool's own origin, never once a redirect leaves it."),
779
+ 'registerTool.mapping': field('{ pathParams?, queryParams?, bodyParam? }', 'Where each argument goes in the request.'),
780
+ 'registerTool.mapping.pathParams': field('string[]', 'Arguments that fill the {name} placeholders in the endpoint.'),
781
+ 'registerTool.mapping.queryParams': field('string[]', 'Arguments added to the query string.'),
782
+ 'registerTool.mapping.bodyParam': field('string', 'The one argument sent as the whole JSON body; without it, a method other than GET sends every argument the path and query leave unused.'),
783
+ 'registerTool.serverUrl': field('string', "The MCP server's URL."),
784
+ 'registerTool.mcpToolName': field('string', "The tool's name on the MCP server."),
785
+ 'registerTool.profile': field('ProfileId', 'The profile this agent tool runs: text, image, or speech. Register it first. Nothing it calls may stop on a gate.'),
786
+ 'registerTool.answeredBy': {
787
+ ...field("'page'", 'Who answers a call to a function tool. With page, the page the agent is on returns the output, and the tool has no handler.', ['page']),
788
+ unset: 'Its handler',
789
+ },
790
+ 'registerTool.maxCallsPerTurn': {
791
+ ...field('number', 'How many times one turn of the calling agent may run this tool.'),
792
+ unset: 'As many as its steps allow',
793
+ },
794
+ 'registerTool.fieldText': {
795
+ ...field('Partial<Record<keyof AgentToolInput, string>>', 'What the calling model reads on a field of an agent tool input, such as text. A field the profile does not take is refused when the tool registers.'),
796
+ unset: 'The fields carry no text',
797
+ },
798
+ 'registerTool.hostFields': {
799
+ ...field('Record<string, ZodType>', 'Fields the calling model fills on an agent tool that only the host reads. onAgentCall gets them as hostInput and turns them into what the agent is sent; the agent never sees them. A name the input already has is refused when the tool registers.'),
800
+ unset: 'The input is only what the profile takes',
801
+ },
802
+ 'registerTool.hostOnly': {
803
+ ...field("Array<'attachments' | 'voice' | 'slots'>", 'Channels the called profile takes that the calling model cannot fill on an agent tool; only onAgentCall sends them. A channel the profile does not take is refused when the tool registers.'),
804
+ unset: 'The model can fill every channel the profile takes',
805
+ },
806
+ 'registerTool.auth': {
578
807
  ...field('ToolAuthConfig', 'How the tool gets its credential.'),
579
808
  unset: 'No credential',
580
809
  },
581
- 'auth.type': field(unionType(TOOL_AUTH_TYPES), 'The kind of credential the tool expects, shown when a turn stops to ask for one.', TOOL_AUTH_TYPES, CREDENTIAL_KINDS),
582
- 'auth.slot': field('string', 'Which of the credentials the turn passes in this tool uses.'),
583
- 'auth.service': field('string', 'The service the person signs in to, as they know it, named when a turn stops to ask for a credential.'),
584
- 'auth.headerName': {
585
- ...field('string', 'The header the credential goes in.'),
810
+ 'registerTool.auth.type': field(unionType(TOOL_AUTH_TYPES), "The kind of credential the tool expects, shown when a turn stops to ask for one; the header follows the stored credential's own kind.", TOOL_AUTH_TYPES, CREDENTIAL_KINDS),
811
+ 'registerTool.auth.slot': field('string', 'Which of the credentials the turn passes in this tool uses.'),
812
+ 'registerTool.auth.service': field('string', 'The service the person signs in to, as they know it, named when a turn stops to ask for a credential.'),
813
+ 'registerTool.auth.headerName': {
814
+ ...field('string', "The header the credential goes in; an API key's own headerName comes first."),
586
815
  unset: 'Authorization',
587
816
  },
588
- 'auth.headerPrefix': {
589
- ...field('string', 'Text put before the credential in the header.'),
817
+ 'registerTool.auth.headerPrefix': {
818
+ ...field('string', "Text put before the credential in the header; an API key's own headerPrefix comes first."),
590
819
  unset: '"Bearer " for tokens, nothing for API keys',
591
820
  },
592
- 'auth.onUnauthenticated': {
593
- ...field(unionType(AUTH_UNAUTHENTICATED_POLICIES), "What happens when the credential is missing, expired or can't be refreshed.", AUTH_UNAUTHENTICATED_POLICIES, {
821
+ 'registerTool.auth.queryParam': {
822
+ ...field('string', 'For a service that takes its API key in the URL: the query parameter the key goes in, in place of a header.'),
823
+ unset: 'The key goes in a header',
824
+ },
825
+ 'registerTool.auth.onUnauthenticated': {
826
+ ...field(unionType(AUTH_UNAUTHENTICATED_POLICIES), "What happens when the credential is missing, expired, can't be refreshed or is refused by the service.", AUTH_UNAUTHENTICATED_POLICIES, {
594
827
  gate: 'The turn stops and asks the host for a credential.',
595
828
  report_to_model: "The model is told the tool isn't signed in, and the turn goes on.",
829
+ fail: 'The call fails as not signed in, for a key the host holds that no person can sign in to replace.',
596
830
  }),
597
831
  unset: 'gate',
598
832
  },
599
- 'auth.scopes': field('string[]', 'The OAuth scopes the tool needs, shown when a turn stops to ask for a credential.'),
600
- 'auth.clientId': field('string', "Your OAuth client id, kept for the host's sign-in flow; the kernel doesn't read it."),
601
- 'auth.redirectUri': field('string', "Your OAuth redirect URI, kept for the host's sign-in flow; the kernel doesn't read it."),
602
- 'playground.authType': field(unionType(PLAYGROUND_AUTH_TYPES), 'The kind of credential the tool expects, or none.', PLAYGROUND_AUTH_TYPES, { none: 'The tool sends no credential.', ...CREDENTIAL_KINDS }),
603
- 'playground.testCredential': field('string', 'A credential for one connection test, never saved.'),
604
- 'playground.stubOutput': {
605
- ...field('Record<string, unknown>', 'The playground has no code to run, so a function tool returns this.'),
833
+ 'registerTool.auth.scopes': field('string[]', 'The OAuth scopes the tool needs, shown when a turn stops to ask for a credential. A service asking for any other scope fails the call, with no new sign-in.'),
834
+ 'registerTool.auth.clientId': field('string', "Your OAuth client id, kept for the host's sign-in flow; the kernel doesn't read it."),
835
+ 'registerTool.auth.redirectUri': field('string', "Your OAuth redirect URI, kept for the host's sign-in flow; the kernel doesn't read it."),
836
+ 'studio.authType': field(unionType(STUDIO_AUTH_TYPES), 'The kind of credential the tool expects, or none.', STUDIO_AUTH_TYPES, { none: 'The tool sends no credential.', ...CREDENTIAL_KINDS }),
837
+ 'studio.testCredential': field('string', "A credential for this tool's connection tests, kept in the tab and never saved."),
838
+ 'studio.stubOutput': {
839
+ ...field('Record<string, unknown>', 'What a function tool returns in the studio, unless a demo handler has its name.'),
606
840
  unset: 'A stand-in built from the output schema',
607
841
  },
608
- 'playground.sampleInput': field('Record<string, unknown>', 'Arguments for the connection test, never saved.'),
609
- 'playground.inputSchema': field('Record<string, unknown>', "The tool's arguments as a JSON Schema, sent to the model."),
610
- 'playground.outputSchema': field('Record<string, unknown>', "The tool's result as a JSON Schema, which a function tool's stand-in result is built from."),
842
+ 'studio.sampleInput': field('Record<string, unknown>', 'Arguments for the connection test, never saved.'),
843
+ 'registerTool.inputSchema': field('Record<string, unknown>', "The tool's arguments as a JSON Schema, sent to the model."),
844
+ 'registerTool.outputSchema': field('Record<string, unknown>', "The tool's result as a JSON Schema, which a function tool's stand-in result is built from."),
611
845
  'registerStructured.jsonSchema': field('Record<string, unknown>', 'The JSON Schema a structured reply must follow, sent to the model as its response format.'),
612
- access: field(unionType(TOOL_ACCESS), "What the tool can change, which taint.afterRemoteRead refuses calls by after a turn reads a remote tool's result.", TOOL_ACCESS, {
846
+ 'registerTool.access': field(unionType(TOOL_ACCESS), "What the tool can change, which taint.afterRemoteRead refuses calls by after a turn reads a remote tool's result.", TOOL_ACCESS, {
613
847
  'read-only': 'Only reads.',
614
848
  'read-write': 'Creates or updates things.',
615
849
  destructive: 'Deletes, charges, or does something else hard to undo.',
616
850
  }),
617
- loadTier: field(unionType(TOOL_LOAD_TIERS), 'When the tool becomes available to the model; live and host profiles load every allowed tool at the start.', TOOL_LOAD_TIERS, {
851
+ 'registerTool.loadTier': field(unionType(TOOL_LOAD_TIERS), 'When the tool becomes available to the model; live and host profiles load every allowed tool at the start.', TOOL_LOAD_TIERS, {
618
852
  T0: 'At the start of every turn.',
619
- T1: 'When tools.t1Policy picks it at the start of a turn.',
620
- T2: 'When the tools.t2Loader tool loads it mid-turn (custom tools only).',
853
+ T2: 'On demand: when tools.t1Policy picks it at the start of a turn, or when the tools.t2Loader tool loads it mid-turn (custom tools only).',
621
854
  }),
622
- permission: field(unionType(TOOL_PERMISSION), "Whether the host must approve a call before it runs; the provider's own tools never ask.", TOOL_PERMISSION, {
855
+ 'registerTool.permission': field(unionType(TOOL_PERMISSION), "Whether the host must approve a call before it runs; the provider's own tools never ask.", TOOL_PERMISSION, {
623
856
  auto: 'Runs without asking.',
624
857
  session_consent: 'Asks once; the approval lasts as long as the host keeps passing it back.',
625
858
  always_confirm: 'Asks every time.',
626
859
  }),
627
- category: field('string', 'A label for grouping tools; nothing reads it yet.'),
628
- labels: field('ToolLabels', "What the transcript says about a call. Each {path} is filled from the call; {results.0.name} steps into a list, and {path|text} shows the text when it's empty."),
629
- 'labels.activity': {
630
- ...field('string', "What the transcript says while a call runs, e.g. 'Saving {title} to your collection'. Each {path} is filled from the call's input."),
860
+ 'registerTool.category': field('string', 'A label for grouping tools; nothing reads it yet.'),
861
+ 'registerTool.labels': field('ToolLabels', "Transcript text for a call. {path} reads a value from the call. {results.0.name} reads a list item. {path|text} shows the text when the value is unusable. With no fallback, the label is dropped. A label is one line, or a list read top to bottom: the first line that fits is shown. A line fits when each {path} has a value and its condition holds, as in 'when status is archived: archiving {title}'. Neighbouring lines that open with '+ ' show together, joined in words. The last line of a list must always fit."),
862
+ 'registerTool.labels.activity': {
863
+ ...field('string | string[]', "Transcript text while a call runs, e.g. 'Saving {title} to your collection'. {path} reads the call's input."),
864
+ unset: 'The tool name, in words',
865
+ },
866
+ 'registerTool.labels.activityPast': {
867
+ ...field('string | string[]', "What the transcript says once a call completes, e.g. 'Found {results.0.name}'. Each {path} is filled from the call's input, then its output."),
631
868
  unset: 'The tool name, in words',
632
869
  },
633
- 'labels.activityPast': {
634
- ...field('string', "What the transcript says once a call completes, e.g. 'Found {results.0.name}'. Each {path} is filled from the call's input, then its output."),
870
+ 'registerTool.labels.activityFailed': {
871
+ ...field('string | string[]', "What the transcript says when a call fails or the host labels it failed, e.g. 'Couldn't save {title}'. Each {path} is filled from the call's input."),
872
+ unset: "Couldn't complete, then the tool name in words",
873
+ },
874
+ 'registerTool.labels.request': {
875
+ ...field('string | string[]', "What the approval card says the agent wants to do, e.g. 'check the weather in {city}'. Each {path} is filled from the call's input."),
635
876
  unset: 'The tool name, in words',
636
877
  },
637
- paths: {
638
- ...field('string[]', 'The turn paths this tool is offered on, where * means every path; a turn with no path gets only * tools.'),
878
+ 'registerTool.paths': {
879
+ ...field('string[]', 'The turn paths this tool is offered on, where * means every path; a turn with no path gets only * tools. Host profiles ignore it.'),
639
880
  unset: 'Every path',
640
881
  },
641
882
  };
642
- /** `lexicon.<key>` resolves to `lexicon.*` with that key's own note. */
883
+ /** `labels.<key>` resolves to `labels.*` with that key's own note. */
643
884
  export function fieldMeta(path) {
644
885
  const meta = PROFILE_FIELDS[path] ?? EXTRA_FIELDS[path];
645
- if (meta || !path.startsWith('lexicon.'))
886
+ if (meta || !path.startsWith('labels.'))
646
887
  return meta;
647
- const key = path.slice('lexicon.'.length);
648
- const wildcard = PROFILE_FIELDS['lexicon.*'];
649
- if (!wildcard || !Object.hasOwn(LEXICON_NOTES, key))
888
+ const key = path.slice('labels.'.length);
889
+ const wildcard = PROFILE_FIELDS['labels.*'];
890
+ if (!wildcard || !Object.hasOwn(LABEL_NOTES, key))
650
891
  return undefined;
651
- return { ...wildcard, doc: LEXICON_NOTES[key] };
892
+ return { ...wildcard, doc: LABEL_NOTES[key] };
652
893
  }
894
+ export { API_EXPORTS, REQUEST_FIELDS } from './api-catalog.js';
653
895
  export { PROFILE_GRAPH, profileGraphFacet, spineFacetsForProfileType } from './profile-graph.js';