@copilotkit/aimock 1.44.0 → 1.45.1

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 (278) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +42 -0
  4. package/README.md +6 -0
  5. package/dist/agui-types.d.cts.map +1 -1
  6. package/dist/aimock-cli.cjs +31 -5
  7. package/dist/aimock-cli.cjs.map +1 -1
  8. package/dist/aimock-cli.d.cts.map +1 -1
  9. package/dist/aimock-cli.d.ts.map +1 -1
  10. package/dist/aimock-cli.js +31 -5
  11. package/dist/aimock-cli.js.map +1 -1
  12. package/dist/bedrock-converse.cjs +312 -39
  13. package/dist/bedrock-converse.cjs.map +1 -1
  14. package/dist/bedrock-converse.d.cts.map +1 -1
  15. package/dist/bedrock-converse.d.ts.map +1 -1
  16. package/dist/bedrock-converse.js +313 -40
  17. package/dist/bedrock-converse.js.map +1 -1
  18. package/dist/bedrock.cjs +195 -42
  19. package/dist/bedrock.cjs.map +1 -1
  20. package/dist/bedrock.d.cts.map +1 -1
  21. package/dist/bedrock.d.ts.map +1 -1
  22. package/dist/bedrock.js +196 -43
  23. package/dist/bedrock.js.map +1 -1
  24. package/dist/chaos.cjs +1 -0
  25. package/dist/chaos.js +1 -1
  26. package/dist/cli.cjs +152 -5
  27. package/dist/cli.cjs.map +1 -1
  28. package/dist/cli.js +155 -8
  29. package/dist/cli.js.map +1 -1
  30. package/dist/cohere.cjs +132 -71
  31. package/dist/cohere.cjs.map +1 -1
  32. package/dist/cohere.d.cts.map +1 -1
  33. package/dist/cohere.d.ts.map +1 -1
  34. package/dist/cohere.js +133 -72
  35. package/dist/cohere.js.map +1 -1
  36. package/dist/config-loader.cjs +62 -4
  37. package/dist/config-loader.cjs.map +1 -1
  38. package/dist/config-loader.d.cts +37 -4
  39. package/dist/config-loader.d.cts.map +1 -1
  40. package/dist/config-loader.d.ts +37 -4
  41. package/dist/config-loader.d.ts.map +1 -1
  42. package/dist/config-loader.js +62 -4
  43. package/dist/config-loader.js.map +1 -1
  44. package/dist/fakes-plugin.cjs +131 -0
  45. package/dist/fakes-plugin.cjs.map +1 -0
  46. package/dist/fakes-plugin.d.cts +23 -0
  47. package/dist/fakes-plugin.d.cts.map +1 -0
  48. package/dist/fakes-plugin.d.ts +23 -0
  49. package/dist/fakes-plugin.d.ts.map +1 -0
  50. package/dist/fakes-plugin.js +127 -0
  51. package/dist/fakes-plugin.js.map +1 -0
  52. package/dist/fixture-loader-services.cjs +1 -1
  53. package/dist/fixture-loader-services.cjs.map +1 -1
  54. package/dist/fixture-loader-services.js +1 -1
  55. package/dist/fixture-loader-services.js.map +1 -1
  56. package/dist/fixture-loader.cjs +389 -27
  57. package/dist/fixture-loader.cjs.map +1 -1
  58. package/dist/fixture-loader.d.cts +47 -3
  59. package/dist/fixture-loader.d.cts.map +1 -1
  60. package/dist/fixture-loader.d.ts +47 -3
  61. package/dist/fixture-loader.d.ts.map +1 -1
  62. package/dist/fixture-loader.js +384 -29
  63. package/dist/fixture-loader.js.map +1 -1
  64. package/dist/gemini-interactions.cjs +250 -52
  65. package/dist/gemini-interactions.cjs.map +1 -1
  66. package/dist/gemini-interactions.d.cts.map +1 -1
  67. package/dist/gemini-interactions.d.ts.map +1 -1
  68. package/dist/gemini-interactions.js +251 -53
  69. package/dist/gemini-interactions.js.map +1 -1
  70. package/dist/gemini.cjs +226 -37
  71. package/dist/gemini.cjs.map +1 -1
  72. package/dist/gemini.d.cts.map +1 -1
  73. package/dist/gemini.d.ts.map +1 -1
  74. package/dist/gemini.js +227 -38
  75. package/dist/gemini.js.map +1 -1
  76. package/dist/helpers.cjs +881 -7
  77. package/dist/helpers.cjs.map +1 -1
  78. package/dist/helpers.d.cts +85 -4
  79. package/dist/helpers.d.cts.map +1 -1
  80. package/dist/helpers.d.ts +85 -4
  81. package/dist/helpers.d.ts.map +1 -1
  82. package/dist/helpers.js +839 -8
  83. package/dist/helpers.js.map +1 -1
  84. package/dist/index.cjs +9 -0
  85. package/dist/index.d.cts +7 -5
  86. package/dist/index.d.ts +7 -5
  87. package/dist/index.js +4 -3
  88. package/dist/jest.cjs +31 -7
  89. package/dist/jest.cjs.map +1 -1
  90. package/dist/jest.d.cts +43 -3
  91. package/dist/jest.d.cts.map +1 -1
  92. package/dist/jest.d.ts +43 -3
  93. package/dist/jest.d.ts.map +1 -1
  94. package/dist/jest.js +33 -9
  95. package/dist/jest.js.map +1 -1
  96. package/dist/journal.cjs +135 -13
  97. package/dist/journal.cjs.map +1 -1
  98. package/dist/journal.d.cts +38 -4
  99. package/dist/journal.d.cts.map +1 -1
  100. package/dist/journal.d.ts +38 -4
  101. package/dist/journal.d.ts.map +1 -1
  102. package/dist/journal.js +135 -13
  103. package/dist/journal.js.map +1 -1
  104. package/dist/live-sanitize.cjs +4 -32
  105. package/dist/live-sanitize.cjs.map +1 -1
  106. package/dist/live-sanitize.js +4 -32
  107. package/dist/live-sanitize.js.map +1 -1
  108. package/dist/llmock.cjs +123 -30
  109. package/dist/llmock.cjs.map +1 -1
  110. package/dist/llmock.d.cts +16 -1
  111. package/dist/llmock.d.cts.map +1 -1
  112. package/dist/llmock.d.ts +16 -1
  113. package/dist/llmock.d.ts.map +1 -1
  114. package/dist/llmock.js +125 -32
  115. package/dist/llmock.js.map +1 -1
  116. package/dist/mcp-fakes-mount.cjs +1 -0
  117. package/dist/mcp-fakes-mount.cjs.map +1 -1
  118. package/dist/mcp-fakes-mount.js +1 -0
  119. package/dist/mcp-fakes-mount.js.map +1 -1
  120. package/dist/mcp-fakes-report.cjs +111 -0
  121. package/dist/mcp-fakes-report.cjs.map +1 -0
  122. package/dist/mcp-fakes-report.d.cts +40 -0
  123. package/dist/mcp-fakes-report.d.cts.map +1 -0
  124. package/dist/mcp-fakes-report.d.ts +40 -0
  125. package/dist/mcp-fakes-report.d.ts.map +1 -0
  126. package/dist/mcp-fakes-report.js +107 -0
  127. package/dist/mcp-fakes-report.js.map +1 -0
  128. package/dist/mcp-fakes.cjs +331 -8
  129. package/dist/mcp-fakes.cjs.map +1 -1
  130. package/dist/mcp-fakes.d.cts +406 -3
  131. package/dist/mcp-fakes.d.cts.map +1 -1
  132. package/dist/mcp-fakes.d.ts +406 -3
  133. package/dist/mcp-fakes.d.ts.map +1 -1
  134. package/dist/mcp-fakes.js +331 -8
  135. package/dist/mcp-fakes.js.map +1 -1
  136. package/dist/mcp-handler.cjs +216 -17
  137. package/dist/mcp-handler.cjs.map +1 -1
  138. package/dist/mcp-handler.d.cts +49 -0
  139. package/dist/mcp-handler.d.cts.map +1 -0
  140. package/dist/mcp-handler.d.ts +49 -0
  141. package/dist/mcp-handler.d.ts.map +1 -0
  142. package/dist/mcp-handler.js +214 -18
  143. package/dist/mcp-handler.js.map +1 -1
  144. package/dist/mcp-mock.cjs +112 -1
  145. package/dist/mcp-mock.cjs.map +1 -1
  146. package/dist/mcp-mock.d.cts +33 -2
  147. package/dist/mcp-mock.d.cts.map +1 -1
  148. package/dist/mcp-mock.d.ts +33 -2
  149. package/dist/mcp-mock.d.ts.map +1 -1
  150. package/dist/mcp-mock.js +112 -1
  151. package/dist/mcp-mock.js.map +1 -1
  152. package/dist/mcp-recorder.cjs +1224 -0
  153. package/dist/mcp-recorder.cjs.map +1 -0
  154. package/dist/mcp-recorder.d.cts +108 -0
  155. package/dist/mcp-recorder.d.cts.map +1 -0
  156. package/dist/mcp-recorder.d.ts +108 -0
  157. package/dist/mcp-recorder.d.ts.map +1 -0
  158. package/dist/mcp-recorder.js +1215 -0
  159. package/dist/mcp-recorder.js.map +1 -0
  160. package/dist/messages.cjs +362 -56
  161. package/dist/messages.cjs.map +1 -1
  162. package/dist/messages.d.cts.map +1 -1
  163. package/dist/messages.d.ts.map +1 -1
  164. package/dist/messages.js +363 -58
  165. package/dist/messages.js.map +1 -1
  166. package/dist/misbehavior.cjs +1523 -0
  167. package/dist/misbehavior.cjs.map +1 -0
  168. package/dist/misbehavior.d.cts +43 -0
  169. package/dist/misbehavior.d.cts.map +1 -0
  170. package/dist/misbehavior.d.ts +43 -0
  171. package/dist/misbehavior.d.ts.map +1 -0
  172. package/dist/misbehavior.js +1511 -0
  173. package/dist/misbehavior.js.map +1 -0
  174. package/dist/ollama.cjs +310 -112
  175. package/dist/ollama.cjs.map +1 -1
  176. package/dist/ollama.d.cts.map +1 -1
  177. package/dist/ollama.d.ts.map +1 -1
  178. package/dist/ollama.js +311 -113
  179. package/dist/ollama.js.map +1 -1
  180. package/dist/openrouter-video.cjs +30 -2
  181. package/dist/openrouter-video.cjs.map +1 -1
  182. package/dist/openrouter-video.d.cts.map +1 -1
  183. package/dist/openrouter-video.d.ts.map +1 -1
  184. package/dist/openrouter-video.js +30 -2
  185. package/dist/openrouter-video.js.map +1 -1
  186. package/dist/record-sanitize.cjs +231 -0
  187. package/dist/record-sanitize.cjs.map +1 -0
  188. package/dist/record-sanitize.js +224 -0
  189. package/dist/record-sanitize.js.map +1 -0
  190. package/dist/recorder.cjs +106 -5
  191. package/dist/recorder.cjs.map +1 -1
  192. package/dist/recorder.d.cts +2 -1
  193. package/dist/recorder.d.cts.map +1 -1
  194. package/dist/recorder.d.ts +2 -1
  195. package/dist/recorder.d.ts.map +1 -1
  196. package/dist/recorder.js +107 -7
  197. package/dist/recorder.js.map +1 -1
  198. package/dist/responses.cjs +667 -171
  199. package/dist/responses.cjs.map +1 -1
  200. package/dist/responses.d.cts +5 -13
  201. package/dist/responses.d.cts.map +1 -1
  202. package/dist/responses.d.ts +5 -13
  203. package/dist/responses.d.ts.map +1 -1
  204. package/dist/responses.js +665 -174
  205. package/dist/responses.js.map +1 -1
  206. package/dist/router.cjs +24 -2
  207. package/dist/router.cjs.map +1 -1
  208. package/dist/router.d.cts.map +1 -1
  209. package/dist/router.d.ts.map +1 -1
  210. package/dist/router.js +25 -4
  211. package/dist/router.js.map +1 -1
  212. package/dist/server.cjs +572 -76
  213. package/dist/server.cjs.map +1 -1
  214. package/dist/server.d.cts.map +1 -1
  215. package/dist/server.d.ts.map +1 -1
  216. package/dist/server.js +574 -78
  217. package/dist/server.js.map +1 -1
  218. package/dist/stream-collapse.cjs +236 -34
  219. package/dist/stream-collapse.cjs.map +1 -1
  220. package/dist/stream-collapse.d.cts +39 -4
  221. package/dist/stream-collapse.d.cts.map +1 -1
  222. package/dist/stream-collapse.d.ts +39 -4
  223. package/dist/stream-collapse.d.ts.map +1 -1
  224. package/dist/stream-collapse.js +235 -35
  225. package/dist/stream-collapse.js.map +1 -1
  226. package/dist/types.cjs.map +1 -1
  227. package/dist/types.d.cts +315 -4
  228. package/dist/types.d.cts.map +1 -1
  229. package/dist/types.d.ts +315 -4
  230. package/dist/types.d.ts.map +1 -1
  231. package/dist/types.js.map +1 -1
  232. package/dist/validate-cli.cjs +36 -4
  233. package/dist/validate-cli.cjs.map +1 -1
  234. package/dist/validate-cli.js +37 -5
  235. package/dist/validate-cli.js.map +1 -1
  236. package/dist/vector-types.d.ts.map +1 -1
  237. package/dist/version.cjs +45 -0
  238. package/dist/version.cjs.map +1 -0
  239. package/dist/version.js +44 -0
  240. package/dist/version.js.map +1 -0
  241. package/dist/vitest.cjs +66 -10
  242. package/dist/vitest.cjs.map +1 -1
  243. package/dist/vitest.d.cts +35 -4
  244. package/dist/vitest.d.cts.map +1 -1
  245. package/dist/vitest.d.ts +35 -4
  246. package/dist/vitest.d.ts.map +1 -1
  247. package/dist/vitest.js +67 -11
  248. package/dist/vitest.js.map +1 -1
  249. package/dist/watcher.cjs +6 -2
  250. package/dist/watcher.cjs.map +1 -1
  251. package/dist/watcher.js +6 -2
  252. package/dist/watcher.js.map +1 -1
  253. package/dist/ws-gemini-live.cjs +210 -101
  254. package/dist/ws-gemini-live.cjs.map +1 -1
  255. package/dist/ws-gemini-live.d.cts +9 -3
  256. package/dist/ws-gemini-live.d.cts.map +1 -1
  257. package/dist/ws-gemini-live.d.ts +9 -3
  258. package/dist/ws-gemini-live.d.ts.map +1 -1
  259. package/dist/ws-gemini-live.js +211 -102
  260. package/dist/ws-gemini-live.js.map +1 -1
  261. package/dist/ws-realtime.cjs +175 -62
  262. package/dist/ws-realtime.cjs.map +1 -1
  263. package/dist/ws-realtime.d.cts +8 -3
  264. package/dist/ws-realtime.d.cts.map +1 -1
  265. package/dist/ws-realtime.d.ts +8 -3
  266. package/dist/ws-realtime.d.ts.map +1 -1
  267. package/dist/ws-realtime.js +176 -63
  268. package/dist/ws-realtime.js.map +1 -1
  269. package/dist/ws-responses.cjs +109 -16
  270. package/dist/ws-responses.cjs.map +1 -1
  271. package/dist/ws-responses.d.cts +8 -12
  272. package/dist/ws-responses.d.cts.map +1 -1
  273. package/dist/ws-responses.d.ts +8 -12
  274. package/dist/ws-responses.d.ts.map +1 -1
  275. package/dist/ws-responses.js +111 -18
  276. package/dist/ws-responses.js.map +1 -1
  277. package/package.json +10 -2
  278. package/skills/write-fixtures/SKILL.md +179 -21
