@copilotkit/aimock 1.46.0 → 1.47.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 (183) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +12 -1
  4. package/README.md +4 -1
  5. package/dist/aws-event-stream.cjs +20 -23
  6. package/dist/aws-event-stream.cjs.map +1 -1
  7. package/dist/aws-event-stream.d.cts +0 -1
  8. package/dist/aws-event-stream.d.ts +0 -1
  9. package/dist/aws-event-stream.js +20 -24
  10. package/dist/aws-event-stream.js.map +1 -1
  11. package/dist/bedrock-converse.cjs +79 -82
  12. package/dist/bedrock-converse.cjs.map +1 -1
  13. package/dist/bedrock-converse.d.cts.map +1 -1
  14. package/dist/bedrock-converse.d.ts.map +1 -1
  15. package/dist/bedrock-converse.js +80 -83
  16. package/dist/bedrock-converse.js.map +1 -1
  17. package/dist/bedrock.cjs +112 -121
  18. package/dist/bedrock.cjs.map +1 -1
  19. package/dist/bedrock.d.cts.map +1 -1
  20. package/dist/bedrock.d.ts.map +1 -1
  21. package/dist/bedrock.js +113 -122
  22. package/dist/bedrock.js.map +1 -1
  23. package/dist/cohere.cjs +58 -106
  24. package/dist/cohere.cjs.map +1 -1
  25. package/dist/cohere.d.cts.map +1 -1
  26. package/dist/cohere.d.ts.map +1 -1
  27. package/dist/cohere.js +60 -108
  28. package/dist/cohere.js.map +1 -1
  29. package/dist/config-loader.d.cts.map +1 -1
  30. package/dist/config-loader.d.ts.map +1 -1
  31. package/dist/fixture-loader.cjs +103 -2
  32. package/dist/fixture-loader.cjs.map +1 -1
  33. package/dist/fixture-loader.d.cts.map +1 -1
  34. package/dist/fixture-loader.d.ts.map +1 -1
  35. package/dist/fixture-loader.js +104 -4
  36. package/dist/fixture-loader.js.map +1 -1
  37. package/dist/gemini-interactions.cjs +63 -121
  38. package/dist/gemini-interactions.cjs.map +1 -1
  39. package/dist/gemini-interactions.d.cts.map +1 -1
  40. package/dist/gemini-interactions.d.ts.map +1 -1
  41. package/dist/gemini-interactions.js +65 -123
  42. package/dist/gemini-interactions.js.map +1 -1
  43. package/dist/gemini.cjs +242 -155
  44. package/dist/gemini.cjs.map +1 -1
  45. package/dist/gemini.d.cts.map +1 -1
  46. package/dist/gemini.d.ts.map +1 -1
  47. package/dist/gemini.js +244 -157
  48. package/dist/gemini.js.map +1 -1
  49. package/dist/helpers.cjs +400 -7
  50. package/dist/helpers.cjs.map +1 -1
  51. package/dist/helpers.d.cts +51 -1
  52. package/dist/helpers.d.cts.map +1 -1
  53. package/dist/helpers.d.ts +51 -1
  54. package/dist/helpers.d.ts.map +1 -1
  55. package/dist/helpers.js +391 -8
  56. package/dist/helpers.js.map +1 -1
  57. package/dist/index.cjs +4 -2
  58. package/dist/index.d.cts +4 -4
  59. package/dist/index.d.ts +4 -4
  60. package/dist/index.js +4 -4
  61. package/dist/mcp-fakes.d.cts.map +1 -1
  62. package/dist/messages.cjs +159 -141
  63. package/dist/messages.cjs.map +1 -1
  64. package/dist/messages.d.cts.map +1 -1
  65. package/dist/messages.d.ts.map +1 -1
  66. package/dist/messages.js +161 -143
  67. package/dist/messages.js.map +1 -1
  68. package/dist/misbehavior.cjs +62 -3
  69. package/dist/misbehavior.cjs.map +1 -1
  70. package/dist/misbehavior.d.cts.map +1 -1
  71. package/dist/misbehavior.d.ts.map +1 -1
  72. package/dist/misbehavior.js +63 -4
  73. package/dist/misbehavior.js.map +1 -1
  74. package/dist/ndjson-writer.cjs +21 -23
  75. package/dist/ndjson-writer.cjs.map +1 -1
  76. package/dist/ndjson-writer.d.cts +0 -1
  77. package/dist/ndjson-writer.d.cts.map +1 -1
  78. package/dist/ndjson-writer.d.ts +0 -1
  79. package/dist/ndjson-writer.d.ts.map +1 -1
  80. package/dist/ndjson-writer.js +21 -24
  81. package/dist/ndjson-writer.js.map +1 -1
  82. package/dist/ollama.cjs +153 -147
  83. package/dist/ollama.cjs.map +1 -1
  84. package/dist/ollama.d.cts.map +1 -1
  85. package/dist/ollama.d.ts.map +1 -1
  86. package/dist/ollama.js +155 -149
  87. package/dist/ollama.js.map +1 -1
  88. package/dist/openrouter-chat.cjs +1 -1
  89. package/dist/openrouter-chat.js +1 -1
  90. package/dist/raw-validation.cjs +177 -0
  91. package/dist/raw-validation.cjs.map +1 -0
  92. package/dist/raw-validation.js +176 -0
  93. package/dist/raw-validation.js.map +1 -0
  94. package/dist/response/deliver.cjs +144 -0
  95. package/dist/response/deliver.cjs.map +1 -0
  96. package/dist/response/deliver.js +143 -0
  97. package/dist/response/deliver.js.map +1 -0
  98. package/dist/response/frame-loop.cjs +102 -0
  99. package/dist/response/frame-loop.cjs.map +1 -0
  100. package/dist/response/frame-loop.js +100 -0
  101. package/dist/response/frame-loop.js.map +1 -0
  102. package/dist/response/lifecycle.cjs +223 -0
  103. package/dist/response/lifecycle.cjs.map +1 -0
  104. package/dist/response/lifecycle.js +211 -0
  105. package/dist/response/lifecycle.js.map +1 -0
  106. package/dist/response/proxy.cjs +71 -0
  107. package/dist/response/proxy.cjs.map +1 -0
  108. package/dist/response/proxy.js +70 -0
  109. package/dist/response/proxy.js.map +1 -0
  110. package/dist/response/raw.cjs +228 -0
  111. package/dist/response/raw.cjs.map +1 -0
  112. package/dist/response/raw.js +227 -0
  113. package/dist/response/raw.js.map +1 -0
  114. package/dist/response/timing.cjs +157 -0
  115. package/dist/response/timing.cjs.map +1 -0
  116. package/dist/response/timing.js +150 -0
  117. package/dist/response/timing.js.map +1 -0
  118. package/dist/responses.cjs +269 -148
  119. package/dist/responses.cjs.map +1 -1
  120. package/dist/responses.d.cts.map +1 -1
  121. package/dist/responses.d.ts.map +1 -1
  122. package/dist/responses.js +270 -149
  123. package/dist/responses.js.map +1 -1
  124. package/dist/router.cjs +20 -4
  125. package/dist/router.cjs.map +1 -1
  126. package/dist/router.d.cts +7 -1
  127. package/dist/router.d.cts.map +1 -1
  128. package/dist/router.d.ts +7 -1
  129. package/dist/router.d.ts.map +1 -1
  130. package/dist/router.js +21 -5
  131. package/dist/router.js.map +1 -1
  132. package/dist/server.cjs +89 -120
  133. package/dist/server.cjs.map +1 -1
  134. package/dist/server.d.cts.map +1 -1
  135. package/dist/server.d.ts.map +1 -1
  136. package/dist/server.js +91 -122
  137. package/dist/server.js.map +1 -1
  138. package/dist/sse-writer.cjs +49 -30
  139. package/dist/sse-writer.cjs.map +1 -1
  140. package/dist/sse-writer.d.cts +0 -1
  141. package/dist/sse-writer.d.cts.map +1 -1
  142. package/dist/sse-writer.d.ts +0 -1
  143. package/dist/sse-writer.d.ts.map +1 -1
  144. package/dist/sse-writer.js +49 -31
  145. package/dist/sse-writer.js.map +1 -1
  146. package/dist/transcription.cjs +19 -33
  147. package/dist/transcription.cjs.map +1 -1
  148. package/dist/transcription.d.cts.map +1 -1
  149. package/dist/transcription.d.ts.map +1 -1
  150. package/dist/transcription.js +20 -34
  151. package/dist/transcription.js.map +1 -1
  152. package/dist/types.cjs.map +1 -1
  153. package/dist/types.d.cts +173 -10
  154. package/dist/types.d.cts.map +1 -1
  155. package/dist/types.d.ts +173 -10
  156. package/dist/types.d.ts.map +1 -1
  157. package/dist/types.js.map +1 -1
  158. package/dist/ws-framing.cjs +12 -6
  159. package/dist/ws-framing.cjs.map +1 -1
  160. package/dist/ws-framing.d.cts +2 -0
  161. package/dist/ws-framing.d.cts.map +1 -1
  162. package/dist/ws-framing.d.ts +2 -0
  163. package/dist/ws-framing.d.ts.map +1 -1
  164. package/dist/ws-framing.js +12 -6
  165. package/dist/ws-framing.js.map +1 -1
  166. package/dist/ws-gemini-live.cjs +120 -93
  167. package/dist/ws-gemini-live.cjs.map +1 -1
  168. package/dist/ws-gemini-live.js +120 -93
  169. package/dist/ws-gemini-live.js.map +1 -1
  170. package/dist/ws-realtime.cjs +80 -70
  171. package/dist/ws-realtime.cjs.map +1 -1
  172. package/dist/ws-realtime.d.cts.map +1 -1
  173. package/dist/ws-realtime.d.ts.map +1 -1
  174. package/dist/ws-realtime.js +80 -70
  175. package/dist/ws-realtime.js.map +1 -1
  176. package/dist/ws-responses.cjs +72 -53
  177. package/dist/ws-responses.cjs.map +1 -1
  178. package/dist/ws-responses.d.cts.map +1 -1
  179. package/dist/ws-responses.d.ts.map +1 -1
  180. package/dist/ws-responses.js +73 -54
  181. package/dist/ws-responses.js.map +1 -1
  182. package/package.json +1 -1
  183. package/skills/write-fixtures/SKILL.md +34 -2
