theorum 0.1.14 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. package/README.md +178 -89
  2. package/esm/mod.d.ts +21 -11
  3. package/esm/mod.js +15 -8
  4. package/esm/src/cli/commands/bench.js +6 -3
  5. package/esm/src/cli/commands/fuzz-canary.d.ts +13 -0
  6. package/esm/src/cli/commands/fuzz-canary.js +195 -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/profile.js +25 -14
  10. package/esm/src/cli/commands/run.d.ts +3 -0
  11. package/esm/src/cli/commands/run.js +22 -30
  12. package/esm/src/cli/commands/test.d.ts +10 -1
  13. package/esm/src/cli/commands/test.js +31 -31
  14. package/esm/src/cli/event-log.d.ts +19 -0
  15. package/esm/src/cli/event-log.js +147 -0
  16. package/esm/src/cli/index.js +45 -11
  17. package/esm/src/cli/matrix/synthesizer.d.ts +7 -9
  18. package/esm/src/cli/matrix/synthesizer.js +32 -106
  19. package/esm/src/guardrails/canary-gate.d.ts +20 -0
  20. package/esm/src/guardrails/canary-gate.js +31 -0
  21. package/esm/src/guardrails/canary.d.ts +26 -0
  22. package/esm/src/guardrails/canary.js +139 -0
  23. package/esm/src/guardrails/corpus/canary-egress-attacks.d.ts +16 -0
  24. package/esm/src/guardrails/corpus/canary-egress-attacks.js +151 -0
  25. package/esm/src/guardrails/corpus/fuzz-inbound.d.ts +11 -0
  26. package/esm/src/guardrails/corpus/fuzz-inbound.js +216 -0
  27. package/esm/src/guardrails/corpus/inbound-payloads.d.ts +10 -0
  28. package/esm/src/guardrails/corpus/inbound-payloads.js +124 -0
  29. package/esm/src/guardrails/corpus/live-attacks.d.ts +20 -0
  30. package/esm/src/guardrails/corpus/live-attacks.js +232 -0
  31. package/esm/src/guardrails/corpus/mod.d.ts +13 -0
  32. package/esm/src/guardrails/corpus/mod.js +11 -0
  33. package/esm/src/guardrails/corpus/secrets.d.ts +16 -0
  34. package/esm/src/guardrails/corpus/secrets.js +16 -0
  35. package/esm/src/guardrails/corpus/strings.d.ts +27 -0
  36. package/esm/src/guardrails/corpus/strings.js +33 -0
  37. package/esm/src/guardrails/corpus/types.d.ts +37 -0
  38. package/esm/src/guardrails/corpus/types.js +6 -0
  39. package/esm/src/guardrails/egress.d.ts +9 -0
  40. package/esm/src/guardrails/egress.js +36 -0
  41. package/esm/src/guardrails/error.js +6 -4
  42. package/esm/src/guardrails/injection.js +28 -5
  43. package/esm/src/guardrails/live-outbound-gate.d.ts +39 -0
  44. package/esm/src/guardrails/live-outbound-gate.js +180 -0
  45. package/esm/src/guardrails/mod.d.ts +13 -4
  46. package/esm/src/guardrails/mod.js +10 -4
  47. package/esm/src/guardrails/quota.js +1 -1
  48. package/esm/src/guardrails/sanitize.d.ts +11 -15
  49. package/esm/src/guardrails/sanitize.js +40 -49
  50. package/esm/src/guardrails/sensitive.js +2 -1
  51. package/esm/src/guardrails/testing.d.ts +10 -0
  52. package/esm/src/guardrails/testing.js +9 -0
  53. package/esm/src/host/client-turn.d.ts +19 -0
  54. package/esm/src/host/client-turn.js +32 -0
  55. package/esm/src/host/mod.d.ts +5 -3
  56. package/esm/src/host/mod.js +4 -3
  57. package/esm/src/interface/blocks.d.ts +20 -0
  58. package/esm/src/interface/blocks.js +180 -0
  59. package/esm/src/interface/from-profile.d.ts +14 -0
  60. package/esm/src/interface/from-profile.js +104 -0
  61. package/esm/src/interface/inputs.d.ts +8 -0
  62. package/esm/src/interface/inputs.js +127 -0
  63. package/esm/src/interface/mod.d.ts +10 -0
  64. package/esm/src/interface/mod.js +9 -0
  65. package/esm/src/interface/types.d.ts +153 -0
  66. package/esm/src/interface/types.js +13 -0
  67. package/esm/src/kernel/engine/delta.d.ts +24 -2
  68. package/esm/src/kernel/engine/delta.js +519 -29
  69. package/esm/src/kernel/engine/live-inbound.d.ts +9 -0
  70. package/esm/src/kernel/engine/live-inbound.js +17 -0
  71. package/esm/src/kernel/engine/repair.js +1 -1
  72. package/esm/src/kernel/engine/runner/gates.d.ts +1 -1
  73. package/esm/src/kernel/engine/runner/gates.js +7 -7
  74. package/esm/src/kernel/engine/runner/mod.js +13 -13
  75. package/esm/src/kernel/engine/runner/state.d.ts +7 -0
  76. package/esm/src/kernel/engine/runner/steps.d.ts +1 -1
  77. package/esm/src/kernel/engine/runner/steps.js +171 -37
  78. package/esm/src/kernel/engine/runner/stream.d.ts +1 -2
  79. package/esm/src/kernel/engine/runner/stream.js +66 -29
  80. package/esm/src/kernel/engine/session/mod.d.ts +23 -0
  81. package/esm/src/kernel/engine/session/mod.js +205 -0
  82. package/esm/src/kernel/mod.d.ts +9 -10
  83. package/esm/src/kernel/mod.js +6 -8
  84. package/esm/src/kernel/registry/attachments.d.ts +4 -2
  85. package/esm/src/kernel/registry/attachments.js +6 -3
  86. package/esm/src/kernel/registry/catalog.d.ts +3 -16
  87. package/esm/src/kernel/registry/catalog.js +5 -81
  88. package/esm/src/kernel/registry/ingress.d.ts +9 -4
  89. package/esm/src/kernel/registry/ingress.js +73 -63
  90. package/esm/src/kernel/registry/profiles.d.ts +35 -12
  91. package/esm/src/kernel/registry/profiles.js +159 -66
  92. package/esm/src/kernel/registry/provider-request.js +11 -7
  93. package/esm/src/kernel/registry/resolve.d.ts +5 -6
  94. package/esm/src/kernel/registry/resolve.js +100 -70
  95. package/esm/src/kernel/registry/vault.d.ts +12 -7
  96. package/esm/src/kernel/registry/vault.js +30 -8
  97. package/esm/src/kernel/schema.d.ts +145 -0
  98. package/esm/src/kernel/schema.js +462 -0
  99. package/esm/src/kernel/stop.d.ts +15 -10
  100. package/esm/src/kernel/stop.js +5 -5
  101. package/esm/src/kernel/tools/execute.d.ts +45 -0
  102. package/esm/src/kernel/tools/execute.js +363 -0
  103. package/esm/src/kernel/tools/harness.d.ts +8 -0
  104. package/esm/src/kernel/tools/harness.js +44 -0
  105. package/esm/src/kernel/tools/invoke.d.ts +10 -0
  106. package/esm/src/kernel/tools/invoke.js +83 -0
  107. package/esm/src/kernel/tools/mod.d.ts +11 -0
  108. package/esm/src/kernel/tools/mod.js +10 -0
  109. package/esm/src/kernel/tools/project.d.ts +12 -0
  110. package/esm/src/kernel/tools/project.js +34 -0
  111. package/esm/src/kernel/tools/registry.d.ts +23 -0
  112. package/esm/src/kernel/tools/registry.js +71 -0
  113. package/esm/src/kernel/tools/resolve.d.ts +29 -0
  114. package/esm/src/kernel/tools/resolve.js +262 -0
  115. package/esm/src/kernel/tools/schema.d.ts +13 -0
  116. package/esm/src/kernel/tools/schema.js +159 -0
  117. package/esm/src/kernel/tools/types.d.ts +216 -0
  118. package/esm/src/kernel/tools/types.js +9 -0
  119. package/esm/src/kernel/types.d.ts +338 -193
  120. package/esm/src/observability/trace-attach.d.ts +6 -4
  121. package/esm/src/observability/trace-attach.js +39 -25
  122. package/esm/src/observability/trace-record.d.ts +11 -9
  123. package/esm/src/observability/trace-record.js +36 -23
  124. package/esm/src/observability/trace-usage.d.ts +10 -3
  125. package/esm/src/observability/trace-usage.js +70 -17
  126. package/esm/src/observability/trace.d.ts +9 -1
  127. package/esm/src/observability/trace.js +11 -3
  128. package/esm/src/presets/google/speech-voices.d.ts +11 -0
  129. package/esm/src/presets/google/speech-voices.js +41 -0
  130. package/esm/src/presets/google.d.ts +36 -24
  131. package/esm/src/presets/google.js +50 -63
  132. package/esm/src/presets/mod.d.ts +2 -2
  133. package/esm/src/presets/mod.js +1 -1
  134. package/esm/src/providers/create-provider.d.ts +14 -14
  135. package/esm/src/providers/create-provider.js +55 -23
  136. package/esm/src/providers/google/interactions/framing.d.ts +15 -0
  137. package/esm/src/providers/google/interactions/framing.js +208 -0
  138. package/esm/src/providers/google/interactions/mod.d.ts +7 -0
  139. package/esm/src/providers/google/interactions/mod.js +7 -0
  140. package/esm/src/providers/google/interactions/stream.d.ts +83 -0
  141. package/esm/src/providers/google/interactions/stream.js +588 -0
  142. package/esm/src/providers/google/keys.d.ts +26 -0
  143. package/esm/src/providers/{keys.js → google/keys.js} +19 -31
  144. package/esm/src/providers/google/live/framing.d.ts +46 -0
  145. package/esm/src/providers/google/live/framing.js +515 -0
  146. package/esm/src/providers/google/live/openapi-schema.d.ts +6 -0
  147. package/esm/src/providers/google/live/openapi-schema.js +45 -0
  148. package/esm/src/providers/google/live/session.d.ts +25 -0
  149. package/esm/src/providers/google/live/session.js +134 -0
  150. package/esm/src/providers/google/live/stream.d.ts +36 -0
  151. package/esm/src/providers/google/live/stream.js +201 -0
  152. package/esm/src/providers/google/urls.d.ts +6 -0
  153. package/esm/src/providers/google/urls.js +6 -0
  154. package/esm/src/providers/local/local.d.ts +30 -0
  155. package/esm/src/providers/{local.js → local/local.js} +66 -126
  156. package/esm/src/providers/local/mod.d.ts +9 -0
  157. package/esm/src/providers/local/mod.js +9 -0
  158. package/esm/src/providers/mod.d.ts +6 -3
  159. package/esm/src/providers/mod.js +3 -1
  160. package/esm/src/providers/openrouter/chat.d.ts +87 -0
  161. package/esm/src/providers/{openrouter.js → openrouter/chat.js} +70 -229
  162. package/esm/src/providers/openrouter/image.d.ts +34 -0
  163. package/esm/src/providers/openrouter/image.js +286 -0
  164. package/esm/src/providers/openrouter/openai/chat-payload.d.ts +24 -0
  165. package/esm/src/providers/openrouter/openai/chat-payload.js +60 -0
  166. package/esm/src/providers/openrouter/openai/compat.d.ts +47 -0
  167. package/esm/src/providers/openrouter/openai/compat.js +194 -0
  168. package/esm/src/providers/openrouter/openai/image-payload.d.ts +18 -0
  169. package/esm/src/providers/openrouter/openai/image-payload.js +84 -0
  170. package/esm/src/providers/openrouter/openai/sdk-messages.d.ts +22 -0
  171. package/esm/src/providers/openrouter/openai/sdk-messages.js +95 -0
  172. package/esm/src/providers/openrouter/resolve-api-key.d.ts +9 -0
  173. package/esm/src/providers/openrouter/resolve-api-key.js +24 -0
  174. package/esm/src/providers/openrouter/speech.d.ts +23 -0
  175. package/esm/src/providers/{speech.js → openrouter/speech.js} +32 -55
  176. package/esm/src/providers/probe.d.ts +1 -0
  177. package/esm/src/providers/probe.js +22 -0
  178. package/esm/src/providers/shared/pcm.d.ts +12 -0
  179. package/esm/src/providers/{pcm.js → shared/pcm.js} +16 -3
  180. package/esm/src/providers/shared/sse.d.ts +18 -0
  181. package/esm/src/providers/shared/sse.js +87 -0
  182. package/esm/src/providers/shared/tool-args.d.ts +17 -0
  183. package/esm/src/providers/shared/tool-args.js +45 -0
  184. package/esm/src/providers/shared/upstream-tap.d.ts +5 -0
  185. package/esm/src/providers/{google-tap.js → shared/upstream-tap.js} +4 -7
  186. package/esm/src/providers/shared/upstream-tape.d.ts +6 -0
  187. package/esm/src/providers/{gemini-tape.js → shared/upstream-tape.js} +12 -22
  188. package/esm/src/providers/types.d.ts +27 -0
  189. package/esm/src/providers/types.js +1 -0
  190. package/package.json +11 -7
  191. package/docs/cli.md +0 -97
  192. package/docs/guardrails.md +0 -178
  193. package/docs/host.md +0 -97
  194. package/docs/kernel.md +0 -404
  195. package/docs/observability.md +0 -105
  196. package/docs/openrouter.md +0 -125
  197. package/docs/presets-google.md +0 -91
  198. package/docs/presets.md +0 -88
  199. package/docs/providers.md +0 -201
  200. package/docs/streaming.md +0 -96
  201. package/esm/src/kernel/engine/boundary.d.ts +0 -10
  202. package/esm/src/kernel/engine/boundary.js +0 -55
  203. package/esm/src/kernel/engine/runner/tools.d.ts +0 -13
  204. package/esm/src/kernel/engine/runner/tools.js +0 -198
  205. package/esm/src/kernel/registry/tools.d.ts +0 -12
  206. package/esm/src/kernel/registry/tools.js +0 -36
  207. package/esm/src/providers/expose-for-tests.d.ts +0 -1
  208. package/esm/src/providers/expose-for-tests.js +0 -25
  209. package/esm/src/providers/gemini-tape.d.ts +0 -2
  210. package/esm/src/providers/google-tap.d.ts +0 -3
  211. package/esm/src/providers/interactions.d.ts +0 -5
  212. package/esm/src/providers/interactions.js +0 -169
  213. package/esm/src/providers/keys.d.ts +0 -19
  214. package/esm/src/providers/local.d.ts +0 -29
  215. package/esm/src/providers/openrouter-mod.d.ts +0 -13
  216. package/esm/src/providers/openrouter-mod.js +0 -12
  217. package/esm/src/providers/openrouter-payload.d.ts +0 -39
  218. package/esm/src/providers/openrouter-payload.js +0 -195
  219. package/esm/src/providers/openrouter.d.ts +0 -15
  220. package/esm/src/providers/pcm.d.ts +0 -7
  221. package/esm/src/providers/provider.d.ts +0 -15
  222. package/esm/src/providers/provider.js +0 -176
  223. package/esm/src/providers/speech.d.ts +0 -23
  224. package/esm/src/providers/sse.d.ts +0 -7
  225. package/esm/src/providers/sse.js +0 -55
  226. package/esm/src/streaming/mod.d.ts +0 -9
  227. package/esm/src/streaming/mod.js +0 -8
  228. /package/esm/src/{streaming → host}/readStreamingJsonStringField.d.ts +0 -0
  229. /package/esm/src/{streaming → host}/readStreamingJsonStringField.js +0 -0
