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
@@ -1,36 +1,11 @@
1
1
  /**
2
2
  * Google / Gemini convenience preset.
3
3
  *
4
- * Kernel stays open (`string` pins). This pack owns Google vocabularies as
5
- * real unions so host profiles stay typed when they opt into the preset.
6
- *
7
- * Call `registerGooglePreset()` at host startup for tools; use the exported
8
- * types/constants when authoring image/speech-adjacent profile fields.
9
- *
10
4
  * @module
11
5
  */
12
6
  import "../../_dnt.polyfills.js";
13
- import { registerTools } from '../kernel/registry/catalog.js';
14
- /** Google Interactions / Gemini grounding builtins. */
15
- const GOOGLE_BUILTIN_TOOLS = {
16
- googleSearch: {
17
- kind: 'builtin',
18
- ui: true,
19
- interactionsType: 'google_search',
20
- openRouterPlugin: 'web',
21
- },
22
- googleMaps: {
23
- kind: 'builtin',
24
- ui: true,
25
- interactionsType: 'google_maps',
26
- conflictsWith: ['googleSearch', 'urlContext'],
27
- },
28
- urlContext: {
29
- kind: 'builtin',
30
- ui: true,
31
- interactionsType: 'url_context',
32
- },
33
- };
7
+ import { registerTools } from '../kernel/tools/mod.js';
8
+ import { GOOGLE_SPEECH_VOICES } from './google/speech-voices.js';
34
9
  /** Common Gemini image input MIME allowlist. */