package/dist/gemini.cjs CHANGED
@@ -6,7 +6,11 @@ const require_chaos = require('./chaos.cjs');
6
6
  const require_misbehavior = require('./misbehavior.cjs');
7
7
  const require_router = require('./router.cjs');
8
8
  const require_recorder = require('./recorder.cjs');
9
- const require_interruption = require('./interruption.cjs');
9
+ const require_logger = require('./logger.cjs');
10
+ const require_timing = require('./response/timing.cjs');
11
+ const require_deliver = require('./response/deliver.cjs');
12
+ const require_proxy = require('./response/proxy.cjs');
13
+ const require_raw = require('./response/raw.cjs');
10
14
  const require_finish = require('./response/finish.cjs');
11
15
  const require_gemini = require('./response/usage/codecs/gemini.cjs');
12
16
 
@@ -149,6 +153,110 @@ function geminiFinishReason(finishReason, defaultReason) {
149
153
  function geminiUsageMetadata(overrides) {
150
154
  return require_gemini.render(overrides);
151
155
  }
156
+ function geminiWholeTextPart(text, partIndex) {
157
+ return [{
158
+ partIndex,
159
+ start: 0,
160
+ end: text.length
161
+ }];
162
+ }
163
+ function geminiGroundingSupports(text, citations, textParts) {
164
+ const segment = (part, from, to) => {
165
+ const partText = text.slice(part.start, part.end);
166
+ const start = Math.min(Math.max(from - part.start, 0), partText.length);
167
+ const end = Math.min(Math.max(to - part.start, start), partText.length);
168
+ const byteOffset = (charIndex) => Buffer.byteLength(partText.slice(0, charIndex), "utf8");
169
+ return {
170
+ partIndex: part.partIndex,
171
+ startIndex: byteOffset(start),
172
+ endIndex: byteOffset(end),
173
+ text: partText.slice(start, end)
174
+ };
175
+ };
176
+ return citations.flatMap((c, i) => {
177
+ const spanned = textParts.filter((p) => Math.max(c.startIndex, p.start) < Math.min(c.endIndex, p.end));
178
+ return (spanned.length > 0 ? spanned : [textParts.find((p) => c.startIndex < p.end) ?? textParts[textParts.length - 1]]).map((p) => ({
179
+ segment: segment(p, c.startIndex, c.endIndex),
180
+ groundingChunkIndices: [i]
181
+ }));
182
+ });
183
+ }
184
+ function geminiGroundingMetadata(text, overrides, textParts) {
185
+ const citations = require_helpers.resolveCitations(text, overrides?.citations);
186
+ if (citations.length === 0 || textParts.length === 0) return void 0;
187
+ return {
188
+ groundingChunks: geminiGroundingChunks(citations),
189
+ groundingSupports: geminiGroundingSupports(text, citations, textParts)
190
+ };
191
+ }
192
+ function geminiGroundingChunks(citations) {
193
+ return citations.map((c) => ({ web: {
194
+ uri: c.url,
195
+ title: c.title
196
+ } }));
197
+ }
198
+ function geminiGroundingField(text, overrides, textParts) {
199
+ const groundingMetadata = geminiGroundingMetadata(text, overrides, textParts);
200
+ return groundingMetadata ? { groundingMetadata } : {};
201
+ }
202
+ function geminiStreamGroundingFields(text, overrides, spans) {
203
+ const fields = spans.map(() => ({}));
204
+ const citations = require_helpers.resolveCitations(text, overrides?.citations);
205
+ if (citations.length === 0 || spans.length === 0) return fields;
206
+ const textParts = spans.map((s, k) => ({
207
+ partIndex: k,
208
+ start: s.start,
209
+ end: s.end
210
+ }));
211
+ const groundingChunks = geminiGroundingChunks(citations);
212
+ for (const support of geminiGroundingSupports(text, citations, textParts)) {
213
+ const k = support.segment.partIndex;
214
+ (fields[k].groundingMetadata ??= {
215
+ groundingChunks,
216
+ groundingSupports: []
217
+ }).groundingSupports.push({
218
+ ...support,
219
+ segment: {
220
+ ...support.segment,
221
+ partIndex: 0
222
+ }
223
+ });
224
+ }
225
+ return fields;
226
+ }
227
+ function geminiTextChunkSpans(text, chunkSize, offset = 0) {
228
+ if (text.length === 0) return [{
229
+ start: offset,
230
+ end: offset
231
+ }];
232
+ const spans = [];
233
+ let start = offset;
234
+ for (const slice of require_helpers.surrogateSafeChunks(text, chunkSize)) {
235
+ spans.push({
236
+ start,
237
+ end: start + slice.length
238
+ });
239
+ start += slice.length;
240
+ }
241
+ return spans;
242
+ }
243
+ function geminiFileParts(overrides) {
244
+ return require_helpers.fixtureFiles(overrides?.files).map((f) => ({ inlineData: {
245
+ mimeType: f.mimeType,
246
+ data: f.data
247
+ } }));
248
+ }
249
+ function geminiFileChunks(overrides) {
250
+ const parts = geminiFileParts(overrides);
251
+ if (parts.length === 0) return [];
252
+ return [{ candidates: [{
253
+ content: {
254
+ role: "model",
255
+ parts
256
+ },
257
+ index: 0
258
+ }] }];
259
+ }
152
260
  function buildGeminiTextStreamChunks(content, chunkSize, reasoning, overrides) {
153
261
  const chunks = [];
154
262
  const effectiveFinish = geminiFinishReason(overrides?.finishReason, "STOP");
@@ -166,9 +274,11 @@ function buildGeminiTextStreamChunks(content, chunkSize, reasoning, overrides) {
166
274
  index: 0
167
275
  }] });
