theorum 0.1.15 → 1.1.3

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 (305) hide show
  1. package/README.md +241 -98
  2. package/esm/mod.d.ts +57 -28
  3. package/esm/mod.js +43 -23
  4. package/esm/src/cli/commands/bench.js +18 -18
  5. package/esm/src/cli/commands/fuzz-canary.d.ts +13 -0
  6. package/esm/src/cli/commands/fuzz-canary.js +191 -0
  7. package/esm/src/cli/commands/fuzz-guardrails.d.ts +3 -5
  8. package/esm/src/cli/commands/fuzz-guardrails.js +4 -581
  9. package/esm/src/cli/commands/guardrails-eval.d.ts +14 -0
  10. package/esm/src/cli/commands/guardrails-eval.js +15 -0
  11. package/esm/src/cli/commands/profile.js +35 -15
  12. package/esm/src/cli/commands/run.d.ts +3 -0
  13. package/esm/src/cli/commands/run.js +23 -32
  14. package/esm/src/cli/commands/test.d.ts +10 -1
  15. package/esm/src/cli/commands/test.js +34 -34
  16. package/esm/src/cli/event-log.d.ts +19 -0
  17. package/esm/src/cli/event-log.js +147 -0
  18. package/esm/src/cli/index.js +57 -11
  19. package/esm/src/cli/matrix/synthesizer.d.ts +10 -12
  20. package/esm/src/cli/matrix/synthesizer.js +45 -118
  21. package/esm/src/guardrails/canary-gate.d.ts +21 -0
  22. package/esm/src/guardrails/canary-gate.js +32 -0
  23. package/esm/src/guardrails/canary.d.ts +34 -0
  24. package/esm/src/guardrails/canary.js +150 -0
  25. package/esm/src/guardrails/corpus/canary-egress-attacks.d.ts +17 -0
  26. package/esm/src/guardrails/corpus/canary-egress-attacks.js +151 -0
  27. package/esm/src/guardrails/corpus/fuzz-inbound.d.ts +11 -0
  28. package/esm/src/guardrails/corpus/fuzz-inbound.js +213 -0
  29. package/esm/src/guardrails/corpus/inbound-payloads.d.ts +10 -0
  30. package/esm/src/guardrails/corpus/inbound-payloads.js +125 -0
  31. package/esm/src/guardrails/corpus/live-attacks.d.ts +20 -0
  32. package/esm/src/guardrails/corpus/live-attacks.js +231 -0
  33. package/esm/src/guardrails/corpus/mod.d.ts +14 -0
  34. package/esm/src/guardrails/corpus/mod.js +11 -0
  35. package/esm/src/guardrails/corpus/secrets.d.ts +17 -0
  36. package/esm/src/guardrails/corpus/secrets.js +17 -0
  37. package/esm/src/guardrails/corpus/strings.d.ts +28 -0
  38. package/esm/src/guardrails/corpus/strings.js +34 -0
  39. package/esm/src/guardrails/corpus/types.d.ts +38 -0
  40. package/esm/src/guardrails/corpus/types.js +6 -0
  41. package/esm/src/guardrails/egress.d.ts +32 -0
  42. package/esm/src/guardrails/egress.js +87 -0
  43. package/esm/src/guardrails/error.d.ts +14 -23
  44. package/esm/src/guardrails/error.js +87 -76
  45. package/esm/src/guardrails/eval/corpus.d.ts +108 -0
  46. package/esm/src/guardrails/eval/corpus.js +978 -0
  47. package/esm/src/guardrails/eval/mod.d.ts +51 -0
  48. package/esm/src/guardrails/eval/mod.js +133 -0
  49. package/esm/src/guardrails/eval/score.d.ts +66 -0
  50. package/esm/src/guardrails/eval/score.js +114 -0
  51. package/esm/src/guardrails/events.d.ts +25 -0
  52. package/esm/src/guardrails/events.js +56 -0
  53. package/esm/src/guardrails/hits.d.ts +24 -0
  54. package/esm/src/guardrails/hits.js +45 -0
  55. package/esm/src/guardrails/injection.js +28 -5
  56. package/esm/src/guardrails/lexicon.d.ts +39 -0
  57. package/esm/src/guardrails/lexicon.js +200 -0
  58. package/esm/src/guardrails/live-outbound-gate.d.ts +41 -0
  59. package/esm/src/guardrails/live-outbound-gate.js +222 -0
  60. package/esm/src/guardrails/mod.d.ts +30 -6
  61. package/esm/src/guardrails/mod.js +20 -5
  62. package/esm/src/guardrails/network.d.ts +19 -0
  63. package/esm/src/guardrails/network.js +234 -0
  64. package/esm/src/guardrails/policy.d.ts +35 -0
  65. package/esm/src/guardrails/policy.js +50 -0
  66. package/esm/src/guardrails/progressive-yield.d.ts +51 -0
  67. package/esm/src/guardrails/progressive-yield.js +98 -0
  68. package/esm/src/guardrails/quota.d.ts +17 -3
  69. package/esm/src/guardrails/quota.js +18 -4
  70. package/esm/src/guardrails/sanitize.d.ts +45 -19
  71. package/esm/src/guardrails/sanitize.js +177 -94
  72. package/esm/src/guardrails/sensitive.js +2 -1
  73. package/esm/src/guardrails/serialize.d.ts +35 -0
  74. package/esm/src/guardrails/serialize.js +58 -0
  75. package/esm/src/guardrails/testing.d.ts +17 -0
  76. package/esm/src/guardrails/testing.js +13 -0
  77. package/esm/src/guardrails/theorum-error.d.ts +12 -0
  78. package/esm/src/guardrails/theorum-error.js +15 -0
  79. package/esm/src/guardrails/tool-directives.d.ts +48 -0
  80. package/esm/src/guardrails/tool-directives.js +124 -0
  81. package/esm/src/guardrails/tool-result.d.ts +93 -0
  82. package/esm/src/guardrails/tool-result.js +276 -0
  83. package/esm/src/guardrails/types.d.ts +291 -0
  84. package/esm/src/guardrails/types.js +72 -0
  85. package/esm/src/host/client-turn.d.ts +19 -0
  86. package/esm/src/host/client-turn.js +36 -0
  87. package/esm/src/host/mint-trace.d.ts +1 -1
  88. package/esm/src/host/mod.d.ts +5 -3
  89. package/esm/src/host/mod.js +4 -3
  90. package/esm/src/kernel/auth/crypto.d.ts +42 -0
  91. package/esm/src/kernel/auth/crypto.js +106 -0
  92. package/esm/src/kernel/auth/mod.d.ts +11 -0
  93. package/esm/src/kernel/auth/mod.js +11 -0
  94. package/esm/src/kernel/auth/oauth.d.ts +47 -0
  95. package/esm/src/kernel/auth/oauth.js +278 -0
  96. package/esm/src/kernel/auth/types.d.ts +133 -0
  97. package/esm/src/kernel/auth/types.js +13 -0
  98. package/esm/src/kernel/engine/delta.d.ts +24 -2
  99. package/esm/src/kernel/engine/delta.js +478 -39
  100. package/esm/src/kernel/engine/live-inbound.d.ts +21 -0
  101. package/esm/src/kernel/engine/live-inbound.js +31 -0
  102. package/esm/src/kernel/engine/live-ingress.d.ts +19 -0
  103. package/esm/src/kernel/engine/live-ingress.js +47 -0
  104. package/esm/src/kernel/engine/repair.js +13 -12
  105. package/esm/src/kernel/engine/runner/gates.d.ts +1 -1
  106. package/esm/src/kernel/engine/runner/gates.js +130 -43
  107. package/esm/src/kernel/engine/runner/mod.d.ts +6 -4
  108. package/esm/src/kernel/engine/runner/mod.js +192 -53
  109. package/esm/src/kernel/engine/runner/schema-validation.js +3 -3
  110. package/esm/src/kernel/engine/runner/stages.d.ts +39 -0
  111. package/esm/src/kernel/engine/runner/stages.js +89 -0
  112. package/esm/src/kernel/engine/runner/state.d.ts +31 -0
  113. package/esm/src/kernel/engine/runner/steps.d.ts +1 -1
  114. package/esm/src/kernel/engine/runner/steps.js +244 -43
  115. package/esm/src/kernel/engine/runner/stream.d.ts +9 -3
  116. package/esm/src/kernel/engine/runner/stream.js +140 -44
  117. package/esm/src/kernel/engine/session/mod.d.ts +25 -0
  118. package/esm/src/kernel/engine/session/mod.js +557 -0
  119. package/esm/src/kernel/interaction-parts.d.ts +14 -0
  120. package/esm/src/kernel/interaction-parts.js +23 -0
  121. package/esm/src/kernel/mod.d.ts +21 -10
  122. package/esm/src/kernel/mod.js +11 -8
  123. package/esm/src/kernel/profile-graph.d.ts +159 -0
  124. package/esm/src/kernel/profile-graph.js +156 -0
  125. package/esm/src/kernel/registry/attachments.d.ts +12 -10
  126. package/esm/src/kernel/registry/attachments.js +33 -27
  127. package/esm/src/kernel/registry/catalog.d.ts +25 -24
  128. package/esm/src/kernel/registry/catalog.js +60 -101
  129. package/esm/src/kernel/registry/ingress.d.ts +9 -4
  130. package/esm/src/kernel/registry/ingress.js +97 -75
  131. package/esm/src/kernel/registry/profile-outputs.d.ts +4 -0
  132. package/esm/src/kernel/registry/profile-outputs.js +8 -0
  133. package/esm/src/kernel/registry/profiles.d.ts +55 -12
  134. package/esm/src/kernel/registry/profiles.js +413 -73
  135. package/esm/src/kernel/registry/provider-request.js +13 -7
  136. package/esm/src/kernel/registry/resolve.d.ts +8 -8
  137. package/esm/src/kernel/registry/resolve.js +169 -154
  138. package/esm/src/kernel/registry/schemas.js +1 -1
  139. package/esm/src/kernel/registry/sole-model.d.ts +8 -0
  140. package/esm/src/kernel/registry/sole-model.js +10 -0
  141. package/esm/src/kernel/registry/system-prompt.d.ts +10 -0
  142. package/esm/src/kernel/registry/system-prompt.js +40 -0
  143. package/esm/src/kernel/registry/system-role.d.ts +8 -0
  144. package/esm/src/kernel/registry/system-role.js +14 -0
  145. package/esm/src/kernel/registry/vault.d.ts +12 -7
  146. package/esm/src/kernel/registry/vault.js +32 -10
  147. package/esm/src/kernel/schema.d.ts +231 -0
  148. package/esm/src/kernel/schema.js +607 -0
  149. package/esm/src/kernel/stages.d.ts +175 -0
  150. package/esm/src/kernel/stages.js +476 -0
  151. package/esm/src/kernel/stop.d.ts +78 -19
  152. package/esm/src/kernel/stop.js +51 -16
  153. package/esm/src/kernel/tools/events.d.ts +41 -0
  154. package/esm/src/kernel/tools/events.js +71 -0
  155. package/esm/src/kernel/tools/execute.d.ts +84 -0
  156. package/esm/src/kernel/tools/execute.js +614 -0
  157. package/esm/src/kernel/tools/harness.d.ts +8 -0
  158. package/esm/src/kernel/tools/harness.js +46 -0
  159. package/esm/src/kernel/tools/invoke.d.ts +10 -0
  160. package/esm/src/kernel/tools/invoke.js +101 -0
  161. package/esm/src/kernel/tools/mod.d.ts +13 -0
  162. package/esm/src/kernel/tools/mod.js +11 -0
  163. package/esm/src/kernel/tools/permission.d.ts +15 -0
  164. package/esm/src/kernel/tools/permission.js +47 -0
  165. package/esm/src/kernel/tools/project.d.ts +12 -0
  166. package/esm/src/kernel/tools/project.js +36 -0
  167. package/esm/src/kernel/tools/registry.d.ts +23 -0
  168. package/esm/src/kernel/tools/registry.js +81 -0
  169. package/esm/src/kernel/tools/remote.d.ts +94 -0
  170. package/esm/src/kernel/tools/remote.js +577 -0
  171. package/esm/src/kernel/tools/resolve.d.ts +39 -0
  172. package/esm/src/kernel/tools/resolve.js +283 -0
  173. package/esm/src/kernel/tools/schema.d.ts +15 -0
  174. package/esm/src/kernel/tools/schema.js +176 -0
  175. package/esm/src/kernel/tools/stage-run.d.ts +105 -0
  176. package/esm/src/kernel/tools/stage-run.js +155 -0
  177. package/esm/src/kernel/tools/types.d.ts +394 -0
  178. package/esm/src/kernel/tools/types.js +9 -0
  179. package/esm/src/kernel/types.d.ts +540 -256
  180. package/esm/src/kernel/util/find-last.d.ts +2 -0
  181. package/esm/src/kernel/util/find-last.js +10 -0
  182. package/esm/src/observability/destinations.d.ts +31 -0
  183. package/esm/src/observability/destinations.js +67 -0
  184. package/esm/src/observability/mod.d.ts +10 -3
  185. package/esm/src/observability/mod.js +6 -2
  186. package/esm/src/observability/policy.d.ts +27 -0
  187. package/esm/src/observability/policy.js +80 -0
  188. package/esm/src/observability/resolve-policy.d.ts +16 -0
  189. package/esm/src/observability/resolve-policy.js +64 -0
  190. package/esm/src/observability/trace-attach.d.ts +8 -4
  191. package/esm/src/observability/trace-attach.js +50 -29
  192. package/esm/src/observability/trace-record.d.ts +23 -13
  193. package/esm/src/observability/trace-record.js +96 -39
  194. package/esm/src/observability/trace-sink.d.ts +19 -0
  195. package/esm/src/observability/trace-sink.js +10 -0
  196. package/esm/src/observability/trace-usage.d.ts +10 -3
  197. package/esm/src/observability/trace-usage.js +70 -17
  198. package/esm/src/observability/trace.d.ts +18 -7
  199. package/esm/src/observability/trace.js +34 -17
  200. package/esm/src/observability/types.d.ts +113 -0
  201. package/esm/src/observability/types.js +11 -0
  202. package/esm/src/presets/google/speech-voices.d.ts +11 -0
  203. package/esm/src/presets/google/speech-voices.js +41 -0
  204. package/esm/src/presets/google.d.ts +36 -24
  205. package/esm/src/presets/google.js +50 -63
  206. package/esm/src/presets/mod.d.ts +2 -2
  207. package/esm/src/presets/mod.js +1 -1
  208. package/esm/src/providers/create-provider.d.ts +20 -17
  209. package/esm/src/providers/create-provider.js +72 -26
  210. package/esm/src/providers/google/interactions/framing.d.ts +23 -0
  211. package/esm/src/providers/google/interactions/framing.js +269 -0
  212. package/esm/src/providers/google/interactions/mod.d.ts +7 -0
  213. package/esm/src/providers/google/interactions/mod.js +7 -0
  214. package/esm/src/providers/google/interactions/stream.d.ts +83 -0
  215. package/esm/src/providers/google/interactions/stream.js +588 -0
  216. package/esm/src/providers/google/keys.d.ts +26 -0
  217. package/esm/src/providers/{keys.js → google/keys.js} +19 -31
  218. package/esm/src/providers/google/live/framing.d.ts +49 -0
  219. package/esm/src/providers/google/live/framing.js +552 -0
  220. package/esm/src/providers/google/live/openapi-schema.d.ts +6 -0
  221. package/esm/src/providers/google/live/openapi-schema.js +46 -0
  222. package/esm/src/providers/google/live/session.d.ts +25 -0
  223. package/esm/src/providers/google/live/session.js +134 -0
  224. package/esm/src/providers/google/live/stream.d.ts +45 -0
  225. package/esm/src/providers/google/live/stream.js +214 -0
  226. package/esm/src/providers/google/urls.d.ts +6 -0
  227. package/esm/src/providers/google/urls.js +6 -0
  228. package/esm/src/providers/local/local.d.ts +30 -0
  229. package/esm/src/providers/{local.js → local/local.js} +66 -126
  230. package/esm/src/providers/local/mod.d.ts +9 -0
  231. package/esm/src/providers/local/mod.js +9 -0
  232. package/esm/src/providers/mod.d.ts +6 -3
  233. package/esm/src/providers/mod.js +3 -1
  234. package/esm/src/providers/openrouter/cache-control.d.ts +24 -0
  235. package/esm/src/providers/openrouter/cache-control.js +23 -0
  236. package/esm/src/providers/openrouter/chat.d.ts +107 -0
  237. package/esm/src/providers/{openrouter.js → openrouter/chat.js} +117 -231
  238. package/esm/src/providers/openrouter/image.d.ts +34 -0
  239. package/esm/src/providers/openrouter/image.js +275 -0
  240. package/esm/src/providers/openrouter/openai/chat-payload.d.ts +24 -0
  241. package/esm/src/providers/openrouter/openai/chat-payload.js +82 -0
  242. package/esm/src/providers/openrouter/openai/compat.d.ts +53 -0
  243. package/esm/src/providers/openrouter/openai/compat.js +213 -0
  244. package/esm/src/providers/openrouter/openai/image-payload.d.ts +18 -0
  245. package/esm/src/providers/openrouter/openai/image-payload.js +90 -0
  246. package/esm/src/providers/openrouter/openai/sdk-messages.d.ts +22 -0
  247. package/esm/src/providers/openrouter/openai/sdk-messages.js +122 -0
  248. package/esm/src/providers/openrouter/resolve-api-key.d.ts +9 -0
  249. package/esm/src/providers/openrouter/resolve-api-key.js +24 -0
  250. package/esm/src/providers/openrouter/speech.d.ts +23 -0
  251. package/esm/src/providers/{speech.js → openrouter/speech.js} +32 -55
  252. package/esm/src/providers/probe.d.ts +1 -0
  253. package/esm/src/providers/probe.js +22 -0
  254. package/esm/src/providers/shared/pcm.d.ts +12 -0
  255. package/esm/src/providers/{pcm.js → shared/pcm.js} +16 -3
  256. package/esm/src/providers/shared/sse.d.ts +18 -0
  257. package/esm/src/providers/shared/sse.js +87 -0
  258. package/esm/src/providers/shared/tool-args.d.ts +17 -0
  259. package/esm/src/providers/shared/tool-args.js +45 -0
  260. package/esm/src/providers/shared/upstream-tap.d.ts +5 -0
  261. package/esm/src/providers/{google-tap.js → shared/upstream-tap.js} +4 -7
  262. package/esm/src/providers/shared/upstream-tape.d.ts +6 -0
  263. package/esm/src/providers/{gemini-tape.js → shared/upstream-tape.js} +12 -22
  264. package/esm/src/providers/types.d.ts +27 -0
  265. package/esm/src/providers/types.js +1 -0
  266. package/package.json +11 -7
  267. package/docs/cli.md +0 -97
  268. package/docs/guardrails.md +0 -178
  269. package/docs/host.md +0 -97
  270. package/docs/kernel.md +0 -404
  271. package/docs/observability.md +0 -105
  272. package/docs/openrouter.md +0 -125
  273. package/docs/presets-google.md +0 -91
  274. package/docs/presets.md +0 -88
  275. package/docs/providers.md +0 -202
  276. package/docs/streaming.md +0 -96
  277. package/esm/src/kernel/engine/boundary.d.ts +0 -10
  278. package/esm/src/kernel/engine/boundary.js +0 -55
  279. package/esm/src/kernel/engine/runner/tools.d.ts +0 -13
  280. package/esm/src/kernel/engine/runner/tools.js +0 -198
  281. package/esm/src/kernel/registry/tools.d.ts +0 -12
  282. package/esm/src/kernel/registry/tools.js +0 -36
  283. package/esm/src/providers/expose-for-tests.d.ts +0 -1
  284. package/esm/src/providers/expose-for-tests.js +0 -25
  285. package/esm/src/providers/gemini-tape.d.ts +0 -2
  286. package/esm/src/providers/google-tap.d.ts +0 -3
  287. package/esm/src/providers/interactions.d.ts +0 -5
  288. package/esm/src/providers/interactions.js +0 -169
  289. package/esm/src/providers/keys.d.ts +0 -19
  290. package/esm/src/providers/local.d.ts +0 -29
  291. package/esm/src/providers/openrouter-mod.d.ts +0 -13
  292. package/esm/src/providers/openrouter-mod.js +0 -12
  293. package/esm/src/providers/openrouter-payload.d.ts +0 -39
  294. package/esm/src/providers/openrouter-payload.js +0 -195
  295. package/esm/src/providers/openrouter.d.ts +0 -15
  296. package/esm/src/providers/pcm.d.ts +0 -7
  297. package/esm/src/providers/provider.d.ts +0 -15
  298. package/esm/src/providers/provider.js +0 -202
  299. package/esm/src/providers/speech.d.ts +0 -23
  300. package/esm/src/providers/sse.d.ts +0 -7
  301. package/esm/src/providers/sse.js +0 -55
  302. package/esm/src/streaming/mod.d.ts +0 -9
  303. package/esm/src/streaming/mod.js +0 -8
  304. /package/esm/src/{streaming → host}/readStreamingJsonStringField.d.ts +0 -0
  305. /package/esm/src/{streaming → host}/readStreamingJsonStringField.js +0 -0
