theorum 0.1.15 → 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 +177 -90
  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 +437 -39
  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 -202
  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 -202
  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);
115
278
  }
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 } };
279
+ else {
280
+ const { data } = directAudio;
281
+ if (typeof data === 'string' && data) {
282
+ events.push({ type: 'media', media: { mimeType: 'audio/pcm', data } });
283
+ }
120
284
  }
121
285
  }
122
- return mediaFromOutputs(interaction.outputs);
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));
308
+ }
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--;
367
+ }
368
+ }
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,12 +453,6 @@ function searchHtml(metadata) {
168
453
  }
169
454
  return undefined;
170
455
  }
171
- function nonEmptyString(value) {
172
- if (typeof value === 'string' && value.trim()) {
173
- return value;
174
- }
175
- return undefined;
176
- }
177
456
  function searchSuggestionsFromRecord(record) {
178
457
  return nonEmptyString(record.search_suggestions ?? record.searchSuggestions);
179
458
  }
@@ -209,15 +488,79 @@ function sourceFromAnnotation(ann) {
209
488
  return undefined;
210
489
  }
211
490
  const kind = String(record.type ?? '');
212
- const title = nonEmptyString(record.title ?? record.name) ?? uri;
491
+ const rawTitle = nonEmptyString(record.title ?? record.name) ?? uri;
213
492
  if (kind === 'place_citation') {
214
- return { type: 'maps', uri, title };
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
+ };
215
504
  }
216
505
  if (kind === 'url_citation' || kind === 'citation' || !kind) {
217
- return { type: 'web', uri, title };
506
+ return { type: 'web', uri, title: rawTitle };
218
507
  }
219
508
  return undefined;
220
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
+ }
221
564
  function appendAnnotationSources(into, annotations) {
222
565
  for (const source of sourcesFromAnnotations(annotations)) {
223
566
  pushUniqueSource(into, source);
@@ -236,9 +579,15 @@ function sourcesFromAnnotations(annotations) {
236
579
  /**
237
580
  * Wrap Interactions tool-result steps (google_search_result, maps, etc.) into grounding.
238
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.
239
587
  */
240
588
  function groundingFromInteractionsTool(step) {
241
589
  const sources = [];
590
+ const chunks = [];
242
591
  appendAnnotationSources(sources, step.annotations);
243
592
  if (Array.isArray(step.content)) {
244
593
  for (const block of step.content) {
@@ -248,12 +597,32 @@ function groundingFromInteractionsTool(step) {
248
597
  }
249
598
  }
250
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
+ }
251
619
  const html = searchSuggestionsHtml(step);
252
- if (!html && sources.length === 0) {
620
+ if (!html && sources.length === 0 && chunks.length === 0) {
253
621
  return undefined;
254
622
  }
255
623
  return {
256
624
  metadata: step,
625
+ ...(chunks.length > 0 ? { chunks } : {}),
257
626
  ...(html ? { searchHtml: html } : {}),
258
627
  sources,
259
628
  };
@@ -283,7 +652,10 @@ function mergeGrounding(a, b) {
283
652
  if (!b) {
284
653
  return a;
285
654
  }
286
- const chunks = [...(a.chunks ?? []), ...(b.chunks ?? [])];
655
+ const chunks = [];
656
+ for (const chunk of [...(a.chunks ?? []), ...(b.chunks ?? [])]) {
657
+ pushUniqueChunk(chunks, chunk);
658
+ }
287
659
  const sources = [...a.sources];
288
660
  for (const source of b.sources) {
289
661
  pushUniqueSource(sources, source);
@@ -391,6 +763,13 @@ const TOOL_KEYS = [
391
763
  'toolTokens',
392
764
  'toolUse',
393
765
  ];
766
+ const INTERMEDIATE_KEYS = [
767
+ 'total_intermediate_tokens',
768
+ 'totalIntermediateTokens',
769
+ 'intermediate_tokens',
770
+ 'intermediateTokens',
771
+ 'intermediate',
772
+ ];
394
773
  const TOTAL_KEYS = [
395
774
  'total_tokens',
396
775
  'totalTokens',
@@ -419,9 +798,17 @@ function extractUsageTokens(raw) {
419
798
  const output = pickNumericField(r, OUTPUT_KEYS);
420
799
  const thinking = pickNumericField(r, THINKING_KEYS);
421
800
  const toolUse = pickNumericField(r, TOOL_KEYS);
422
- const total = pickNumericField(r, TOTAL_KEYS) || input + output + thinking + toolUse;
423
- if (input > 0 || output > 0 || thinking > 0 || toolUse > 0 || total > 0) {
424
- 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
+ };
425
812
  }
426
813
  return undefined;
427
814
  }
@@ -453,13 +840,23 @@ function extractTokenEvent(event, interaction) {
453
840
  function eventsFromComplete(event, alreadyText) {
454
841
  const interaction = asRecord(event.interaction) ?? event;
455
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
+ }
456
852
  const outputText = interaction.output_text;
457
- if (!alreadyText && typeof outputText === 'string' && outputText) {
853
+ if (!sawText && typeof outputText === 'string' && outputText) {
458
854
  events.push({ type: 'text', text: outputText });
855
+ sawText = true;
856
+ }
857
+ if (!events.some((item) => item.type === 'media')) {
858
+ events.push(...mediaEventsFromComplete(event));
459
859
  }
460
- const media = mediaFromComplete(event);
461
- if (media)
462
- events.push(media);
463
860
  const tokenEvent = extractTokenEvent(event, interaction);
464
861
  if (tokenEvent)
465
862
  events.push(tokenEvent);
@@ -493,12 +890,13 @@ function doneFromInteractionStatus(interaction, event) {
493
890
  ...(typeof interaction.id === 'string' ? { interactionId: interaction.id } : {}),
494
891
  };
495
892
  }
496
- function tryStructured(text) {
893
+ /** Parse model text as structured JSON. Invalid JSON is a hard failure — never silent skip. */
894
+ function parseStructuredOutput(text) {
497
895
  try {
498
- return { type: 'structured', structured: JSON.parse(text) };
896
+ return { ok: true, structured: JSON.parse(text) };
499
897
  }
500
898
  catch {
501
- return undefined;
899
+ return { ok: false, error: 'structured output was not valid JSON' };
502
900
  }
503
901
  }
504
- 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 };