168
276
  }
169
- for (let i = 0; i < content.length; i += chunkSize) {
170
- const slice = content.slice(i, i + chunkSize);
171
- const isLast = i + chunkSize >= content.length;
277
+ chunks.push(...geminiFileChunks(overrides));
278
+ const grounding = geminiStreamGroundingFields(content, overrides, geminiTextChunkSpans(content, chunkSize));
279
+ const contentSlices = require_helpers.surrogateSafeChunks(content, chunkSize);
280
+ contentSlices.forEach((slice, k) => {
281
+ const isLast = k === contentSlices.length - 1;
172
282
  const chunk = {
173
283
  candidates: [{
174
284
  content: {
@@ -176,12 +286,13 @@ function buildGeminiTextStreamChunks(content, chunkSize, reasoning, overrides) {
176
286
  parts: [{ text: slice }]
177
287
  },
178
288
  index: 0,
179
- ...isLast ? { finishReason: effectiveFinish } : {}
289
+ ...isLast ? { finishReason: effectiveFinish } : {},
290
+ ...grounding[k]
180
291
  }],
181
292
  ...isLast ? { usageMetadata: usage } : {}
182
293
  };
183
294
  chunks.push(chunk);
184
- }
295
+ });
185
296
  if (content.length === 0) chunks.push({
186
297
  candidates: [{
187
298
  content: {
@@ -189,6 +300,7 @@ function buildGeminiTextStreamChunks(content, chunkSize, reasoning, overrides) {
189
300
  parts: [{ text: "" }]
190
301
  },
191
302
  finishReason: effectiveFinish,
303
+ ...grounding[0],
192
304
  index: 0
193
305
  }],
194
306
  usageMetadata: usage
@@ -219,6 +331,8 @@ function buildGeminiToolCallStreamChunks(toolCalls, chunkSize, logger, reasoning
219
331
  index: 0
220
332
  }] });
221
333
  }