@@ -1,126 +1,83 @@
1
1
  /**
2
2
  * Profile resolution for THEORUM turns.
3
3
  *
4
- * This module validates caller overrides, selects models and tools, normalizes
5
- * input parts, and produces the provider request state consumed by `runTurn`.
6
- *
7
4
  * @module
8
5
  */
6
+ import { mintCanary } from '../../guardrails/canary.js';
9
7
  import { TheorumError } from '../../guardrails/error.js';
8
+ import { resolveGuardrailPolicy } from '../../guardrails/policy.js';
10
9
  import { sanitizeTurnRequest } from '../../guardrails/sanitize.js';
11
- import { mintCanary } from '../engine/boundary.js';
12
- import { CATALOG, clampThinkingLevel, getTool, listBuiltinIds, requireModelSpec, } from './catalog.js';
13
- import { assertImageGrounding, assertSpeechRole, resolveImageFormat, resolveInputParts, } from './ingress.js';
10
+ import { profileTurnResumption } from '../stop.js';
11
+ import { projectTools } from '../tools/project.js';
12
+ import { resolveTurnTools } from '../tools/resolve.js';
13
+ import { requireModelBinding } from './catalog.js';
14
+ import { assertOutputMode, assertSpeechRole, resolveImageFormat, resolveInputParts, } from './ingress.js';
14
15
  import { getProfile } from './profiles.js';
