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,86 +1,10 @@
1
1
  /**
2
- * Tool catalog and MIME helpers.
3
- *
4
- * Model wire metadata is host-owned on `profile.model.config`. This module only
5
- * keeps builtin/custom tool descriptors and shared MIME utilities.
2
+ * MIME helpers and model binding utilities.
6
3
  *
7
4
  * @module
8
5
  */
9
6
  import { TheorumError } from '../../guardrails/error.js';
10
- const ASK_USER_SCHEMA = {
11
- type: 'object',
12
- properties: {
13
- kind: { type: 'string', enum: ['confirm', 'choice', 'text'] },
14
- prompt: { type: 'string' },
15
- options: { type: 'array', items: { type: 'string' } },
16
- },
17
- required: ['kind', 'prompt'],
18
- };
19
- /** Harness tools that always ship with THEORUM. */
20
- const HARNESS_TOOLS = {
21
- askUser: { kind: 'custom', ui: true, schema: ASK_USER_SCHEMA },
22
- };
23
- /** Live tool catalog. Starts with harness tools; presets/hosts register more. */
24
- const CATALOG = {
25
- tools: { ...HARNESS_TOOLS },
26
- };
27
- /** Register or replace tool descriptors (idempotent per id). */
28
- function registerTools(entries) {
29
- Object.assign(CATALOG.tools, entries);
30
- }
31
- /** Look up one registered tool descriptor. */
32
- function getTool(id) {
33
- return CATALOG.tools[id];
34
- }
35
- /** Ids of all registered provider builtins. */
36
- function listBuiltinIds() {
37
- return Object.entries(CATALOG.tools)
38
- .filter(([, entry]) => entry.kind === 'builtin')
39
- .map(([id]) => id);
40
- }
41
- /** Restore harness-only tools (tests / host reloads). */
42
- function resetTools() {
43
- for (const id of Object.keys(CATALOG.tools)) {
44
- delete CATALOG.tools[id];
45
- }
46
- Object.assign(CATALOG.tools, HARNESS_TOOLS);
47
- }
48
- /** MIME essence → normalized media part category (shared ingress map). */
49
- const MEDIA_INPUT_KINDS = {
50
- 'image/png': 'image',
51
- 'image/jpeg': 'image',
52
- 'image/jpg': 'image',
53
- 'image/webp': 'image',
54
- 'image/heic': 'image',
55
- 'image/heif': 'image',
56
- 'audio/wav': 'audio',
57
- 'audio/x-wav': 'audio',
58
- 'audio/mpeg': 'audio',
59
- 'audio/mp3': 'audio',
60
- 'audio/aiff': 'audio',
61
- 'audio/aac': 'audio',
62
- 'audio/ogg': 'audio',
63
- 'audio/flac': 'audio',
64
- 'audio/webm': 'audio',
65
- 'audio/mp4': 'audio',
66
- 'audio/pcm': 'audio',
67
- 'video/mp4': 'video',
68
- 'video/mpeg': 'video',
69
- 'video/quicktime': 'video',
70
- 'video/x-msvideo': 'video',
71
- 'video/x-flv': 'video',
72
- 'video/mpg': 'video',
73
- 'video/webm': 'video',
74
- 'video/wmv': 'video',
75
- 'video/x-ms-wmv': 'video',
76
- 'video/3gpp': 'video',
77
- 'application/pdf': 'document',
78
- 'text/plain': 'document',
79
- 'text/csv': 'document',
80
- 'text/markdown': 'document',
81
- 'text/html': 'document',
82
- 'application/json': 'document',
83
- };
7
+ import { MEDIA_INPUT_KINDS } from '../schema.js';
84
8
  function mimeEssence(mime) {
85
9
  const [base] = mime.split(';');
86
10
  return (base ?? '').trim().toLowerCase();
@@ -98,38 +22,73 @@ function mimeAllowed(accept, mime) {
98
22
  function mediaKindForMime(mime) {
99
23
  return MEDIA_INPUT_KINDS[mimeEssence(mime)];
100
24
  }
101
- /** Require a host-declared model spec for an allowed profile model id. */
102
- function requireModelSpec(profile, modelId) {
103
- const spec = profile.model.config[modelId];
104
- if (!spec) {
105
- throw new TheorumError(`Profile ${profile.id} has no model spec for '${modelId}'`);
25
+ /** The `accept` list a profile declares for one input channel, if it declares one. */
26
+ function profileAccept(profile, channel) {
27
+ if (profile.type === 'speech' || profile.type === 'live' || profile.type === 'host') {
28
+ return undefined;
29
+ }
30
+ const inputs = profile.inputs;
31
+ return channel === 'voice' ? inputs?.voice?.accept : inputs?.attachments?.accept;
32
+ }
33
+ /**
34
+ * Which `TurnInput` channel of a profile accepts this MIME, or `undefined` when
35
+ * the profile accepts it nowhere (or the kernel cannot classify it at all).
36
+ *
37
+ * The one public answer to "does this profile take this file". Hosts route and
38
+ * filter channel ingress with it instead of keeping their own MIME table: the
39
+ * profile's `accept` lists are the whole declaration.
40
+ */
41
+ function mediaChannelForMime(profile, mime) {
42
+ if (!mediaKindForMime(mime)) {
43
+ return undefined;
44
+ }
45
+ for (const channel of ['attachments', 'voice']) {
46
+ const accept = profileAccept(profile, channel);
47
+ if (accept && mimeAllowed(accept, mime)) {
48
+ return channel;
49
+ }
50
+ }
51
+ return undefined;
52
+ }
53
+ /** Require a host-declared model binding for a profile model id. */
54
+ function requireModelBinding(profile, modelId) {
55
+ const binding = profile.models[modelId];
56
+ if (!binding) {
57
+ throw new TheorumError(`Profile ${profile.id} has no model binding for '${modelId}'`); // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
58
+ }
59
+ return binding;
60
+ }
61
+ function effortLevels(binding) {
62
+ if (!binding?.efforts) {
63
+ return [];
106
64
  }
107
- return spec;
65
+ return Object.values(binding.efforts);
108
66
  }
109
- function clampLevels(entry, level) {
110
- if (!entry?.thinkingLevels || entry.thinkingLevels.length === 0) {
67
+ function clampLevels(binding, level) {
68
+ const legal = effortLevels(binding);
69
+ if (legal.length === 0) {
111
70
  return level;
112
71
  }
113
- if (entry.thinkingLevels.includes(level)) {
72
+ if (legal.includes(level)) {
114
73
  return level;
115
74
  }
116
- const fallback = entry.thinking.off;
117
- if (entry.thinkingLevels.includes(fallback)) {
75
+ const fallbackAlias = binding?.defaultEffort;
76
+ const fallback = fallbackAlias ? binding?.efforts?.[fallbackAlias] : undefined;
77
+ if (fallback && legal.includes(fallback)) {
118
78
  return fallback;
119
79
  }
120
- const first = entry.thinkingLevels[0];
121
- return first ?? level;
80
+ return legal[0] ?? level;
122
81
  }
123
- /** Clamp a requested thinking level to what the model spec accepts. */
124
- function clampThinkingLevel(spec, level) {
125
- return clampLevels(spec, level);
82
+ /** Clamp a requested thinking level to what the model binding accepts. */
83
+ function clampThinkingLevel(binding, level) {
84
+ return clampLevels(binding, level);
126
85
  }
127
- /** Look up a model spec by provider-native API id within a host specs map. */
128
- function modelEntryByApiId(specs, apiId) {
129
- return Object.values(specs).find((m) => m.apiId === apiId);
86
+ /** Look up a model binding by provider-native API id within a host map. */
87
+ function modelEntryByApiId(bindings, apiId) {
88
+ return Object.values(bindings).find((m) => m.apiId === apiId);
130
89
  }
131
- /** Clamp thinking level using a provider-native API id within a host specs map. */
132
- function clampThinkingLevelForApiId(specs, apiId, level) {
133
- return clampLevels(modelEntryByApiId(specs, apiId), level);
90
+ /** Clamp thinking level using a provider-native API id within a host map. */
91
+ function clampThinkingLevelForApiId(bindings, apiId, level) {
92
+ return clampLevels(modelEntryByApiId(bindings, apiId), level);
134
93
  }
135
- export { CATALOG, clampThinkingLevel, clampThinkingLevelForApiId, getTool, listBuiltinIds, mediaKindForMime, mimeAllowed, mimeEssence, modelEntryByApiId, registerTools, requireModelSpec, resetTools, };
94
+ export { clampThinkingLevel, clampThinkingLevelForApiId, mediaChannelForMime, mediaKindForMime, mimeAllowed, mimeEssence, modelEntryByApiId, profileAccept, requireModelBinding, };
@@ -5,9 +5,14 @@
5
5
  *
6
6
  * @module
7
7
  */
8
- import type { BuiltinToolId, ImageResponseFormat, InteractionPart, ModelId, Profile, TurnRequest } from '../types.js';
8
+ import type { ImageResponseFormat, InteractionPart, ModelId, Profile, TurnRequest } from '../types.js';
9
+ /**
10
+ * Provider wire formats (JSON schema, image, speech) are mutually exclusive.
11
+ * Typed profiles make illegal mixes unrepresentable; this remains a safety net
12
+ * for responseFormat structured on text vs accidental dual modes.
13
+ */
14
+ declare function assertOutputMode(profile: Profile, structuredId: string | null): void;
9
15
  declare function assertSpeechRole(profile: Profile): void;
10
- declare function resolveImageFormat(profile: Profile, _model: ModelId, slots?: Record<string, string>): ImageResponseFormat | null;
16
+ declare function resolveImageFormat(profile: Profile): ImageResponseFormat | null;
11
17
  declare function resolveInputParts(profile: Profile, model: ModelId, req: TurnRequest): InteractionPart[];
12
- declare function assertImageGrounding(profile: Profile, model: ModelId, builtins: BuiltinToolId[]): void;
13
- export { assertImageGrounding, assertSpeechRole, resolveImageFormat, resolveInputParts };
18
+ export { assertOutputMode, assertSpeechRole, resolveImageFormat, resolveInputParts };
@@ -5,119 +5,139 @@
5
5
  *
6
6
  * @module
7
7
  */
8
+ import { wrapUserData } from '../../guardrails/canary.js';
8
9
  import { TheorumError } from '../../guardrails/error.js';
9
- import { wrapUserData } from '../engine/boundary.js';
10
10
  import { synthesizeRepairPrompt } from '../engine/repair.js';
11
- import { assertAttachmentLimits, requireMediaLimits } from './attachments.js';
12
- import { mediaKindForMime, mimeAllowed, mimeEssence } from './catalog.js';
13
- function listedValue(allowed, value) {
14
- if (!value) {
15
- return undefined;
11
+ import { isSpeechFormatAllowedForProtocol } from '../schema.js';
12
+ import { assertAttachmentLimits, isTurnMediaRef, requireMediaLimits } from './attachments.js';
13
+ import { mediaKindForMime, mimeAllowed, mimeEssence, profileAccept, } from './catalog.js';
14
+ import { getStructured } from './schemas.js';
15
+ function usesStructuredResponseFormat(structuredId) {
16
+ if (!structuredId) {
17
+ return false;
18
+ }
19
+ const spec = getStructured(structuredId);
20
+ return spec.enforced === 'responseFormat' && spec.jsonSchema != null;
21
+ }
22
+ function activePrimaryOutputModes(profile, structuredId) {
23
+ const modes = [];
24
+ if (usesStructuredResponseFormat(structuredId)) {
25
+ modes.push('structured');
16
26
  }
17
- if (!allowed || allowed.length === 0) {
18
- return value;
27
+ if (profile.type === 'image') {
28
+ modes.push('image');
19
29
  }
20
- if (allowed.includes(value)) {
21
- return value;
30
+ if (profile.type === 'speech') {
31
+ modes.push('speech');
22
32
  }
23
- return undefined;
33
+ return modes;
24
34
  }
25
- function resolveSlotOrPin(profileId, label, slotValue, pin, allow) {
26
- if (slotValue !== undefined) {
27
- const fromSlot = listedValue(allow, slotValue);
28
- if (!fromSlot) {
29
- throw new TheorumError(`Unknown image ${label} for ${profileId}`);
30
- }
31
- return fromSlot;
32
- }
33
- if (pin === undefined) {
34
- return undefined;
35
- }
36
- const fromPin = listedValue(allow, pin);
37
- if (!fromPin) {
38
- throw new TheorumError(`Unknown image ${label} for ${profileId}`);
35
+ /**
36
+ * Provider wire formats (JSON schema, image, speech) are mutually exclusive.
37
+ * Typed profiles make illegal mixes unrepresentable; this remains a safety net
38
+ * for responseFormat structured on text vs accidental dual modes.
39
+ */
40
+ function assertOutputMode(profile, structuredId) {
41
+ const active = activePrimaryOutputModes(profile, structuredId);
42
+ if (active.length <= 1) {
43
+ return;
39
44
  }
40
- return fromPin;
45
+ throw new TheorumError(`Profile ${profile.id} declares multiple output wire formats (${active.join(', ')}). ` + // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
46
+ `Only one of responseFormat JSON schema (outputs.structured with enforced ` + // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
47
+ `'responseFormat'), image, or speech may be active.`);
41
48
  }
42
- function assertImageRole(profile) {
43
- const pins = profile.outputs.image;
44
- if (!pins) {
45
- throw new TheorumError(`Profile ${profile.id} requests image output but does not set outputs.image`);
49
+ function assertImagePins(profile) {
50
+ if (profile.type !== 'image') {
51
+ throw new TheorumError(`Profile ${profile.id} is not type 'image'`); // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
46
52
  }
47
- assertExclusiveNativeOutput(profile, 'image');
48
- return pins;
53
+ return profile.image;
54
+ }
55
+ function defaultBinding(profile) {
56
+ const ids = Object.keys(profile.models);
57
+ const id = profile.defaultModel ?? (ids.length === 1 ? ids[0] : undefined);
58
+ return id ? profile.models[id] : undefined;
49
59
  }
50
60
  function assertSpeechRole(profile) {
51
- if (!profile.outputs.speech) {
61
+ if (profile.type !== 'speech') {
52
62
  return;
53
63
  }
54
- assertExclusiveNativeOutput(profile, 'speech');
55
- if (profile.outputs.speech.format === 'mp3' && profile.model.protocol === 'geminiInteractions') {
56
- throw new TheorumError(`Profile ${profile.id}: outputs.speech.format 'mp3' requires protocol 'openAi' ` +
64
+ const format = profile.speech.format;
65
+ const binding = defaultBinding(profile);
66
+ if (format && binding && !isSpeechFormatAllowedForProtocol(binding.protocol, format)) {
67
+ throw new TheorumError(`Profile ${profile.id}: speech.format '${format}' requires protocol 'openAi' ` + // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
57
68
  `(geminiInteractions speech returns PCM and emits WAV)`);
58
69
  }
59
70
  }
60
- function assertExclusiveNativeOutput(profile, kind) {
61
- const other = kind === 'image' ? 'speech' : 'image';
62
- if (profile.outputs[other]) {
63
- throw new TheorumError(`Profile ${profile.id} cannot mix outputs.${kind} with outputs.${other}`);
64
- }
65
- if (profile.outputs.structured !== null && profile.outputs.structured !== undefined) {
66
- throw new TheorumError(`Profile ${profile.id} cannot mix structured JSON with native ${kind} output`);
67
- }
68
- }
69
- function resolveImageFormat(profile, _model, slots) {
70
- if (!profile.outputs.image) {
71
+ function resolveImageFormat(profile) {
72
+ if (profile.type !== 'image') {
71
73
  return null;
72
74
  }
73
- const pins = assertImageRole(profile);
74
- const aspectRatio = resolveSlotOrPin(profile.id, 'aspect', slots?.aspectRatio, pins.aspectRatio, profile.inputs.slots?.aspectRatio);
75
- const size = resolveSlotOrPin(profile.id, 'size', slots?.size, pins.size, profile.inputs.slots?.size);
76
- if (!(aspectRatio && size)) {
77
- throw new TheorumError(`Unknown image aspect or size for ${profile.id}`);
78
- }
75
+ const pins = assertImagePins(profile);
79
76
  return {
80
77
  type: 'image',
81
- mimeType: pins.mimeType ?? 'image/jpeg',
82
- aspectRatio,
83
- size,
78
+ mimeType: pins.mimeType,
79
+ aspectRatio: pins.aspectRatio,
80
+ size: pins.size,
81
+ includeText: pins.includeText === true,
84
82
  };
85
83
  }
86
84
  function assertMediaMime(mime) {
87
85
  const kind = mediaKindForMime(mime);
88
86
  if (!kind) {
89
- throw new TheorumError(`MIME '${mime}' is not a supported media input type`);
87
+ throw new TheorumError(`MIME '${mime}' is not a supported media input type`); // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
90
88
  }
91
89
  return kind;
92
90
  }
91
+ function profileInputs(profile) {
92
+ if (profile.type === 'speech' || profile.type === 'live' || profile.type === 'host') {
93
+ return undefined;
94
+ }
95
+ return profile.inputs;
96
+ }
97
+ /**
98
+ * Normalize accepted attachments into provider parts. Inline blobs and
99
+ * references share MIME acceptance and kind resolution; references carry the
100
+ * uri through untouched (no base64, no byte limits — the host owns the upload).
101
+ */
93
102
  function mediaParts(profile, model, blobs, channel) {
94
- const accept = channel === 'voice' ? profile.inputs.voice?.accept : profile.inputs.attachments?.accept;
103
+ const accept = profileAccept(profile, channel);
95
104
  if (!accept) {
96
- throw new TheorumError(`Profile ${profile.id} does not accept ${channel}`);
105
+ throw new TheorumError(`Profile ${profile.id} does not accept ${channel}`); // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
97
106
  }
98
- const maxInputImages = profile.outputs.image?.maxInputImages;
107
+ const maxInputImages = profile.type === 'image' ? profile.image.maxInputImages : undefined;
99
108
  const imageCount = blobs.filter((blob) => mediaKindForMime(blob.mimeType) === 'image').length;
100
109
  if (maxInputImages !== undefined && imageCount > maxInputImages) {
101
- throw new TheorumError(`At most ${maxInputImages} reference images on ${model}`);
110
+ throw new TheorumError(`At most ${maxInputImages} reference images on ${model}`); // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
102
111
  }
103
112
  return blobs.map((blob) => {
104
113
  const kind = assertMediaMime(blob.mimeType);
105
114
  if (!mimeAllowed(accept, blob.mimeType)) {
106
- throw new TheorumError(`MIME '${blob.mimeType}' is not accepted on ${profile.id}`);
115
+ throw new TheorumError(`MIME '${blob.mimeType}' is not accepted on ${profile.id}`); // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
107
116
  }
108
117
  const essence = mimeEssence(blob.mimeType);
109
- return {
110
- type: kind,
111
- mimeType: essence === 'image/jpg' ? 'image/jpeg' : essence,
112
- data: blob.data,
113
- };
118
+ const mimeType = essence === 'image/jpg' ? 'image/jpeg' : essence;
119
+ if (isTurnMediaRef(blob)) {
120
+ return { type: kind, mimeType, uri: blob.uri };
121
+ }
122
+ return { type: kind, mimeType, data: blob.data };
114
123
  });
115
124
  }
116
125
  function extractTextPart(profile, req) {
117
126
  const { text, repair, history } = req.input ?? {};
118
- if (profile.inputs.text === false) {
127
+ if (profile.type === 'speech') {
128
+ if (!text?.trim()) {
129
+ throw new TheorumError(`Profile ${profile.id} (speech) requires text input`); // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
130
+ }
131
+ let promptText = text;
132
+ if (repair) {
133
+ promptText = synthesizeRepairPrompt({ profile, repair, history });
134
+ }
135
+ return { type: 'text', text: wrapUserData(promptText) };
136
+ }
137
+ const inputs = profileInputs(profile);
138
+ if (inputs?.text === false) {
119
139
  if (text) {
120
- throw new TheorumError(`Profile ${profile.id} does not accept text input`);
140
+ throw new TheorumError(`Profile ${profile.id} does not accept text input`); // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
121
141
  }
122
142
  return null;
123
143
  }
@@ -131,6 +151,13 @@ function extractTextPart(profile, req) {
131
151
  return { type: 'text', text: wrapUserData(promptText) };
132
152
  }
133
153
  function extractMediaParts(profile, model, req) {
154
+ if (profile.type === 'speech') {
155
+ const { attachments, voice } = req.input ?? {};
156
+ if ((attachments?.length ?? 0) + (voice?.length ?? 0) > 0) {
157
+ throw new TheorumError(`Profile ${profile.id} (speech) does not accept media input`); // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
158
+ }
159
+ return [];
160
+ }
134
161
  const { attachments, voice } = req.input ?? {};
135
162
  const files = attachments ?? [];
136
163
  const clips = voice ?? [];
@@ -155,9 +182,4 @@ function resolveInputParts(profile, model, req) {
155
182
  parts.push(...extractMediaParts(profile, model, req));
156
183
  return parts;
157
184
  }
158
- function assertImageGrounding(profile, model, builtins) {
159
- if (profile.outputs.image?.allowsGrounding === false && builtins.length > 0) {
160
- throw new TheorumError(`Grounding tools are not valid on ${model}`);
161
- }
162
- }
163
- export { assertImageGrounding, assertSpeechRole, resolveImageFormat, resolveInputParts };
185
+ export { assertOutputMode, assertSpeechRole, resolveImageFormat, resolveInputParts };
@@ -0,0 +1,4 @@
1
+ import type { Profile, ProfileOutputsSpec } from '../types.js';
2
+ /** Turn-based `outputs` pins — absent on `type: 'live'` and `type: 'host'`. */
3
+ declare function profileTurnOutputs(profile: Profile): ProfileOutputsSpec | undefined;
4
+ export { profileTurnOutputs };
@@ -0,0 +1,8 @@
1
+ /** Turn-based `outputs` pins — absent on `type: 'live'` and `type: 'host'`. */
2
+ function profileTurnOutputs(profile) {
3
+ if (profile.type === 'live' || profile.type === 'host') {
4
+ return undefined;
5
+ }
6
+ return profile.outputs;
7
+ }
8
+ export { profileTurnOutputs };
@@ -1,23 +1,66 @@
1
1
  /**
2
2
  * Runtime profile registry for host-owned THEORUM profiles.
3
3
  *
4
- * THEORUM ships profile types and defaults, not application profiles. Host apps
5
- * register their profiles at process startup or test setup.
4
+ * THEORUM ships profile types — not application profiles and not invented defaults.
5
+ * Hosts must pass required fields explicitly (`type`, `models`, …).
6
6
  *
7
7
  * @module
8
8
  */
9
- import type { Profile } from '../types.js';
10
- /** Host-authored profile definition, with defaults applied to omitted sections. */
11
- export type ProfileDefinition = {
9
+ import { type HostGuardrailsSpec, type ProfileGuardrailsSpec } from '../../guardrails/types.js';
10
+ import type { ProfileObservabilitySpec } from '../../observability/types.js';
11
+ import type { HostProfile, HostProfileToolsSpec, ImageProfile, LiveProfile, LiveProfileToolsSpec, ModelBinding, ModelId, Profile, ProfileIdentity, ProfileInputsSpec, ProfileModelFields, ProfileOutputsSpec, ProfileToolsSpec, ProfileTurnBehaviourSpec, SpeechProfile } from '../types.js';
12
+ export type ProfileDefinitionBase = {
12
13
  id: Profile['id'];
13
- identity?: Partial<Profile['identity']>;
14
- model: Partial<Profile['model']> & Pick<Profile['model'], 'allow' | 'config'>;
15
- tools?: Partial<Profile['tools']>;
16
- inputs?: Partial<Profile['inputs']>;
17
- outputs?: Partial<Profile['outputs']>;
18
- guardrails?: Partial<Profile['guardrails']>;
14
+ identity: ProfileIdentity;
15
+ models: Record<ModelId, ModelBinding>;
16
+ defaultModel?: ModelId;
17
+ allowModelSelect?: boolean;
18
+ maxSteps?: number;
19
+ key?: ProfileModelFields['key'];
20
+ outputs?: ProfileOutputsSpec;
21
+ guardrails?: ProfileGuardrailsSpec;
22
+ observability?: ProfileObservabilitySpec;
19
23
  };
20
- /** Define a typed profile with stable defaults for optional properties. */
24
+ export type TextProfileDefinition = ProfileDefinitionBase & {
25
+ type: 'text';
26
+ tools: ProfileToolsSpec;
27
+ inputs: ProfileInputsSpec;
28
+ turnBehaviour?: ProfileTurnBehaviourSpec;
29
+ };
30
+ export type ImageProfileDefinition = ProfileDefinitionBase & {
31
+ type: 'image';
32
+ image: NonNullable<ImageProfile['image']>;
33
+ tools: ProfileToolsSpec;
34
+ inputs: ProfileInputsSpec;
35
+ turnBehaviour?: ProfileTurnBehaviourSpec;
36
+ };
37
+ export type SpeechProfileDefinition = ProfileDefinitionBase & {
38
+ type: 'speech';
39
+ speech: NonNullable<SpeechProfile['speech']>;
40
+ turnBehaviour?: ProfileTurnBehaviourSpec;
41
+ };
42
+ export type LiveProfileDefinition = ProfileDefinitionBase & {
43
+ type: 'live';
44
+ live: NonNullable<LiveProfile['live']>;
45
+ tools: LiveProfileToolsSpec;
46
+ /** Inject gate only — resumption is `live.sessionResumption`. */
47
+ turnBehaviour?: Pick<ProfileTurnBehaviourSpec, 'allowSteering'>;
48
+ };
49
+ /** Host-driven tool ceiling — no models, identity, inputs, outputs, turnBehaviour, key, or maxSteps. */
50
+ export type HostProfileDefinition = {
51
+ type: 'host';
52
+ id: Profile['id'];
53
+ tools: HostProfileToolsSpec;
54
+ /** Only the guards that fire on the `invokeTool` path — see {@link HostGuardrailsSpec}. */
55
+ guardrails?: HostGuardrailsSpec;
56
+ observability?: ProfileObservabilitySpec;
57
+ };
58
+ /** Host-authored profile definition — discriminated on `type`. No THEORUM defaults. */
59
+ export type ProfileDefinition = TextProfileDefinition | ImageProfileDefinition | SpeechProfileDefinition | LiveProfileDefinition | HostProfileDefinition;
60
+ /** Define a typed profile. Required fields must be set explicitly; optional fields stay optional. */
61
+ declare function defineProfile(input: LiveProfileDefinition): LiveProfile;
62
+ declare function defineProfile(input: HostProfileDefinition): HostProfile;
63
+ declare function defineProfile(input: Exclude<ProfileDefinition, LiveProfileDefinition | HostProfileDefinition>): Exclude<Profile, LiveProfile | HostProfile>;
21
64
  declare function defineProfile(input: ProfileDefinition): Profile;
22
65
  /** Register one host-owned profile in the process-local registry. */
23
66
  declare function registerProfile(profileInput: Profile | ProfileDefinition): void;