334
+ chunks.push(...geminiFileChunks(overrides));
335
+ require_helpers.resolveCitations("", overrides?.citations);
222
336
  const parts = toolCalls.map((tc) => parseToolCallPart(tc, logger));
223
337
  chunks.push({
224
338
  candidates: [{
@@ -239,6 +353,8 @@ function buildGeminiTextResponse(content, reasoning, overrides) {
239
353
  text: reasoning,
240
354
  thought: true
241
355
  });
356
+ parts.push(...geminiFileParts(overrides));
357
+ const textParts = geminiWholeTextPart(content, parts.length);
242
358
  parts.push({ text: content });
243
359
  return {
244
360
  candidates: [{
@@ -247,17 +363,20 @@ function buildGeminiTextResponse(content, reasoning, overrides) {
247
363
  parts
248
364
  },
249
365
  finishReason: geminiFinishReason(overrides?.finishReason, "STOP"),
366
+ ...geminiGroundingField(content, overrides, textParts),
250
367
  index: 0
251
368
  }],
252
369
  usageMetadata: geminiUsageMetadata(overrides)
253
370
  };
254
371
  }
255
372
  function buildGeminiToolCallResponse(toolCalls, logger, reasoning, overrides) {
373
+ require_helpers.resolveCitations("", overrides?.citations);
256
374
  const parts = [];
257
375
  if (reasoning) parts.push({
258
376
  text: reasoning,
259
377
  thought: true
260
378
  });
379
+ parts.push(...geminiFileParts(overrides));
261
380
  parts.push(...toolCalls.map((tc) => parseToolCallPart(tc, logger)));
262
381
  return {
263
382
  candidates: [{
@@ -286,9 +405,19 @@ function buildGeminiContentWithToolCallsStreamChunks(content, toolCalls, chunkSi
286
405
  index: 0
287
406
  }] });
288
407
  }
408
+ chunks.push(...geminiFileChunks(overrides));
289
409
  if (blocks && blocks.length > 0) {
290
410
  const outcome = require_helpers.resolveFixtureBlockOutcome(blocks);
291
411
  const resolved = outcome.ordered;
412
+ const spans = [];
413
+ let offset = 0;
414
+ for (const block of resolved) {
415
+ if (block.type !== "text") continue;
416
+ spans.push(...geminiTextChunkSpans(block.text, chunkSize, offset));
417
+ offset += block.text.length;
418
+ }
419
+ const grounding = geminiStreamGroundingFields(outcome.content, overrides, spans);
420
+ let textChunk = 0;
292
421
  resolved.forEach((block, i) => {
293
422
  const isLast = i === resolved.length - 1;
294
423
  const finishReason = isLast ? geminiFinishReason(overrides?.finishReason, outcome.hasToolCalls ? "FUNCTION_CALL" : "STOP") : void 0;
@@ -318,47 +447,53 @@ function buildGeminiContentWithToolCallsStreamChunks(content, toolCalls, chunkSi
318
447
  parts: [{ text: "" }]
319
448
  },
320
449
  ...finishReason ? { finishReason } : {},
450
+ ...grounding[textChunk++],
321
451
  index: 0
322
452
  }],
323
453
  ...isLast ? { usageMetadata: geminiUsageMetadata(overrides) } : {}
324
454
  });
325
- else for (let j = 0; j < text.length; j += chunkSize) {
326
- const slice = text.slice(j, j + chunkSize);
327
- const lastSlice = j + chunkSize >= text.length;
328
- const sliceFinish = isLast && lastSlice ? finishReason : void 0;
329
- chunks.push({
330
- candidates: [{
331
- content: {
332
- role: "model",
333
- parts: [{ text: slice }]
334
- },
335
- ...sliceFinish ? { finishReason: sliceFinish } : {},
336
- index: 0
337
- }],
338
- ...isLast && lastSlice ? { usageMetadata: geminiUsageMetadata(overrides) } : {}
455
+ else {
456
+ const slices = require_helpers.surrogateSafeChunks(text, chunkSize);
457
+ slices.forEach((slice, j) => {
458
+ const lastSlice = j === slices.length - 1;
459
+ const sliceFinish = isLast && lastSlice ? finishReason : void 0;
460
+ chunks.push({
461
+ candidates: [{
462
+ content: {
463
+ role: "model",
464
+ parts: [{ text: slice }]
465
+ },
466
+ ...sliceFinish ? { finishReason: sliceFinish } : {},
467
+ ...grounding[textChunk++],
468
+ index: 0
469
+ }],
470
+ ...isLast && lastSlice ? { usageMetadata: geminiUsageMetadata(overrides) } : {}
471
+ });
339
472
  });
340
473
  }
341
474
  }
342
475
  });
343
476
  return chunks;
344
477
  }
