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
@@ -3,25 +3,26 @@
3
3
  *
4
4
  * THEORUM keeps the public `ModelProvider` and `TurnEvent` contract; AI SDK
5
5
  * owns the OpenRouter call, stream parsing, provider compatibility, and tool
6
- * call normalization.
6
+ * call normalization. Message assembly delegates to `openai/sdk-messages.ts`.
7
7
  *
8
8
  * @module
9
9
  */
10
10
  import { createOpenRouter } from '@openrouter/ai-sdk-provider';
11
- import { jsonSchema, streamText, tool, } from 'ai';
12
- import { isAbortError, toErrorEvent } from '../guardrails/error.js';
13
- import { tryStructured } from '../kernel/engine/delta.js';
14
- import { getStructured } from '../kernel/registry/schemas.js';
15
- import { turnStopFromOpenRouter } from '../kernel/stop.js';
16
- import { exposeForTests } from './expose-for-tests.js';
17
- import { resolveOpenRouterModel, resolveOpenRouterPlugins, toOpenRouterPayload, } from './openrouter-payload.js';
18
- function trimApiKey(explicitKey) {
11
+ import { jsonSchema, streamText, tool } from 'ai';
12
+ import { isAbortError, TheorumError, toErrorEvent } from '../../guardrails/error.js';
13
+ import { parseStructuredOutput } from '../../kernel/engine/delta.js';
14
+ import { turnStopFromOpenAiFinishReason } from '../../kernel/stop.js';
15
+ import { resolveOpenRouterPlugins } from './openai/chat-payload.js';
16
+ import { openAiGatewayHeaders, resolveResponseFormat } from './openai/compat.js';
17
+ import { buildAiSdkMessages } from './openai/sdk-messages.js';
18
+ import { resolveOpenAiGatewayApiKey } from './resolve-api-key.js';
19
+ export function trimApiKey(explicitKey) {
19
20
  if (explicitKey?.trim()) {
20
21
  return explicitKey.trim();
21
22
  }
22
23
  return undefined;
23
24
  }
24
- function createAccumulator() {
25
+ export function createAccumulator() {
25
26
  return {
26
27
  text: '',
27
28
  evidenceSeen: false,
@@ -29,127 +30,35 @@ function createAccumulator() {
29
30
  errored: false,
30
31
  };
31
32
  }
32
- function mediaPart(part) {
33
- if (part.type === 'text') {
34
- return { type: 'text', text: part.text };
35
- }
36
- if (part.type === 'image') {
37
- return {
38
- type: 'image',
39
- image: `data:${part.mimeType};base64,${part.data}`,
40
- };
41
- }
42
- return { type: 'file', mediaType: part.mimeType, data: part.data };
43
- }
44
- function contentFromParts(parts) {
45
- if (parts.every((part) => part.type === 'text')) {
46
- return parts
47
- .map((part) => (part.type === 'text' ? part.text : ''))
48
- .filter(Boolean)
49
- .join('\n');
50
- }
51
- return parts.map(mediaPart);
52
- }
53
- function parseToolInput(raw) {
54
- try {
55
- return JSON.parse(raw);
56
- }
57
- catch {
58
- return { _raw: raw };
59
- }
60
- }
61
- function stringDefault(value, fallback) {
62
- return value === undefined ? fallback : value;
63
- }
64
- function fallbackToolCallId(name) {
65
- return `call_${stringDefault(name, 'tool')}`;
66
- }
67
- function toolResultContent(msg) {
68
- const toolName = stringDefault(msg.name, 'tool');
69
- return {
70
- role: 'tool',
71
- content: [
72
- {
73
- type: 'tool-result',
74
- toolCallId: stringDefault(msg.tool_call_id, fallbackToolCallId(msg.name)),
75
- toolName,
76
- output: { type: 'text', value: stringDefault(msg.content, '') },
77
- },
78
- ],
79
- };
80
- }
81
- function assistantToolCallContent(msg) {
82
- if (!msg.tool_calls || msg.tool_calls.length === 0) {
83
- return null;
84
- }
85
- return {
86
- role: 'assistant',
87
- content: msg.tool_calls.map((call) => ({
88
- type: 'tool-call',
89
- toolCallId: call.id,
90
- toolName: call.function.name,
91
- input: parseToolInput(call.function.arguments),
92
- })),
93
- };
94
- }
95
- function historyMessage(msg) {
96
- if (msg.role === 'tool') {
97
- return toolResultContent(msg);
98
- }
99
- return assistantToolCallContent(msg) || contentHistoryMessage(msg);
100
- }
101
- function contentFromOptionalParts(parts, text) {
102
- if (!parts || parts.length === 0) {
103
- return stringDefault(text, '');
104
- }
105
- return contentFromParts(parts);
106
- }
107
- function contentHistoryMessage(msg) {
108
- const content = contentFromOptionalParts(msg.parts, msg.content);
109
- return { role: msg.role, content };
110
- }
111
- function sourceEvent(part) {
33
+ export function sourceEvent(part) {
112
34
  if (part.sourceType !== 'url') {
113
35
  return {
114
36
  type: 'evidence',
115
37
  evidence: { provider: 'openrouter', raw: part },
116
38
  };
117
39
  }
118
- const title = part.title ?? part.url;
40
+ const title = part.title ?? part.url ?? '';
119
41
  return {
120
42
  type: 'evidence',
121
43
  evidence: {
122
44
  provider: 'openrouter',
123
45
  raw: part,
124
- citations: [part.url],
125
- sources: [{ title, uri: part.url, type: 'web' }],
46
+ citations: part.url ? [part.url] : [],
47
+ sources: part.url ? [{ title, uri: part.url, type: 'web' }] : [],
126
48
  },
127
49
  };
128
50
  }
129
- function buildMessages(req) {
130
- const messages = [];
131
- for (const msg of req.history ?? []) {
132
- const wired = historyMessage(msg);
133
- if (wired) {
134
- messages.push(wired);
135
- }
136
- }
137
- if (req.input.length > 0) {
138
- messages.push({ role: 'user', content: contentFromParts(req.input) });
139
- }
140
- return messages;
141
- }
142
- function schemaForTool(decl) {
51
+ export function schemaForTool(decl) {
143
52
  return decl.parameters ?? { type: 'object', properties: {}, additionalProperties: true };
144
53
  }
145
- function buildTools(dynamicTools) {
146
- if (!dynamicTools || dynamicTools.length === 0) {
54
+ export function buildTools(wireTools) {
55
+ if (!wireTools || wireTools.length === 0) {
147
56
  return undefined;
148
57
  }
149
58
  const tools = {};
150
- for (const decl of dynamicTools) {
59
+ for (const decl of wireTools) {
151
60
  tools[decl.name] = tool({
152
- description: decl.description ?? '',
61
+ description: decl.description,
153
62
  inputSchema: jsonSchema(schemaForTool(decl)),
154
63
  });
155
64
  }
@@ -167,7 +76,7 @@ function openRouterSettings(req) {
167
76
  settings.web_search_options = {};
168
77
  return settings;
169
78
  }
170
- function tokensFromUsage(usage) {
79
+ export function tokensFromUsage(usage) {
171
80
  const input = usage.inputTokens ?? 0;
172
81
  const output = usage.outputTokens ?? 0;
173
82
  const total = usage.totalTokens ?? input + output;
@@ -180,23 +89,23 @@ function tokensFromUsage(usage) {
180
89
  total,
181
90
  };
182
91
  }
183
- function rawRecord(value) {
92
+ export function rawRecord(value) {
184
93
  if (value && typeof value === 'object' && !Array.isArray(value)) {
185
94
  return value;
186
95
  }
187
96
  return undefined;
188
97
  }
189
- function stringArray(value) {
98
+ export function stringArray(value) {
190
99
  if (!Array.isArray(value)) {
191
100
  return undefined;
192
101
  }
193
102
  const out = value.filter((item) => typeof item === 'string');
194
103
  return out.length > 0 ? out : undefined;
195
104
  }
196
- function metadataRecord(raw, key) {
105
+ export function metadataRecord(raw, key) {
197
106
  return rawRecord(raw[key]);
198
107
  }
199
- function citationCandidates(raw) {
108
+ export function citationCandidates(raw) {
200
109
  const openrouter = metadataRecord(raw, 'openrouter') ?? {};
201
110
  return [
202
111
  raw.citations,
@@ -207,7 +116,7 @@ function citationCandidates(raw) {
207
116
  metadataRecord(openrouter, 'provider_metadata')?.citations,
208
117
  ];
209
118
  }
210
- function nestedCitations(raw) {
119
+ export function nestedCitations(raw) {
211
120
  for (const candidate of citationCandidates(raw)) {
212
121
  const citations = stringArray(candidate);
213
122
  if (citations) {
@@ -216,7 +125,7 @@ function nestedCitations(raw) {
216
125
  }
217
126
  return undefined;
218
127
  }
219
- function metadataAnnotations(raw) {
128
+ export function metadataAnnotations(raw) {
220
129
  const openrouter = metadataRecord(raw, 'openrouter');
221
130
  if (Array.isArray(raw.annotations)) {
222
131
  return raw.annotations;
@@ -226,7 +135,7 @@ function metadataAnnotations(raw) {
226
135
  }
227
136
  return undefined;
228
137
  }
229
- function evidenceFromMetadata(metadata, acc) {
138
+ export function evidenceFromMetadata(metadata, acc) {
230
139
  const raw = rawRecord(metadata);
231
140
  if (!raw || acc.evidenceSeen) {
232
141
  return undefined;
@@ -242,7 +151,7 @@ function evidenceFromMetadata(metadata, acc) {
242
151
  evidence: { provider: 'openrouter', raw, citations, annotations },
243
152
  };
244
153
  }
245
- function toolArguments(input) {
154
+ export function toolArguments(input) {
246
155
  if (input && typeof input === 'object' && !Array.isArray(input)) {
247
156
  return input;
248
157
  }
@@ -251,10 +160,10 @@ function toolArguments(input) {
251
160
  }
252
161
  return { value: input };
253
162
  }
254
- function toolResultData(output) {
163
+ export function toolResultData(output) {
255
164
  return rawRecord(output);
256
165
  }
257
- function rawThoughtEvent(raw) {
166
+ export function rawThoughtEvent(raw) {
258
167
  const choices = raw.choices;
259
168
  if (!Array.isArray(choices)) {
260
169
  return undefined;
@@ -267,7 +176,7 @@ function rawThoughtEvent(raw) {
267
176
  }
268
177
  return undefined;
269
178
  }
270
- function rawChoiceMessageEvidence(raw, acc) {
179
+ export function rawChoiceMessageEvidence(raw, acc) {
271
180
  const choices = raw.choices;
272
181
  if (!Array.isArray(choices)) {
273
182
  return undefined;
@@ -284,7 +193,7 @@ function rawChoiceMessageEvidence(raw, acc) {
284
193
  }
285
194
  return undefined;
286
195
  }
287
- function rawEvents(raw, acc) {
196
+ export function rawEvents(raw, acc) {
288
197
  const record = rawRecord(raw);
289
198
  if (!record) {
290
199
  return [];
@@ -316,7 +225,7 @@ function rawEvents(raw, acc) {
316
225
  }
317
226
  return events;
318
227
  }
319
- function toolCallEvent(part) {
228
+ export function toolCallEvent(part) {
320
229
  return {
321
230
  type: 'tool',
322
231
  tool: {
@@ -326,32 +235,30 @@ function toolCallEvent(part) {
326
235
  },
327
236
  };
328
237
  }
329
- function toolResultEvent(part) {
238
+ export function toolResultEvent(part) {
239
+ const output = typeof part.output === 'string' ? part.output : toolResultData(part.output);
330
240
  return {
331
241
  type: 'tool',
332
242
  tool: {
333
243
  name: part.toolName,
334
244
  arguments: toolArguments(part.input),
335
- result: {
336
- status: 'ok',
337
- data: toolResultData(part.output),
338
- finding: typeof part.output === 'string' ? part.output : undefined,
339
- },
340
245
  id: part.toolCallId,
246
+ phase: 'complete',
247
+ output,
341
248
  },
342
249
  };
343
250
  }
344
- function tokenEvent(part) {
251
+ export function tokenEvent(part) {
345
252
  const tokens = tokensFromUsage(part.totalUsage);
346
253
  return tokens ? { type: 'tokens', tokens } : undefined;
347
254
  }
348
- function providerMetadataEvent(part, acc) {
255
+ export function providerMetadataEvent(part, acc) {
349
256
  if (!('providerMetadata' in part)) {
350
257
  return undefined;
351
258
  }
352
259
  return evidenceFromMetadata(part.providerMetadata, acc);
353
260
  }
354
- function eventFromPart(part, acc) {
261
+ export function eventFromPart(part, acc) {
355
262
  const mapped = primaryEventFromPart(part, acc);
356
263
  if (mapped) {
357
264
  return [mapped];
@@ -359,7 +266,7 @@ function eventFromPart(part, acc) {
359
266
  const evidence = providerMetadataEvent(part, acc);
360
267
  return evidence ? [evidence] : [];
361
268
  }
362
- function primaryEventFromPart(part, acc) {
269
+ export function primaryEventFromPart(part, acc) {
363
270
  switch (part.type) {
364
271
  case 'text-delta':
365
272
  acc.text += part.text;
@@ -385,59 +292,61 @@ function primaryEventFromPart(part, acc) {
385
292
  return undefined;
386
293
  }
387
294
  }
388
- function finishEvent(part, acc) {
389
- if ('finishReason' in part && part.finishReason != null) {
295
+ export function finishEvent(part, acc) {
296
+ if (part.finishReason != null) {
390
297
  acc.finishReason = String(part.finishReason);
391
298
  }
392
299
  if (acc.emittedTokens) {
393
300
  return undefined;
394
301
  }
395
- const event = tokenEvent(part);
302
+ if (!part.totalUsage) {
303
+ return undefined;
304
+ }
305
+ const event = tokenEvent({ totalUsage: part.totalUsage });
396
306
  if (event) {
397
307
  acc.emittedTokens = true;
398
308
  }
399
309
  return event;
400
310
  }
401
- function* finalEvents(req, acc) {
311
+ export function* finalEvents(req, acc) {
402
312
  if (acc.errored) {
403
313
  return;
404
314
  }
405
315
  if (req.structured && acc.text) {
406
- const structured = tryStructured(acc.text);
407
- if (structured) {
408
- yield structured;
316
+ const parsed = parseStructuredOutput(acc.text);
317
+ if (!parsed.ok) {
318
+ yield toErrorEvent(new TheorumError(parsed.error));
319
+ return;
409
320
  }
321
+ yield { type: 'structured', structured: parsed.structured };
410
322
  }
411
323
  yield {
412
324
  type: 'done',
413
- stop: turnStopFromOpenRouter(acc.finishReason, acc.nativeFinishReason),
325
+ stop: turnStopFromOpenAiFinishReason(acc.finishReason, acc.nativeFinishReason),
414
326
  };
415
327
  }
416
328
  function createStreamContext(req, config, apiKey) {
417
329
  const openrouter = createOpenRouter({
418
330
  apiKey,
419
331
  baseURL: config.baseUrl,
420
- headers: openRouterHeaders(config),
332
+ headers: openAiGatewayHeaders(config),
421
333
  fetch: config.fetch,
422
334
  compatibility: 'strict',
423
335
  });
424
336
  return {
425
337
  openrouter,
426
- modelName: resolveOpenRouterModel(req.model, config.modelMap, {
427
- apiId: req.apiId,
428
- openRouterId: req.openRouterId,
429
- }),
338
+ modelName: req.apiId,
430
339
  };
431
340
  }
432
341
  function streamTextOptions(req, context) {
433
342
  return {
434
343
  model: context.openrouter.chat(context.modelName, openRouterSettings(req)),
435
344
  instructions: req.system,
436
- messages: buildMessages(req),
345
+ messages: buildAiSdkMessages(req),
437
346
  allowSystemInMessages: true,
438
347
  temperature: req.temperature,
439
348
  maxOutputTokens: req.maxOutputTokens,
440
- tools: buildTools(req.dynamicTools),
349
+ tools: buildTools(req.wireTools),
441
350
  providerOptions: providerOptionsFor(req),
442
351
  include: { rawChunks: true },
443
352
  abortSignal: req.signal,
@@ -448,7 +357,7 @@ async function* yieldAiSdkStream(req, acc, context) {
448
357
  const result = streamText(streamTextOptions(req, context));
449
358
  for await (const part of result.stream) {
450
359
  if (part.type === 'raw') {
451
- req.tapGemini?.(rawRecord(part.rawValue) ?? { rawValue: part.rawValue });
360
+ req.tapUpstream?.(rawRecord(part.rawValue) ?? { rawValue: part.rawValue });
452
361
  for (const event of rawEvents(part.rawValue, acc)) {
453
362
  yield event;
454
363
  }
@@ -459,13 +368,16 @@ async function* yieldAiSdkStream(req, acc, context) {
459
368
  }
460
369
  }
461
370
  }
462
- function missingOpenRouterKey() {
371
+ export function missingOpenRouterKey() {
463
372
  return toErrorEvent('missing OpenRouter API key');
464
373
  }
465
374
  async function* streamOpenRouter(req, config) {
466
- const apiKey = trimApiKey(config.apiKey);
467
- if (!apiKey) {
468
- yield missingOpenRouterKey();
375
+ let apiKey;
376
+ try {
377
+ apiKey = resolveOpenAiGatewayApiKey(config, req.keySlot);
378
+ }
379
+ catch (err) {
380
+ yield toErrorEvent(err);
469
381
  return;
470
382
  }
471
383
  const acc = createAccumulator();
@@ -481,27 +393,12 @@ async function* streamOpenRouter(req, config) {
481
393
  yield toErrorEvent(err);
482
394
  }
483
395
  }
484
- function responseFormatFor(req) {
485
- if (!req.structured)
486
- return undefined;
487
- const spec = getStructured(req.structured);
488
- if (!spec.jsonSchema)
489
- return undefined;
490
- return {
491
- type: 'json_schema',
492
- json_schema: {
493
- name: String(req.structured),
494
- strict: true,
495
- schema: spec.jsonSchema,
496
- },
497
- };
498
- }
499
- function providerOptionsFor(req) {
396
+ export function providerOptionsFor(req) {
500
397
  const openrouter = {};
501
398
  if (req.thinking !== 'none') {
502
399
  openrouter.reasoning = { effort: req.thinking };
503
400
  }
504
- const responseFormat = responseFormatFor(req);
401
+ const responseFormat = resolveResponseFormat(req.structured);
505
402
  if (responseFormat) {
506
403
  openrouter.response_format = responseFormat;
507
404
  }
@@ -509,65 +406,9 @@ function providerOptionsFor(req) {
509
406
  return undefined;
510
407
  return { openrouter };
511
408
  }
512
- function openRouterHeaders(config) {
513
- const headers = {};
514
- if (config.siteUrl) {
515
- headers['HTTP-Referer'] = config.siteUrl;
516
- }
517
- if (config.siteName) {
518
- headers['X-Title'] = config.siteName;
519
- }
520
- return Object.keys(headers).length > 0 ? headers : undefined;
521
- }
522
409
  /** Create a `ModelProvider` backed by OpenRouter through AI SDK Core. */
523
- function createOpenRouterProvider(config = {}) {
410
+ export function createOpenRouterProvider(config = {}) {
524
411
  return {
525
412
  complete: (req) => streamOpenRouter(req, config),
526
413
  };
527
414
  }
528
- export { createOpenRouterProvider, resolveOpenRouterModel, toOpenRouterPayload };
529
- exposeForTests('openrouter', {
530
- trimApiKey,
531
- createAccumulator,
532
- mediaPart,
533
- contentFromParts,
534
- parseToolInput,
535
- stringDefault,
536
- fallbackToolCallId,
537
- toolResultContent,
538
- assistantToolCallContent,
539
- historyMessage,
540
- contentFromOptionalParts,
541
- contentHistoryMessage,
542
- buildMessages,
543
- schemaForTool,
544
- buildTools,
545
- openRouterSettings,
546
- tokensFromUsage,
547
- rawRecord,
548
- stringArray,
549
- metadataRecord,
550
- citationCandidates,
551
- nestedCitations,
552
- metadataAnnotations,
553
- evidenceFromMetadata,
554
- toolArguments,
555
- toolResultData,
556
- rawThoughtEvent,
557
- rawChoiceMessageEvidence,
558
- rawEvents,
559
- toolCallEvent,
560
- toolResultEvent,
561
- tokenEvent,
562
- providerMetadataEvent,
563
- eventFromPart,
564
- primaryEventFromPart,
565
- finishEvent,
566
- sourceEvent,
567
- finalEvents,
568
- responseFormatFor,
569
- providerOptionsFor,
570
- openRouterHeaders,
571
- missingOpenRouterKey,
572
- streamTextOptions,
573
- });
@@ -0,0 +1,34 @@
1
+ /**
2
+ * OpenAI-compatible image generation transport (internal).
3
+ *
4
+ * Hosts use `createProvider(profile, { openAiGateway })` — this module is selected
5
+ * when the profile is an openAi image role on OpenRouter.
6
+ *
7
+ * Image-only turns POST `/images`. When `image.includeText` is set, chat
8
+ * completions carry an OpenRouter image-generation server tool so the model may
9
+ * return interleaved assistant text and images.
10
+ *
11
+ * @module
12
+ */
13
+ import type { ModelProvider, ProviderCompleteRequest, TurnEvent } from '../../kernel/types.js';
14
+ import type { OpenAiGatewayConfig } from '../types.js';
15
+ /** OpenRouter chat server tool for inline image generation. */
16
+ export declare const OPENROUTER_IMAGE_TOOL = "openrouter:image_generation";
17
+ export type ImageProviderConfig = OpenAiGatewayConfig;
18
+ export declare function buildImageHeaders(apiKey: string, config: ImageProviderConfig): Record<string, string>;
19
+ export declare function mediaFromImagesResponse(body: Record<string, unknown>, fallbackMime?: string): {
20
+ mimeType: string;
21
+ data: string;
22
+ } | null;
23
+ export declare function fetchImageAsBase64(url: string, fetchFn: typeof fetch, signal?: AbortSignal): Promise<{
24
+ mimeType: string;
25
+ data: string;
26
+ } | null>;
27
+ export declare function markdownImageUrls(content: string): string[];
28
+ export declare function plainTextFromContent(content: unknown): string;
29
+ export declare function buildInterleavedChatPayload(req: ProviderCompleteRequest): Record<string, unknown>;
30
+ export declare function yieldInterleavedChat(req: ProviderCompleteRequest, config: ImageProviderConfig, apiKey: string): AsyncGenerator<TurnEvent>;
31
+ export declare function yieldImagesEndpoint(req: ProviderCompleteRequest, config: ImageProviderConfig, apiKey: string): AsyncGenerator<TurnEvent>;
32
+ export declare function streamImage(req: ProviderCompleteRequest, config?: ImageProviderConfig): AsyncGenerator<TurnEvent>;
33
+ /** Internal ModelProvider for openAi image roles on OpenRouter. */
34
+ export declare function createImageProvider(config?: ImageProviderConfig): ModelProvider;