@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/messages.cjs CHANGED
@@ -8,7 +8,10 @@ const require_misbehavior = require('./misbehavior.cjs');
8
8
  const require_router = require('./router.cjs');
9
9
  const require_anthropic = require('./response/usage/codecs/anthropic.cjs');
10
10
  const require_recorder = require('./recorder.cjs');
11
- const require_interruption = require('./interruption.cjs');
11
+ const require_timing = require('./response/timing.cjs');
12
+ const require_deliver = require('./response/deliver.cjs');
13
+ const require_proxy = require('./response/proxy.cjs');
14
+ const require_raw = require('./response/raw.cjs');
12
15
  const require_finish = require('./response/finish.cjs');
13
16
 
14
17
  //#region src/messages.ts
@@ -232,6 +235,76 @@ function pushRedactedThinkingStreamEvents(events, startIndex, redactedThinking)
232
235
  }
233
236
  return blockIndex;
234
237
  }
238
+ /**
239
+ * Fixture `citations` as Anthropic `web_search_result_location` citations, the
240
+ * URL-sourced citation type (see `CitationsWebSearchResultLocation` in
241
+ * `@anthropic-ai/sdk`), grouped per text block. `texts` are the turn's text
242
+ * blocks in order; citation offsets index their joined text, the same
243
+ * convention as Chat Completions and Gemini. Each citation goes only to the
244
+ * last text block whose start offset in the joined text is at or before the
245
+ * citation's `startIndex`, with `cited_text` sliced from that block's own text
246
+ * (cut off at the block's end).
247
+ * `web_search_result_location` carries no offsets, so nothing is rebased.
248
+ * `encrypted_index` is opaque on the real wire, so aimock fills a stable
249
+ * base64 placeholder. File citations are skipped (see {@link resolveCitations}):
250
+ * Anthropic has no citation type for an OpenAI file ID.
251
+ */
252
+ function claudeBlockCitations(texts, overrides) {
253
+ const starts = [];
254
+ let offset = 0;
255
+ for (const text of texts) {
256
+ starts.push(offset);
257
+ offset += text.length;
258
+ }
259
+ const perBlock = texts.map(() => []);
260
+ if (texts.length === 0) {
261
+ require_helpers.resolveCitations("", overrides?.citations);
262
+ return perBlock;
263
+ }
264
+ require_helpers.resolveCitations(texts.join(""), overrides?.citations).forEach((c, i) => {
265
+ let owner = 0;
266
+ for (let k = 1; k < starts.length; k++) if (starts[k] <= c.startIndex) owner = k;
267
+ const start = starts[owner];
268
+ perBlock[owner].push({
269
+ type: "web_search_result_location",
270
+ cited_text: texts[owner].slice(c.startIndex - start, c.endIndex - start),
271
+ url: c.url,
272
+ title: c.title,
273
+ encrypted_index: Buffer.from(`aimock-citation:${i}:${c.url}`).toString("base64")
274
+ });
275
+ });
276
+ return perBlock;
277
+ }
278
+ /** {@link claudeBlockCitations} for a turn with a single text block. */
279
+ function claudeCitations(text, overrides) {
280
+ return claudeBlockCitations([text], overrides)[0];
281
+ }
282
+ /** A text content block; `citations` is added only when it has some. */
283
+ function claudeTextBlock(text, citations) {
284
+ return citations.length > 0 ? {
285
+ type: "text",
286
+ text,
287
+ citations
288
+ } : {
289
+ type: "text",
290
+ text
291
+ };
292
+ }
293
+ /**
294
+ * Streamed form of a text block's citations: one `citations_delta` per
295
+ * citation, after the block's text deltas and before its `content_block_stop`,
296
+ * as in the Anthropic citations streaming example. Nothing when it has none.
297
+ */
298
+ function claudeCitationDeltas(citations, index) {
299
+ return citations.map((citation) => ({
300
+ type: "content_block_delta",
301
+ index,
302
+ delta: {
303
+ type: "citations_delta",
304
+ citation
305
+ }
306
+ }));
307
+ }
235
308
  function buildClaudeTextStreamEvents(content, model, chunkSize, reasoning, overrides, reasoningSignature, redactedThinking) {
236
309
  const msgId = overrides?.id ?? require_helpers.generateMessageId();
237
310
  const effectiveModel = overrides?.model ?? model;
@@ -306,6 +379,7 @@ function buildClaudeTextStreamEvents(content, model, chunkSize, reasoning, overr
306
379
  }
307
380
  });