@@ -5,82 +5,72 @@
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
11
  import { assertAttachmentLimits, requireMediaLimits } from './attachments.js';
12
12
  import { mediaKindForMime, mimeAllowed, mimeEssence } from './catalog.js';
13
- function listedValue(allowed, value) {
14
- if (!value) {
15
- return undefined;
16
- }
17
- if (!allowed || allowed.length === 0) {
18
- return value;
19
- }
20
- if (allowed.includes(value)) {
21
- return value;
13
+ import { getStructured } from './schemas.js';
14
+ function usesStructuredResponseFormat(structuredId) {
15
+ if (!structuredId) {
16
+ return false;
22
17
  }
23
- return undefined;
18
+ const spec = getStructured(structuredId);
19
+ return spec.enforced === 'responseFormat' && spec.jsonSchema != null;
24
20
  }
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;
21
+ function activePrimaryOutputModes(profile, structuredId) {
22
+ const modes = [];
23
+ if (usesStructuredResponseFormat(structuredId)) {
24
+ modes.push('structured');
32
25
  }
33
- if (pin === undefined) {
34
- return undefined;
26
+ if (profile.type === 'image') {
27
+ modes.push('image');
28
+ }
29
+ if (profile.type === 'speech') {
30
+ modes.push('speech');
35
31
  }
36
- const fromPin = listedValue(allow, pin);
37
- if (!fromPin) {
38
- throw new TheorumError(`Unknown image ${label} for ${profileId}`);
32
+ return modes;
33
+ }
34
+ /**
35
+ * Provider wire formats (JSON schema, image, speech) are mutually exclusive.
36
+ * Typed profiles make illegal mixes unrepresentable; this remains a safety net
37
+ * for responseFormat structured on text vs accidental dual modes.
38
+ */
39
+ function assertOutputMode(profile, structuredId) {
40
+ const active = activePrimaryOutputModes(profile, structuredId);
41
+ if (active.length <= 1) {
42
+ return;
39
43
  }
40
- return fromPin;
44
+ throw new TheorumError(`Profile ${profile.id} declares multiple output wire formats (${active.join(', ')}). ` +
45
+ `Only one of responseFormat JSON schema (outputs.structured with enforced ` +
46
+ `'responseFormat'), image, or speech may be active.`);
41
47
  }
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`);
48
+ function assertImagePins(profile) {
49
+ if (profile.type !== 'image') {
50
+ throw new TheorumError(`Profile ${profile.id} is not type 'image'`);
46
51
  }
47
- assertExclusiveNativeOutput(profile, 'image');
48
- return pins;
52
+ return profile.image;
49
53
  }
50
54
  function assertSpeechRole(profile) {
51
- if (!profile.outputs.speech) {
55
+ if (profile.type !== 'speech') {
52
56
  return;
53
57
  }
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' ` +
58
+ if (profile.speech.format === 'mp3' && profile.model.protocol === 'geminiInteractions') {
59
+ throw new TheorumError(`Profile ${profile.id}: speech.format 'mp3' requires protocol 'openAi' ` +
57
60
  `(geminiInteractions speech returns PCM and emits WAV)`);
58
61
  }
59
62
  }
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) {
63
+ function resolveImageFormat(profile) {
64
+ if (profile.type !== 'image') {
71
65
  return null;
72
66
  }
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
- }
67
+ const pins = assertImagePins(profile);
79
68
  return {
80
69
  type: 'image',
81
- mimeType: pins.mimeType ?? 'image/jpeg',
82
- aspectRatio,
83
- size,
70
+ mimeType: pins.mimeType,
71
+ aspectRatio: pins.aspectRatio,
72
+ size: pins.size,
73
+ includeText: pins.includeText === true,
84
74
  };
85
75
  }
86
76
  function assertMediaMime(mime) {
@@ -90,12 +80,19 @@ function assertMediaMime(mime) {
90
80
  }
91
81
  return kind;
92
82
  }
83
+ function profileInputs(profile) {
84
+ if (profile.type === 'speech') {
85
+ return undefined;
86
+ }
87
+ return profile.inputs;
88
+ }
93
89
  function mediaParts(profile, model, blobs, channel) {
94
- const accept = channel === 'voice' ? profile.inputs.voice?.accept : profile.inputs.attachments?.accept;
90
+ const inputs = profileInputs(profile);
91
+ const accept = channel === 'voice' ? inputs?.voice?.accept : inputs?.attachments?.accept;
95
92
  if (!accept) {
96
93
  throw new TheorumError(`Profile ${profile.id} does not accept ${channel}`);
97
94
  }
98
- const maxInputImages = profile.outputs.image?.maxInputImages;
95
+ const maxInputImages = profile.type === 'image' ? profile.image.maxInputImages : undefined;
99
96
  const imageCount = blobs.filter((blob) => mediaKindForMime(blob.mimeType) === 'image').length;
100
97
  if (maxInputImages !== undefined && imageCount > maxInputImages) {
101
98
  throw new TheorumError(`At most ${maxInputImages} reference images on ${model}`);
@@ -115,7 +112,18 @@ function mediaParts(profile, model, blobs, channel) {
115
112
  }
116
113
  function extractTextPart(profile, req) {
117
114
  const { text, repair, history } = req.input ?? {};
118
- if (profile.inputs.text === false) {
115
+ if (profile.type === 'speech') {
116
+ if (!text?.trim()) {
117
+ throw new TheorumError(`Profile ${profile.id} (speech) requires text input`);
118
+ }
119
+ let promptText = text;
120
+ if (repair) {
121
+ promptText = synthesizeRepairPrompt({ profile, repair, history });
122
+ }
123
+ return { type: 'text', text: wrapUserData(promptText) };
124
+ }
125
+ const inputs = profileInputs(profile);
126
+ if (inputs?.text === false) {
119
127
  if (text) {
120
128
  throw new TheorumError(`Profile ${profile.id} does not accept text input`);
121
129
  }
@@ -131,6 +139,13 @@ function extractTextPart(profile, req) {
131
139
  return { type: 'text', text: wrapUserData(promptText) };
132
140
  }
133
141
  function extractMediaParts(profile, model, req) {
142
+ if (profile.type === 'speech') {
143
+ const { attachments, voice } = req.input ?? {};
144
+ if ((attachments?.length ?? 0) + (voice?.length ?? 0) > 0) {
145
+ throw new TheorumError(`Profile ${profile.id} (speech) does not accept media input`);
146
+ }
147
+ return [];
148
+ }
134
149
  const { attachments, voice } = req.input ?? {};
135
150
  const files = attachments ?? [];
136
151
  const clips = voice ?? [];
@@ -155,9 +170,4 @@ function resolveInputParts(profile, model, req) {
155
170
  parts.push(...extractMediaParts(profile, model, req));
156
171
  return parts;
157
172
  }
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 };
173
+ export { assertOutputMode, assertSpeechRole, resolveImageFormat, resolveInputParts };
@@ -1,23 +1,46 @@
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`, `model.protocol`, `model.provider`, …).
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 { ImageProfile, LiveProfile, LiveProfileModelSpec, Profile, ProfileGuardrailsSpec, ProfileIdentity, ProfileInputsSpec, ProfileModelSpec, ProfileOutputsSpec, ProfileToolsSpec, ProfileTurnResumptionSpec, SpeechProfile, TurnProfileModelSpec } from '../types.js';
10
+ export type ProfileDefinitionBase<P extends ProfileModelSpec = ProfileModelSpec> = {
12
11
  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']>;
12
+ identity: ProfileIdentity;
13
+ model: P;
14
+ outputs?: ProfileOutputsSpec;
15
+ guardrails?: ProfileGuardrailsSpec;
19
16
  };
20
- /** Define a typed profile with stable defaults for optional properties. */
17
+ export type TextProfileDefinition = ProfileDefinitionBase<TurnProfileModelSpec> & {
18
+ type: 'text';
19
+ tools: ProfileToolsSpec;
20
+ inputs: ProfileInputsSpec;
21
+ turnResumption?: ProfileTurnResumptionSpec;
22
+ };
23
+ export type ImageProfileDefinition = ProfileDefinitionBase<TurnProfileModelSpec> & {
24
+ type: 'image';
25
+ image: NonNullable<ImageProfile['image']>;
26
+ tools: ProfileToolsSpec;
27
+ inputs: ProfileInputsSpec;
28
+ turnResumption?: ProfileTurnResumptionSpec;
29
+ };
30
+ export type SpeechProfileDefinition = ProfileDefinitionBase<TurnProfileModelSpec> & {
31
+ type: 'speech';
32
+ speech: NonNullable<SpeechProfile['speech']>;
33
+ turnResumption?: ProfileTurnResumptionSpec;
34
+ };
35
+ export type LiveProfileDefinition = ProfileDefinitionBase<LiveProfileModelSpec> & {
36
+ type: 'live';
37
+ live: NonNullable<LiveProfile['live']>;
38
+ tools: ProfileToolsSpec;
39
+ inputs?: ProfileInputsSpec;
40
+ };
41
+ /** Host-authored profile definition — discriminated on `type`. No THEORUM defaults. */
42
+ export type ProfileDefinition = TextProfileDefinition | ImageProfileDefinition | SpeechProfileDefinition | LiveProfileDefinition;
43
+ /** Define a typed profile. Required fields must be set explicitly; optional fields stay optional. */
21
44
  declare function defineProfile(input: ProfileDefinition): Profile;
22
45
  /** Register one host-owned profile in the process-local registry. */
23
46
  declare function registerProfile(profileInput: Profile | ProfileDefinition): void;
@@ -1,12 +1,14 @@
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`, `model.protocol`, `model.provider`, …).
6
6
  *
7
7
  * @module
8
8
  */
9
9
  import { TheorumError } from '../../guardrails/error.js';
10
+ import { isValidPair, isValidProfileProtocol, protocolsForProfileType } from '../schema.js';
11
+ import { getTool } from '../tools/registry.js';
10
12
  const profiles = new Map();
11
13
  function assertModelSpecs(profileId, allow, config) {
12
14
  for (const id of allow) {
@@ -15,69 +17,96 @@ function assertModelSpecs(profileId, allow, config) {
15
17
  }
16
18
  }
17
19
  }
18
- function buildDefaultIdentity(id, identity) {
19
- return {
20
- handle: identity?.handle ?? id,
21
- chat: identity?.chat,
22
- system: identity?.system,
23
- systemByRole: identity?.systemByRole,
24
- };
25
- }
26
- function buildDefaultModel(profileId, model) {
27
- assertModelSpecs(profileId, model.allow, model.config);
28
- return {
29
- protocol: model.protocol ?? 'geminiInteractions',
30
- provider: model.provider ?? 'google',
31
- allow: model.allow,
32
- config: model.config,
33
- thinking: model.thinking ?? 'minimal',
34
- controls: model.controls ?? [],
35
- maxSteps: model.maxSteps ?? 1,
36
- key: model.key ?? 'freeA',
37
- select: model.select,
38
- };
39
- }
40
- function buildDefaultInputs(inputs) {
41
- return {
42
- text: inputs?.text ?? true,
43
- attachments: inputs?.attachments,
44
- voice: inputs?.voice,
45
- maxFiles: inputs?.maxFiles,
46
- maxBytes: inputs?.maxBytes,
47
- maxTurnBytes: inputs?.maxTurnBytes,
48
- limitsByMime: inputs?.limitsByMime,
49
- slots: inputs?.slots,
50
- };
51
- }
52
- function buildDefaultOutputs(outputs) {
53
- return {
54
- structured: outputs?.structured ?? null,
55
- image: outputs?.image,
56
- speech: outputs?.speech,
57
- validation: outputs?.validation,
58
- streaming: outputs?.streaming,
59
- };
20
+ function assertProtocolProvider(profileId, model) {
21
+ if (!model.protocol) {
22
+ throw new TheorumError(`Profile ${profileId} must set model.protocol`);
23
+ }
24
+ if (!model.provider) {
25
+ throw new TheorumError(`Profile ${profileId} must set model.provider`);
26
+ }
27
+ if (!isValidPair(model.protocol, model.provider)) {
28
+ throw new TheorumError(`Profile ${profileId}: protocol '${model.protocol}' is not valid for provider '${model.provider}'`);
29
+ }
60
30
  }
61
- function buildDefaultGuardrails(guardrails) {
62
- return {
63
- quota: guardrails?.quota,
64
- canary: guardrails?.canary ?? true,
65
- sanitizeInput: guardrails?.sanitizeInput ?? true,
66
- redactSensitive: guardrails?.redactSensitive ?? true,
67
- egress: guardrails?.egress,
68
- };
31
+ function assertTypeProtocol(profile) {
32
+ const protocol = profile.model.protocol;
33
+ if (isValidProfileProtocol(profile.type, protocol))
34
+ return;
35
+ const valid = protocolsForProfileType(profile.type).join(', ');
36
+ throw new TheorumError(`Profile ${profile.id}: type '${profile.type}' cannot use protocol '${protocol}'. Supported: ${valid}`);
69
37
  }
70
- /** Define a typed profile with stable defaults for optional properties. */
38
+ /** Define a typed profile. Required fields must be set explicitly; optional fields stay optional. */
71
39
  function defineProfile(input) {
72
- return {
73
- id: input.id,
74
- identity: buildDefaultIdentity(input.id, input.identity),
75
- model: buildDefaultModel(input.id, input.model),
76
- tools: { allow: input.tools?.allow ?? [] },
77
- inputs: buildDefaultInputs(input.inputs),
78
- outputs: buildDefaultOutputs(input.outputs),
79
- guardrails: buildDefaultGuardrails(input.guardrails),
40
+ assertProtocolProvider(input.id, input.model);
41
+ assertModelSpecs(input.id, input.model.allow, input.model.config);
42
+ const identity = {
43
+ handle: input.identity.handle,
44
+ system: input.identity.system,
45
+ systemByRole: input.identity.systemByRole,
80
46
  };
47
+ const outputs = input.outputs;
48
+ const guardrails = input.guardrails;
49
+ let profile;
50
+ switch (input.type) {
51
+ case 'text':
52
+ profile = {
53
+ type: 'text',
54
+ id: input.id,
55
+ identity,
56
+ model: input.model,
57
+ tools: input.tools,
58
+ inputs: input.inputs,
59
+ outputs,
60
+ turnResumption: input.turnResumption,
61
+ guardrails,
62
+ };
63
+ break;
64
+ case 'image':
65
+ profile = {
66
+ type: 'image',
67
+ id: input.id,
68
+ identity,
69
+ model: input.model,
70
+ image: input.image,
71
+ tools: input.tools,
72
+ inputs: input.inputs,
73
+ outputs,
74
+ turnResumption: input.turnResumption,
75
+ guardrails,
76
+ };
77
+ break;
78
+ case 'speech':
79
+ profile = {
80
+ type: 'speech',
81
+ id: input.id,
82
+ identity,
83
+ model: input.model,
84
+ speech: input.speech,
85
+ outputs,
86
+ turnResumption: input.turnResumption,
87
+ guardrails,
88
+ };
89
+ break;
90
+ case 'live':
91
+ profile = {
92
+ type: 'live',
93
+ id: input.id,
94
+ identity,
95
+ model: input.model,
96
+ live: input.live,
97
+ tools: input.tools,
98
+ inputs: input.inputs,
99
+ outputs,
100
+ guardrails,
101
+ };
102
+ break;
103
+ default: {
104
+ const _exhaustive = input;
105
+ throw new TheorumError(`Unknown profile type '${String(_exhaustive)}'`);
106
+ }
107
+ }
108
+ assertTypeProtocol(profile);
109
+ return profile;
81
110
  }
82
111
  function assertCompactionSpec(profileId, modelId, spec) {
83
112
  const tag = `Profile ${profileId} model ${modelId} compaction`;
@@ -111,16 +140,80 @@ function assertCompactionRetain(tag, spec) {
111
140
  throw new TheorumError(`${tag}: previousExchanges >= 1 must be an integer`);
112
141
  }
113
142
  }
114
- /** Register one host-owned profile in the process-local registry. */
115
- function registerProfile(profileInput) {
116
- const profile = defineProfile(profileInput);
117
- assertModelSpecs(profile.id, profile.model.allow, profile.model.config);
118
- const { attachments, voice, maxFiles, maxBytes, maxTurnBytes } = profile.inputs;
143
+ function profileToolsAllow(profile) {
144
+ if (profile.type === 'speech') {
145
+ return [];
146
+ }
147
+ return profile.tools.allow;
148
+ }
149
+ function assertCustomToolsOnly(profile) {
150
+ for (const id of profileToolsAllow(profile)) {
151
+ const tool = getTool(id);
152
+ if (tool?.type === 'builtin') {
153
+ throw new TheorumError(`Profile ${profile.id} lists builtin '${id}' in tools.allow — declare it on model.config.*.builtInTools instead`);
154
+ }
155
+ }
156
+ }
157
+ function assertProfileToolLoader(profile) {
158
+ if (profile.type === 'speech') {
159
+ return;
160
+ }
161
+ const loaderId = profile.tools.t2Loader;
162
+ if (!loaderId) {
163
+ return;
164
+ }
165
+ if (!profile.tools.allow.includes(loaderId)) {
166
+ throw new TheorumError(`Profile ${profile.id} tools.t2Loader '${loaderId}' must also be listed in tools.allow`);
167
+ }
168
+ const tool = getTool(loaderId);
169
+ if (tool?.type !== 'function') {
170
+ throw new TheorumError(`Profile ${profile.id} tools.t2Loader '${loaderId}' must be a registered type: 'function' tool`);
171
+ }
172
+ }
173
+ function assertModelBuiltInTools(profile) {
174
+ for (const [modelId, spec] of Object.entries(profile.model.config)) {
175
+ for (const id of spec.builtInTools ?? []) {
176
+ const tool = getTool(id);
177
+ if (tool?.type !== 'builtin') {
178
+ throw new TheorumError(`Profile ${profile.id} model '${modelId}' lists '${id}' in builtInTools — not a registered builtin`);
179
+ }
180
+ }
181
+ }
182
+ }
183
+ function assertCompactionOnlyOnText(profile) {
184
+ if (profile.type === 'text') {
185
+ return;
186
+ }
187
+ for (const [modelId, spec] of Object.entries(profile.model.config)) {
188
+ if (spec.compaction) {
189
+ throw new TheorumError(`Profile ${profile.id} model '${modelId}': compaction is only valid on type 'text'`);
190
+ }
191
+ }
192
+ }
193
+ function assertMediaLimits(profile) {
194
+ if (profile.type === 'speech') {
195
+ return;
196
+ }
197
+ const inputs = profile.inputs;
198
+ if (!inputs) {
199
+ return;
200
+ }
201
+ const { attachments, voice, maxFiles, maxBytes, maxTurnBytes } = inputs;
119
202
  if (attachments || voice) {
120
203
  if (!(maxFiles && maxBytes && maxTurnBytes)) {
121
204
  throw new TheorumError(`Profile ${profile.id} must set maxFiles, maxBytes, and maxTurnBytes`);
122
205
  }
123
206
  }
207
+ }
208
+ /** Register one host-owned profile in the process-local registry. */
209
+ function registerProfile(profileInput) {
210
+ const profile = defineProfile(profileInput);
211
+ assertModelSpecs(profile.id, profile.model.allow, profile.model.config);
212
+ assertCustomToolsOnly(profile);
213
+ assertProfileToolLoader(profile);
214
+ assertModelBuiltInTools(profile);
215
+ assertCompactionOnlyOnText(profile);
216
+ assertMediaLimits(profile);
124
217
  for (const [modelId, spec] of Object.entries(profile.model.config)) {
125
218
  if (spec.compaction) {
126
219
  assertCompactionSpec(profile.id, modelId, spec.compaction);
@@ -1,25 +1,29 @@
1
1
  /** Build the provider request projection shared by execution and tracing. */
2
2
  function providerCompleteRequest(generation, system) {
3
+ const isInteractions = generation.transport === 'interactions';
3
4
  return {
4
5
  model: generation.model,
5
6
  apiId: generation.apiId,
6
- openRouterId: generation.openRouterId,
7
- previousInteractionId: generation.previousInteractionId,
8
- store: generation.store,
7
+ previousInteractionId: isInteractions ? generation.previousInteractionId : undefined,
8
+ store: isInteractions ? generation.store : undefined,
9
+ stream: isInteractions ? generation.stream : undefined,
9
10
  thinking: generation.thinking,
10
- summaries: generation.summaries,
11
+ summaries: isInteractions ? generation.summaries : undefined,
11
12
  maxOutputTokens: generation.maxOutputTokens,
12
13
  temperature: generation.temperature,
13
14
  builtins: generation.builtins,
15
+ googleMapsLocation: isInteractions ? generation.googleMapsLocation : undefined,
14
16
  system,
15
17
  input: generation.input,
16
18
  history: generation.history,
17
- dynamicTools: generation.dynamicTools,
18
- dynamicToolLoader: generation.dynamicToolLoader,
19
+ interactionOnlyInput: isInteractions ? generation.interactionOnlyInput : undefined,
20
+ wireTools: generation.tools.wire,
19
21
  structured: generation.structured,
20
22
  image: generation.image,
21
23
  speech: generation.speech,
22
- geminiBucket: generation.geminiBucket,
24
+ live: generation.live,
25
+ sessionResumptionHandle: generation.sessionResumptionHandle,
26
+ keySlot: generation.keySlot,
23
27
  };
24
28
  }
25
29
  export { providerCompleteRequest };
@@ -1,19 +1,18 @@
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
  */
9
- import type { Profile, ProjectedProfile, ResolvedGeneration, ToolId, TurnRequest } from '../types.js';
10
- declare function assertToolAllowed(profile: Profile, name: ToolId): void;
6
+ import type { ModelId, Profile, ProjectedProfile, ResolvedGeneration, TurnRequest } from '../types.js';
7
+ declare function pickModel(profile: Profile, select?: string): ModelId;
11
8
  /** Resolve a host `TurnRequest` into provider-ready generation state. */
12
9
  declare function resolveTurn(req: TurnRequest): {
13
10
  profile: Profile;
14
11
  generation: ResolvedGeneration;
15
12
  };
13
+ /** Project a profile object into a safe host/UI inspection object. */
14
+ declare function projectProfileObject(profile: Profile): ProjectedProfile;
16
15
  /** Project a registered profile into a safe host/UI inspection object. */
17
16
  declare function projectProfile(id: Profile['id']): ProjectedProfile;
18
17
  declare function pickSystemRole(profile: Profile, requested?: string): string;
19
- export { assertToolAllowed, pickSystemRole, projectProfile, resolveTurn };
18
+ export { pickModel, pickSystemRole, projectProfile, projectProfileObject, resolveTurn };