478
+ const grounding = geminiStreamGroundingFields(content, overrides, geminiTextChunkSpans(content, chunkSize));
345
479
  if (content.length === 0) chunks.push({ candidates: [{
346
480
  content: {
347
481
  role: "model",
348
482
  parts: [{ text: "" }]
349
483
  },
484
+ ...grounding[0],
350
485
  index: 0
351
486
  }] });
352
- else for (let i = 0; i < content.length; i += chunkSize) {
353
- const slice = content.slice(i, i + chunkSize);
487
+ else require_helpers.surrogateSafeChunks(content, chunkSize).forEach((slice, k) => {
354
488
  chunks.push({ candidates: [{
355
489
  content: {
356
490
  role: "model",
357
491
  parts: [{ text: slice }]
358
492
  },
493
+ ...grounding[k],
359
494
  index: 0
360
495
  }] });
361
- }
496
+ });
362
497
  const parts = toolCalls.map((tc) => parseToolCallPart(tc, logger));
363
498
  chunks.push({
364
499
  candidates: [{
@@ -379,18 +514,31 @@ function buildGeminiContentWithToolCallsResponse(content, toolCalls, logger, rea
379
514
  text: reasoning,
380
515
  thought: true
381
516
  });
517
+ parts.push(...geminiFileParts(overrides));
382
518
  let blockHasTools;
519
+ let groundingText = content;
520
+ const textParts = [];
383
521
  if (blocks && blocks.length > 0) {
384
522
  const outcome = require_helpers.resolveFixtureBlockOutcome(blocks);
385
523
  const resolved = outcome.ordered;
386
524
  blockHasTools = outcome.hasToolCalls;
525
+ groundingText = outcome.content;
387
526
  for (const block of resolved) if (block.type === "toolCall") parts.push(parseToolCallPart({
388
527
  name: block.name,
389
528
  arguments: block.arguments,
390
529
  id: block.id
391
530
  }, logger));
392
- else parts.push({ text: block.text });
531
+ else {
532
+ const start = textParts.length > 0 ? textParts[textParts.length - 1].end : 0;
533
+ textParts.push({
534
+ partIndex: parts.length,
535
+ start,
536
+ end: start + block.text.length
537
+ });
538
+ parts.push({ text: block.text });
539
+ }
393
540
  } else {
541
+ textParts.push(...geminiWholeTextPart(content, parts.length));
394
542
  parts.push({ text: content });
395
543
  parts.push(...toolCalls.map((tc) => parseToolCallPart(tc, logger)));
396
544
  }
@@ -401,6 +549,7 @@ function buildGeminiContentWithToolCallsResponse(content, toolCalls, logger, rea
401
549
  parts
402
550
  },
403
551
  finishReason: geminiFinishReason(overrides?.finishReason, blockHasTools === false ? "STOP" : "FUNCTION_CALL"),
552
+ ...geminiGroundingField(groundingText, overrides, textParts),
404
553
  index: 0
405
554
  }],
406
555
  usageMetadata: geminiUsageMetadata(overrides)
@@ -452,30 +601,19 @@ function buildGeminiAudioStreamChunks(audio, toolCalls, logger, effReasoning) {
452
601
  usageMetadata: require_gemini.render()
453
602
  }];
454
603
  }
