@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
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.cts","names":[],"sources":["../src/server.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;AA8X+B,UAAd,cAAA,CAAc;mBAEhB,CAAA,MAAA,CAAA,EAAA,MAAA,CAAA,EAAA,IAAA;QACJ,EADD,MAAA,CAAK,MACJ;SAEC,EAFD,OAEC;KACG,EAAA,MAAA;UACQ,EAFX,eAEW;aACP,EAFD,aAEC;qBACC,EAFM,qBAEN;cACI,EAFL,cAEK;EAAmB,aAAA,EADvB,eACuB;EAo9EvB,iBAAA,EAp9EI,mBAo9EW;;;;;;;;UAAf,eAAA;UACP;UACA;cACI;;aAED;;iBAMS,YAAA,WACV,qBACA,4BACD;;WAA+B;sBACtB,kBACjB,QAAQ"}
1
+ {"version":3,"file":"server.d.cts","names":[],"sources":["../src/server.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;AA8X+B,UAAd,cAAA,CAAc;mBAEhB,CAAA,MAAA,CAAA,EAAA,MAAA,CAAA,EAAA,IAAA;QACJ,EADD,MAAA,CAAK,MACJ;SAEC,EAFD,OAEC;KACG,EAAA,MAAA;UACQ,EAFX,eAEW;aACP,EAFD,aAEC;qBACC,EAFM,qBAEN;cACI,EAFL,cAEK;EAAmB,aAAA,EADvB,eACuB;EA88EvB,iBAAA,EA98EI,mBA88EW;;;;;;;;UAAf,eAAA;UACP;UACA;cACI;;aAED;;iBAMS,YAAA,WACV,qBACA,4BACD;;WAA+B;sBACtB,kBACjB,QAAQ"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","names":[],"sources":["../src/server.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;AA8X+B,UAAd,cAAA,CAAc;mBAEhB,CAAA,MAAA,CAAA,EAAA,MAAA,CAAA,EAAA,IAAA;QACJ,EADD,MAAA,CAAK,MACJ;SAEC,EAFD,OAEC;KACG,EAAA,MAAA;UACQ,EAFX,eAEW;aACP,EAFD,aAEC;qBACC,EAFM,qBAEN;cACI,EAFL,cAEK;EAAmB,aAAA,EADvB,eACuB;EAo9EvB,iBAAA,EAp9EI,mBAo9EW;;;;;;;;UAAf,eAAA;UACP;UACA;cACI;;aAED;;iBAMS,YAAA,WACV,qBACA,4BACD;;WAA+B;sBACtB,kBACjB,QAAQ"}
1
+ {"version":3,"file":"server.d.ts","names":[],"sources":["../src/server.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;AA8X+B,UAAd,cAAA,CAAc;mBAEhB,CAAA,MAAA,CAAA,EAAA,MAAA,CAAA,EAAA,IAAA;QACJ,EADD,MAAA,CAAK,MACJ;SAEC,EAFD,OAEC;KACG,EAAA,MAAA;UACQ,EAFX,eAEW;aACP,EAFD,aAEC;qBACC,EAFM,qBAEN;cACI,EAFL,cAEK;EAAmB,aAAA,EADvB,eACuB;EA88EvB,iBAAA,EA98EI,mBA88EW;;;;;;;;UAAf,eAAA;UACP;UACA;cACI;;aAED;;iBAMS,YAAA,WACV,qBACA,4BACD;;WAA+B;sBACtB,kBACjB,QAAQ"}
package/dist/server.js CHANGED
@@ -4,9 +4,9 @@ import { DEFAULT_TEST_ID } from "./constants.js";
4
4
  import { build, msg, quote } from "./message-text.js";
5
5
  import { markAuthenticatedRequest, resolveInboundAuth, validateRequestApiKey, writeApiKeyHttpRejection, writeApiKeyUpgradeRejection } from "./api-key-auth.js";
6
6
  import { parsePolicy, resolvePolicy, resolveRequestDecisions, strictOverrideField } from "./response/policy.js";
7
- import { RequestBodyTooLargeError, buildContentWithToolCallsChunks, buildContentWithToolCallsCompletion, buildOpenAIContentFilterChunks, buildOpenAIContentFilterCompletion, buildOpenAIReasoningChunks, buildOpenAIReasoningCompletion, buildOpenAIRefusalChunks, buildOpenAIRefusalCompletion, buildTextChunks, buildTextCompletion, buildToolCallChunks, buildToolCallCompletion, buildUsageChunk, createServerFixtureList, describeMatch, extractOverrides, fixtureToolCallErrorCode, flattenHeaders, getContext, getTestId, googleFixtureToolCallErrorDetails, isAudioResponse, isCombinedFixtureResponse, isEmbeddingResponse, isErrorResponse, isExtendedResponsesToolsList, isFixtureToolCallError, isImageResponse, isJSONResponse, isJsonObject, isResponseFactory, isServerOwnedFixtures, isTextResponse, isToolCallResponse, isTranscriptionResponse, isVideoResponse, markMintedRequestId, prepareOpenAIChatMisbehavior, readBody, readBodyBufferBounded, requireEmittedFunctionToolCalls, requireServedFunctionToolCalls, resolveFixtureBlockOutcome, resolveOpenAIChatMisbehaviorUsage, resolveReasoningForModel, resolveRequestId, resolveResponse, resolveTestId, resolveUsage, runWithToolArgumentsScope, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, validateToolsField, withoutResponsesToolKeys, wouldProxyMiss } from "./helpers.js";
7
+ import { RequestBodyTooLargeError, buildContentWithToolCallsChunks, buildContentWithToolCallsCompletion, buildOpenAIContentFilterChunks, buildOpenAIContentFilterCompletion, buildOpenAIReasoningChunks, buildOpenAIReasoningCompletion, buildOpenAIRefusalChunks, buildOpenAIRefusalCompletion, buildTextChunks, buildTextCompletion, buildToolCallChunks, buildToolCallCompletion, buildUsageChunk, createServerFixtureList, describeMatch, extractOverrides, fixtureToolCallErrorCode, flattenHeaders, getContext, getTestId, googleFixtureToolCallErrorDetails, isAudioResponse, isCombinedFixtureResponse, isEmbeddingResponse, isErrorResponse, isExtendedResponsesToolsList, isFixtureToolCallError, isImageResponse, isJSONResponse, isJsonObject, isRawResponse, isResponseFactory, isServerOwnedFixtures, isTextResponse, isToolCallResponse, isTranscriptionResponse, isVideoResponse, markMintedRequestId, prepareOpenAIChatMisbehavior, readBody, readBodyBufferBounded, rejectGeneratedFiles, requireEmittedFunctionToolCalls, requireServedFunctionToolCalls, resolveFixtureBlockOutcome, resolveOpenAIChatMisbehaviorUsage, resolveReasoningForModel, resolveRequestId, resolveResponse, resolveTestId, resolveUsage, runWithToolArgumentsScope, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, validateToolsField, withoutResponsesToolKeys, wouldProxyMiss } from "./helpers.js";
8
8
  import { normalizeLiveOptions } from "./live-fixture.js";
9
- import { calculateTotalDelay, delay, writeErrorResponse, writeSSEStream } from "./sse-writer.js";
9
+ import { sseCodec, writeErrorResponse } from "./sse-writer.js";
10
10
  import { CHAOS_FIELDS, CHAOS_FIELD_NAMES, applyChaosAction, awaitChaosLatency, describeUnwritableReason, evaluateChaos, isChaosScope, parseChaosField, resetChaosWarnings, resolveChaosConfig, responseGoneReason } from "./chaos.js";
11
11
  import { MISBEHAVIOR_CATALOG, WIRE_SUPPORT, getFixtureMisbehaviorPosition, parseMisbehavior, parseMisbehaviorHeader, planMisbehavior, recordMisbehaviorOutcome, resolveMisbehaviorSeed, resolveMisbehaviorShortCircuit, setFixtureMisbehaviorPosition } from "./misbehavior.js";
12
12
  import { FixtureLoadError, INJECTED_STATUS_RANGE, MisbehaviorConfigError, claimOneShotError, clearFixtureQueue, enableHeldFixtureMisbehavior, entryToFixture, isInjectableStatus, isOneShotError, markFixtureResponsesToolsExtended, queueOneShotError, releaseOneShotError, responsesToolsExtendedView, responsesToolsViewSource, validateFixtures } from "./fixture-loader.js";
@@ -14,12 +14,16 @@ import { matchFixtureDiagnostic, recordMatchOptions } from "./router.js";
14
14
  import { proxyAndRecord } from "./recorder.js";
15
15
  import { Logger } from "./logger.js";
16
16
  import { Journal } from "./journal.js";
17
- import { createInterruptionSignal } from "./interruption.js";
17
+ import { CLIENT_ABORTED } from "./response/lifecycle.js";
18
+ import { extendRecordedTimings, misbehaviorPayloadLength } from "./response/timing.js";
19
+ import { deliverJson, deliverStream } from "./response/deliver.js";
20
+ import { proxyInterruption, withHangUpDetection } from "./response/proxy.js";
18
21
  import { collect, idsFor } from "./response/catalog/collect.js";
19
- import { anthropicPage } from "./response/catalog/anthropic-page.js";
20
- import { geminiPage } from "./response/catalog/gemini-page.js";
21
22
  import { coreModelIds, renderAnthropicModel, renderAnthropicModels, renderGeminiModel, renderGeminiModels, renderOllamaTags, renderOpenAIModel, renderOpenAIModels } from "./response/catalog/render.js";
22
23
  import { buildOpenRouterCandidates, handleOpenRouterCredits, handleOpenRouterKey, handleOpenRouterModels, isOpenRouterPath, resolveOpenRouterShaping, serializeOpenRouterError, shapeOpenRouterChunks, shapeOpenRouterCompletion } from "./openrouter-chat.js";
24
+ import { serveRawResponse } from "./response/raw.js";
25
+ import { anthropicPage } from "./response/catalog/anthropic-page.js";
26
+ import { geminiPage } from "./response/catalog/gemini-page.js";
23
27
  import { handleResponses } from "./responses.js";
24
28
  import { handleMessages } from "./messages.js";
25
29
  import { handleGemini } from "./gemini.js";
@@ -211,6 +215,7 @@ function fixtureResponseKind(response) {
211
215
  if (isCombinedFixtureResponse(response)) return "contentWithToolCalls";
212
216
  if (isToolCallResponse(response)) return "toolCalls";
213
217
  if (isTextResponse(response)) return "text";
218
+ if (isRawResponse(response)) return "raw";
214
219
  return "unknown";
215
220
  }
216
221
  /**
@@ -896,7 +901,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
896
901
  const clientAbort = clientAbortedMidBody(req, res, err);
897
902
  const bodyLimitDisconnect = clientFault && (req.destroyed || res.destroyed);
898
903
  if (clientAbort || bodyLimitDisconnect) {
899
- const interruptReason = bodyLimitDisconnect ? "request body exceeded size limit" : "client aborted";
904
+ const interruptReason = bodyLimitDisconnect ? "request body exceeded size limit" : CLIENT_ABORTED;
900
905
  defaults.logger.warn(bodyLimitDisconnect ? `${route}: ${msg}` : `${route}: client aborted mid-body (${msg})`);
901
906
  journal.add({
902
907
  method: req.method ?? "POST",
@@ -1045,7 +1050,10 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1045
1050
  body._endpointType = "chat";
1046
1051
  body._context = getContext(req);
1047
1052
  const testId = getTestId(req);
1048
- const matchOptions = recordMatchOptions(!!(defaults.record && providerKey), defaults.logger);
1053
+ const matchOptions = {
1054
+ ...recordMatchOptions(!!(defaults.record && providerKey), defaults.logger),
1055
+ wire: "openai-chat"
1056
+ };
1049
1057
  let fixture = null;
1050
1058
  let skippedBySequenceOrTurn = 0;
1051
1059
  let preResolvedResponse = null;
@@ -1075,10 +1083,11 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1075
1083
  skippedBySequenceOrTurn = Math.max(skippedBySequenceOrTurn, attempt.skippedBySequenceOrTurn);
1076
1084
  if (!attempt.fixture) return null;
1077
1085
  const response = await resolveResponse(attempt.fixture, probe);
1086
+ const isError = isErrorResponse(response) || isRawResponse(response) && (response.raw.status ?? 200) >= 400;
1078
1087
  return {
1079
1088
  fixture: attempt.fixture,
1080
1089
  response,
1081
- isError: isErrorResponse(response)
1090
+ isError
1082
1091
  };
1083
1092
  };
1084
1093
  for (const candidate of candidates) {
@@ -1092,7 +1101,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1092
1101
  matchCounts = journal.getFixtureMatchCountsForTest(testId);
1093
1102
  responseModel = candidate;
1094
1103
  if (outcome.isError) {
1095
- const errorFallsThrough = outcome.response.fallthrough !== false;
1104
+ const errorFallsThrough = !isErrorResponse(outcome.response) || outcome.response.fallthrough !== false;
1096
1105
  if (allowFallbacks && errorFallsThrough) {
1097
1106
  lastErrorFixture = outcome.fixture;
1098
1107
  lastErrorResponse = outcome.response;
@@ -1209,22 +1218,9 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1209
1218
  });
1210
1219
  }
1211
1220
  } : void 0;
1212
- let clientHungUp = false;
1213
- const noteClientHangUp = () => {
1214
- if (!res.writableEnded) clientHungUp = true;
1215
- };
1216
- req.socket?.on("end", noteClientHangUp);
1217
- req.socket?.on("error", noteClientHangUp);
1218
- let outcome;
1219
- try {
1220
- outcome = await proxyAndRecord(req, res, body, providerKey, req.url ?? COMPLETIONS_PATH, fixtures, defaults, raw, hookOptions);
1221
- } finally {
1222
- req.socket?.off("end", noteClientHangUp);
1223
- req.socket?.off("error", noteClientHangUp);
1224
- }
1221
+ const { outcome, clientHungUp } = await withHangUpDetection(req, res, () => proxyAndRecord(req, res, body, providerKey, req.url ?? COMPLETIONS_PATH, fixtures, defaults, raw, hookOptions));
1225
1222
  if (outcome === "handled_by_hook") return;
1226
1223
  if (outcome !== "not_configured") {
1227
- const destroyedMidStream = res.destroyed && !res.writableEnded;
1228
1224
  journal.add({
1229
1225
  method: req.method ?? "POST",
1230
1226
  path: req.url ?? COMPLETIONS_PATH,
@@ -1234,10 +1230,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1234
1230
  status: res.statusCode ?? 200,
1235
1231
  fixture: null,
1236
1232
  source: "proxy",
1237
- ...destroyedMidStream ? {
1238
- interrupted: true,
1239
- interruptReason: clientHungUp ? "client aborted" : "proxy stream destroyed"
1240
- } : {}
1233
+ ...proxyInterruption(res, clientHungUp)
1241
1234
  }
1242
1235
  });
1243
1236
  recordBypass("proxied");
@@ -1324,20 +1317,23 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1324
1317
  const emitStreamingUsage = body.stream === true && body.stream_options?.include_usage === true || openRouter;
1325
1318
  const streamingPromptText = body.messages.map((m) => typeof m.content === "string" ? m.content : Array.isArray(m.content) ? m.content.map((p) => p.text ?? "").join("") : "").join("");
1326
1319
  const resolveStreamingUsageTokens = (overrides, completionText) => appliedUsage ?? resolveUsage(overrides, streamingPromptText, completionText);
1327
- const resolveRecordedTimings = (chunkCount) => {
1328
- const timings = fixture.recordedTimings;
1329
- if (!appliedPlan?.duplicateId || !timings) return timings;
1330
- const gaps = timings.interChunkDelaysMs;
1331
- return {
1332
- ...timings,
1333
- interChunkDelaysMs: Array.from({ length: Math.max(gaps.length, chunkCount - 1) }, (_, index) => gaps[index] ?? gaps.at(-1) ?? 0)
1334
- };
1335
- };
1336
- const awaitNonStreamingTiming = (contentLength) => {
1337
- if (fixture.nonStreamingTiming !== true) return Promise.resolve();
1338
- if (!fixture.streamingProfile && !fixture.recordedTimings) return Promise.resolve();
1339
- const chunkCount = Math.max(1, Math.ceil(contentLength / chunkSize));
1340
- return delay(calculateTotalDelay(contentLength, chunkSize, fixture.streamingProfile, resolveRecordedTimings(chunkCount), fixture.replaySpeed ?? defaults.replaySpeed));
1320
+ const nonStreamingOpts = (payloadLength) => ({
1321
+ surface: "openai-chat",
1322
+ payloadLength,
1323
+ chunkSize,
1324
+ recordedTimings: fixture.recordedTimings,
1325
+ duplicateId: !!appliedPlan?.duplicateId,
1326
+ replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed
1327
+ });
1328
+ const streamTiming = (frameCount) => ({
1329
+ latency,
1330
+ streamingProfile: fixture.streamingProfile,
1331
+ recordedTimings: extendRecordedTimings(fixture.recordedTimings, frameCount, !!appliedPlan?.duplicateId),
1332
+ replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed
1333
+ });
1334
+ const commitNonStream = async (entry, reply, payloadLength) => {
1335
+ recordOutcome(entry);
1336
+ await deliverJson(res, entry, fixture, () => reply, nonStreamingOpts(payloadLength));
1341
1337
  };
1342
1338
  const shapeORCompletion = (completion, overrides) => {
1343
1339
  if (appliedUsage) completion.usage = appliedUsage;
@@ -1351,6 +1347,24 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1351
1347
  if (usageChunk) shapeOpenRouterChunks([usageChunk], shaping, overrides?.id !== void 0);
1352
1348
  };
1353
1349
  const openRouterProcessing = !!(openRouter && fixture.openRouterProcessing);
1350
+ if (isRawResponse(response)) {
1351
+ await serveRawResponse({
1352
+ wire: "openai-chat",
1353
+ stream: body.stream === true,
1354
+ req,
1355
+ res,
1356
+ fixture,
1357
+ response,
1358
+ body,
1359
+ journal,
1360
+ defaults,
1361
+ testId,
1362
+ misbehaviorSummary: evaluation.summary,
1363
+ errorWire: "openai",
1364
+ openRouter
1365
+ });
1366
+ return;
1367
+ }
1354
1368
  if (isErrorResponse(response)) {
1355
1369
  const status = response.status ?? 500;
1356
1370
  recordOutcome(journal.add({
@@ -1395,32 +1409,18 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1395
1409
  fixture
1396
1410
  }
1397
1411
  });
1398
- recordOutcome(entry);
1399
- if (body.stream !== true) {
1400
- const completion = appliedPlan.stop === "refusal" ? buildOpenAIRefusalCompletion(appliedPlan.refusal ?? "", responseModel, overrides, body.messages) : appliedPlan.stop === "content_filter" ? buildOpenAIContentFilterCompletion(responseModel, overrides, body.messages) : buildOpenAIReasoningCompletion(appliedPlan.reasoning ?? "", responseModel, openRouter, overrides, body.messages);
1401
- await awaitNonStreamingTiming((appliedPlan.stop === "refusal" ? appliedPlan.refusal ?? "" : appliedPlan.stop === "content_filter" ? "" : appliedPlan.reasoning ?? "").length);
1402
- res.writeHead(200, { "Content-Type": "application/json" });
1403
- res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
1404
- } else {
1412
+ rejectGeneratedFiles(response, wire);
1413
+ if (body.stream !== true) await commitNonStream(entry, shapeORCompletion(appliedPlan.stop === "refusal" ? buildOpenAIRefusalCompletion(appliedPlan.refusal ?? "", responseModel, overrides, body.messages) : appliedPlan.stop === "content_filter" ? buildOpenAIContentFilterCompletion(responseModel, overrides, body.messages) : buildOpenAIReasoningCompletion(appliedPlan.reasoning ?? "", responseModel, openRouter, overrides, body.messages), overrides), misbehaviorPayloadLength(appliedPlan, appliedReasoning));
1414
+ else {
1405
1415
  const chunks = appliedPlan.stop === "refusal" ? buildOpenAIRefusalChunks(appliedPlan.refusal ?? "", responseModel, chunkSize, overrides) : appliedPlan.stop === "content_filter" ? buildOpenAIContentFilterChunks(responseModel, overrides) : buildOpenAIReasoningChunks(appliedPlan.reasoning ?? "", responseModel, chunkSize, openRouter, overrides);
1406
1416
  const usageChunk = emitStreamingUsage ? buildUsageChunk(chunks[0].id, overrides?.model ?? responseModel, chunks[0].created, resolveStreamingUsageTokens(overrides, ""), overrides?.systemFingerprint) : void 0;
1407
1417
  shapeORChunks(chunks, usageChunk, overrides);
1408
- const interruption = createInterruptionSignal(fixture);
1409
- if (!await writeSSEStream(res, chunks, {
1410
- latency,
1411
- streamingProfile: fixture.streamingProfile,
1412
- signal: interruption?.signal,
1413
- onChunkSent: interruption?.tick,
1418
+ recordOutcome(entry);
1419
+ await deliverStream(res, entry, fixture, sseCodec({
1420
+ done: true,
1414
1421
  usageChunk,
1415
- recordedTimings: resolveRecordedTimings(chunks.length),
1416
- replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
1417
1422
  openRouterProcessing
1418
- })) {
1419
- if (!res.writableEnded) res.destroy();
1420
- entry.response.interrupted = true;
1421
- entry.response.interruptReason = interruption?.reason();
1422
- }
1423
- interruption?.cleanup();
1423
+ }), chunks, streamTiming(chunks.length));
1424
1424
  }
1425
1425
  return;
1426
1426
  }
@@ -1441,37 +1441,26 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1441
1441
  fixture
1442
1442
  }
1443
1443
  });
1444
- recordOutcome(journalEntry);
1444
+ rejectGeneratedFiles(response, wire);
1445
1445
  const toolCalls = requireEmittedFunctionToolCalls(response, wire);
1446
1446
  const completionText = blockOutcome ? blockOutcome.content + blockOutcome.toolCalls.map((tc) => tc.name + tc.arguments).join("") : (response.content ?? "") + toolCalls.map((tc) => tc.name + tc.arguments).join("");
1447
1447
  if (!streaming) {
1448
- await awaitNonStreamingTiming((effReasoning?.length ?? 0) + completionText.length);
1449
1448
  const completion = buildContentWithToolCallsCompletion(blockOutcome?.content ?? response.content ?? "", blockOutcome?.toolCalls ?? toolCalls, responseModel, effReasoning, blockOutcome ? {
1450
1449
  ...overrides,
1451
1450
  finishReason: overrides?.finishReason ?? (blockOutcome.hasToolCalls ? "tool_calls" : "stop")
1452
1451
  } : overrides, body.messages);
1453
- res.writeHead(200, { "Content-Type": "application/json" });
1454
- res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
1452
+ const contentLength = (effReasoning?.length ?? 0) + completionText.length;
1453
+ await commitNonStream(journalEntry, shapeORCompletion(completion, overrides), contentLength);
1455
1454
  } else {
1456
1455
  const chunks = buildContentWithToolCallsChunks(blockOutcome?.content ?? response.content ?? "", blockOutcome?.toolCalls ?? toolCalls, responseModel, chunkSize, effReasoning, overrides, blockOutcome?.ordered);
1457
1456
  const usageChunk = emitStreamingUsage ? buildUsageChunk(chunks[0]?.id ?? "chatcmpl-unknown", overrides?.model ?? responseModel, chunks[0]?.created ?? Math.floor(Date.now() / 1e3), resolveStreamingUsageTokens(overrides, completionText), overrides?.systemFingerprint) : void 0;
1458
1457
  shapeORChunks(chunks, usageChunk, overrides);
1459
- const interruption = createInterruptionSignal(fixture);
1460
- if (!await writeSSEStream(res, chunks, {
1461
- latency,
1462
- streamingProfile: fixture.streamingProfile,
1463
- signal: interruption?.signal,
1464
- onChunkSent: interruption?.tick,
1458
+ recordOutcome(journalEntry);
1459
+ await deliverStream(res, journalEntry, fixture, sseCodec({
1460
+ done: true,
1465
1461
  usageChunk,
1466
- recordedTimings: resolveRecordedTimings(chunks.length),
1467
- replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
1468
1462
  openRouterProcessing
1469
- })) {
1470
- if (!res.writableEnded) res.destroy();
1471
- journalEntry.response.interrupted = true;
1472
- journalEntry.response.interruptReason = interruption?.reason();
1473
- }
1474
- interruption?.cleanup();
1463
+ }), chunks, streamTiming(chunks.length));
1475
1464
  }
1476
1465
  return;
1477
1466
  }
@@ -1490,32 +1479,21 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1490
1479
  fixture
1491
1480
  }
1492
1481
  });
1493
- recordOutcome(journalEntry);
1482
+ rejectGeneratedFiles(response, wire);
1494
1483
  if (body.stream !== true) {
1495
- await awaitNonStreamingTiming((effReasoning?.length ?? 0) + response.content.length);
1496
1484
  const completion = buildTextCompletion(response.content, responseModel, effReasoning, overrides, body.messages);
1497
- res.writeHead(200, { "Content-Type": "application/json" });
1498
- res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
1485
+ const contentLength = (effReasoning?.length ?? 0) + response.content.length;
1486
+ await commitNonStream(journalEntry, shapeORCompletion(completion, overrides), contentLength);
1499
1487
  } else {
1500
1488
  const chunks = buildTextChunks(response.content, responseModel, chunkSize, effReasoning, overrides);
1501
1489
  const usageChunk = emitStreamingUsage ? buildUsageChunk(chunks[0]?.id ?? "chatcmpl-unknown", overrides?.model ?? responseModel, chunks[0]?.created ?? Math.floor(Date.now() / 1e3), resolveStreamingUsageTokens(overrides, response.content), overrides?.systemFingerprint) : void 0;
1502
1490
  shapeORChunks(chunks, usageChunk, overrides);
1503
- const interruption = createInterruptionSignal(fixture);
1504
- if (!await writeSSEStream(res, chunks, {
1505
- latency,
1506
- streamingProfile: fixture.streamingProfile,
1507
- signal: interruption?.signal,
1508
- onChunkSent: interruption?.tick,
1491
+ recordOutcome(journalEntry);
1492
+ await deliverStream(res, journalEntry, fixture, sseCodec({
1493
+ done: true,
1509
1494
  usageChunk,
1510
- recordedTimings: resolveRecordedTimings(chunks.length),
1511
- replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
1512
1495
  openRouterProcessing
1513
- })) {
1514
- if (!res.writableEnded) res.destroy();
1515
- journalEntry.response.interrupted = true;
1516
- journalEntry.response.interruptReason = interruption?.reason();
1517
- }
1518
- interruption?.cleanup();
1496
+ }), chunks, streamTiming(chunks.length));
1519
1497
  }
1520
1498
  return;
1521
1499
  }
@@ -1534,35 +1512,24 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1534
1512
  fixture
1535
1513
  }
1536
1514
  });
1537
- recordOutcome(journalEntry);
1515
+ rejectGeneratedFiles(response, wire);
1538
1516
  const toolCalls = requireServedFunctionToolCalls(response, wire);
1539
1517
  if (body.stream !== true) {
1540
1518
  const toolCallText = toolCalls.map((tc) => tc.name + tc.arguments).join("");
1541
- await awaitNonStreamingTiming((effReasoning?.length ?? 0) + toolCallText.length);
1542
1519
  const completion = buildToolCallCompletion(toolCalls, responseModel, effReasoning, overrides, body.messages);
1543
- res.writeHead(200, { "Content-Type": "application/json" });
1544
- res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
1520
+ const contentLength = (effReasoning?.length ?? 0) + toolCallText.length;
1521
+ await commitNonStream(journalEntry, shapeORCompletion(completion, overrides), contentLength);
1545
1522
  } else {
1546
1523
  const chunks = buildToolCallChunks(toolCalls, responseModel, chunkSize, effReasoning, overrides);
1547
1524
  const completionText = toolCalls.map((tc) => tc.name + tc.arguments).join("");
1548
1525
  const usageChunk = emitStreamingUsage ? buildUsageChunk(chunks[0]?.id ?? "chatcmpl-unknown", overrides?.model ?? responseModel, chunks[0]?.created ?? Math.floor(Date.now() / 1e3), resolveStreamingUsageTokens(overrides, completionText), overrides?.systemFingerprint) : void 0;
1549
1526
  shapeORChunks(chunks, usageChunk, overrides);
1550
- const interruption = createInterruptionSignal(fixture);
1551
- if (!await writeSSEStream(res, chunks, {
1552
- latency,
1553
- streamingProfile: fixture.streamingProfile,
1554
- signal: interruption?.signal,
1555
- onChunkSent: interruption?.tick,
1527
+ recordOutcome(journalEntry);
1528
+ await deliverStream(res, journalEntry, fixture, sseCodec({
1529
+ done: true,
1556
1530
  usageChunk,
1557
- recordedTimings: resolveRecordedTimings(chunks.length),
1558
- replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
1559
1531
  openRouterProcessing
1560
- })) {
1561
- if (!res.writableEnded) res.destroy();
1562
- journalEntry.response.interrupted = true;
1563
- journalEntry.response.interruptReason = interruption?.reason();
1564
- }
1565
- interruption?.cleanup();
1532
+ }), chunks, streamTiming(chunks.length));
1566
1533
  }
1567
1534
  return;
1568
1535
  }
@@ -1822,10 +1789,12 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
1822
1789
  return status >= 400 && status < 500 ? "invalid_request_error" : "server_error";
1823
1790
  }
1824
1791
  /**
1825
- * Envelope for a fixture tool-call error (see isFixtureToolCallError: invalid
1826
- * JSON arguments on an object wire, a custom tool call on a non-Responses
1827
- * wire, or a malformed fixture tool call). It is the provider envelope aimock
1828
- * emits for that route, with the aimock `code` in it: Google's
1792
+ * Envelope for a fixture error (see isFixtureToolCallError: invalid JSON
1793
+ * arguments on an object wire, a custom tool call on a non-Responses wire, a
1794
+ * malformed fixture tool call, generated `files` on a wire that cannot carry
1795
+ * them, or a malformed fixture `citations` or `files` value). It is the
1796
+ * provider envelope aimock emits for that route, with the aimock `code` in
1797
+ * it: Google's
1829
1798
  * `google.rpc.ErrorInfo` detail on Gemini and Vertex AI, a string
1830
1799
  * `error.code` on Gemini Interactions and Anthropic Messages, a `reason`
1831
1800
  * member on Bedrock (a `code` member would replace `__type` as the AWS SDK's
@@ -1955,7 +1924,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
1955
1924
  const existing = lastJournalEntryFor(req);
1956
1925
  const bodyLimitDisconnect = clientFault && (req.destroyed || res.destroyed);
1957
1926
  if ((clientAbort || bodyLimitDisconnect) && !res.headersSent) {
1958
- const interruptReason = bodyLimitDisconnect ? "request body exceeded size limit" : "client aborted";
1927
+ const interruptReason = bodyLimitDisconnect ? "request body exceeded size limit" : CLIENT_ABORTED;
1959
1928
  if (existing) {
1960
1929
  if (!existing.response.interrupted) {
1961
1930
  existing.response.status = 0;
@@ -2011,7 +1980,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2011
1980
  type: errorTypeForStatus(status)
2012
1981
  }).body);
2013
1982
  } else {
2014
- const midStreamReason = clientAbort ? "client aborted" : "handler crashed mid-stream";
1983
+ const midStreamReason = clientAbort ? CLIENT_ABORTED : "handler crashed mid-stream";
2015
1984
  if (existing) {
2016
1985
  if (res.writableEnded) {
2017
1986
  if (existing.response.error === void 0) existing.response.error = msg;