35
10
  const GOOGLE_IMAGE_INPUT_MIMES = [
36
11
  'image/png',
@@ -41,40 +16,6 @@ const GOOGLE_IMAGE_INPUT_MIMES = [
41
16
  ];
42
17
  /** Common voice/audio input MIME allowlist for Gemini multimodal. */
43
18
  const GOOGLE_VOICE_INPUT_MIMES = ['audio/webm', 'audio/wav', 'audio/mpeg', 'audio/mp4'];
44
- /** Common Gemini TTS voice names for `outputs.speech.voice`. */
45
- const GOOGLE_SPEECH_VOICES = [
46
- 'Zephyr',
47
- 'Puck',
48
- 'Charon',
49
- 'Kore',
50
- 'Fenrir',
51
- 'Leda',
52
- 'Orus',
53
- 'Aoede',
54
- 'Callirrhoe',
55
- 'Autonoe',
56
- 'Enceladus',
57
- 'Iapetus',
58
- 'Umbriel',
59
- 'Algieba',
60
- 'Despina',
61
- 'Erinome',
62
- 'Algenib',
63
- 'Rasalgethi',
64
- 'Laomedeia',
65
- 'Achernar',
66
- 'Alnilam',
67
- 'Schedar',
68
- 'Gacrux',
69
- 'Pulcherrima',
70
- 'Achird',
71
- 'Zubenelgenubi',
72
- 'Vindemiatrix',
73
- 'Sadachbia',
74
- 'Sadaltager',
75
- 'Sulafat',
76
- ];
77
- /** Aspect ratios commonly accepted by Gemini image models. */
78
19
  const GOOGLE_IMAGE_ASPECT_RATIOS = [
79
20
  '1:1',
80
21
  '3:2',
@@ -87,9 +28,55 @@ const GOOGLE_IMAGE_ASPECT_RATIOS = [
87
28
  '16:9',
88
29
  '21:9',
89
30
  ];
90
- /** Image sizes commonly accepted by Gemini Flash Lite image. */
91
31
  const GOOGLE_IMAGE_SIZES = ['1K'];
92
- /** Register Google provider builtins into the process-local tool catalog. */
32
+ const GOOGLE_BUILTIN_TOOLS = [
33
+ {
34
+ type: 'builtin',
35
+ name: 'googleSearch',
36
+ description: 'Google Search grounding',
37
+ category: 'grounding',
38
+ access: 'read-only',
39
+ paths: ['*'],
40
+ loadTier: 'T0',
41
+ permission: 'auto',
42
+ forcePaidKey: true,
43
+ wire: { interactions: 'google_search', openRouter: 'web' },
44
+ },
45
+ {
46
+ type: 'builtin',
47
+ name: 'googleMaps',
48
+ description: 'Google Maps grounding',
49
+ category: 'grounding',
50
+ access: 'read-only',
51
+ paths: ['*'],
52
+ loadTier: 'T0',
53
+ permission: 'auto',
54
+ wire: { interactions: 'google_maps' },
55
+ },
56
+ {
57
+ type: 'builtin',
58
+ name: 'urlContext',
59
+ description: 'Fetch URL context',
60
+ category: 'grounding',
61
+ access: 'read-only',
62
+ paths: ['*'],
63
+ loadTier: 'T0',
64
+ permission: 'auto',
65
+ wire: { interactions: 'url_context' },
66
+ },
67
+ {
68
+ type: 'builtin',
69
+ name: 'codeExecution',
70
+ description: 'Google code execution',
71
+ category: 'grounding',
72
+ access: 'read-only',
73
+ paths: ['*'],
74
+ loadTier: 'T0',
75
+ permission: 'auto',
76
+ wire: { interactions: 'code_execution' },
77
+ },
78
+ ];
79
+ /** Register Google provider builtins into the process-local tool registry. */
93
80
  function registerGooglePreset() {
94
81
  registerTools(GOOGLE_BUILTIN_TOOLS);
95
82
  }
@@ -7,5 +7,5 @@
7
7
  * @module
8
8
  */
9
9
  import "../../_dnt.polyfills.js";
10
- export type { GoogleImageAspectRatio, GoogleImageInputMime, GoogleImagePins, GoogleImageSize, GoogleVoiceInputMime, } from './google.js';
11
- export { GOOGLE_BUILTIN_TOOLS, GOOGLE_IMAGE_ASPECT_RATIOS, GOOGLE_IMAGE_INPUT_MIMES, GOOGLE_IMAGE_SIZES, GOOGLE_VOICE_INPUT_MIMES, registerGooglePreset, } from './google.js';
10
+ export type { GoogleImageAspectRatio, GoogleImageInputMime, GoogleImagePins, GoogleImageSize, GoogleSpeechVoice, GoogleVoiceInputMime, } from './google.js';
11
+ export { GOOGLE_BUILTIN_TOOLS, GOOGLE_IMAGE_ASPECT_RATIOS, GOOGLE_IMAGE_INPUT_MIMES, GOOGLE_IMAGE_SIZES, GOOGLE_SPEECH_VOICES, GOOGLE_VOICE_INPUT_MIMES, registerGooglePreset, } from './google.js';
@@ -7,4 +7,4 @@
7
7
  * @module
8
8
  */
9
9
  import "../../_dnt.polyfills.js";
10
- export { GOOGLE_BUILTIN_TOOLS, GOOGLE_IMAGE_ASPECT_RATIOS, GOOGLE_IMAGE_INPUT_MIMES, GOOGLE_IMAGE_SIZES, GOOGLE_VOICE_INPUT_MIMES, registerGooglePreset, } from './google.js';
10
+ export { GOOGLE_BUILTIN_TOOLS, GOOGLE_IMAGE_ASPECT_RATIOS, GOOGLE_IMAGE_INPUT_MIMES, GOOGLE_IMAGE_SIZES, GOOGLE_SPEECH_VOICES, GOOGLE_VOICE_INPUT_MIMES, registerGooglePreset, } from './google.js';
@@ -2,35 +2,35 @@
2
2
  * Host provider factory — the single public door for binding a profile to a transport.
3
3
  *
4
4
  * Routes from `profile.model.protocol` / `provider` (and whether the profile is
5
- * a speech role). Adapters under this folder are internal implementation.
5
+ * a speech or image role). Adapters under this folder are internal implementation.
6
6
  *
7
- * The OpenRouter / Vercel AI SDK stack is loaded only when an `openAi` +
8
- * `openrouter` chat provider actually runs `complete` — not when this module
9
- * is imported, and not for Google or local paths.
7
+ * Every adapter graph is loaded only when that transport's first `complete` runs —
8
+ * not when this module is imported.
10
9
  *
11
10
  * @module
12
11
  */
13
12
  import type { ModelProvider, Profile } from '../kernel/types.js';
14
- import type { GeminiTransport } from './keys.js';
15
- import { type LocalProviderConfig } from './local.js';
16
- import type { OpenRouterConfig } from './openrouter-payload.js';
13
+ import type { GeminiTransport } from './google/keys.js';
14
+ import type { LocalProviderConfig, OpenAiGatewayConfig } from './types.js';
17
15
  /** Credentials supplied by the host when creating a provider. */
18
16
  export interface CreateProviderOptions {
19
- /** Google Interactions (chat, image, and speech when protocol is geminiInteractions). */
17
+ /** Google Interactions (text, image, and speech when protocol is geminiInteractions). */
20
18
  gemini?: GeminiTransport;
21
19
  /**
22
- * OpenRouter-compatible credentials for `openAi` profiles.
23
- * Used for chat completions or `/audio/speech` when the profile is a speech role.
24
- * Optional `voice` is a fallback when `outputs.speech.voice` is omitted.
20
+ * OpenAI-gateway credentials for `openAi` profiles (OpenRouter or compatible).
21
+ * Used for chat completions, `/images`, or `/audio/speech` depending on output role.
22
+ * Optional `voice` is a fallback when `speech.voice` is omitted.
25
23
  */
26
- openRouter?: OpenRouterConfig & {
24
+ openAiGateway?: OpenAiGatewayConfig & {
27
25
  voice?: string;
28
26
  };
29
27
  /** Local OpenAI-compatible server (Ollama, llama.cpp, vLLM, LM Studio). */
30
28
  local?: LocalProviderConfig;
31
29
  }
30
+ export declare function isSpeechRole(profile: Profile): boolean;
31
+ export declare function isImageRole(profile: Profile): boolean;
32
32
  /**
33
- * Create a `ModelProvider` for a profile.
34
- * One call: protocol/provider (and speech role) pick the transport.
33
+ * Create a `ModelProvider` for a turn-based profile (text / image / speech).
34
+ * Live profiles use `runSession` — `createProvider` rejects geminiLive.
35
35
  */
36
36
  export declare function createProvider(profile: Profile, options?: CreateProviderOptions): ModelProvider;
@@ -2,59 +2,91 @@
2
2
  * Host provider factory — the single public door for binding a profile to a transport.
3
3
  *
4
4
  * Routes from `profile.model.protocol` / `provider` (and whether the profile is
5
- * a speech role). Adapters under this folder are internal implementation.
5
+ * a speech or image role). Adapters under this folder are internal implementation.
6
6
  *
7
- * The OpenRouter / Vercel AI SDK stack is loaded only when an `openAi` +
8
- * `openrouter` chat provider actually runs `complete` — not when this module
9
- * is imported, and not for Google or local paths.
7
+ * Every adapter graph is loaded only when that transport's first `complete` runs —
8
+ * not when this module is imported.
10
9
  *
11
10
  * @module
12
11
  */
13
12
  import { TheorumError } from '../guardrails/error.js';
14
- import { exposeForTests } from './expose-for-tests.js';
15
- import { createLocalProvider } from './local.js';
16
- import { createInteractionsProvider } from './provider.js';
17
- import { createSpeechProvider } from './speech.js';
18
- function isSpeechRole(profile) {
19
- return profile.outputs.speech !== undefined;
13
+ import { isValidPair } from '../kernel/schema.js';
14
+ import { markModuleLoad } from './probe.js';
15
+ export function isSpeechRole(profile) {
16
+ return profile.type === 'speech';
17
+ }
18
+ export function isImageRole(profile) {
19
+ return profile.type === 'image';
20
20
  }
21
21
  /**
22
- * Defer loading `@openrouter/ai-sdk-provider` / `ai` until the first `complete`.
23
- * Keeps Google and local hosts free of the Vercel SDK graph.
22
+ * Lazy-load an adapter on first `complete`. When `THEORUM_IMPORT_PROBE=1`,
23
+ * emits `LOADED:<label>` exactly once at load time (import-isolation tests).
24
24
  */
25
- function lazyOpenRouterChat(config) {
25
+ function lazyAdapter(label, load) {
26
26
  let pending;
27
27
  return {
28
28
  async *complete(req) {
29
- pending ??= import('./openrouter.js').then((m) => m.createOpenRouterProvider(config));
29
+ pending ??= (async () => {
30
+ markModuleLoad(label);
31
+ return await load();
32
+ })();
30
33
  yield* (await pending).complete(req);
31
34
  },
32
35
  };
33
36
  }
37
+ function lazyOpenRouterChat(config) {
38
+ return lazyAdapter('openrouter-chat', () => import('./openrouter/chat.js').then((m) => m.createOpenRouterProvider(config)));
39
+ }
40
+ function lazyGoogleInteractions(config) {
41
+ return lazyAdapter('google-interactions-adapter', () => import('./google/interactions/mod.js').then((m) => m.createInteractionsProvider(config)));
42
+ }
43
+ function lazySpeech(config) {
44
+ return lazyAdapter('openrouter-speech', () => import('./openrouter/speech.js').then((m) => m.createSpeechProvider(config)));
45
+ }
46
+ function lazyImage(config) {
47
+ return lazyAdapter('openrouter-image', () => import('./openrouter/image.js').then((m) => m.createImageProvider(config)));
48
+ }
49
+ function lazyLocal(config) {
50
+ return lazyAdapter('local-adapter', () => import('./local/local.js').then((m) => m.createLocalProvider(config)));
51
+ }
34
52
  /**
35
- * Create a `ModelProvider` for a profile.
36
- * One call: protocol/provider (and speech role) pick the transport.
53
+ * Create a `ModelProvider` for a turn-based profile (text / image / speech).
54
+ * Live profiles use `runSession` — `createProvider` rejects geminiLive.
37
55
  */
38
56
  export function createProvider(profile, options = {}) {
39
57
  const { protocol, provider } = profile.model;
58
+ if (!isValidPair(protocol, provider)) {
59
+ throw new TheorumError(`createProvider: unsupported protocol/provider pair '${protocol}'/'${provider}'`);
60
+ }
40
61
  if (protocol === 'geminiInteractions' && provider === 'google') {
41
62
  if (!options.gemini) {
42
63
  throw new TheorumError('createProvider requires gemini transport for google Interactions');
43
64
  }
44
- return createInteractionsProvider(options.gemini);
65
+ return lazyGoogleInteractions(options.gemini);
66
+ }
67
+ if (protocol === 'geminiLive' && provider === 'google') {
68
+ throw new TheorumError("createProvider does not support type 'live' / geminiLive — use runSession(req, { gemini })");
45
69
  }
46
70
  if (protocol === 'openAi' && provider === 'openrouter') {
47
- if (!options.openRouter) {
48
- throw new TheorumError('createProvider requires openRouter config for openAi/openrouter');
71
+ if (!options.openAiGateway) {
72
+ throw new TheorumError('createProvider requires openAiGateway config for openAi/openrouter');
49
73
  }
50
74
  if (isSpeechRole(profile)) {
51
- return createSpeechProvider(options.openRouter);
75
+ return lazySpeech(options.openAiGateway);
76
+ }
77
+ if (isImageRole(profile)) {
78
+ return lazyImage(options.openAiGateway);
52
79
  }
53
- return lazyOpenRouterChat(options.openRouter);
80
+ return lazyOpenRouterChat(options.openAiGateway);
54
81
  }
55
82
  if (protocol === 'openAi' && provider === 'local') {
56
- return createLocalProvider(options.local);
83
+ if (isImageRole(profile)) {
84
+ throw new TheorumError('createProvider: type image requires openrouter provider for openAi protocol');
85
+ }
86
+ return lazyLocal(options.local);
57
87
  }
88
+ // Exhaustiveness guard: isValidPair above already rejects unknown pairs, so
89
+ // this throw is unreachable at runtime. It exists so TypeScript errors if a
90
+ // new protocol/provider is added to PROTOCOL_PROVIDERS without a branch here.
58
91
  throw new TheorumError(`createProvider: unsupported protocol/provider pair '${protocol}'/'${provider}'`);
59
92
  }
60
- exposeForTests('create-provider', { isSpeechRole, createProvider, lazyOpenRouterChat });
@@ -0,0 +1,15 @@
1
+ import type { InteractionPart, ProviderCompleteRequest, TurnHistoryMessage } from '../../../kernel/types.js';
2
+ export declare function camelToSnake(key: string): string;
3
+ export declare function toGoogleValue(value: unknown): unknown;
4
+ export declare function wirePart(part: InteractionPart): Record<string, string>;
5
+ export declare function userInputStep(parts: InteractionPart[]): Record<string, unknown>;
6
+ export declare function historyStep(msg: TurnHistoryMessage): Record<string, unknown>;
7
+ export declare function systemHoldsUserInput(system: string, parts: InteractionPart[]): boolean;
8
+ export declare function jsonResponseFormat(schema: Record<string, unknown>): unknown[];
9
+ export declare function attachResponseFormat(req: ProviderCompleteRequest, camel: Record<string, unknown>): void;
10
+ export declare function attachSpeechConfig(req: ProviderCompleteRequest, generationConfig: Record<string, unknown>): void;
11
+ export declare function inputStepsFromRequest(req: ProviderCompleteRequest): Record<string, unknown>[];
12
+ export declare function applyOptionalRequestFields(req: ProviderCompleteRequest, camel: Record<string, unknown>): void;
13
+ export declare function baseInteractionsBody(req: ProviderCompleteRequest): Record<string, unknown>;
14
+ /** Compatibility wrapper for callers that need the complete wire body. */
15
+ export declare function toInteractionsBody(req: ProviderCompleteRequest): Record<string, unknown>;
@@ -0,0 +1,208 @@
1
+ import { TheorumError } from '../../../guardrails/error.js';
2
+ import { getStructured } from '../../../kernel/registry/schemas.js';
3
+ import { getTool } from '../../../kernel/tools/registry.js';
4
+ export function camelToSnake(key) {
5
+ return key.replaceAll(/[A-Z]/g, (ch) => `_${ch.toLowerCase()}`);
6
+ }
7
+ export function toGoogleValue(value) {
8
+ if (Array.isArray(value)) {
9
+ return value.map(toGoogleValue);
10
+ }
11
+ if (value && typeof value === 'object') {
12
+ const out = {};
13
+ for (const [key, nested] of Object.entries(value)) {
14
+ // JSON Schema property names must stay as authored (e.g. correctAnswer in
15
+ // both properties and required). Snake-casing breaks Gemini validation.
16
+ if (key === 'schema' || key === 'parameters') {
17
+ out[camelToSnake(key)] = nested;
18
+ continue;
19
+ }
20
+ out[camelToSnake(key)] = toGoogleValue(nested);
21
+ }
22
+ return out;
23
+ }
24
+ return value;
25
+ }
26
+ export function wirePart(part) {
27
+ if (part.type === 'text') {
28
+ return { type: 'text', text: part.text };
29
+ }
30
+ return { type: part.type, mimeType: part.mimeType, data: part.data };
31
+ }
32
+ const USER_INPUT = 'user_input';
33
+ export function userInputStep(parts) {
34
+ return { type: USER_INPUT, content: parts.map(wirePart) };
35
+ }
36
+ function functionResultStep(msg) {
37
+ return {
38
+ type: 'function_result',
39
+ name: msg.name ?? '',
40
+ call_id: msg.tool_call_id ?? '',
41
+ result: [{ type: 'text', text: msg.content ?? '' }],
42
+ };
43
+ }
44
+ export function historyStep(msg) {
45
+ if (msg.role === 'tool') {
46
+ return functionResultStep(msg);
47
+ }
48
+ const isAssistant = msg.role === 'assistant';
49
+ // Google Interactions input steps: assistant history is `model_output` (not `model_turn`).
50
+ const type = isAssistant ? 'model_output' : 'user_input';
51
+ if (msg.parts && msg.parts.length > 0) {
52
+ return { type, content: msg.parts.map(wirePart) };
53
+ }
54
+ return { type, content: [{ type: 'text', text: msg.content ?? '' }] };
55
+ }
56
+ export function systemHoldsUserInput(system, parts) {
57
+ for (const part of parts) {
58
+ if (part.type === 'text' && part.text && system.includes(part.text)) {
59
+ return true;
60
+ }
61
+ }
62
+ return false;
63
+ }
64
+ export function jsonResponseFormat(schema) {
65
+ return [{ type: 'text', mimeType: 'application/json', schema }];
66
+ }
67
+ export function attachResponseFormat(req, camel) {
68
+ if (req.speech) {
69
+ if (req.image) {
70
+ throw new TheorumError('cannot mix speech and image response formats');
71
+ }
72
+ if (req.structured) {
73
+ throw new TheorumError('cannot mix speech and structured response formats');
74
+ }
75
+ camel.responseFormat = { type: 'audio' };
76
+ camel.responseModalities = ['audio'];
77
+ return;
78
+ }
79
+ if (req.image) {
80
+ const imageEntry = {
81
+ type: 'image',
82
+ mimeType: req.image.mimeType,
83
+ };
84
+ if (req.image.aspectRatio) {
85
+ imageEntry.aspectRatio = req.image.aspectRatio;
86
+ }
87
+ if (req.image.size) {
88
+ imageEntry.imageSize = req.image.size;
89
+ }
90
+ // Post–May 2026 Interactions API: object = image-only; array = text + image.
91
+ camel.responseFormat = req.image.includeText ? [{ type: 'text' }, imageEntry] : imageEntry;
92
+ return;
93
+ }
94
+ if (!req.structured) {
95
+ return;
96
+ }
97
+ const spec = getStructured(req.structured);
98
+ if (spec.enforced !== 'responseFormat' || !spec.jsonSchema) {
99
+ return;
100
+ }
101
+ camel.responseFormat = jsonResponseFormat(spec.jsonSchema);
102
+ }
103
+ export function attachSpeechConfig(req, generationConfig) {
104
+ if (!req.speech) {
105
+ return;
106
+ }
107
+ const voice = req.speech.voice;
108
+ if (!voice) {
109
+ return;
110
+ }
111
+ generationConfig.speechConfig = [{ voice }];
112
+ }
113
+ function wireInteractionsFunctionTool(decl) {
114
+ return {
115
+ type: 'function',
116
+ name: decl.name,
117
+ description: decl.description,
118
+ parameters: decl.parameters,
119
+ };
120
+ }
121
+ function wireGoogleMapsTool(req) {
122
+ const loc = req.googleMapsLocation;
123
+ if (loc && Number.isFinite(loc.latitude) && Number.isFinite(loc.longitude)) {
124
+ return {
125
+ type: 'google_maps',
126
+ latitude: loc.latitude,
127
+ longitude: loc.longitude,
128
+ };
129
+ }
130
+ return { type: 'google_maps' };
131
+ }
132
+ function wireInteractionsTools(req) {
133
+ const tools = [];
134
+ for (const id of req.builtins) {
135
+ const entry = getTool(id);
136
+ const type = entry?.type === 'builtin' ? entry.wire.interactions : undefined;
137
+ if (!type) {
138
+ throw new TheorumError(`Builtin '${id}' has no Interactions wire type`);
139
+ }
140
+ if (type === 'google_maps') {
141
+ tools.push(wireGoogleMapsTool(req));
142
+ continue;
143
+ }
144
+ tools.push({ type });
145
+ }
146
+ for (const decl of req.wireTools ?? []) {
147
+ tools.push(wireInteractionsFunctionTool(decl));
148
+ }
149
+ return tools;
150
+ }
151
+ export function inputStepsFromRequest(req) {
152
+ if (req.interactionOnlyInput && req.interactionOnlyInput.length > 0) {
153
+ return req.interactionOnlyInput;
154
+ }
155
+ const inputSteps = [];
156
+ for (const h of req.history ?? []) {
157
+ inputSteps.push(historyStep(h));
158
+ }
159
+ if (req.input.length > 0 || inputSteps.length === 0) {
160
+ inputSteps.push(userInputStep(req.input));
161
+ }
162
+ return inputSteps;
163
+ }
164
+ export function applyOptionalRequestFields(req, camel) {
165
+ if (req.store !== undefined) {
166
+ camel.store = req.store;
167
+ }
168
+ if (req.previousInteractionId) {
169
+ camel.previousInteractionId = req.previousInteractionId;
170
+ }
171
+ if (req.system) {
172
+ if (systemHoldsUserInput(req.system, req.input)) {
173
+ throw new TheorumError('User payload must not be copied into system instructions');
174
+ }
175
+ camel.systemInstruction = req.system;
176
+ }
177
+ const tools = wireInteractionsTools(req);
178
+ if (tools.length > 0) {
179
+ camel.tools = tools;
180
+ }
181
+ }
182
+ export function baseInteractionsBody(req) {
183
+ const generationConfig = {
184
+ temperature: req.temperature,
185
+ maxOutputTokens: req.maxOutputTokens,
186
+ };
187
+ if (req.speech) {
188
+ // TTS models reject chat thinking knobs; voice lives under speech_config.
189
+ attachSpeechConfig(req, generationConfig);
190
+ }
191
+ else {
192
+ generationConfig.thinkingLevel = req.thinking;
193
+ generationConfig.thinkingSummaries = req.summaries;
194
+ }
195
+ return {
196
+ model: req.apiId,
197
+ stream: req.stream ?? true,
198
+ input: inputStepsFromRequest(req),
199
+ generationConfig,
200
+ };
201
+ }
202
+ /** Compatibility wrapper for callers that need the complete wire body. */
203
+ export function toInteractionsBody(req) {
204
+ const body = baseInteractionsBody(req);
205
+ attachResponseFormat(req, body);
206
+ applyOptionalRequestFields(req, body);
207
+ return toGoogleValue(body);
208
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Google Interactions provider module.
3
+ *
4
+ * @module
5
+ */
6
+ export { camelToSnake, toInteractionsBody } from './framing.js';
7
+ export { createInteractionsProvider } from './stream.js';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Google Interactions provider module.
3
+ *
4
+ * @module
5
+ */
6
+ export { camelToSnake, toInteractionsBody } from './framing.js';
7
+ export { createInteractionsProvider } from './stream.js';
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Google Interactions provider adapter.
3
+ *
4
+ * This adapter converts THEORUM's provider-neutral request into the Google
5
+ * Interactions wire format and streams normalized `TurnEvent` objects.
6
+ * Speech-role turns use `response_format: audio` + `speech_config` (same
7
+ * transport as chat/image).
8
+ *
9
+ * @module
10
+ */
11
+ import type { ModelProvider, ProviderCompleteRequest, TurnEvent } from '../../../kernel/types.js';
12
+ import { type ParsedToolArguments } from '../../shared/tool-args.js';
13
+ import { type GeminiTransport } from '../keys.js';
14
+ export interface PendingFunctionCall {
15
+ id?: string;
16
+ name?: string;
17
+ arguments: string;
18
+ }
19
+ export interface StreamFold {
20
+ text: string;
21
+ functionCalls: Map<number, PendingFunctionCall>;
22
+ emittedToolKeys: Set<string>;
23
+ emittedEvidenceKeys: Set<string>;
24
+ codeSteps: Map<number, Record<string, unknown>>;
25
+ sawStreamedMedia: boolean;
26
+ }
27
+ export declare function isRawPcmMime(mime: string): boolean;
28
+ export declare function normalizeSpeechMedia(event: TurnEvent, speech: boolean): TurnEvent;
29
+ export declare function newStreamFold(): StreamFold;
30
+ export declare function eventType(payload: Record<string, unknown>): string;
31
+ export declare function isDeltaEvent(type: string): boolean;
32
+ export declare function isCompleteEvent(type: string): boolean;
33
+ export declare function yieldGrounding(event: Record<string, unknown>): Generator<TurnEvent>;
34
+ export declare function yieldTokens(event: Record<string, unknown>): Generator<TurnEvent>;
35
+ export declare function functionCallKey(tool: {
36
+ name?: string;
37
+ id?: string;
38
+ arguments?: Record<string, unknown>;
39
+ }): string;
40
+ export declare function yieldEvidenceStep(step: Record<string, unknown>, emittedKeys: Set<string>): Generator<TurnEvent>;
41
+ export declare function isCompleteCodeStep(step: Record<string, unknown>): boolean;
42
+ export declare function foldStepStart(payload: Record<string, unknown>, fold: StreamFold): TurnEvent[];
43
+ export type { ParsedToolArguments };
44
+ export declare function parseArgumentsObject(raw: unknown): ParsedToolArguments;
45
+ export declare function emitUniqueToolEvent(tool: {
46
+ id?: string;
47
+ name: string;
48
+ arguments: Record<string, unknown>;
49
+ phase?: 'error';
50
+ failure?: {
51
+ code: string;
52
+ message: string;
53
+ details?: unknown;
54
+ };
55
+ }, fold: StreamFold): TurnEvent[];
56
+ /** Emit a tool call, or a structured tool failure when arguments cannot be parsed. */
57
+ export declare function emitToolCallFromRawArguments(tool: {
58
+ id?: string;
59
+ name: string;
60
+ }, rawArguments: unknown, fold: StreamFold): TurnEvent[];
61
+ export declare function foldArgumentsDelta(delta: Record<string, unknown>, index: number, fold: StreamFold): void;
62
+ export declare function foldFunctionCallDelta(delta: Record<string, unknown>, fold: StreamFold): TurnEvent[];
63
+ export declare function foldDeltaPayload(payload: Record<string, unknown>, fold: StreamFold): TurnEvent[];
64
+ export declare function readData(part: Record<string, unknown>): string | undefined;
65
+ export declare function readMime(part: Record<string, unknown>): string | undefined;
66
+ export declare function yieldMediaChunk(part: Record<string, unknown>): Generator<TurnEvent>;
67
+ export declare function scanMediaParts(content: unknown): Generator<TurnEvent>;
68
+ export declare function emitPendingFunctionCall(index: number, fold: StreamFold): TurnEvent[];
69
+ export declare function foldStepStop(payload: Record<string, unknown>, fold: StreamFold): TurnEvent[];
70
+ export declare function foldInteractionSteps(interaction: Record<string, unknown>, fold: StreamFold): TurnEvent[];
71
+ export declare function foldCompleteEvents(payload: Record<string, unknown>, fold: StreamFold): TurnEvent[];
72
+ export declare function foldPayload(payload: Record<string, unknown>, fold: StreamFold): TurnEvent[];
73
+ export declare function finalizeStructured(req: ProviderCompleteRequest, fold: StreamFold): Generator<TurnEvent>;
74
+ export declare function scanInteractionsMedia(json: Record<string, unknown>): Generator<TurnEvent>;
75
+ export declare function isVoiceProfile(req: ProviderCompleteRequest): boolean;
76
+ export declare function shouldReportMissingSpeechAudio(req: ProviderCompleteRequest, fold: StreamFold): boolean;
77
+ /** Speech-role turns must receive real audio; never invent PCM from text bytes. */
78
+ export declare function missingSpeechAudioError(): Generator<TurnEvent>;
79
+ export declare function readApiErrorMessage(record: Record<string, unknown>): string | null;
80
+ export declare function readNonOkErrorMessage(response: Response): Promise<string>;
81
+ export declare function withTap(req: ProviderCompleteRequest, transport: GeminiTransport): GeminiTransport;
82
+ /** Create a `ModelProvider` backed by Google Interactions HTTP / SSE. */
83
+ export declare function createInteractionsProvider(transport: GeminiTransport): ModelProvider;