455
- async function writeGeminiSSEStream(res, chunks, optionsOrLatency) {
456
- const opts = typeof optionsOrLatency === "number" ? { latency: optionsOrLatency } : optionsOrLatency ?? {};
457
- const latency = opts.latency ?? 0;
458
- const profile = opts.streamingProfile;
459
- const { recordedTimings, replaySpeed } = opts;
460
- const signal = opts.signal;
461
- const onChunkSent = opts.onChunkSent;
462
- if (res.writableEnded) return true;
463
- res.setHeader("Content-Type", "text/event-stream");
464
- res.setHeader("Cache-Control", "no-cache");
465
- res.setHeader("Connection", "keep-alive");
466
- let chunkIndex = 0;
467
- for (const chunk of chunks) {
468
- const chunkDelay = require_sse_writer.calculateDelay(chunkIndex, profile, latency, recordedTimings, replaySpeed);
469
- if (chunkDelay > 0) await require_sse_writer.delay(chunkDelay, signal);
470
- if (signal?.aborted) return false;
471
- if (res.writableEnded) return true;
472
- res.write(`data: ${JSON.stringify(chunk)}\n\n`);
473
- onChunkSent?.();
474
- if (signal?.aborted) return false;
475
- chunkIndex++;
476
- }
477
- if (!res.writableEnded) res.end();
478
- return true;
604
+ const GEMINI_SSE = require_sse_writer.sseCodec({ done: false });
605
+ const SILENT_LOGGER = new require_logger.Logger("silent");
606
+ /**
607
+ * The reasoning a misbehavior reply's non-streaming wait is sized with: the
608
+ * plan response's reasoning through the same model and strict gate the
609
+ * builders apply. The gate runs with a silent logger, so the builder's own
610
+ * warning stays the only one. A reasoning-only plan carries its reasoning on
611
+ * the plan, and `misbehaviorPayloadLength` reads it there.
612
+ */
613
+ function misbehaviorPayloadReasoning(plan, model, strict) {
614
+ const response = plan.response;
615
+ if (plan.reasoning !== void 0 || !(require_helpers.isCombinedFixtureResponse(response) || require_helpers.isTextResponse(response) || require_helpers.isToolCallResponse(response))) return;
616
+ return require_helpers.resolveReasoningForModel(response.reasoning, model, strict, SILENT_LOGGER);
479
617
  }
480
618
  /** Prepare native fault output once; the journal describes these exact parts. */
481
619
  function prepareGeminiMisbehavior(plan, request, streaming, chunkSize, logger, strict) {
@@ -508,7 +646,11 @@ function prepareGeminiMisbehavior(plan, request, streaming, chunkSize, logger, s
508
646
  candidate.finishReason = "UNEXPECTED_TOOL_CALL";
509
647
  chunks = [{ candidates: [candidate] }];
510
648
  } else if (fault === "stop-length-mid-tool") {
511
- candidate.content.parts = content ? [{ text: content }] : [];
649
+ const parts = geminiFileParts(overrides);
650
+ const textPartIndex = parts.length;
651
+ if (content) parts.push({ text: content });
652
+ candidate.content.parts = parts;
653
+ if (content) Object.assign(candidate, geminiGroundingField(content, overrides, geminiWholeTextPart(content, textPartIndex)));
512
654
  candidate.finishReason = "MAX_TOKENS";
513
655
  chunks = [{ candidates: [candidate] }];
514
656
  } else if (fault === "empty-response") {
@@ -626,7 +768,10 @@ async function handleGemini(req, res, raw, model, streaming, fixtures, journal,
626
768
  completionReq._endpointType = "chat";
627
769
  completionReq._context = require_helpers.getContext(req);
628
770
  const testId = require_helpers.getTestId(req);
629
- const { fixture, skippedBySequenceOrTurn } = require_router.matchFixtureDiagnostic(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform, require_router.recordMatchOptions(!!defaults.record, defaults.logger));
771
+ const { fixture, skippedBySequenceOrTurn } = require_router.matchFixtureDiagnostic(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform, {
772
+ ...require_router.recordMatchOptions(!!defaults.record, defaults.logger),
773
+ wire: "gemini"
774
+ });
630
775
  const path = req.url ?? `/v1beta/models/${model}:generateContent`;
631
776
  if (fixture) logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);
632
777
  else logger.debug(`No fixture matched for request`);
@@ -661,7 +806,7 @@ async function handleGemini(req, res, raw, model, streaming, fixtures, journal,
661
806
  return;
662
807
  }
663
808
  if (decisions.upstream && defaults.record) {
664
- const outcome = await require_recorder.proxyAndRecord(req, res, completionReq, providerKey, path, fixtures, defaults, raw);
809
+ const { outcome, clientHungUp } = await require_proxy.withHangUpDetection(req, res, () => require_recorder.proxyAndRecord(req, res, completionReq, providerKey, path, fixtures, defaults, raw));
665
810
  if (outcome === "handled_by_hook") return;
666
811
  if (outcome !== "not_configured") {
667
812
  journal.add({
@@ -672,7 +817,8 @@ async function handleGemini(req, res, raw, model, streaming, fixtures, journal,
672
817
  response: {
673
818
  status: res.statusCode ?? 200,
674
819
  fixture: null,
675
- source: "proxy"
820
+ source: "proxy",
821
+ ...require_proxy.proxyInterruption(res, clientHungUp)
676
822
  }
677
823
  });
678
824
  return;
@@ -736,8 +882,21 @@ async function handleGemini(req, res, raw, model, streaming, fixtures, journal,
736
882
  const latency = fixture.latency ?? defaults.latency;
737
883
  const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
738
884
  const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;
885
+ const streamTiming = {
886
+ latency,
887
+ streamingProfile: fixture.streamingProfile,
888
+ recordedTimings: fixture.recordedTimings,
889
+ replaySpeed
890
+ };
891
+ const jsonOpts = (payloadLength, duplicateId = false) => ({
892
+ surface: "other",
893
+ payloadLength,
894
+ chunkSize,
895
+ recordedTimings: fixture.recordedTimings,
896
+ duplicateId,
897
+ replaySpeed
898
+ });
739
899
  if (misbehavior.kind === "applied") {
740
- const prepared = prepareGeminiMisbehavior(misbehavior, completionReq, streaming, chunkSize, logger, require_policy.resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression);
741
900
  const entry = journal.add({
742
901
  method: req.method ?? "POST",
743
902
  path,
@@ -748,6 +907,8 @@ async function handleGemini(req, res, raw, model, streaming, fixtures, journal,
748
907
  fixture
749
908
  }
750
909
  });
910
+ const strictReasoning = require_policy.resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression;
911
+ const prepared = prepareGeminiMisbehavior(misbehavior, completionReq, streaming, chunkSize, logger, strictReasoning);
751
912
  require_misbehavior.recordMisbehaviorOutcome({
752
913
  entry,
753
914
  summary: prepared.summary,
@@ -755,24 +916,26 @@ async function handleGemini(req, res, raw, model, streaming, fixtures, journal,
755
916
  testId
756
917
  });
757
918
  if (!streaming) {
758
- res.writeHead(200, { "Content-Type": "application/json" });
759
- res.end(JSON.stringify(prepared.chunks[0]));
760
- } else {
761
- const interruption = require_interruption.createInterruptionSignal(fixture);
762
- if (!await writeGeminiSSEStream(res, prepared.chunks, {
763
- latency,
764
- streamingProfile: fixture.streamingProfile,
765
- recordedTimings: fixture.recordedTimings,
766
- replaySpeed,
767
- signal: interruption?.signal,
768
- onChunkSent: interruption?.tick
769
- })) {
770
- if (!res.writableEnded) res.destroy();
771
- entry.response.interrupted = true;
772
- entry.response.interruptReason = interruption?.reason();
773
- }
774
- interruption?.cleanup();
775
- }
919
+ const preparedBody = prepared.chunks[0];
920
+ await require_deliver.deliverJson(res, entry, fixture, () => preparedBody, jsonOpts(require_timing.misbehaviorPayloadLength(misbehavior, misbehaviorPayloadReasoning(misbehavior, completionReq.model, strictReasoning)), !!misbehavior.duplicateId));
921
+ } else await require_deliver.deliverStream(res, entry, fixture, GEMINI_SSE, prepared.chunks, streamTiming);
922
+ return;
923
+ }
924
+ if (require_helpers.isRawResponse(response)) {
925
+ await require_raw.serveRawResponse({
926
+ wire: "gemini",
927
+ stream: streaming,
928
+ req,
929
+ res,
930
+ fixture,
931
+ response,
932
+ body: completionReq,
933
+ journal,
934
+ defaults,
935
+ testId,
936
+ misbehaviorSummary: misbehavior.summary,
937
+ errorWire: "gemini"
938
+ });
776
939
  return;
777
940
  }
778
941
  if (require_helpers.isErrorResponse(response)) {
@@ -819,27 +982,8 @@ async function handleGemini(req, res, raw, model, streaming, fixtures, journal,
819
982
  testId
820
983
  });
821
984
  const audioToolCalls = Array.isArray(response.toolCalls) ? require_helpers.requireServedFunctionToolCalls(response, wire) : [];
822
- if (!streaming) {
823
- const body = buildGeminiAudioResponse(response, audioToolCalls, logger, effReasoning);
824
- res.writeHead(200, { "Content-Type": "application/json" });
825
- res.end(JSON.stringify(body));
826
- } else {
827
- const chunks = buildGeminiAudioStreamChunks(response, audioToolCalls, logger, effReasoning);
828
- const interruption = require_interruption.createInterruptionSignal(fixture);
829
- if (!await writeGeminiSSEStream(res, chunks, {
830
- latency,
831
- streamingProfile: fixture.streamingProfile,
832
- recordedTimings: fixture.recordedTimings,
833
- replaySpeed,
834
- signal: interruption?.signal,
835
- onChunkSent: interruption?.tick
836
- })) {
837
- if (!res.writableEnded) res.destroy();
838
- journalEntry.response.interrupted = true;
839
- journalEntry.response.interruptReason = interruption?.reason();
840
- }
841
- interruption?.cleanup();
842
- }
985
+ if (!streaming) await require_deliver.deliverJson(res, journalEntry, fixture, () => buildGeminiAudioResponse(response, audioToolCalls, logger, effReasoning), jsonOpts(require_timing.servedPayloadLength(response, effReasoning)));
986
+ else await require_deliver.deliverStream(res, journalEntry, fixture, GEMINI_SSE, buildGeminiAudioStreamChunks(response, audioToolCalls, logger, effReasoning), streamTiming);
843
987
  return;
844
988
  }
845
989
  if (require_helpers.isCombinedFixtureResponse(response)) {
@@ -864,27 +1008,8 @@ async function handleGemini(req, res, raw, model, streaming, fixtures, journal,
864
1008
  testId
865
1009
  });
866
1010
  const functionToolCalls = require_helpers.requireEmittedFunctionToolCalls(response, wire);
867
- if (!streaming) {
868
- const body = buildGeminiContentWithToolCallsResponse(response.content ?? "", functionToolCalls, logger, effReasoning, overrides, response.blocks);
869
- res.writeHead(200, { "Content-Type": "application/json" });
870
- res.end(JSON.stringify(body));
871
- } else {
872
- const chunks = buildGeminiContentWithToolCallsStreamChunks(response.content ?? "", functionToolCalls, chunkSize, logger, effReasoning, overrides, response.blocks);
873
- const interruption = require_interruption.createInterruptionSignal(fixture);
874
- if (!await writeGeminiSSEStream(res, chunks, {
875
- latency,
876
- streamingProfile: fixture.streamingProfile,
877
- recordedTimings: fixture.recordedTimings,
878
- replaySpeed,
879
- signal: interruption?.signal,
880
- onChunkSent: interruption?.tick
881
- })) {
882
- if (!res.writableEnded) res.destroy();
883
- journalEntry.response.interrupted = true;
884
- journalEntry.response.interruptReason = interruption?.reason();
885
- }
886
- interruption?.cleanup();
887
- }
1011
+ if (!streaming) await require_deliver.deliverJson(res, journalEntry, fixture, () => buildGeminiContentWithToolCallsResponse(response.content ?? "", functionToolCalls, logger, effReasoning, overrides, response.blocks), jsonOpts(require_timing.servedPayloadLength(response, effReasoning)));
1012
+ else await require_deliver.deliverStream(res, journalEntry, fixture, GEMINI_SSE, buildGeminiContentWithToolCallsStreamChunks(response.content ?? "", functionToolCalls, chunkSize, logger, effReasoning, overrides, response.blocks), streamTiming);
888
1013
  return;
889
1014
  }
890
1015
  if (require_helpers.isTextResponse(response)) {
@@ -908,27 +1033,8 @@ async function handleGemini(req, res, raw, model, streaming, fixtures, journal,
908
1033
  defaults,
909
1034
  testId
910
1035
  });
911
- if (!streaming) {
912
- const body = buildGeminiTextResponse(response.content, effReasoning, overrides);
913
- res.writeHead(200, { "Content-Type": "application/json" });
914
- res.end(JSON.stringify(body));
915
- } else {
916
- const chunks = buildGeminiTextStreamChunks(response.content, chunkSize, effReasoning, overrides);
917
- const interruption = require_interruption.createInterruptionSignal(fixture);
918
- if (!await writeGeminiSSEStream(res, chunks, {
919
- latency,
920
- streamingProfile: fixture.streamingProfile,
921
- recordedTimings: fixture.recordedTimings,
922
- replaySpeed,
923
- signal: interruption?.signal,
924
- onChunkSent: interruption?.tick
925
- })) {
926
- if (!res.writableEnded) res.destroy();
927
- journalEntry.response.interrupted = true;
928
- journalEntry.response.interruptReason = interruption?.reason();
929
- }
930
- interruption?.cleanup();
931
- }
1036
+ if (!streaming) await require_deliver.deliverJson(res, journalEntry, fixture, () => buildGeminiTextResponse(response.content, effReasoning, overrides), jsonOpts(require_timing.servedPayloadLength(response, effReasoning)));
1037
+ else await require_deliver.deliverStream(res, journalEntry, fixture, GEMINI_SSE, buildGeminiTextStreamChunks(response.content, chunkSize, effReasoning, overrides), streamTiming);
932
1038
  return;
933
1039
  }
934
1040
  if (require_helpers.isToolCallResponse(response)) {
@@ -953,27 +1059,8 @@ async function handleGemini(req, res, raw, model, streaming, fixtures, journal,
953
1059
  testId
954
1060
  });
955
1061
  const functionToolCalls = require_helpers.requireEmittedFunctionToolCalls(response, wire);
956
- if (!streaming) {
957
- const body = buildGeminiToolCallResponse(functionToolCalls, logger, effReasoning, overrides);
958
- res.writeHead(200, { "Content-Type": "application/json" });
959
- res.end(JSON.stringify(body));
960
- } else {
961
- const chunks = buildGeminiToolCallStreamChunks(functionToolCalls, chunkSize, logger, effReasoning, overrides);
962
- const interruption = require_interruption.createInterruptionSignal(fixture);
963
- if (!await writeGeminiSSEStream(res, chunks, {
964
- latency,
965
- streamingProfile: fixture.streamingProfile,
966
- recordedTimings: fixture.recordedTimings,
967
- replaySpeed,
968
- signal: interruption?.signal,
969
- onChunkSent: interruption?.tick
970
- })) {
971
- if (!res.writableEnded) res.destroy();
972
- journalEntry.response.interrupted = true;
973
- journalEntry.response.interruptReason = interruption?.reason();
974
- }
975
- interruption?.cleanup();
976
- }
1062
+ if (!streaming) await require_deliver.deliverJson(res, journalEntry, fixture, () => buildGeminiToolCallResponse(functionToolCalls, logger, effReasoning, overrides), jsonOpts(require_timing.servedPayloadLength(response, effReasoning)));
1063
+ else await require_deliver.deliverStream(res, journalEntry, fixture, GEMINI_SSE, buildGeminiToolCallStreamChunks(functionToolCalls, chunkSize, logger, effReasoning, overrides), streamTiming);
977
1064
  return;
978
1065
  }
979
1066
  require_misbehavior.recordMisbehaviorOutcome({