@@ -1,6 +1,7 @@
1
- import { extractOverrides, flattenHeaders, generateToolCallId, getContext, getTestId, isContentWithToolCallsResponse, isErrorResponse, isJsonObject, isTextResponse, isToolCallResponse, resolveFixtureBlockOutcome, resolveResponse, resolveStrictMode, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField } from "./helpers.js";
1
+ import { extractOverrides, flattenHeaders, generateToolCallId, getContext, getTestId, isCombinedFixtureResponse, isErrorResponse, isJsonObject, isTextResponse, isToolCallResponse, prepareOpenAIChatMisbehavior, requireEmittedFunctionToolCalls, resolveFixtureBlockOutcome, resolveOpenAIChatMisbehaviorUsage, resolveResponse, resolveStrictMode, servedToolArgs, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField } from "./helpers.js";
2
2
  import { calculateDelay, delay, writeErrorResponse } from "./sse-writer.js";
3
3
  import { applyChaosAsync } from "./chaos.js";
4
+ import { planMisbehavior, recordMisbehaviorOutcome } from "./misbehavior.js";
4
5
  import { matchFixtureDiagnostic } from "./router.js";
5
6
  import { proxyAndRecord } from "./recorder.js";
6
7
  import { createInterruptionSignal } from "./interruption.js";
