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
@@ -48,12 +48,173 @@ function eventsFromAudio(delta) {
48
48
  }
49
49
  return [];
50
50
  }
51
+ function nonEmptyString(value) {
52
+ if (typeof value === 'string' && value.trim()) {
53
+ return value;
54
+ }
55
+ return undefined;
56
+ }
57
+ function isCodeExecutionType(type) {
58
+ return type === 'code_execution_call' || type === 'code_execution_result';
59
+ }
60
+ function isGoogleBuiltinStepType(type) {
61
+ return type.startsWith('google_') || type === 'url_context_call' || type === 'url_context_result';
62
+ }
63
+ function googleBuiltinEvidence(raw) {
64
+ const type = String(raw.type ?? '');
65
+ return {
66
+ type: 'evidence',
67
+ evidence: {
68
+ provider: 'google',
69
+ raw,
70
+ ...(type ? { kind: type } : {}),
71
+ },
72
+ };
73
+ }
74
+ function mergeSandboxString(prev, next) {
75
+ if (typeof next !== 'string') {
76
+ return typeof prev === 'string' ? prev : undefined;
77
+ }
78
+ if (typeof prev !== 'string' || !prev) {
79
+ return next;
80
+ }
81
+ if (next.startsWith(prev) || prev.endsWith(next)) {
82
+ return next.length >= prev.length ? next : prev;
83
+ }
84
+ return prev + next;
85
+ }
86
+ function argumentsField(record) {
87
+ if (!record) {
88
+ return undefined;
89
+ }
90
+ return record.arguments;
91
+ }
92
+ function mergedArgObject(prevArgs, incomingArgs) {
93
+ const language = incomingArgs?.language;
94
+ return {
95
+ ...prevArgs,
96
+ ...incomingArgs,
97
+ code: mergeSandboxString(prevArgs?.code, incomingArgs?.code),
98
+ language: language === undefined ? prevArgs?.language : language,
99
+ };
100
+ }
101
+ function mergeStringArguments(prev, next) {
102
+ if (typeof next === 'string') {
103
+ return mergeSandboxString(prev, next);
104
+ }
105
+ if (typeof prev === 'string') {
106
+ return mergeSandboxString(prev, next);
107
+ }
108
+ if (next !== undefined) {
109
+ return next;
110
+ }
111
+ return prev;
112
+ }
113
+ function mergeCodeExecutionArguments(existing, incoming) {
114
+ const prevArgs = asRecord(argumentsField(existing));
115
+ const incomingArgs = asRecord(incoming.arguments);
116
+ if (prevArgs) {
117
+ return mergedArgObject(prevArgs, incomingArgs);
118
+ }
119
+ if (incomingArgs) {
120
+ return mergedArgObject(prevArgs, incomingArgs);
121
+ }
122
+ return mergeStringArguments(argumentsField(existing), incoming.arguments);
123
+ }
124
+ /** Merge a later code-exec delta/step onto an earlier snapshot (partial code/stdout). */
125
+ function mergeCodeExecutionPayload(existing, incoming) {
126
+ const next = { ...existing, ...incoming };
127
+ const argumentsValue = mergeCodeExecutionArguments(existing, incoming);
128
+ if (argumentsValue !== undefined) {
129
+ next.arguments = argumentsValue;
130
+ }
131
+ const result = mergeSandboxString(existing?.result, incoming.result);
132
+ if (result !== undefined) {
133
+ next.result = result;
134
+ }
135
+ return next;
136
+ }
137
+ function codeFromArguments(args) {
138
+ if (typeof args === 'string') {
139
+ return { code: args };
140
+ }
141
+ const rec = asRecord(args);
142
+ if (!rec) {
143
+ return {};
144
+ }
145
+ return {
146
+ ...(typeof rec.code === 'string' ? { code: rec.code } : {}),
147
+ ...(typeof rec.language === 'string' ? { language: rec.language } : {}),
148
+ };
149
+ }
150
+ function sandboxReportedError(raw) {
151
+ if (raw.is_error === undefined && raw.isError === undefined) {
152
+ return undefined;
153
+ }
154
+ return raw.is_error === true || raw.isError === true;
155
+ }
156
+ function applyCodeExecutionFields(evidence, raw) {
157
+ const type = String(raw.type ?? '');
158
+ if (isCodeExecutionType(type)) {
159
+ evidence.kind = type;
160
+ }
161
+ const fromArgs = codeFromArguments(raw.arguments);
162
+ if (fromArgs.code) {
163
+ evidence.code = fromArgs.code;
164
+ }
165
+ if (fromArgs.language) {
166
+ evidence.language = fromArgs.language;
167
+ }
168
+ if (typeof raw.result === 'string') {
169
+ evidence.result = raw.result;
170
+ }
171
+ const isError = sandboxReportedError(raw);
172
+ if (isError !== undefined) {
173
+ evidence.isError = isError;
174
+ }
175
+ const id = nonEmptyString(raw.id);
176
+ if (id) {
177
+ evidence.id = id;
178
+ }
179
+ const callId = nonEmptyString(raw.call_id ?? raw.callId);
180
+ if (callId) {
181
+ evidence.callId = callId;
182
+ }
183
+ }
184
+ /** Normalize a Google `code_execution_*` step/delta into an `evidence` event. */
185
+ function codeExecutionEvidence(raw) {
186
+ const evidence = { provider: 'google', raw };
187
+ applyCodeExecutionFields(evidence, raw);
188
+ return { type: 'evidence', evidence };
189
+ }
190
+ /** Stable key so streamed deltas and batched `steps[]` do not double-emit. */
191
+ function codeExecutionStepKey(raw, index) {
192
+ const type = String(raw.type ?? 'code_execution');
193
+ const id = nonEmptyString(raw.id) ?? nonEmptyString(raw.call_id ?? raw.callId);
194
+ if (id) {
195
+ return `${type}:${id}`;
196
+ }
197
+ const fromArgs = codeFromArguments(raw.arguments);
198
+ if (fromArgs.code) {
199
+ return `${type}:code:${fromArgs.code}`;
200
+ }
201
+ if (typeof raw.result === 'string') {
202
+ return `${type}:result:${raw.result}`;
203
+ }
204
+ if (raw.result !== undefined) {
205
+ return `${type}:result:${JSON.stringify(raw.result)}`;
206
+ }
207
+ return `${type}:idx:${String(index ?? '')}`;
208
+ }
51
209
  function eventsFromDelta(deltaValue) {
52
210
  const delta = asRecord(deltaValue);
53
211
  if (!delta) {
54
212
  return [];
55
213
  }
56
214
  const deltaType = String(delta.type ?? '');
215
+ if (isCodeExecutionType(deltaType)) {
216
+ return [codeExecutionEvidence(delta)];
217
+ }
57
218
  if (deltaType === 'thought_summary' || deltaType === 'thought') {
58
219
  return eventIfText('thought', delta);
59
220
  }
@@ -86,51 +247,166 @@ function mediaFromOutputItem(item) {
86
247
  }
87
248
  return mediaFromRecord(rec);
88
249
  }