308
381
  }
382
+ events.push(...claudeCitationDeltas(claudeCitations(content, overrides), blockIndex));
309
383
  events.push({
310
384
  type: "content_block_stop",
311
385
  index: blockIndex
@@ -322,6 +396,7 @@ function buildClaudeTextStreamEvents(content, model, chunkSize, reasoning, overr
322
396
  return events;
323
397
  }
324
398
  function buildClaudeToolCallStreamEvents(toolCalls, model, chunkSize, logger, reasoning, overrides, reasoningSignature, redactedThinking) {
399
+ require_helpers.resolveCitations("", overrides?.citations);
325
400
  const msgId = overrides?.id ?? require_helpers.generateMessageId();
326
401
  const effectiveModel = overrides?.model ?? model;
327
402
  const signature = reasoningSignature ?? PLACEHOLDER_SIGNATURE;
@@ -443,10 +518,7 @@ function buildClaudeTextResponse(content, model, reasoning, overrides, reasoning
443
518
  thinking: reasoning,
444
519
  signature: reasoningSignature ?? PLACEHOLDER_SIGNATURE
445
520
  });
446
- contentBlocks.push({
447
- type: "text",
448
- text: content
449
- });
521
+ contentBlocks.push(claudeTextBlock(content, claudeCitations(content, overrides)));
450
522
  return {
451
523
  id: overrides?.id ?? require_helpers.generateMessageId(),
452
524
  type: "message",
@@ -459,6 +531,7 @@ function buildClaudeTextResponse(content, model, reasoning, overrides, reasoning
459
531
  };
460
532
  }
461
533
  function buildClaudeToolCallResponse(toolCalls, model, logger, reasoning, overrides, reasoningSignature, redactedThinking) {
534
+ require_helpers.resolveCitations("", overrides?.citations);
462
535
  const contentBlocks = [];
463
536
  pushRedactedThinkingBlocks(contentBlocks, redactedThinking);
464
537
  if (reasoning) contentBlocks.push({
@@ -544,6 +617,8 @@ function buildClaudeContentWithToolCallsStreamEvents(content, toolCalls, model,
544
617
  if (blocks && blocks.length > 0) {
545
618
  const outcome = require_helpers.resolveFixtureBlockOutcome(blocks);
546
619
  const ordered = outcome.ordered;
620
+ const textCitations = claudeBlockCitations(ordered.flatMap((b) => b.type === "text" ? [b.text] : []), overrides);
621
+ let textPos = 0;
547
622
  for (const block of ordered) if (block.type === "text") {
548
623
  events.push({
549
624
  type: "content_block_start",
@@ -564,6 +639,7 @@ function buildClaudeContentWithToolCallsStreamEvents(content, toolCalls, model,
564
639
  }
565
640
  });
566
641
  }
642
+ events.push(...claudeCitationDeltas(textCitations[textPos++] ?? [], blockIndex));
567
643
  events.push({
568
644
  type: "content_block_stop",
569
645
  index: blockIndex
@@ -629,6 +705,7 @@ function buildClaudeContentWithToolCallsStreamEvents(content, toolCalls, model,
629
705
  }
630
706
  });
631
707
  }
708
+ events.push(...claudeCitationDeltas(claudeCitations(content, overrides), blockIndex));
632
709
  events.push({
633
710
  type: "content_block_stop",
634
711
  index: blockIndex
@@ -696,21 +773,17 @@ function buildClaudeContentWithToolCallsResponse(content, toolCalls, model, logg
696
773
  if (blocks && blocks.length > 0) {
697
774
  const outcome = require_helpers.resolveFixtureBlockOutcome(blocks);
698
775
  const ordered = outcome.ordered;
776
+ const textCitations = claudeBlockCitations(ordered.flatMap((b) => b.type === "text" ? [b.text] : []), overrides);
777
+ let textPos = 0;
699
778
  blockHasTools = outcome.hasToolCalls;
700
- for (const block of ordered) if (block.type === "text") contentBlocks.push({
701
- type: "text",
702
- text: block.text
703
- });
779
+ for (const block of ordered) if (block.type === "text") contentBlocks.push(claudeTextBlock(block.text, textCitations[textPos++] ?? []));
704
780
  else contentBlocks.push(toolUseBlock({
705
781
  name: block.name,
706
782
  arguments: block.arguments,
707
783
  id: block.id
708
784
  }));
709
785
  } else {
710
- contentBlocks.push({
711
- type: "text",
712
- text: content
713
- });
786
+ contentBlocks.push(claudeTextBlock(content, claudeCitations(content, overrides)));
714
787
  for (const tc of toolCalls) contentBlocks.push(toolUseBlock(tc));
715
788
  }
716
789
  return {
@@ -780,8 +853,10 @@ function prepareClaudeMisbehavior(plan, context) {
780
853
  outputTokens: require_helpers.estimateTokens(content + servedToolCalls.map((call) => call.name + call.arguments).join("") + (reasoning ?? "") + (plan.refusal ?? ""))
781
854
  });
782
855
  const usage = require_anthropic.render(resolvedUsage, "messages-final", "applied-misbehavior");
856
+ const { citations, ...rest } = require_helpers.extractOverrides(response);
783
857
  const overrides = {
784
- ...require_helpers.extractOverrides(response),
858
+ ...rest,
859
+ ...context.citations ? { citations } : {},
785
860
  ...plan.stop === "length" ? { finishReason: "length" } : {},
786
861
  usage
787
862
  };
@@ -804,6 +879,7 @@ function prepareClaudeMisbehavior(plan, context) {
804
879
  usage
805
880
  };
806
881
  if (!context.stream) return {
882
+ reasoning,
807
883
  stream: false,
808
884
  body,
809
885
  plan: preparedPlan,
@@ -853,6 +929,7 @@ function prepareClaudeMisbehavior(plan, context) {
853
929
  usage: require_anthropic.render(resolvedUsage, "messages-delta", "applied-misbehavior")
854
930
  }, { type: "message_stop" });
855
931
  return {
932
+ reasoning,
856
933
  stream: true,
857
934
  events,
858
935
  plan: preparedPlan,
@@ -878,12 +955,14 @@ function prepareClaudeMisbehavior(plan, context) {
878
955
  usage
879
956
  };
880
957
  if (!context.stream) return {
958
+ reasoning,
881
959
  stream: false,
882
960
  body,
883
961
  plan: preparedPlan,
884
962
  usage
885
963
  };
886
964
  return {
965
+ reasoning,
887
966
  stream: true,
888
967
  events: [
889
968
  {
@@ -920,6 +999,7 @@ function prepareClaudeMisbehavior(plan, context) {
920
999
  if (plan.summary.fault === "stop-length-mid-tool") {
921
1000
  const cutIndex = events.filter((event) => event.type === "content_block_start" && require_helpers.isJsonObject(event.content_block) && event.content_block.type === "tool_use")[plan.target?.index ?? 0]?.index;
922
1001
  return {
1002
+ reasoning,
923
1003
  stream: true,
924
1004
  events: events.filter((event) => !(event.type === "content_block_stop" && event.index === cutIndex)),
925
1005
  plan: preparedPlan,
@@ -927,6 +1007,7 @@ function prepareClaudeMisbehavior(plan, context) {
927
1007
  };
928
1008
  }
929
1009
  return {
1010
+ reasoning,
930
1011
  stream: true,
931
1012
  events,
932
1013
  plan: preparedPlan,
@@ -940,51 +1021,13 @@ function prepareClaudeMisbehavior(plan, context) {
940
1021
  artifacts.redactedThinking
941
1022
  ];
942
1023
  return {
1024
+ reasoning,
943
1025
  stream: false,
944
1026
  body: require_helpers.isCombinedFixtureResponse(preparedResponse) ? buildClaudeContentWithToolCallsResponse(preparedResponse.content ?? "", toolCalls, model, context.logger, ...args, preparedResponse.blocks) : require_helpers.isToolCallResponse(preparedResponse) ? buildClaudeToolCallResponse(toolCalls, model, context.logger, ...args) : buildClaudeTextResponse(content, model, ...args),
945
1027
  plan: preparedPlan,
946
1028
  usage
947
1029
  };
948
1030
  }
949
- async function writeClaudeSSEStream(res, events, optionsOrLatency) {
950
- const opts = typeof optionsOrLatency === "number" ? { latency: optionsOrLatency } : optionsOrLatency ?? {};
951
- const latency = opts.latency ?? 0;
952
- const profile = opts.streamingProfile;
953
- const { recordedTimings, replaySpeed } = opts;
954
- const onChunkSent = opts.onChunkSent;
955
- if (res.writableEnded) return true;
956
- if (res.destroyed) return false;
957
- const controller = new AbortController();
958
- const signal = controller.signal;
959
- const abort = () => controller.abort();
960
- const onClose = () => {
961
- if (!res.writableFinished) abort();
962
- };
963
- res.once("close", onClose);
964
- opts.signal?.addEventListener("abort", abort, { once: true });
965
- if (opts.signal?.aborted) abort();
966
- try {
967
- res.setHeader("Content-Type", "text/event-stream");
968
- res.setHeader("Cache-Control", "no-cache");
969
- res.setHeader("Connection", "keep-alive");
970
- let chunkIndex = 0;
971
- for (const event of events) {
972
- const chunkDelay = require_sse_writer.calculateDelay(chunkIndex, profile, latency, recordedTimings, replaySpeed);
973
- if (chunkDelay > 0) await require_sse_writer.delay(chunkDelay, signal);
974
- if (signal.aborted || res.destroyed) return false;
975
- if (res.writableEnded) return true;
976
- res.write(`event: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`);
977
- onChunkSent?.();
978
- if (signal.aborted || res.destroyed) return false;
979
- chunkIndex++;
980
- }
981
- if (!res.writableEnded) res.end();
982
- return true;
983
- } finally {
984
- res.off("close", onClose);
985
- opts.signal?.removeEventListener("abort", abort);
986
- }
987
- }
988
1031
  async function handleMessages(req, res, raw, fixtures, journal, defaults, setCorsHeaders) {
989
1032
  const { logger } = defaults;
990
1033
  setCorsHeaders(res);
@@ -1112,7 +1155,10 @@ async function handleMessages(req, res, raw, fixtures, journal, defaults, setCor
1112
1155
  const completionReq = claudeToCompletionRequest(claudeReq);
1113
1156
  completionReq._context = require_helpers.getContext(req);
1114
1157
  const testId = require_helpers.getTestId(req);
1115
- const { fixture, skippedBySequenceOrTurn } = require_router.matchFixtureDiagnostic(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform, require_router.recordMatchOptions(!!defaults.record, defaults.logger));
1158
+ const { fixture, skippedBySequenceOrTurn } = require_router.matchFixtureDiagnostic(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform, {
1159
+ ...require_router.recordMatchOptions(!!defaults.record, defaults.logger),
1160
+ wire: "anthropic"
1161
+ });
1116
1162
  if (fixture) {
1117
1163
  journal.incrementFixtureMatchCount(fixture, fixtures, testId);
1118
1164
  logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);
@@ -1150,7 +1196,7 @@ async function handleMessages(req, res, raw, fixtures, journal, defaults, setCor
1150
1196
  return;
1151
1197
  }
1152
1198
  if (defaults.record) {
1153
- const outcome = await require_recorder.proxyAndRecord(req, res, completionReq, "anthropic", req.url ?? "/v1/messages", fixtures, defaults, raw);
1199
+ const { outcome, clientHungUp } = await require_proxy.withHangUpDetection(req, res, () => require_recorder.proxyAndRecord(req, res, completionReq, "anthropic", req.url ?? "/v1/messages", fixtures, defaults, raw));
1154
1200
  if (outcome === "handled_by_hook") return;
1155
1201
  if (outcome !== "not_configured") {
1156
1202
  journal.add({
@@ -1161,7 +1207,8 @@ async function handleMessages(req, res, raw, fixtures, journal, defaults, setCor
1161
1207
  response: {
1162
1208
  status: res.statusCode ?? 200,
1163
1209
  fixture: null,
1164
- source: "proxy"
1210
+ source: "proxy",
1211
+ ...require_proxy.proxyInterruption(res, clientHungUp)
1165
1212
  }
1166
1213
  });
1167
1214
  return;
@@ -1222,14 +1269,22 @@ async function handleMessages(req, res, raw, fixtures, journal, defaults, setCor
1222
1269
  }
1223
1270
  const latency = fixture.latency ?? defaults.latency;
1224
1271
  const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
1272
+ const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;
1273
+ const streamTiming = {
1274
+ latency,
1275
+ streamingProfile: fixture.streamingProfile,
1276
+ recordedTimings: fixture.recordedTimings,
1277
+ replaySpeed
1278
+ };
1279
+ const jsonOpts = (payloadLength, duplicateId = false) => ({
1280
+ surface: "other",
1281
+ payloadLength,
1282
+ chunkSize,
1283
+ recordedTimings: fixture.recordedTimings,
1284
+ duplicateId,
1285
+ replaySpeed
1286
+ });
1225
1287
  if (misbehavior.kind === "applied") {
1226
- const prepared = prepareClaudeMisbehavior(misbehavior, {
1227
- request: completionReq,
1228
- stream: claudeReq.stream === true,
1229
- chunkSize,
1230
- logger,
1231
- strict: require_policy.resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression
1232
- });
1233
1288
  const entry = journal.add({
1234
1289
  method: req.method ?? "POST",
1235
1290
  path: req.url ?? "/v1/messages",
@@ -1240,6 +1295,15 @@ async function handleMessages(req, res, raw, fixtures, journal, defaults, setCor
1240
1295
  fixture
1241
1296
  }
1242
1297
  });
1298
+ require_helpers.rejectGeneratedFiles(misbehavior.response, "Anthropic Messages");
1299
+ const prepared = prepareClaudeMisbehavior(misbehavior, {
1300
+ request: completionReq,
1301
+ stream: claudeReq.stream === true,
1302
+ chunkSize,
1303
+ logger,
1304
+ strict: require_policy.resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression,
1305
+ citations: true
1306
+ });
1243
1307
  require_misbehavior.recordMisbehaviorOutcome({
1244
1308
  entry,
1245
1309
  summary: prepared.plan.summary,
@@ -1247,27 +1311,26 @@ async function handleMessages(req, res, raw, fixtures, journal, defaults, setCor
1247
1311
  testId
1248
1312
  });
1249
1313
  if (!prepared.stream) {
1250
- res.writeHead(200, { "Content-Type": "application/json" });
1251
- res.end(JSON.stringify(prepared.body));
1252
- } else {
1253
- const interruption = require_interruption.createInterruptionSignal(fixture);
1254
- try {
1255
- if (!await writeClaudeSSEStream(res, prepared.events, {
1256
- latency,
1257
- streamingProfile: fixture.streamingProfile,
1258
- recordedTimings: fixture.recordedTimings,
1259
- replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
1260
- signal: interruption?.signal,
1261
- onChunkSent: interruption?.tick
1262
- })) {
1263
- if (!res.writableEnded) res.destroy();
1264
- entry.response.interrupted = true;
1265
- entry.response.interruptReason = interruption?.reason() ?? "client aborted";
1266
- }
1267
- } finally {
1268
- interruption?.cleanup();
1269
- }
1270
- }
1314
+ const preparedBody = prepared.body;
1315
+ await require_deliver.deliverJson(res, entry, fixture, () => preparedBody, jsonOpts(require_timing.misbehaviorPayloadLength(misbehavior, misbehavior.reasoning === void 0 ? prepared.reasoning : void 0), !!misbehavior.duplicateId));
1316
+ } else await require_deliver.deliverStream(res, entry, fixture, require_sse_writer.typedSseCodec(), prepared.events, streamTiming);
1317
+ return;
1318
+ }
1319
+ if (require_helpers.isRawResponse(response)) {
1320
+ await require_raw.serveRawResponse({
1321
+ wire: "anthropic",
1322
+ stream: claudeReq.stream === true,
1323
+ req,
1324
+ res,
1325
+ fixture,
1326
+ response,
1327
+ body: completionReq,
1328
+ journal,
1329
+ defaults,
1330
+ testId,
1331
+ misbehaviorSummary: misbehavior.summary,
1332
+ errorWire: "messages"
1333
+ });
1271
1334
  return;
1272
1335
  }
1273
1336
  if (require_helpers.isErrorResponse(response)) {
@@ -1312,27 +1375,12 @@ async function handleMessages(req, res, raw, fixtures, journal, defaults, setCor
1312
1375
  defaults,
1313
1376
  testId
1314
1377
  });
1378
+ require_helpers.rejectGeneratedFiles(response, "Anthropic Messages");
1315
1379
  const functionToolCalls = require_helpers.requireEmittedFunctionToolCalls(response, "Anthropic Messages");
1316
- if (claudeReq.stream !== true) {
1317
- const body = buildClaudeContentWithToolCallsResponse(response.content ?? "", functionToolCalls, completionReq.model, logger, effReasoning, overrides, effReasoningSignature, effRedactedThinking, response.blocks);
1318
- res.writeHead(200, { "Content-Type": "application/json" });
1319
- res.end(JSON.stringify(body));
1320
- } else {
1380
+ if (claudeReq.stream !== true) await require_deliver.deliverJson(res, journalEntry, fixture, () => buildClaudeContentWithToolCallsResponse(response.content ?? "", functionToolCalls, completionReq.model, logger, effReasoning, overrides, effReasoningSignature, effRedactedThinking, response.blocks), jsonOpts(require_timing.servedPayloadLength(response, effReasoning)));
1381
+ else {
1321
1382
  const events = buildClaudeContentWithToolCallsStreamEvents(response.content ?? "", functionToolCalls, completionReq.model, chunkSize, logger, effReasoning, overrides, effReasoningSignature, effRedactedThinking, response.blocks);
1322
- const interruption = require_interruption.createInterruptionSignal(fixture);
1323
- if (!await writeClaudeSSEStream(res, events, {
1324
- latency,
1325
- streamingProfile: fixture.streamingProfile,
1326
- recordedTimings: fixture.recordedTimings,
1327
- replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
1328
- signal: interruption?.signal,
1329
- onChunkSent: interruption?.tick
1330
- })) {
1331
- if (!res.writableEnded) res.destroy();
1332
- journalEntry.response.interrupted = true;
1333
- journalEntry.response.interruptReason = interruption?.reason() ?? "client aborted";
1334
- }
1335
- interruption?.cleanup();
1383
+ await require_deliver.deliverStream(res, journalEntry, fixture, require_sse_writer.typedSseCodec(), events, streamTiming);
1336
1384
  }
1337
1385
  return;
1338
1386
  }
@@ -1358,26 +1406,11 @@ async function handleMessages(req, res, raw, fixtures, journal, defaults, setCor
1358
1406
  defaults,
1359
1407
  testId
1360
1408
  });
1361
- if (claudeReq.stream !== true) {
1362
- const body = buildClaudeTextResponse(response.content, completionReq.model, effReasoning, overrides, effReasoningSignature, effRedactedThinking);
1363
- res.writeHead(200, { "Content-Type": "application/json" });
1364
- res.end(JSON.stringify(body));
1365
- } else {
1409
+ require_helpers.rejectGeneratedFiles(response, "Anthropic Messages");
1410
+ if (claudeReq.stream !== true) await require_deliver.deliverJson(res, journalEntry, fixture, () => buildClaudeTextResponse(response.content, completionReq.model, effReasoning, overrides, effReasoningSignature, effRedactedThinking), jsonOpts(require_timing.servedPayloadLength(response, effReasoning)));
1411
+ else {
1366
1412
  const events = buildClaudeTextStreamEvents(response.content, completionReq.model, chunkSize, effReasoning, overrides, effReasoningSignature, effRedactedThinking);
1367
- const interruption = require_interruption.createInterruptionSignal(fixture);
1368
- if (!await writeClaudeSSEStream(res, events, {
1369
- latency,
1370
- streamingProfile: fixture.streamingProfile,
1371
- recordedTimings: fixture.recordedTimings,
1372
- replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
1373
- signal: interruption?.signal,
1374
- onChunkSent: interruption?.tick
1375
- })) {
1376
- if (!res.writableEnded) res.destroy();
1377
- journalEntry.response.interrupted = true;
1378
- journalEntry.response.interruptReason = interruption?.reason() ?? "client aborted";
1379
- }
1380
- interruption?.cleanup();
1413
+ await require_deliver.deliverStream(res, journalEntry, fixture, require_sse_writer.typedSseCodec(), events, streamTiming);
1381
1414
  }
1382
1415
  return;
1383
1416
  }
@@ -1403,27 +1436,12 @@ async function handleMessages(req, res, raw, fixtures, journal, defaults, setCor
1403
1436
  defaults,
1404
1437
  testId
1405
1438
  });
1439
+ require_helpers.rejectGeneratedFiles(response, "Anthropic Messages");
1406
1440
  const functionToolCalls = require_helpers.requireEmittedFunctionToolCalls(response, "Anthropic Messages");
1407
- if (claudeReq.stream !== true) {
1408
- const body = buildClaudeToolCallResponse(functionToolCalls, completionReq.model, logger, effReasoning, overrides, effReasoningSignature, effRedactedThinking);
1409
- res.writeHead(200, { "Content-Type": "application/json" });
1410
- res.end(JSON.stringify(body));
1411
- } else {
1441
+ if (claudeReq.stream !== true) await require_deliver.deliverJson(res, journalEntry, fixture, () => buildClaudeToolCallResponse(functionToolCalls, completionReq.model, logger, effReasoning, overrides, effReasoningSignature, effRedactedThinking), jsonOpts(require_timing.servedPayloadLength(response, effReasoning)));
1442
+ else {
1412
1443
  const events = buildClaudeToolCallStreamEvents(functionToolCalls, completionReq.model, chunkSize, logger, effReasoning, overrides, effReasoningSignature, effRedactedThinking);
1413
- const interruption = require_interruption.createInterruptionSignal(fixture);
1414
- if (!await writeClaudeSSEStream(res, events, {
1415
- latency,
1416
- streamingProfile: fixture.streamingProfile,
1417
- recordedTimings: fixture.recordedTimings,
1418
- replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
1419
- signal: interruption?.signal,
1420
- onChunkSent: interruption?.tick
1421
- })) {
1422
- if (!res.writableEnded) res.destroy();
1423
- journalEntry.response.interrupted = true;
1424
- journalEntry.response.interruptReason = interruption?.reason() ?? "client aborted";
1425
- }
1426
- interruption?.cleanup();
1444
+ await require_deliver.deliverStream(res, journalEntry, fixture, require_sse_writer.typedSseCodec(), events, streamTiming);
1427
1445
  }
1428
1446
  return;
1429
1447
  }