@@ -215,18 +216,12 @@ function buildInteractionsTextResponse(content, model, interactionId, overrides)
215
216
  };
216
217
  }
217
218
  function buildFunctionCallStep(tc, logger) {
218
- let argsObj;
219
- try {
220
- argsObj = JSON.parse(tc.arguments || "{}");
221
- } catch {
222
- logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
223
- argsObj = {};
224
- }
219
+ const args = servedToolArgs(tc, "object", logger);
225
220
  return {
226
221
  type: "function_call",
227
222
  id: tc.id || generateToolCallId(),
228
223
  name: tc.name,
229
- arguments: argsObj
224
+ arguments: args.value
230
225
  };
231
226
  }
232
227
  function buildInteractionsToolCallResponse(toolCalls, model, interactionId, logger, overrides) {
@@ -360,13 +355,7 @@ function buildInteractionsToolCallSSEEvents(toolCalls, interactionId, logger, ov
360
355
  });
361
356
  for (let idx = 0; idx < toolCalls.length; idx++) {
362
357
  const tc = toolCalls[idx];
363
- let argsObj;
364
- try {
365
- argsObj = JSON.parse(tc.arguments || "{}");
366
- } catch {
367
- logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
368
- argsObj = {};
369
- }
358
+ const args = servedToolArgs(tc, "string", logger);
370
359
  events.push({
371
360
  event_type: "step.start",
372
361
  index: idx,
@@ -383,7 +372,7 @@ function buildInteractionsToolCallSSEEvents(toolCalls, interactionId, logger, ov
383
372
  index: idx,
384
373
  delta: {
385
374
  type: "arguments_delta",
386
- arguments: JSON.stringify(argsObj)
375
+ arguments: args.text
387
376
  },
388
377
  event_id: nextEventId()
389
378
  });
@@ -439,13 +428,7 @@ function pushTextStepEvents(events, index, content, chunkSize) {
439
428
  });
440
429
  }