89
- function mediaFromOutputs(outputs) {
250
+ function mediaEventsFromOutputs(outputs) {
90
251
  if (!Array.isArray(outputs)) {
91
- return undefined;
252
+ return [];
92
253
  }
254
+ const events = [];
93
255
  for (const item of outputs) {
94
256
  const media = mediaFromOutputItem(item);
95
257
  if (media) {
96
- return media;
258
+ events.push(media);
97
259
  }
98
260
  }
99
- return undefined;
261
+ return events;
100
262
  }
101
- function mediaFromComplete(event) {
263
+ function mediaEventsFromComplete(event) {
102
264
  const interaction = asRecord(event.interaction) ?? event;
265
+ const events = [];
103
266
  const directImage = asRecord(interaction.output_image);
104
267
  if (directImage) {
105
268
  const media = mediaFromRecord(directImage);
106
269
  if (media) {
107
- return media;
270
+ events.push(media);
108
271
  }
109
272
  }
110
273
  const directAudio = asRecord(interaction.output_audio);
111
274
  if (directAudio) {
112
275
  const media = mediaFromRecord(directAudio);
113
276
  if (media) {
114
- return media;
277
+ events.push(media);
278
+ }
279
+ else {
280
+ const { data } = directAudio;
281
+ if (typeof data === 'string' && data) {
282
+ events.push({ type: 'media', media: { mimeType: 'audio/pcm', data } });
283
+ }
284
+ }
285
+ }
286
+ if (events.length === 0) {
287
+ events.push(...mediaEventsFromOutputs(interaction.outputs));
288
+ }
289
+ return events;
290
+ }
291
+ function eventsFromModelOutputContent(step, alreadyText) {
292
+ const events = [];
293
+ const content = step.content;
294
+ if (!Array.isArray(content)) {
295
+ return events;
296
+ }
297
+ for (const block of content) {
298
+ const rec = asRecord(block);
299
+ if (!rec) {
300
+ continue;
301
+ }
302
+ const type = String(rec.type ?? '');
303
+ if (type === 'text' && !alreadyText) {
304
+ events.push(...eventIfText('text', rec));
305
+ }
306
+ if (type === 'image' || type === 'media') {
307
+ events.push(...eventsFromImage(rec));
115
308
  }
116
- // Google TTS convenience field: base64 PCM without mime.
117
- const { data } = directAudio;
118
- if (typeof data === 'string' && data) {
119
- return { type: 'media', media: { mimeType: 'audio/pcm', data } };
309
+ if (type === 'audio' || type === 'output_audio') {
310
+ events.push(...eventsFromAudio(rec));
311
+ }
312
+ }
313
+ return events;
314
+ }
315
+ function eventsFromInteractionStep(step, sawText) {
316
+ const type = String(step.type ?? '');
317
+ if (isCodeExecutionType(type)) {
318
+ return { events: [codeExecutionEvidence(step)], sawText };
319
+ }
320
+ if (isGoogleBuiltinStepType(type)) {
321
+ return { events: [googleBuiltinEvidence(step)], sawText };
322
+ }
323
+ if (type !== 'model_output') {
324
+ return { events: [], sawText };
325
+ }
326
+ const fromOutput = eventsFromModelOutputContent(step, sawText);
327
+ return {
328
+ events: fromOutput,
329
+ sawText: sawText || fromOutput.some((item) => item.type === 'text'),
330
+ };
331
+ }
332
+ /**
333
+ * Replay a completed Interactions `steps[]` array (non-SSE or stream tail).
334
+ * Emits typed code-execution evidence plus model_output text/media.
335
+ */
336
+ function eventsFromInteractionSteps(steps, alreadyText) {
337
+ const events = [];
338
+ let sawText = alreadyText;
339
+ for (const stepValue of steps) {
340
+ const step = asRecord(stepValue);
341
+ if (!step) {
342
+ continue;
343
+ }
344
+ const next = eventsFromInteractionStep(step, sawText);
345
+ events.push(...next.events);
346
+ sawText = next.sawText;
347
+ }
348
+ return events;
349
+ }
350
+ function cleanMapsTitle(title) {
351
+ const suffix = 'google maps';
352
+ const trimmed = title.trimEnd();
353
+ if (trimmed.length < suffix.length) {
354
+ return title.trim();
355
+ }
356
+ if (trimmed.slice(-suffix.length).toLowerCase() !== suffix) {
357
+ return title.trim();
358
+ }
359
+ let end = trimmed.length - suffix.length;
360
+ while (end > 0 && /\s/.test(trimmed[end - 1])) {
361
+ end--;
362
+ }
363
+ if (end > 0 && trimmed[end - 1] === '-') {
364
+ end--;
365
+ while (end > 0 && /\s/.test(trimmed[end - 1])) {
366
+ end--;
120
367
  }
121
368
  }
122
- return mediaFromOutputs(interaction.outputs);
369
+ return trimmed.slice(0, end).trimEnd();
370
+ }
371
+ function isPrimaryMapsPlace(name, uri) {
372
+ if (/^Review of\b/i.test(name)) {
373
+ return false;
374
+ }
375
+ if (/\/maps\/reviews\//i.test(uri)) {
376
+ return false;
377
+ }
378
+ return true;
379
+ }
380
+ function placeIdFromRecord(record) {
381
+ return nonEmptyString(record.placeId ?? record.place_id);
123
382
  }
124
383
  function sourceFromMaps(maps) {
125
- const uri = maps.uri ?? maps.googleMapsUri ?? maps.google_maps_uri;
384
+ const uri = maps.uri ?? maps.googleMapsUri ?? maps.google_maps_uri ?? maps.url;
126
385
  if (typeof uri !== 'string' || !uri) {
127
386
  return undefined;
128
387
  }
129
- const title = maps.title ?? maps.name;
388
+ const rawTitle = maps.title ?? maps.name;
389
+ const title = typeof rawTitle === 'string' && rawTitle ? cleanMapsTitle(rawTitle) : uri;
390
+ const placeId = placeIdFromRecord(maps);
130
391
  return {
131
392
  type: 'maps',
132
393
  uri,
133
- title: typeof title === 'string' && title ? title : uri,
394
+ title,
395
+ ...(placeId ? { placeId } : {}),
396
+ };
397
+ }
398
+ /** Classic grounding chunk shape hosts already parse (`chunks[].maps`). */
399
+ function chunkFromMapsPlace(place) {
400
+ const source = sourceFromMaps(place);
401
+ if (!source || !isPrimaryMapsPlace(source.title, source.uri)) {
402
+ return undefined;
403
+ }
404
+ return {
405
+ maps: {
406
+ title: source.title,
407
+ uri: source.uri,
408
+ ...(source.placeId ? { placeId: source.placeId } : {}),
409
+ },
134
410
  };
135
411
  }
136
412
  function sourceFromWeb(web) {
@@ -149,9 +425,18 @@ function pushUniqueSource(sources, source) {
149
425
  if (!source) {
150
426
  return;
151
427
  }
152
- if (!sources.some((item) => item.uri === source.uri && item.type === source.type)) {
153
- sources.push(source);
428
+ if (sources.some((item) => {
429
+ if (item.type !== source.type) {
430
+ return false;
431
+ }
432
+ if (source.placeId && item.placeId && source.placeId === item.placeId) {
433
+ return true;
434
+ }
435
+ return item.uri === source.uri;
436
+ })) {
437
+ return;
154
438
  }
439
+ sources.push(source);
155
440
  }
156
441
  function groundingChunks(metadata) {
157
442
  const chunks = metadata.groundingChunks ?? metadata.grounding_chunks;
@@ -168,6 +453,180 @@ function searchHtml(metadata) {
168
453
  }
169
454
  return undefined;
170
455
  }
456
+ function searchSuggestionsFromRecord(record) {
457
+ return nonEmptyString(record.search_suggestions ?? record.searchSuggestions);
458
+ }
459
+ function firstSearchSuggestions(items) {
460
+ if (!Array.isArray(items)) {
461
+ return undefined;
462
+ }
463
+ for (const item of items) {
464
+ const record = asRecord(item);
465
+ if (!record) {
466
+ continue;
467
+ }
468
+ const html = searchSuggestionsFromRecord(record);
469
+ if (html) {
470
+ return html;
471
+ }
472
+ }
473
+ return undefined;
474
+ }
475
+ /** Interactions streams search chips as `search_suggestions` HTML, not classic entry points. */
476
+ function searchSuggestionsHtml(step) {
477
+ return (searchSuggestionsFromRecord(step) ??
478
+ firstSearchSuggestions(step.result) ??
479
+ firstSearchSuggestions(step.content));
480
+ }
481
+ function sourceFromAnnotation(ann) {
482
+ const record = asRecord(ann);
483
+ if (!record) {
484
+ return undefined;
485
+ }
486
+ const uri = nonEmptyString(record.url ?? record.uri);
487
+ if (!uri) {
488
+ return undefined;
489
+ }
490
+ const kind = String(record.type ?? '');
491
+ const rawTitle = nonEmptyString(record.title ?? record.name) ?? uri;
492
+ if (kind === 'place_citation') {
493
+ const title = cleanMapsTitle(rawTitle);
494
+ if (!isPrimaryMapsPlace(title, uri)) {
495
+ return undefined;
496
+ }
497
+ const placeId = placeIdFromRecord(record);
498
+ return {
499
+ type: 'maps',
500
+ uri,
501
+ title,
502
+ ...(placeId ? { placeId } : {}),
503
+ };
504
+ }
505
+ if (kind === 'url_citation' || kind === 'citation' || !kind) {
506
+ return { type: 'web', uri, title: rawTitle };
507
+ }
508
+ return undefined;
509
+ }
510
+ function pushUniqueChunk(chunks, chunk) {
511
+ const maps = asRecord(asRecord(chunk)?.maps);
512
+ if (!maps) {
513
+ chunks.push(chunk);
514
+ return;
515
+ }
516
+ const placeId = placeIdFromRecord(maps);
517
+ const uri = nonEmptyString(maps.uri ?? maps.url);
518
+ if (chunks.some((existing) => {
519
+ const existingMaps = asRecord(asRecord(existing)?.maps);
520
+ if (!existingMaps) {
521
+ return false;
522
+ }
523
+ const existingId = placeIdFromRecord(existingMaps);
524
+ if (placeId && existingId && placeId === existingId) {
525
+ return true;
526
+ }
527
+ return Boolean(uri && nonEmptyString(existingMaps.uri ?? existingMaps.url) === uri);
528
+ })) {
529
+ return;
530
+ }
531
+ chunks.push(chunk);
532
+ }
533
+ /** Interactions `google_maps_result.result[].places[]` — not classic groundingChunks. */
534
+ function chunksAndSourcesFromMapsResult(result) {
535
+ const chunks = [];
536
+ const sources = [];
537
+ if (!Array.isArray(result)) {
538
+ return { chunks, sources };
539
+ }
540
+ for (const entry of result) {
541
+ const record = asRecord(entry);
542
+ if (!record) {
543
+ continue;
544
+ }
545
+ const places = record.places;
546
+ if (!Array.isArray(places)) {
547
+ continue;
548
+ }
549
+ for (const placeValue of places) {
550
+ const place = asRecord(placeValue);
551
+ if (!place) {
552
+ continue;
553
+ }
554
+ const chunk = chunkFromMapsPlace(place);
555
+ if (!chunk) {
556
+ continue;
557
+ }
558
+ pushUniqueChunk(chunks, chunk);
559
+ pushUniqueSource(sources, sourceFromMaps(place));
560
+ }
561
+ }
562
+ return { chunks, sources };
563
+ }
564
+ function appendAnnotationSources(into, annotations) {
565
+ for (const source of sourcesFromAnnotations(annotations)) {
566
+ pushUniqueSource(into, source);
567
+ }
568
+ }
569
+ function sourcesFromAnnotations(annotations) {
570
+ if (!Array.isArray(annotations)) {
571
+ return [];
572
+ }
573
+ const sources = [];
574
+ for (const ann of annotations) {
575
+ pushUniqueSource(sources, sourceFromAnnotation(ann));
576
+ }
577
+ return sources;
578
+ }
579
+ /**
580
+ * Wrap Interactions tool-result steps (google_search_result, maps, etc.) into grounding.
581
+ * Preserves the raw step on `metadata` so hosts can inspect everything Gemini returned.
582
+ *
583
+ * Maps places arrive as `google_maps_result.result[].places[]` and as
584
+ * `place_citation` annotations on model_output content — not classic
585
+ * `grounding_metadata.groundingChunks`. Emit both `sources` and `chunks`
586
+ * (`chunks[].maps`) so hosts share one parse shape.
587
+ */
588
+ function groundingFromInteractionsTool(step) {
589
+ const sources = [];
590
+ const chunks = [];
591
+ appendAnnotationSources(sources, step.annotations);
592
+ if (Array.isArray(step.content)) {
593
+ for (const block of step.content) {
594
+ const record = asRecord(block);
595
+ if (record) {
596
+ appendAnnotationSources(sources, record.annotations);
597
+ }
598
+ }
599
+ }
600
+ for (const source of sources) {
601
+ if (source.type !== 'maps') {
602
+ continue;
603
+ }
604
+ pushUniqueChunk(chunks, {
605
+ maps: {
606
+ title: source.title,
607
+ uri: source.uri,
608
+ ...(source.placeId ? { placeId: source.placeId } : {}),
609
+ },
610
+ });
611
+ }
612
+ const fromMapsResult = chunksAndSourcesFromMapsResult(step.result);
613
+ for (const chunk of fromMapsResult.chunks) {
614
+ pushUniqueChunk(chunks, chunk);
615
+ }
616
+ for (const source of fromMapsResult.sources) {
617
+ pushUniqueSource(sources, source);
618
+ }
619
+ const html = searchSuggestionsHtml(step);
620
+ if (!html && sources.length === 0 && chunks.length === 0) {
621
+ return undefined;
622
+ }
623
+ return {
624
+ metadata: step,
625
+ ...(chunks.length > 0 ? { chunks } : {}),
626
+ ...(html ? { searchHtml: html } : {}),
627
+ sources,
628
+ };
629
+ }
171
630
  function sourcesFromChunks(chunks) {
172
631
  const sources = [];
173
632
  for (const chunk of chunks) {
@@ -193,7 +652,10 @@ function mergeGrounding(a, b) {
193
652
  if (!b) {
194
653
  return a;
195
654
  }
196
- const chunks = [...(a.chunks ?? []), ...(b.chunks ?? [])];
655
+ const chunks = [];
656
+ for (const chunk of [...(a.chunks ?? []), ...(b.chunks ?? [])]) {
657
+ pushUniqueChunk(chunks, chunk);
658
+ }
197
659
  const sources = [...a.sources];
198
660
  for (const source of b.sources) {
199
661
  pushUniqueSource(sources, source);
@@ -238,6 +700,8 @@ function groundingFromStep(stepValue) {
238
700
  grounding = mergeGrounding(grounding, groundingFromMetadata(groundingMetadataFromRecord(asRecord(block) ?? {})));
239
701
  }
240
702
  }
703
+ // Interactions search/maps tool results: chips + citations live on the step/delta itself.
704
+ grounding = mergeGrounding(grounding, groundingFromInteractionsTool(step));
241
705
  return grounding;
242
706
  }
243
707
  function groundingFromEvent(event) {
@@ -299,6 +763,13 @@ const TOOL_KEYS = [
299
763
  'toolTokens',
300
764
  'toolUse',
301
765
  ];
766
+ const INTERMEDIATE_KEYS = [
767
+ 'total_intermediate_tokens',
768
+ 'totalIntermediateTokens',
769
+ 'intermediate_tokens',
770
+ 'intermediateTokens',
771
+ 'intermediate',
772
+ ];
302
773
  const TOTAL_KEYS = [
303
774
  'total_tokens',
304
775
  'totalTokens',
@@ -327,9 +798,17 @@ function extractUsageTokens(raw) {
327
798
  const output = pickNumericField(r, OUTPUT_KEYS);
328
799
  const thinking = pickNumericField(r, THINKING_KEYS);
329
800
  const toolUse = pickNumericField(r, TOOL_KEYS);
330
- const total = pickNumericField(r, TOTAL_KEYS) || input + output + thinking + toolUse;
331
- if (input > 0 || output > 0 || thinking > 0 || toolUse > 0 || total > 0) {
332
- return { input, output, thinking, toolUse, total };
801
+ const intermediate = pickNumericField(r, INTERMEDIATE_KEYS);
802
+ const total = pickNumericField(r, TOTAL_KEYS) || input + output + thinking + toolUse + intermediate;
803
+ if (input > 0 || output > 0 || thinking > 0 || toolUse > 0 || intermediate > 0 || total > 0) {
804
+ return {
805
+ input,
806
+ output,
807
+ thinking,
808
+ toolUse,
809
+ total,
810
+ ...(intermediate > 0 ? { intermediate } : {}),
811
+ };
333
812
  }
334
813
  return undefined;
335
814
  }
@@ -361,13 +840,23 @@ function extractTokenEvent(event, interaction) {
361
840
  function eventsFromComplete(event, alreadyText) {
362
841
  const interaction = asRecord(event.interaction) ?? event;
363
842
  const events = [];
843
+ let sawText = alreadyText;
844
+ const steps = interaction.steps;
845
+ if (Array.isArray(steps)) {
846
+ const fromSteps = eventsFromInteractionSteps(steps, sawText);
847
+ events.push(...fromSteps);
848
+ if (fromSteps.some((item) => item.type === 'text')) {
849
+ sawText = true;
850
+ }
851
+ }
364
852
  const outputText = interaction.output_text;
365
- if (!alreadyText && typeof outputText === 'string' && outputText) {
853
+ if (!sawText && typeof outputText === 'string' && outputText) {
366
854
  events.push({ type: 'text', text: outputText });
855
+ sawText = true;
856
+ }
857
+ if (!events.some((item) => item.type === 'media')) {
858
+ events.push(...mediaEventsFromComplete(event));
367
859
  }
368
- const media = mediaFromComplete(event);
369
- if (media)
370
- events.push(media);
371
860
  const tokenEvent = extractTokenEvent(event, interaction);
372
861
  if (tokenEvent)
373
862
  events.push(tokenEvent);
@@ -401,12 +890,13 @@ function doneFromInteractionStatus(interaction, event) {
401
890
  ...(typeof interaction.id === 'string' ? { interactionId: interaction.id } : {}),
402
891
  };
403
892
  }
404
- function tryStructured(text) {
893
+ /** Parse model text as structured JSON. Invalid JSON is a hard failure — never silent skip. */
894
+ function parseStructuredOutput(text) {
405
895
  try {
406
- return { type: 'structured', structured: JSON.parse(text) };
896
+ return { ok: true, structured: JSON.parse(text) };
407
897
  }
408
898
  catch {
409
- return undefined;
899
+ return { ok: false, error: 'structured output was not valid JSON' };
410
900
  }
411
901
  }
412
- export { eventsFromComplete, eventsFromDelta, extractTokenEvent, extractUsageTokens, groundingFromEvent, tryStructured, };
902
+ export { codeExecutionEvidence, codeExecutionStepKey, eventsFromComplete, eventsFromDelta, eventsFromInteractionSteps, extractTokenEvent, extractUsageTokens, googleBuiltinEvidence, groundingFromEvent, isCodeExecutionType, isGoogleBuiltinStepType, mergeCodeExecutionPayload, parseStructuredOutput, };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Live realtime inbound text — same sanitize + user_data fence as runTurn ingress.
3
+ *
4
+ * @module
5
+ */
6
+ import type { Profile } from '../types.js';
7
+ /** Sanitize profile-controlled inbound text and wrap with user_data fencing. */
8
+ declare function prepareLiveInboundText(profile: Profile, text: string): string;
9
+ export { prepareLiveInboundText };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Live realtime inbound text — same sanitize + user_data fence as runTurn ingress.
3
+ *
4
+ * @module
5
+ */
6
+ import { wrapUserData } from '../../guardrails/canary.js';
7
+ import { sanitizeText } from '../../guardrails/sanitize.js';
8
+ /** Sanitize profile-controlled inbound text and wrap with user_data fencing. */
9
+ function prepareLiveInboundText(profile, text) {
10
+ const guardrails = profile.guardrails;
11
+ const safe = sanitizeText(text, {
12
+ sanitizeInput: guardrails?.sanitizeInput === true,
13
+ redactSensitive: guardrails?.redactSensitive === true,
14
+ });
15
+ return wrapUserData(safe);
16
+ }
17
+ export { prepareLiveInboundText };
@@ -16,7 +16,7 @@ function formatHistoryBlock(messages) {
16
16
  function synthesizeRepairPrompt(args) {
17
17
  const { profile, repair, history } = args;
18
18
  const guidance = repair.guidance ||
19
- profile.outputs.validation?.repairGuidance ||
19
+ profile.outputs?.validation?.repairGuidance ||
20
20
  'Revise the previous output so it satisfies the validator rejection. Preserve the intended user-facing substance unless the guidance says otherwise.';
21
21
  const historyBlock = formatHistoryBlock(scopeHistory(history));
22
22
  let prompt = `## OUTPUT REPAIR REQUEST\n\n`;
@@ -1,4 +1,4 @@
1
1
  import type { ModelProvider, Profile, ResolvedGeneration, TurnEvent, TurnRequest } from '../../types.js';
2
2
  import type { StepExecutionState } from './state.js';
3
- declare function runAttemptsWithValidation(safe: TurnRequest, profile: Profile, generation: ResolvedGeneration, system: string, provider: ModelProvider, gemini: Record<string, unknown>[], state: StepExecutionState): AsyncGenerator<TurnEvent>;
3
+ declare function runAttemptsWithValidation(safe: TurnRequest, profile: Profile, generation: ResolvedGeneration, system: string, provider: ModelProvider, upstream: Record<string, unknown>[], state: StepExecutionState): AsyncGenerator<TurnEvent>;
4
4
  export { runAttemptsWithValidation };