15
- import { resolveGeminiBucket } from './vault.js';
16
- function applyBuiltinMutualExclusions(requested) {
17
- return requested.filter((id) => {
18
- const conflicts = getTool(id)?.conflictsWith ?? [];
19
- return !conflicts.some((other) => requested.includes(other));
20
- });
21
- }
22
- function firstSelectKey(selectMap) {
23
- const [key] = Object.keys(selectMap);
24
- return key;
25
- }
26
- function lookupSelectId(profile, select) {
27
- const { select: selectMap } = profile.model;
28
- if (!selectMap) {
29
- return undefined;
30
- }
31
- let key = select;
32
- if (!key) {
33
- key = firstSelectKey(selectMap);
34
- }
35
- if (!key) {
36
- return undefined;
37
- }
38
- return selectMap[key];
39
- }
40
- function pickModel(profile, select) {
41
- if (profile.model.select) {
42
- const id = lookupSelectId(profile, select);
43
- if (!(id && profile.model.allow.includes(id))) {
44
- let label = '';
45
- if (select) {
46
- label = select;
47
- }
48
- throw new TheorumError(`Unknown model select '${label}' for ${profile.id}`);
16
+ import { soleModelId } from './sole-model.js';
17
+ import { resolveTurnSystemPrompt } from './system-prompt.js';
18
+ import { providerUsesKeySlots, resolveKeySlot } from './vault.js';
19
+ /** Narrow to a model-binding profile; `host` never runs a model. */
20
+ function requireModelProfile(profile, door) {
21
+ if (profile.type === 'host') {
22
+ throw new TheorumError(`Profile ${profile.id}: type 'host' never runs a model — ${door} is not supported; execute tools with invokeTool`);
23
+ }
24
+ return profile;
25
+ }
26
+ function pickModel(profile, requested) {
27
+ if (requested) {
28
+ if (!profile.allowModelSelect) {
29
+ throw new TheorumError(`Profile ${profile.id} does not allow model selection`); // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
49
30
  }
50
- return id;
51
- }
52
- const [only] = profile.model.allow;
53
- if (!only) {
54
- throw new TheorumError(`Profile ${profile.id} has no models`);
31
+ if (!profile.models[requested]) {
32
+ throw new TheorumError(`Unknown model '${requested}' for ${profile.id}`); // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
33
+ }
34
+ return requested;
55
35
  }
56
- return only;
57
- }
58
- function thinkingFromControl(spec, thinkingOn) {
59
- if (thinkingOn) {
60
- return spec.thinking.on;
36
+ const defaultId = profile.defaultModel ?? soleModelId(profile.models);
37
+ if (!defaultId || !profile.models[defaultId]) {
38
+ throw new TheorumError(`Profile ${profile.id} has no default model`); // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
61
39
  }
62
- return spec.thinking.off;
40
+ return defaultId;
63
41
  }
64
- function pinnedLevel(pinned, key) {
65
- if (!key) {
42
+ function resolveEffort(profile, binding, modelId, requested) {
43
+ const efforts = binding.efforts;
44
+ if (!efforts || Object.keys(efforts).length === 0) {
45
+ if (requested) {
46
+ throw new TheorumError(`Profile ${profile.id} model '${modelId}' has no selectable efforts`); // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
47
+ }
66
48
  return undefined;
67
49
  }
68
- return pinned[key];
69
- }
70
- function thinkingFromPin(profile, select) {
71
- const pinned = profile.model.thinking;
72
- if (typeof pinned === 'string') {
73
- return pinned;
74
- }
75
- if (!pinned) {
76
- throw new TheorumError(`Profile ${profile.id} must pin thinking or list it in controls`);
77
- }
78
- const fromSelect = pinnedLevel(pinned, select);
79
- if (fromSelect) {
80
- return fromSelect;
81
- }
82
- const fromFirst = pinnedLevel(pinned, firstSelectKey(profile.model.select ?? {}));
83
- if (fromFirst) {
84
- return fromFirst;
85
- }
86
- throw new TheorumError(`Profile ${profile.id} must pin thinking or list it in controls`);
87
- }
88
- function resolveThinking(profile, spec, thinkingOn, select) {
89
- const raw = profile.model.controls?.includes('thinking')
90
- ? thinkingFromControl(spec, thinkingOn)
91
- : thinkingFromPin(profile, select);
92
- return clampThinkingLevel(spec, raw);
93
- }
94
- function resolveSummaries(profile, spec, thinkingOn) {
95
- if (profile.model.controls?.includes('thinking')) {
96
- if (thinkingOn) {
97
- return spec.summaries.on;
50
+ const keys = Object.keys(efforts);
51
+ if (requested) {
52
+ if (!binding.allowEffortSelect) {
53
+ throw new TheorumError(`Profile ${profile.id} model '${modelId}' does not allow effort selection`);
98
54
  }
99
- return spec.summaries.off;
55
+ const level = efforts[requested];
56
+ if (!level) {
57
+ throw new TheorumError(`Unknown effort '${requested}' for ${profile.id} model '${modelId}'`); // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
58
+ }
59
+ return level;
100
60
  }
101
- return spec.summaries.on;
102
- }
103
- function isGatedOn(requested, id) {
104
- if (!requested) {
105
- return false;
61
+ const alias = binding.defaultEffort ?? (keys.length === 1 ? keys[0] : undefined);
62
+ if (!alias) {
63
+ throw new TheorumError(`Profile ${profile.id} model '${modelId}' must set defaultEffort when more than one effort is declared`);
106
64
  }
107
- return requested[id] === true;
108
- }
109
- function resolveBuiltins(profile, requested) {
110
- const allowed = profile.tools.allow.filter((id) => CATALOG.tools[id]?.kind === 'builtin');
111
- const picked = listBuiltinIds().filter((id) => allowed.includes(id) && isGatedOn(requested, id));
112
- return applyBuiltinMutualExclusions(picked);
113
- }
114
- function resolveCustom(profile, requested) {
115
- return profile.tools.allow.filter((id) => CATALOG.tools[id]?.kind === 'custom' && isGatedOn(requested, id));
65
+ return efforts[alias];
116
66
  }
117
- function assertToolAllowed(profile, name) {
118
- if (!profile.tools.allow.includes(name)) {
119
- throw new TheorumError(`Tool '${name}' is not allowed on ${profile.id}`);
67
+ function resolveSummaries(binding) {
68
+ if (binding.summaries === true) {
69
+ return 'auto';
120
70
  }
71
+ if (binding.summaries === false) {
72
+ return 'none';
73
+ }
74
+ return undefined;
121
75
  }
122
76
  function resolveStructured(profile, slots) {
123
- const { structured } = profile.outputs;
77
+ if (profile.type === 'live') {
78
+ return null;
79
+ }
80
+ const structured = profile.outputs?.structured;
124
81
  if (!structured) {
125
82
  return null;
126
83
  }
@@ -136,82 +93,140 @@ function resolveStructured(profile, slots) {
136
93
  }
137
94
  return structured.fallback;
138
95
  }
96
+ /**
97
+ * THEORUM prefers SSE when the host omits `outputs.streaming.mode`.
98
+ * Explicit `'buffered'` opts out; `'sse'` (or omit) yields `stream: true`.
99
+ */
100
+ function resolveStreamFlag(profile) {
101
+ if (profile.type === 'live') {
102
+ return true;
103
+ }
104
+ return profile.outputs?.streaming?.mode !== 'buffered';
105
+ }
106
+ function resolveStore(binding, reqStore) {
107
+ if (reqStore !== undefined) {
108
+ return reqStore;
109
+ }
110
+ return binding.store;
111
+ }
112
+ function resolveTransport(profile, binding) {
113
+ if (profile.type === 'live') {
114
+ return 'geminiLive';
115
+ }
116
+ if (binding.protocol === 'geminiInteractions' && binding.provider === 'google') {
117
+ return 'interactions';
118
+ }
119
+ return 'openAiCompat';
120
+ }
121
+ function assertTurnResumption(profile, req) {
122
+ if (!req.continueFrom) {
123
+ return;
124
+ }
125
+ if (profile.type === 'live') {
126
+ throw new TheorumError(`Profile ${profile.id}: type 'live' uses live.sessionResumption, not turnBehaviour.resumption/continueFrom`);
127
+ }
128
+ const policy = profileTurnResumption(profile);
129
+ const max = policy?.maxContinues;
130
+ if (max === undefined) {
131
+ return;
132
+ }
133
+ const attempt = req.continuation;
134
+ if (attempt === undefined) {
135
+ throw new TheorumError(`Profile ${profile.id}: continueFrom requires TurnRequest.continuation when turnBehaviour.resumption.maxContinues is set`);
136
+ }
137
+ if (attempt < 1) {
138
+ throw new TheorumError(`Profile ${profile.id}: continuation must be >= 1`); // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
139
+ }
140
+ if (attempt > max) {
141
+ throw new TheorumError(`Profile ${profile.id}: continuation ${attempt} exceeds turnBehaviour.resumption.maxContinues (${max})`);
142
+ }
143
+ }
139
144
  /** Resolve a host `TurnRequest` into provider-ready generation state. */
140
145
  function resolveTurn(req) {
141
146
  const safe = sanitizeTurnRequest(req);
142
147
  const input = safe.input ?? {};
143
- const profile = getProfile(safe.profile);
144
- const model = pickModel(profile, safe.select);
145
- const spec = requireModelSpec(profile, model);
146
- const thinkingOn = safe.thinking === true;
147
- const builtins = resolveBuiltins(profile, safe.tools);
148
- assertImageGrounding(profile, model, builtins);
148
+ const profile = requireModelProfile(getProfile(safe.profile), 'resolveTurn');
149
+ assertTurnResumption(profile, safe);
150
+ const model = pickModel(profile, safe.model);
151
+ const binding = requireModelBinding(profile, model);
152
+ const toolSnapshot = resolveTurnTools(profile, safe, model);
153
+ const builtins = toolSnapshot.builtins;
154
+ const structured = resolveStructured(profile, input.slots);
155
+ assertOutputMode(profile, structured);
149
156
  assertSpeechRole(profile);
150
- const geminiBucket = profile.model.provider === 'google'
151
- ? resolveGeminiBucket(profile.model.key ?? 'freeA', spec, builtins)
157
+ const pinnedKey = profile.key ?? binding.key;
158
+ const keySlot = providerUsesKeySlots(binding.provider)
159
+ ? resolveKeySlot(pinnedKey, binding, builtins, binding.provider === 'google')
152
160
  : undefined;
161
+ const previousInteractionId = binding.persistViaInteractionId === false ? undefined : safe.previousInteractionId;
153
162
  return {
154
163
  profile,
155
164
  generation: {
156
165
  model,
157
- apiId: spec.apiId,
158
- openRouterId: spec.openRouterId,
159
- previousInteractionId: safe.previousInteractionId,
160
- store: safe.store,
161
- thinking: resolveThinking(profile, spec, thinkingOn, safe.select),
162
- summaries: resolveSummaries(profile, spec, thinkingOn),
163
- maxOutputTokens: spec.maxOutputTokens,
164
- temperature: spec.temperature,
166
+ apiId: binding.apiId,
167
+ transport: resolveTransport(profile, binding),
168
+ previousInteractionId,
169
+ store: resolveStore(binding, safe.store),
170
+ stream: resolveStreamFlag(profile),
171
+ thinking: resolveEffort(profile, binding, model, safe.effort),
172
+ summaries: resolveSummaries(binding),
173
+ maxOutputTokens: binding.maxOutputTokens,
174
+ temperature: binding.temperature,
165
175
  builtins,
166
- custom: resolveCustom(profile, safe.tools),
167
- dynamicTools: safe.dynamicTools,
168
- dynamicToolLoader: safe.dynamicToolLoader,
176
+ googleMapsLocation: safe.googleMapsLocation,
177
+ cache: binding.cache,
178
+ sessionId: safe.sessionId,
179
+ tools: toolSnapshot,
169
180
  sessionPermissions: safe.sessionPermissions,
170
181
  history: input.history,
171
- maxSteps: profile.model.maxSteps ?? 1,
172
- structured: resolveStructured(profile, input.slots),
173
- image: resolveImageFormat(profile, model, input.slots),
174
- speech: profile.outputs.speech,
182
+ maxSteps: profile.maxSteps,
183
+ structured,
184
+ image: resolveImageFormat(profile),
185
+ speech: profile.type === 'speech' ? profile.speech : undefined,
186
+ live: profile.type === 'live' ? profile.live : undefined,
175
187
  input: resolveInputParts(profile, model, safe),
176
- geminiBucket,
177
- canary: profile.guardrails.canary !== false ? mintCanary() : '',
188
+ keySlot,
189
+ canary: resolveGuardrailPolicy(profile.guardrails).canary ? mintCanary() : '',
190
+ sessionResumptionHandle: safe.sessionResumptionHandle ?? input.sessionResumptionHandle,
191
+ resolvedSystem: resolveTurnSystemPrompt(profile, safe),
192
+ host: safe.host,
178
193
  },
179
194
  };
180
195
  }
181
196
  function primaryImageSpec(profile) {
182
- return profile.outputs.image;
197
+ return profile.type === 'image' ? profile.image : null;
183
198
  }
184
- /** Project a registered profile into a safe host/UI inspection object. */
185
- function projectProfile(id) {
186
- const profile = getProfile(id);
187
- const { model, identity, tools, inputs, outputs } = profile;
188
- const { select, allow, maxSteps, controls } = model;
189
- const { handle, chat } = identity;
190
- const { slots } = inputs;
199
+ function profileInputsOrNull(profile) {
200
+ if (profile.type === 'speech' || profile.type === 'live') {
201
+ return null;
202
+ }
203
+ return profile.inputs ?? null;
204
+ }
205
+ /** Project a profile object into a safe host/UI inspection object. */
206
+ function projectProfileObject(input) {
207
+ const profile = requireModelProfile(input, 'projectProfile');
208
+ const { identity } = profile;
209
+ const inputs = profileInputsOrNull(profile);
210
+ const outputs = profile.type === 'live' ? null : (profile.outputs ?? null);
191
211
  return {
192
212
  id: profile.id,
193
- handle,
194
- chat: chat !== false,
195
- maxSteps: maxSteps ?? 1,
196
- models: allow,
197
- select: select ?? null,
198
- controls: controls ?? [],
199
- tools: tools.allow.map((name) => ({
200
- name,
201
- ...(CATALOG.tools[name] ?? { kind: 'custom', ui: true }),
202
- })),
213
+ type: profile.type,
214
+ handle: identity.handle,
215
+ models: profile.models,
216
+ defaultModel: profile.defaultModel,
217
+ allowModelSelect: profile.allowModelSelect,
218
+ maxSteps: profile.maxSteps,
219
+ key: profile.key,
220
+ tools: projectTools(profile),
203
221
  inputs,
204
- slots: slots ?? {},
205
222
  outputs,
206
223
  image: primaryImageSpec(profile),
224
+ speech: profile.type === 'speech' ? profile.speech : null,
225
+ live: profile.type === 'live' ? profile.live : null,
207
226
  };
208
227
  }
209
- function pickSystemRole(profile, requested) {
210
- const { identity } = profile;
211
- const { handle, systemByRole } = identity;
212
- if (requested && systemByRole && Object.hasOwn(systemByRole, requested)) {
213
- return requested;
214
- }
215
- return handle;
228
+ /** Project a registered profile into a safe host/UI inspection object. */
229
+ function projectProfile(id) {
230
+ return projectProfileObject(getProfile(id));
216
231
  }
217
- export { assertToolAllowed, pickSystemRole, projectProfile, resolveTurn };
232
+ export { pickModel, projectProfile, projectProfileObject, requireModelProfile, resolveTurn };
@@ -16,7 +16,7 @@ function registerStructured(id, spec) {
16
16
  function getStructured(id) {
17
17
  const spec = schemas.get(id);
18
18
  if (!spec) {
19
- throw new TheorumError(`Unknown structured schema '${id}'`);
19
+ throw new TheorumError(`Unknown structured schema '${id}'`); // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
20
20
  }
21
21
  return spec;
22
22
  }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Shared model-id helpers for profile registration and resolution.
3
+ *
4
+ * @module
5
+ */
6
+ import type { ModelBinding, ModelId } from '../types.js';
7
+ /** When exactly one model is declared, that id is the implicit default. */
8
+ export declare function soleModelId(models: Record<ModelId, ModelBinding>): ModelId | undefined;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Shared model-id helpers for profile registration and resolution.
3
+ *
4
+ * @module
5
+ */
6
+ /** When exactly one model is declared, that id is the implicit default. */
7
+ export function soleModelId(models) {
8
+ const ids = Object.keys(models);
9
+ return ids.length === 1 ? ids[0] : undefined;
10
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Synchronous system-prompt resolution for a turn or session.
3
+ * Snapshotted in `resolveTurn` before any async work — never mutate registry per request.
4
+ *
5
+ * @module
6
+ */
7
+ import type { ModelProfile, TurnRequest } from '../types.js';
8
+ /** Merge profile + host turn system synchronously at resolve time. */
9
+ declare function resolveTurnSystemPrompt(profile: ModelProfile, req: TurnRequest): string;
10
+ export { resolveTurnSystemPrompt };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Synchronous system-prompt resolution for a turn or session.
3
+ * Snapshotted in `resolveTurn` before any async work — never mutate registry per request.
4
+ *
5
+ * @module
6
+ */
7
+ import { lexiconText } from '../../guardrails/lexicon.js';
8
+ import { detectionForTrust, resolveGuardrailPolicy } from '../../guardrails/policy.js';
9
+ import { sanitizeText } from '../../guardrails/sanitize.js';
10
+ import { profileTurnResumption } from '../stop.js';
11
+ import { pickSystemRole } from './system-role.js';
12
+ /**
13
+ * Author-time system text for a role (handle or `systemByRole`).
14
+ *
15
+ * Routed through the guardrail policy at `trust: 'trusted'` so the exemption is
16
+ * declared at the point it applies rather than implied by never calling the
17
+ * sanitizer. Trusted resolves to no detection, so the text reaches the provider
18
+ * verbatim — `req.system`, which the host assembles per turn, is handled as
19
+ * `assembled` in `sanitizeTurnRequest` and is not exempt.
20
+ */
21
+ function systemFromProfile(profile, role) {
22
+ const { identity } = profile;
23
+ const { systemByRole, system } = identity;
24
+ const text = systemByRole?.[role] || system || '';
25
+ if (!text) {
26
+ return '';
27
+ }
28
+ const policy = resolveGuardrailPolicy(profile.guardrails);
29
+ return sanitizeText(text, detectionForTrust(policy, 'trusted'));
30
+ }
31
+ /** Merge profile + host turn system synchronously at resolve time. */
32
+ function resolveTurnSystemPrompt(profile, req) {
33
+ const role = pickSystemRole(profile, req.input?.role);
34
+ // Profile override wins; otherwise the registered lexicon default.
35
+ const continueSys = req.continueFrom
36
+ ? lexiconText('continue.instruction', {}, profileTurnResumption(profile)?.continueInstruction)
37
+ : '';
38
+ return [systemFromProfile(profile, role), req.system, continueSys].filter(Boolean).join('\n\n');
39
+ }
40
+ export { resolveTurnSystemPrompt };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Resolve which `systemByRole` key (or handle) applies for a turn.
3
+ *
4
+ * @module
5
+ */
6
+ import type { ModelProfile } from '../types.js';
7
+ declare function pickSystemRole(profile: ModelProfile, requested?: string): string;
8
+ export { pickSystemRole };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Resolve which `systemByRole` key (or handle) applies for a turn.
3
+ *
4
+ * @module
5
+ */
6
+ function pickSystemRole(profile, requested) {
7
+ const { identity } = profile;
8
+ const { handle, systemByRole } = identity;
9
+ if (requested && systemByRole && Object.hasOwn(systemByRole, requested)) {
10
+ return requested;
11
+ }
12
+ return handle;
13
+ }
14
+ export { pickSystemRole };
@@ -1,11 +1,16 @@
1
1
  /**
2
- * Gemini vault slot selection for Google Interactions transport.
3
- *
4
- * Host-owned policy over `ModelSpec.key` / `keyBuiltins`. Pure; no network.
2
+ * Vault key-slot selection for credentialed transports (Google, OpenRouter, …).
5
3
  *
6
4
  * @module
7
5
  */
8
- import type { BuiltinToolId, GeminiBucket, GeminiFreeBucket, ModelSpec } from '../types.js';
9
- /** Pick the vault slot for a turn from profile key, model pin, and builtins. */
10
- declare function resolveGeminiBucket(profileKey: GeminiFreeBucket, spec: ModelSpec, builtins: BuiltinToolId[]): GeminiBucket;
11
- export { resolveGeminiBucket };
6
+ import type { BuiltinToolId, KeySlot, ModelBinding, Provider } from '../types.js';
7
+ /** Providers that resolve a vault `keySlot` on each turn. */
8
+ export declare function providerUsesKeySlots(provider: Provider): boolean;
9
+ /**
10
+ * Pick the key slot for a turn from profile key, model pin, and enabled builtins.
11
+ *
12
+ * When `required` is false and nothing pins a slot, returns `undefined` so hosts
13
+ * can use a single flat `apiKey` (OpenRouter without a vault).
14
+ */
15
+ declare function resolveKeySlot(profileKey: KeySlot | undefined, binding: ModelBinding, builtins: BuiltinToolId[], required: boolean): KeySlot | undefined;
16
+ export { resolveKeySlot };
@@ -1,18 +1,40 @@
1
1
  /**
2
- * Gemini vault slot selection for Google Interactions transport.
3
- *
4
- * Host-owned policy over `ModelSpec.key` / `keyBuiltins`. Pure; no network.
2
+ * Vault key-slot selection for credentialed transports (Google, OpenRouter, …).
5
3
  *
6
4
  * @module
7
5
  */
8
- /** Pick the vault slot for a turn from profile key, model pin, and builtins. */
9
- function resolveGeminiBucket(profileKey, spec, builtins) {
10
- if (spec.key) {
11
- return spec.key;
6
+ import { TheorumError } from '../../guardrails/error.js';
7
+ import { getTool } from '../tools/registry.js';
8
+ function builtinForcesPaid(id) {
9
+ const tool = getTool(id);
10
+ if (tool?.type !== 'builtin') {
11
+ return false;
12
12
  }
13
- if (builtins.some((id) => !spec.keyBuiltins.includes(id))) {
13
+ return tool.forcePaidKey === true;
14
+ }
15
+ /** Providers that resolve a vault `keySlot` on each turn. */
16
+ export function providerUsesKeySlots(provider) {
17
+ return provider === 'google' || provider === 'openrouter';
18
+ }
19
+ /**
20
+ * Pick the key slot for a turn from profile key, model pin, and enabled builtins.
21
+ *
22
+ * When `required` is false and nothing pins a slot, returns `undefined` so hosts
23
+ * can use a single flat `apiKey` (OpenRouter without a vault).
24
+ */
25
+ function resolveKeySlot(profileKey, binding, builtins, required) {
26
+ if (binding.key) {
27
+ return binding.key;
28
+ }
29
+ if (builtins.some((id) => builtinForcesPaid(id))) {
14
30
  return 'paid';
15
31
  }
16
- return profileKey;
32
+ if (profileKey) {
33
+ return profileKey;
34
+ }
35
+ if (required) {
36
+ throw new TheorumError('Profile must set key or models.*.key'); // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
37
+ }
38
+ return undefined;
17
39
  }
18
- export { resolveGeminiBucket };
40
+ export { resolveKeySlot };