441
430
  function pushFunctionCallStepEvents(events, index, tc, logger) {
442
- let argsObj;
443
- try {
444
- argsObj = JSON.parse(tc.arguments || "{}");
445
- } catch {
446
- logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
447
- argsObj = {};
448
- }
431
+ const args = servedToolArgs(tc, "string", logger);
449
432
  events.push({
450
433
  event_type: "step.start",
451
434
  index,
@@ -462,7 +445,7 @@ function pushFunctionCallStepEvents(events, index, tc, logger) {
462
445
  index,
463
446
  delta: {
464
447
  type: "arguments_delta",
465
- arguments: JSON.stringify(argsObj)
448
+ arguments: args.text
466
449
  },
467
450
  event_id: nextEventId()
468
451
  });
@@ -539,6 +522,35 @@ async function writeGeminiInteractionsSSEStream(res, events, optionsOrLatency) {
539
522
  if (!res.writableEnded) res.end();
540
523
  return true;
541
524
  }
525
+ function prepareInteractionsMisbehavior(plan, streaming) {
526
+ if (!streaming && plan.summary.fault === "stop-length-mid-tool") {
527
+ const response = plan.response;
528
+ const combined = isCombinedFixtureResponse(response);
529
+ const outcome = combined && response.blocks?.length ? resolveFixtureBlockOutcome(response.blocks) : void 0;
530
+ const toolCalls = (outcome?.toolCalls ?? (combined || isToolCallResponse(response) ? response.toolCalls ?? [] : [])).filter((_, index) => index !== plan.target?.index);
531
+ let callIndex = 0;
532
+ const blocks = outcome?.ordered.filter((block) => block.type === "text" || callIndex++ !== plan.target?.index);
533
+ plan = {
534
+ ...plan,
535
+ response: {
536
+ ...response,
537
+ toolCalls,
538
+ ...blocks ? { blocks } : {}
539
+ }
540
+ };
541
+ }
542
+ return prepareOpenAIChatMisbehavior(plan);
543
+ }
544
+ function finalizeInteractionsMisbehavior(events, plan) {
545
+ if (plan.kind !== "applied" || plan.summary.fault !== "stop-length-mid-tool") return events;
546
+ return events.map((event) => event.event_type === "interaction.completed" && isJsonObject(event.interaction) ? {
547
+ ...event,
548
+ interaction: {
549
+ ...event.interaction,
550
+ status: "incomplete"
551
+ }
552
+ } : event);
553
+ }
542
554
  async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaults, setCorsHeaders) {
543
555
  const { logger } = defaults;
544
556
  setCorsHeaders(res);
@@ -655,29 +667,172 @@ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaul
655
667
  writeErrorResponse(res, 404, JSON.stringify(buildInteractionsErrorResponse("No fixture matched", "NOT_FOUND")));
656
668
  return;
657
669
  }
658
- const response = await resolveResponse(fixture, completionReq);
670
+ let response = await resolveResponse(fixture, completionReq);
671
+ let misbehavior = planMisbehavior({
672
+ wire: "gemini-interactions",
673
+ fixture,
674
+ response,
675
+ request: completionReq,
676
+ stream: streaming,
677
+ emitsToolCallIds: true,
678
+ defaults,
679
+ rawHeaders: req.headers,
680
+ url: req.url
681
+ });
682
+ if (misbehavior.kind === "error") {
683
+ recordMisbehaviorOutcome({
684
+ entry: journal.add({
685
+ method: req.method ?? "POST",
686
+ path: urlPath,
687
+ headers: flattenHeaders(req.headers),
688
+ body: completionReq,
689
+ response: {
690
+ status: misbehavior.status,
691
+ fixture
692
+ }
693
+ }),
694
+ summary: misbehavior.summary,
695
+ defaults,
696
+ testId
697
+ });
698
+ if (!misbehavior.summary?.evaluations.some((evaluation) => evaluation.outcome === "error")) logger.error(`${req.method ?? "POST"} ${urlPath}: ${misbehavior.message}`);
699
+ writeErrorResponse(res, misbehavior.status, JSON.stringify(buildInteractionsErrorResponse(misbehavior.message, misbehavior.code)));
700
+ return;
701
+ }
702
+ if (misbehavior.kind === "applied") {
703
+ misbehavior = prepareInteractionsMisbehavior(misbehavior, streaming);
704
+ response = misbehavior.response;
705
+ }
706
+ const appliedUsage = misbehavior.kind === "applied" ? resolveOpenAIChatMisbehaviorUsage(misbehavior, completionReq) : void 0;
659
707
  const latency = fixture.latency ?? defaults.latency;
660
708
  const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
661
709
  const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;
662
710
  if (isErrorResponse(response)) {
663
711
  const status = response.status ?? 500;
664
- journal.add({
712
+ recordMisbehaviorOutcome({
713
+ entry: journal.add({
714
+ method: req.method ?? "POST",
715
+ path: urlPath,
716
+ headers: flattenHeaders(req.headers),
717
+ body: completionReq,
718
+ response: {
719
+ status,
720
+ fixture
721
+ }
722
+ }),
723
+ summary: misbehavior.summary,
724
+ defaults,
725
+ testId
726
+ });
727
+ writeErrorResponse(res, status, JSON.stringify(buildInteractionsErrorResponse(response.error.message, response.error.type ?? "ERROR")), { retryAfter: response.retryAfter });
728
+ return;
729
+ }
730
+ if (misbehavior.kind === "applied" && misbehavior.summary.fault === "reasoning-only") {
731
+ const journalEntry = journal.add({
665
732
  method: req.method ?? "POST",
666
733
  path: urlPath,
667
734
  headers: flattenHeaders(req.headers),
668
735
  body: completionReq,
669
736
  response: {
670
- status,
737
+ status: 200,
671
738
  fixture
672
739
  }
673
740
  });
674
- writeErrorResponse(res, status, JSON.stringify(buildInteractionsErrorResponse(response.error.message, response.error.type ?? "ERROR")), { retryAfter: response.retryAfter });
741
+ recordMisbehaviorOutcome({
742
+ entry: journalEntry,
743
+ summary: misbehavior.summary,
744
+ defaults,
745
+ testId
746
+ });
747
+ const interactionId = nextInteractionId();
748
+ const usage = {
749
+ ...interactionsUsage({ usage: appliedUsage }),
750
+ total_output_tokens: 0,
751
+ total_thought_tokens: appliedUsage?.completion_tokens ?? 0
752
+ };
753
+ if (!streaming) {
754
+ const overrides = isTextResponse(response) || isToolCallResponse(response) || isCombinedFixtureResponse(response) ? extractOverrides(response) : void 0;
755
+ res.writeHead(200, { "Content-Type": "application/json" });
756
+ res.end(JSON.stringify({
757
+ id: interactionId,
758
+ model: overrides?.model ?? model,
759
+ status: "incomplete",
760
+ steps: [{
761
+ type: "thought",
762
+ signature: "",
763
+ summary: [{
764
+ type: "text",
765
+ text: misbehavior.reasoning ?? ""
766
+ }]
767
+ }],
768
+ usage
769
+ }));
770
+ return;
771
+ }
772
+ const events = [
773
+ {
774
+ event_type: "interaction.created",
775
+ interaction: {
776
+ id: interactionId,
777
+ status: "in_progress"
778
+ },
779
+ event_id: nextEventId()
780
+ },
781
+ {
782
+ event_type: "step.start",
783
+ index: 0,
784
+ step: { type: "thought" },
785
+ event_id: nextEventId()
786
+ },
787
+ {
788
+ event_type: "step.delta",
789
+ index: 0,
790
+ delta: {
791
+ type: "thought_summary",
792
+ content: {
793
+ type: "text",
794
+ text: misbehavior.reasoning ?? ""
795
+ }
796
+ },
797
+ event_id: nextEventId()
798
+ },
799
+ {
800
+ event_type: "step.stop",
801
+ index: 0,
802
+ event_id: nextEventId()
803
+ },
804
+ {
805
+ event_type: "interaction.completed",
806
+ interaction: {
807
+ id: interactionId,
808
+ status: "incomplete",
809
+ usage
810
+ },
811
+ event_id: nextEventId()
812
+ }
813
+ ];
814
+ const interruption = createInterruptionSignal(fixture);
815
+ if (!await writeGeminiInteractionsSSEStream(res, events, {
816
+ latency,
817
+ streamingProfile: fixture.streamingProfile,
818
+ recordedTimings: fixture.recordedTimings,
819
+ replaySpeed,
820
+ signal: interruption?.signal,
821
+ onChunkSent: interruption?.tick
822
+ })) {
823
+ if (!res.writableEnded) res.destroy();
824
+ journalEntry.response.interrupted = true;
825
+ journalEntry.response.interruptReason = interruption?.reason();
826
+ }
827
+ interruption?.cleanup();
675
828
  return;
676
829
  }
677
- const interactionId = nextInteractionId();
678
- if (isContentWithToolCallsResponse(response)) {
830
+ if (isCombinedFixtureResponse(response)) {
679
831
  if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Gemini Interactions API — ignoring");
680
- const overrides = extractOverrides(response);
832
+ const overrides = {
833
+ ...extractOverrides(response),
834
+ ...appliedUsage ? { usage: appliedUsage } : {}
835
+ };
681
836
  const journalEntry = journal.add({
682
837
  method: req.method ?? "POST",
683
838
  path: urlPath,
@@ -688,14 +843,25 @@ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaul
688
843
  fixture
689
844
  }
690
845
  });
846
+ recordMisbehaviorOutcome({
847
+ entry: journalEntry,
848
+ summary: misbehavior.summary,
849
+ defaults,
850
+ testId
851
+ });
852
+ const functionToolCalls = requireEmittedFunctionToolCalls(response, "Gemini Interactions");
853
+ const interactionId = nextInteractionId();
691
854
  if (!streaming) {
692
- const body = buildInteractionsContentWithToolCallsResponse(response.content ?? "", response.toolCalls ?? [], model, interactionId, logger, overrides, response.blocks);
855
+ const body = buildInteractionsContentWithToolCallsResponse(response.content ?? "", functionToolCalls, model, interactionId, logger, overrides, response.blocks);
693
856
  res.writeHead(200, { "Content-Type": "application/json" });
694
- res.end(JSON.stringify(body));
857
+ res.end(JSON.stringify(misbehavior.kind === "applied" && misbehavior.summary.fault === "stop-length-mid-tool" ? {
858
+ ...body,
859
+ status: "incomplete"
860
+ } : body));
695
861
  } else {
696
- const events = buildInteractionsContentWithToolCallsSSEEvents(response.content ?? "", response.toolCalls ?? [], interactionId, chunkSize, logger, overrides, response.blocks);
862
+ const events = buildInteractionsContentWithToolCallsSSEEvents(response.content ?? "", functionToolCalls, interactionId, chunkSize, logger, overrides, response.blocks);
697
863
  const interruption = createInterruptionSignal(fixture);
698
- if (!await writeGeminiInteractionsSSEStream(res, events, {
864
+ if (!await writeGeminiInteractionsSSEStream(res, finalizeInteractionsMisbehavior(events, misbehavior), {
699
865
  latency,
700
866
  streamingProfile: fixture.streamingProfile,
701
867
  recordedTimings: fixture.recordedTimings,
@@ -713,7 +879,10 @@ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaul
713
879
  }
714
880
  if (isTextResponse(response)) {
715
881
  if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Gemini Interactions API — ignoring");
716
- const overrides = extractOverrides(response);
882
+ const overrides = {
883
+ ...extractOverrides(response),
884
+ ...appliedUsage ? { usage: appliedUsage } : {}
885
+ };
717
886
  const journalEntry = journal.add({
718
887
  method: req.method ?? "POST",
719
888
  path: urlPath,
@@ -724,14 +893,24 @@ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaul
724
893
  fixture
725
894
  }
726
895
  });
896
+ recordMisbehaviorOutcome({
897
+ entry: journalEntry,
898
+ summary: misbehavior.summary,
899
+ defaults,
900
+ testId
901
+ });
902
+ const interactionId = nextInteractionId();
727
903
  if (!streaming) {
728
904
  const body = buildInteractionsTextResponse(response.content, model, interactionId, overrides);
729
905
  res.writeHead(200, { "Content-Type": "application/json" });
730
- res.end(JSON.stringify(body));
906
+ res.end(JSON.stringify(misbehavior.kind === "applied" && misbehavior.summary.fault === "stop-length-mid-tool" ? {
907
+ ...body,
908
+ status: "incomplete"
909
+ } : body));
731
910
  } else {
732
911
  const events = buildInteractionsTextSSEEvents(response.content, interactionId, chunkSize, overrides);
733
912
  const interruption = createInterruptionSignal(fixture);
734
- if (!await writeGeminiInteractionsSSEStream(res, events, {
913
+ if (!await writeGeminiInteractionsSSEStream(res, finalizeInteractionsMisbehavior(events, misbehavior), {
735
914
  latency,
736
915
  streamingProfile: fixture.streamingProfile,
737
916
  recordedTimings: fixture.recordedTimings,
@@ -749,7 +928,10 @@ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaul
749
928
  }
750
929
  if (isToolCallResponse(response)) {
751
930
  if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Gemini Interactions API — ignoring");
752
- const overrides = extractOverrides(response);
931
+ const overrides = {
932
+ ...extractOverrides(response),
933
+ ...appliedUsage ? { usage: appliedUsage } : {}
934
+ };
753
935
  const journalEntry = journal.add({
754
936
  method: req.method ?? "POST",
755
937
  path: urlPath,
@@ -760,14 +942,25 @@ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaul
760
942
  fixture
761
943
  }
762
944
  });
945
+ recordMisbehaviorOutcome({
946
+ entry: journalEntry,
947
+ summary: misbehavior.summary,
948
+ defaults,
949
+ testId
950
+ });
951
+ const functionToolCalls = requireEmittedFunctionToolCalls(response, "Gemini Interactions");
952
+ const interactionId = nextInteractionId();
763
953
  if (!streaming) {
764
- const body = buildInteractionsToolCallResponse(response.toolCalls, model, interactionId, logger, overrides);
954
+ const body = buildInteractionsToolCallResponse(functionToolCalls, model, interactionId, logger, overrides);
765
955
  res.writeHead(200, { "Content-Type": "application/json" });
766
- res.end(JSON.stringify(body));
956
+ res.end(JSON.stringify(misbehavior.kind === "applied" && misbehavior.summary.fault === "stop-length-mid-tool" ? {
957
+ ...body,
958
+ status: "incomplete"
959
+ } : body));
767
960
  } else {
768
- const events = buildInteractionsToolCallSSEEvents(response.toolCalls, interactionId, logger, overrides);
961
+ const events = buildInteractionsToolCallSSEEvents(functionToolCalls, interactionId, logger, overrides);
769
962
  const interruption = createInterruptionSignal(fixture);
770
- if (!await writeGeminiInteractionsSSEStream(res, events, {
963
+ if (!await writeGeminiInteractionsSSEStream(res, finalizeInteractionsMisbehavior(events, misbehavior), {
771
964
  latency,
772
965
  streamingProfile: fixture.streamingProfile,
773
966
  recordedTimings: fixture.recordedTimings,
@@ -783,15 +976,20 @@ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaul
783
976
  }
784
977
  return;
785
978
  }
786
- journal.add({
787
- method: req.method ?? "POST",
788
- path: urlPath,
789
- headers: flattenHeaders(req.headers),
790
- body: completionReq,
791
- response: {
792
- status: 500,
793
- fixture
794
- }
979
+ recordMisbehaviorOutcome({
980
+ entry: journal.add({
981
+ method: req.method ?? "POST",
982
+ path: urlPath,
983
+ headers: flattenHeaders(req.headers),
984
+ body: completionReq,
985
+ response: {
986
+ status: 500,
987
+ fixture
988
+ }
989
+ }),
990
+ summary: misbehavior.summary,
991
+ defaults,
992
+ testId
795
993
  });
796
994
  writeErrorResponse(res, 500, JSON.stringify(buildInteractionsErrorResponse("Fixture response did not match any known type", "INTERNAL")));
797
995
  }