@copilotkit/aimock 1.24.1 → 1.26.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 (206) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +38 -0
  4. package/README.md +21 -11
  5. package/dist/agui-types.d.cts.map +1 -1
  6. package/dist/agui-types.d.ts.map +1 -1
  7. package/dist/aws-event-stream.cjs +2 -1
  8. package/dist/aws-event-stream.cjs.map +1 -1
  9. package/dist/aws-event-stream.d.cts +3 -1
  10. package/dist/aws-event-stream.d.cts.map +1 -1
  11. package/dist/aws-event-stream.d.ts +3 -1
  12. package/dist/aws-event-stream.d.ts.map +1 -1
  13. package/dist/aws-event-stream.js +2 -1
  14. package/dist/aws-event-stream.js.map +1 -1
  15. package/dist/bedrock-converse.cjs +8 -2
  16. package/dist/bedrock-converse.cjs.map +1 -1
  17. package/dist/bedrock-converse.d.cts.map +1 -1
  18. package/dist/bedrock-converse.d.ts.map +1 -1
  19. package/dist/bedrock-converse.js +8 -2
  20. package/dist/bedrock-converse.js.map +1 -1
  21. package/dist/bedrock.cjs +8 -2
  22. package/dist/bedrock.cjs.map +1 -1
  23. package/dist/bedrock.d.cts.map +1 -1
  24. package/dist/bedrock.d.ts.map +1 -1
  25. package/dist/bedrock.js +8 -2
  26. package/dist/bedrock.js.map +1 -1
  27. package/dist/cli.cjs +36 -1
  28. package/dist/cli.cjs.map +1 -1
  29. package/dist/cli.js +36 -1
  30. package/dist/cli.js.map +1 -1
  31. package/dist/cohere.cjs +206 -2
  32. package/dist/cohere.cjs.map +1 -1
  33. package/dist/cohere.d.cts.map +1 -1
  34. package/dist/cohere.d.ts.map +1 -1
  35. package/dist/cohere.js +207 -4
  36. package/dist/cohere.js.map +1 -1
  37. package/dist/config-loader.d.ts.map +1 -1
  38. package/dist/elevenlabs-audio.cjs +173 -1
  39. package/dist/elevenlabs-audio.cjs.map +1 -1
  40. package/dist/elevenlabs-audio.d.cts.map +1 -1
  41. package/dist/elevenlabs-audio.d.ts.map +1 -1
  42. package/dist/elevenlabs-audio.js +173 -2
  43. package/dist/elevenlabs-audio.js.map +1 -1
  44. package/dist/embeddings.cjs +1 -1
  45. package/dist/embeddings.cjs.map +1 -1
  46. package/dist/embeddings.js +1 -1
  47. package/dist/embeddings.js.map +1 -1
  48. package/dist/fal-audio.cjs +2 -4
  49. package/dist/fal-audio.cjs.map +1 -1
  50. package/dist/fal-audio.js +2 -4
  51. package/dist/fal-audio.js.map +1 -1
  52. package/dist/fal.cjs +2 -2
  53. package/dist/fal.cjs.map +1 -1
  54. package/dist/fal.d.cts.map +1 -1
  55. package/dist/fal.d.ts.map +1 -1
  56. package/dist/fal.js +2 -2
  57. package/dist/fal.js.map +1 -1
  58. package/dist/fixture-loader.cjs +16 -3
  59. package/dist/fixture-loader.cjs.map +1 -1
  60. package/dist/fixture-loader.d.cts.map +1 -1
  61. package/dist/fixture-loader.d.ts.map +1 -1
  62. package/dist/fixture-loader.js +16 -3
  63. package/dist/fixture-loader.js.map +1 -1
  64. package/dist/gemini-embeddings.cjs +166 -0
  65. package/dist/gemini-embeddings.cjs.map +1 -0
  66. package/dist/gemini-embeddings.js +166 -0
  67. package/dist/gemini-embeddings.js.map +1 -0
  68. package/dist/gemini-interactions.cjs +10 -2
  69. package/dist/gemini-interactions.cjs.map +1 -1
  70. package/dist/gemini-interactions.d.cts.map +1 -1
  71. package/dist/gemini-interactions.d.ts.map +1 -1
  72. package/dist/gemini-interactions.js +10 -2
  73. package/dist/gemini-interactions.js.map +1 -1
  74. package/dist/gemini.cjs +12 -2
  75. package/dist/gemini.cjs.map +1 -1
  76. package/dist/gemini.d.cts.map +1 -1
  77. package/dist/gemini.d.ts.map +1 -1
  78. package/dist/gemini.js +12 -2
  79. package/dist/gemini.js.map +1 -1
  80. package/dist/helpers.cjs +70 -33
  81. package/dist/helpers.cjs.map +1 -1
  82. package/dist/helpers.d.cts +9 -5
  83. package/dist/helpers.d.cts.map +1 -1
  84. package/dist/helpers.d.ts +9 -5
  85. package/dist/helpers.d.ts.map +1 -1
  86. package/dist/helpers.js +68 -34
  87. package/dist/helpers.js.map +1 -1
  88. package/dist/images.cjs +295 -13
  89. package/dist/images.cjs.map +1 -1
  90. package/dist/images.d.cts +9 -1
  91. package/dist/images.d.cts.map +1 -1
  92. package/dist/images.d.ts +9 -1
  93. package/dist/images.d.ts.map +1 -1
  94. package/dist/images.js +294 -14
  95. package/dist/images.js.map +1 -1
  96. package/dist/index.cjs +1 -1
  97. package/dist/index.d.cts +2 -2
  98. package/dist/index.d.ts +2 -2
  99. package/dist/index.js +1 -1
  100. package/dist/llmock.cjs +16 -1
  101. package/dist/llmock.cjs.map +1 -1
  102. package/dist/llmock.d.cts +2 -0
  103. package/dist/llmock.d.cts.map +1 -1
  104. package/dist/llmock.d.ts +2 -0
  105. package/dist/llmock.d.ts.map +1 -1
  106. package/dist/llmock.js +16 -1
  107. package/dist/llmock.js.map +1 -1
  108. package/dist/messages.cjs +9 -2
  109. package/dist/messages.cjs.map +1 -1
  110. package/dist/messages.d.cts.map +1 -1
  111. package/dist/messages.d.ts.map +1 -1
  112. package/dist/messages.js +9 -2
  113. package/dist/messages.js.map +1 -1
  114. package/dist/metrics.cjs +2 -0
  115. package/dist/metrics.cjs.map +1 -1
  116. package/dist/metrics.d.cts.map +1 -1
  117. package/dist/metrics.d.ts.map +1 -1
  118. package/dist/metrics.js +2 -0
  119. package/dist/metrics.js.map +1 -1
  120. package/dist/ndjson-writer.cjs +2 -1
  121. package/dist/ndjson-writer.cjs.map +1 -1
  122. package/dist/ndjson-writer.d.cts +3 -2
  123. package/dist/ndjson-writer.d.cts.map +1 -1
  124. package/dist/ndjson-writer.d.ts +3 -2
  125. package/dist/ndjson-writer.d.ts.map +1 -1
  126. package/dist/ndjson-writer.js +2 -1
  127. package/dist/ndjson-writer.js.map +1 -1
  128. package/dist/ollama.cjs +197 -2
  129. package/dist/ollama.cjs.map +1 -1
  130. package/dist/ollama.d.cts.map +1 -1
  131. package/dist/ollama.d.ts.map +1 -1
  132. package/dist/ollama.js +198 -4
  133. package/dist/ollama.js.map +1 -1
  134. package/dist/recorder.cjs +49 -5
  135. package/dist/recorder.cjs.map +1 -1
  136. package/dist/recorder.d.cts.map +1 -1
  137. package/dist/recorder.d.ts.map +1 -1
  138. package/dist/recorder.js +49 -5
  139. package/dist/recorder.js.map +1 -1
  140. package/dist/responses.cjs +11 -2
  141. package/dist/responses.cjs.map +1 -1
  142. package/dist/responses.d.cts.map +1 -1
  143. package/dist/responses.d.ts.map +1 -1
  144. package/dist/responses.js +11 -2
  145. package/dist/responses.js.map +1 -1
  146. package/dist/server.cjs +196 -49
  147. package/dist/server.cjs.map +1 -1
  148. package/dist/server.d.cts.map +1 -1
  149. package/dist/server.d.ts.map +1 -1
  150. package/dist/server.js +201 -54
  151. package/dist/server.js.map +1 -1
  152. package/dist/speech.cjs +1 -1
  153. package/dist/speech.cjs.map +1 -1
  154. package/dist/speech.js +1 -1
  155. package/dist/speech.js.map +1 -1
  156. package/dist/sse-writer.cjs +48 -10
  157. package/dist/sse-writer.cjs.map +1 -1
  158. package/dist/sse-writer.d.cts +12 -4
  159. package/dist/sse-writer.d.cts.map +1 -1
  160. package/dist/sse-writer.d.ts +12 -4
  161. package/dist/sse-writer.d.ts.map +1 -1
  162. package/dist/sse-writer.js +48 -10
  163. package/dist/sse-writer.js.map +1 -1
  164. package/dist/transcription.cjs +9 -6
  165. package/dist/transcription.cjs.map +1 -1
  166. package/dist/transcription.d.cts +2 -2
  167. package/dist/transcription.d.cts.map +1 -1
  168. package/dist/transcription.d.ts +2 -2
  169. package/dist/transcription.d.ts.map +1 -1
  170. package/dist/transcription.js +8 -7
  171. package/dist/transcription.js.map +1 -1
  172. package/dist/types.d.cts +45 -3
  173. package/dist/types.d.cts.map +1 -1
  174. package/dist/types.d.ts +45 -3
  175. package/dist/types.d.ts.map +1 -1
  176. package/dist/video.cjs +1 -1
  177. package/dist/video.cjs.map +1 -1
  178. package/dist/video.d.cts.map +1 -1
  179. package/dist/video.d.ts.map +1 -1
  180. package/dist/video.js +1 -1
  181. package/dist/video.js.map +1 -1
  182. package/dist/ws-gemini-live.cjs +14 -4
  183. package/dist/ws-gemini-live.cjs.map +1 -1
  184. package/dist/ws-gemini-live.d.cts +1 -0
  185. package/dist/ws-gemini-live.d.cts.map +1 -1
  186. package/dist/ws-gemini-live.d.ts +1 -0
  187. package/dist/ws-gemini-live.d.ts.map +1 -1
  188. package/dist/ws-gemini-live.js +15 -5
  189. package/dist/ws-gemini-live.js.map +1 -1
  190. package/dist/ws-realtime.cjs +21 -4
  191. package/dist/ws-realtime.cjs.map +1 -1
  192. package/dist/ws-realtime.d.cts +1 -0
  193. package/dist/ws-realtime.d.cts.map +1 -1
  194. package/dist/ws-realtime.d.ts +1 -0
  195. package/dist/ws-realtime.d.ts.map +1 -1
  196. package/dist/ws-realtime.js +22 -5
  197. package/dist/ws-realtime.js.map +1 -1
  198. package/dist/ws-responses.cjs +8 -5
  199. package/dist/ws-responses.cjs.map +1 -1
  200. package/dist/ws-responses.d.cts +1 -0
  201. package/dist/ws-responses.d.cts.map +1 -1
  202. package/dist/ws-responses.d.ts +1 -0
  203. package/dist/ws-responses.d.ts.map +1 -1
  204. package/dist/ws-responses.js +9 -6
  205. package/dist/ws-responses.js.map +1 -1
  206. package/package.json +2 -2
@@ -595,9 +595,13 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
595
595
  }
596
596
  }, isBeta);
597
597
  const content = response.content;
598
+ const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;
599
+ const { recordedTimings } = fixture;
600
+ let eventIndex = 0;
598
601
  for (let i = 0; i < content.length; i += chunkSize) {
599
602
  if (ws.isClosed) break;
600
- if (latency > 0) await require_sse_writer.delay(latency, interruption?.signal);
603
+ const chunkDelay = require_sse_writer.calculateDelay(eventIndex, void 0, latency, recordedTimings, replaySpeed);
604
+ if (chunkDelay > 0) await require_sse_writer.delay(chunkDelay, interruption?.signal);
601
605
  if (interruption?.signal.aborted) {
602
606
  interrupted = true;
603
607
  break;
@@ -611,6 +615,7 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
611
615
  content_index: contentIndex,
612
616
  delta: content.slice(i, i + chunkSize)
613
617
  }, isBeta);
618
+ eventIndex++;
614
619
  interruption?.tick();
615
620
  if (interruption?.signal.aborted) {
616
621
  interrupted = true;
@@ -710,7 +715,8 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
710
715
  const args = tc.arguments;
711
716
  for (let i = 0; i < args.length; i += chunkSize) {
712
717
  if (ws.isClosed) break;
713
- if (latency > 0) await require_sse_writer.delay(latency, interruption?.signal);
718
+ const chunkDelay = require_sse_writer.calculateDelay(eventIndex, void 0, latency, recordedTimings, replaySpeed);
719
+ if (chunkDelay > 0) await require_sse_writer.delay(chunkDelay, interruption?.signal);
714
720
  if (interruption?.signal.aborted) {
715
721
  interrupted = true;
716
722
  break;
@@ -724,6 +730,7 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
724
730
  call_id: callId,
725
731
  delta: args.slice(i, i + chunkSize)
726
732
  }, isBeta);
733
+ eventIndex++;
727
734
  interruption?.tick();
728
735
  if (interruption?.signal.aborted) {
729
736
  interrupted = true;
@@ -860,11 +867,15 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
860
867
  }
861
868
  }, isBeta);
862
869
  const content = response.content;
870
+ const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;
871
+ const { recordedTimings } = fixture;
863
872
  const interruption = require_interruption.createInterruptionSignal(fixture);
864
873
  let interrupted = false;
874
+ let eventIndex = 0;
865
875
  for (let i = 0; i < content.length; i += chunkSize) {
866
876
  if (ws.isClosed) break;
867
- if (latency > 0) await require_sse_writer.delay(latency, interruption?.signal);
877
+ const chunkDelay = require_sse_writer.calculateDelay(eventIndex, void 0, latency, recordedTimings, replaySpeed);
878
+ if (chunkDelay > 0) await require_sse_writer.delay(chunkDelay, interruption?.signal);
868
879
  if (interruption?.signal.aborted) {
869
880
  interrupted = true;
870
881
  break;
@@ -878,6 +889,7 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
878
889
  content_index: contentIndex,
879
890
  delta: content.slice(i, i + chunkSize)
880
891
  }, isBeta);
892
+ eventIndex++;
881
893
  interruption?.tick();
882
894
  if (interruption?.signal.aborted) {
883
895
  interrupted = true;
@@ -981,7 +993,10 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
981
993
  }, isBeta);
982
994
  const outputItems = [];
983
995
  const interruption = require_interruption.createInterruptionSignal(fixture);
996
+ const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;
997
+ const { recordedTimings } = fixture;
984
998
  let interrupted = false;
999
+ let eventIndex = 0;
985
1000
  for (let tcIdx = 0; tcIdx < response.toolCalls.length; tcIdx++) {
986
1001
  const tc = response.toolCalls[tcIdx];
987
1002
  const callId = tc.id ?? require_helpers.generateToolCallId();
@@ -1011,7 +1026,8 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
1011
1026
  const args = tc.arguments;
1012
1027
  for (let i = 0; i < args.length; i += chunkSize) {
1013
1028
  if (ws.isClosed) break;
1014
- if (latency > 0) await require_sse_writer.delay(latency, interruption?.signal);
1029
+ const chunkDelay = require_sse_writer.calculateDelay(eventIndex, void 0, latency, recordedTimings, replaySpeed);
1030
+ if (chunkDelay > 0) await require_sse_writer.delay(chunkDelay, interruption?.signal);
1015
1031
  if (interruption?.signal.aborted) {
1016
1032
  interrupted = true;
1017
1033
  break;
@@ -1026,6 +1042,7 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
1026
1042
  call_id: callId,
1027
1043
  delta: chunk
1028
1044
  }, isBeta);
1045
+ eventIndex++;
1029
1046
  interruption?.tick();
1030
1047
  if (interruption?.signal.aborted) {
1031
1048
  interrupted = true;
@@ -1 +1 @@
1
- {"version":3,"file":"ws-realtime.cjs","names":["generateToolCallId","DEFAULT_TEST_ID","matchFixture","resolveStrictMode","flattenHeaders","strictOverrideField","resolveResponse","isErrorResponse","isContentWithToolCallsResponse","createInterruptionSignal","delay","isTextResponse","isToolCallResponse"],"sources":["../src/ws-realtime.ts"],"sourcesContent":["/**\n * WebSocket handler for OpenAI Realtime API.\n *\n * Accepts Realtime API messages (session.update, conversation.item.create,\n * response.create) over WebSocket and sends back Realtime API events as\n * individual WebSocket text frames.\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport type { ChatCompletionRequest, ChatMessage, Fixture } from \"./types.js\";\nimport { matchFixture } from \"./router.js\";\nimport {\n generateToolCallId,\n flattenHeaders,\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n} from \"./helpers.js\";\nimport { createInterruptionSignal } from \"./interruption.js\";\nimport { delay } from \"./sse-writer.js\";\nimport { DEFAULT_TEST_ID, type Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport type { WebSocketConnection } from \"./ws-framing.js\";\n\n/** Generate a Realtime-API-style ID with underscore separator (e.g. event_xxx, item_xxx). */\nfunction realtimeId(prefix: string): string {\n return `${prefix}_${randomBytes(12).toString(\"base64url\")}`;\n}\n\n// ─── Realtime protocol types ────────────────────────────────────────────────\n\ninterface RealtimeItem {\n type: \"message\" | \"function_call\" | \"function_call_output\";\n id?: string;\n role?: \"user\" | \"assistant\" | \"system\";\n content?: Array<{ type: string; text?: string; url?: string; transcript?: string | null }>;\n name?: string;\n call_id?: string;\n arguments?: string;\n output?: string;\n}\n\ninterface SessionConfig {\n model: string;\n modalities: string[];\n instructions: string;\n tools: unknown[];\n voice: string | null;\n input_audio_format: string | null;\n output_audio_format: string | null;\n input_audio_noise_reduction: { type: string } | null;\n input_audio_transcription: { model: string } | null;\n turn_detection: unknown | null;\n temperature: number;\n type: \"conversation\" | \"transcription\" | \"translation\";\n reasoning: { effort: string } | null;\n}\n\ninterface RealtimeMessage {\n type: string;\n event_id?: string;\n session?: Partial<SessionConfig>;\n item?: RealtimeItem;\n response?: {\n modalities?: string[];\n instructions?: string;\n [key: string]: unknown;\n };\n}\n\n// ─── Conversion helpers ─────────────────────────────────────────────────────\n\nexport function realtimeItemsToMessages(\n items: RealtimeItem[],\n instructions?: string,\n logger?: Logger,\n): ChatMessage[] {\n const messages: ChatMessage[] = [];\n\n if (instructions) {\n messages.push({ role: \"system\", content: instructions });\n }\n\n for (const item of items) {\n if (item.type === \"message\") {\n const role =\n item.role === \"assistant\" ? \"assistant\" : item.role === \"system\" ? \"system\" : \"user\";\n\n // Check if content contains multimodal input types (input_text, input_image, input_audio)\n const hasMultimodal = item.content?.some(\n (p) => p.type === \"input_text\" || p.type === \"input_image\" || p.type === \"input_audio\",\n );\n\n if (hasMultimodal && item.content) {\n // Map realtime input content types to ChatMessage content parts\n const mappedContent = item.content.map((part) => {\n if (part.type === \"input_text\") {\n return { type: \"text\" as const, text: part.text ?? \"\" };\n }\n if (part.type === \"input_image\") {\n return {\n type: \"image_url\" as const,\n image_url: { url: part.url ?? \"\" },\n };\n }\n if (part.type === \"input_audio\") {\n return { type: \"text\" as const, text: \"[audio input]\" };\n }\n // Pass through unknown content types as-is\n return part;\n });\n messages.push({ role, content: mappedContent });\n } else {\n // Existing behavior: extract text from first content element\n const text = item.content?.[0]?.text ?? \"\";\n messages.push({ role, content: text });\n }\n } else if (item.type === \"function_call\") {\n if (!item.name) {\n logger?.warn(\"Realtime function_call item missing 'name'\");\n }\n messages.push({\n role: \"assistant\",\n content: null,\n tool_calls: [\n {\n id: item.call_id ?? generateToolCallId(),\n type: \"function\",\n function: {\n name: item.name ?? \"\",\n arguments: item.arguments ?? \"\",\n },\n },\n ],\n });\n } else if (item.type === \"function_call_output\") {\n if (!item.output) {\n logger?.warn(\"Realtime function_call_output item missing 'output'\");\n }\n messages.push({\n role: \"tool\",\n content: item.output ?? \"\",\n tool_call_id: item.call_id,\n });\n }\n }\n\n return messages;\n}\n\n// ─── GA -> Beta translation ─────────────────────────────────────────────────\n\n/** GA -> Beta event name mapping */\nconst GA_TO_BETA_EVENT: Record<string, string> = {\n \"response.output_text.delta\": \"response.text.delta\",\n \"response.output_text.done\": \"response.text.done\",\n \"response.output_audio.delta\": \"response.audio.delta\",\n \"response.output_audio.done\": \"response.audio.done\",\n \"response.output_audio_transcript.delta\": \"response.audio_transcript.delta\",\n \"response.output_audio_transcript.done\": \"response.audio_transcript.done\",\n \"conversation.item.added\": \"conversation.item.created\",\n};\n\n/** GA -> Beta content type mapping */\nconst GA_TO_BETA_CONTENT_TYPE: Record<string, string> = {\n output_text: \"text\",\n output_audio: \"audio\",\n};\n\n/** Events suppressed in Beta mode (GA-only events) */\nconst BETA_SUPPRESSED_EVENTS = new Set([\"conversation.item.done\"]);\n\nfunction translateGAToBeta(event: Record<string, unknown>): Record<string, unknown> | null {\n const type = event.type as string;\n if (BETA_SUPPRESSED_EVENTS.has(type)) return null;\n\n const translated = { ...event };\n if (GA_TO_BETA_EVENT[type]) {\n translated.type = GA_TO_BETA_EVENT[type];\n }\n\n // Translate content types in nested structures\n if (translated.part && typeof translated.part === \"object\") {\n const part = { ...(translated.part as Record<string, unknown>) };\n if (typeof part.type === \"string\" && GA_TO_BETA_CONTENT_TYPE[part.type]) {\n part.type = GA_TO_BETA_CONTENT_TYPE[part.type];\n }\n translated.part = part;\n }\n if (translated.content_part && typeof translated.content_part === \"object\") {\n const cp = { ...(translated.content_part as Record<string, unknown>) };\n if (typeof cp.type === \"string\" && GA_TO_BETA_CONTENT_TYPE[cp.type]) {\n cp.type = GA_TO_BETA_CONTENT_TYPE[cp.type];\n }\n translated.content_part = cp;\n }\n // Translate content arrays\n if (Array.isArray(translated.content)) {\n translated.content = (translated.content as Record<string, unknown>[]).map((c) => {\n if (typeof c.type === \"string\" && GA_TO_BETA_CONTENT_TYPE[c.type]) {\n return { ...c, type: GA_TO_BETA_CONTENT_TYPE[c.type] };\n }\n return c;\n });\n }\n // Translate item.content arrays (response.output_item.added/done, conversation.item.added)\n if (translated.item && typeof translated.item === \"object\") {\n const item = { ...(translated.item as Record<string, unknown>) };\n delete item.phase; // GA-only field\n if (Array.isArray(item.content)) {\n item.content = (item.content as Record<string, unknown>[]).map((c) => {\n if (typeof c.type === \"string\" && GA_TO_BETA_CONTENT_TYPE[c.type]) {\n return { ...c, type: GA_TO_BETA_CONTENT_TYPE[c.type] };\n }\n return c;\n });\n }\n translated.item = item;\n }\n // Translate response.output[].content arrays (response.done)\n if (translated.response && typeof translated.response === \"object\") {\n const resp = { ...(translated.response as Record<string, unknown>) };\n if (Array.isArray(resp.output)) {\n resp.output = (resp.output as Record<string, unknown>[]).map((outItem) => {\n const o = { ...(outItem as Record<string, unknown>) };\n if (Array.isArray(o.content)) {\n o.content = (o.content as Record<string, unknown>[]).map((c) =>\n typeof c.type === \"string\" && GA_TO_BETA_CONTENT_TYPE[c.type]\n ? { ...c, type: GA_TO_BETA_CONTENT_TYPE[c.type] }\n : c,\n );\n }\n return o;\n });\n }\n translated.response = resp;\n }\n\n // Flatten GA session config for Beta (session.created / session.updated)\n if (type === \"session.created\" || type === \"session.updated\") {\n if (translated.session && typeof translated.session === \"object\") {\n const session = { ...(translated.session as Record<string, unknown>) };\n if (session.audio && typeof session.audio === \"object\") {\n const audio = session.audio as Record<string, unknown>;\n session.voice = audio.voice;\n session.input_audio_format = audio.input_audio_format;\n session.output_audio_format = audio.output_audio_format;\n session.input_audio_transcription = audio.input_audio_transcription;\n delete session.audio;\n }\n delete session.type;\n delete session.reasoning;\n translated.session = session;\n }\n }\n\n return translated;\n}\n\n// ─── Event sending ──────────────────────────────────────────────────────────\n\nfunction sendEvent(ws: WebSocketConnection, event: Record<string, unknown>, isBeta: boolean): void {\n const out = { ...event, event_id: event.event_id ?? realtimeId(\"event\") };\n if (isBeta) {\n const translated = translateGAToBeta(out);\n if (translated === null) return; // suppressed in Beta mode\n ws.send(JSON.stringify(translated));\n } else {\n ws.send(JSON.stringify(out));\n }\n}\n\nfunction buildErrorRealtimeEvent(\n ws: WebSocketConnection,\n message: string,\n isBeta: boolean,\n type = \"invalid_request_error\",\n code?: string,\n): void {\n sendEvent(ws, { type: \"error\", error: { message, type, code } }, isBeta);\n}\n\n// ─── Main handler ───────────────────────────────────────────────────────────\n\nexport function handleWebSocketRealtime(\n ws: WebSocketConnection,\n fixtures: Fixture[],\n journal: Journal,\n defaults: {\n latency: number;\n chunkSize: number;\n model: string;\n logger: Logger;\n strict?: boolean;\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest;\n testId?: string;\n upgradeHeaders?: import(\"node:http\").IncomingHttpHeaders;\n },\n): void {\n const { logger } = defaults;\n const sessionId = realtimeId(\"sess\");\n\n const isBeta = defaults.upgradeHeaders?.[\"openai-beta\"]\n ? String(defaults.upgradeHeaders[\"openai-beta\"]).includes(\"realtime=v1\")\n : false;\n\n const session: SessionConfig = {\n model: defaults.model,\n modalities: [\"text\"],\n instructions: \"\",\n tools: [],\n voice: null,\n input_audio_format: null,\n output_audio_format: null,\n input_audio_noise_reduction: null,\n input_audio_transcription: null,\n turn_detection: null,\n temperature: 0.8,\n type: \"conversation\",\n reasoning: null,\n };\n\n const conversationItems: RealtimeItem[] = [];\n\n // Send session.created immediately on connect (GA format — shim flattens for Beta)\n sendEvent(\n ws,\n {\n type: \"session.created\",\n session: {\n id: sessionId,\n object: \"realtime.session\",\n model: session.model,\n expires_at: Math.floor(Date.now() / 1000) + 3600,\n modalities: session.modalities,\n instructions: session.instructions,\n tools: session.tools,\n tool_choice: \"auto\",\n temperature: session.temperature,\n max_response_output_tokens: \"inf\",\n audio: {\n voice: session.voice,\n input_audio_format: session.input_audio_format,\n output_audio_format: session.output_audio_format,\n input_audio_noise_reduction: session.input_audio_noise_reduction,\n input_audio_transcription: session.input_audio_transcription,\n },\n turn_detection: session.turn_detection,\n type: session.type,\n reasoning: session.reasoning,\n },\n },\n isBeta,\n );\n\n // Serialize message processing to prevent event interleaving\n let pending = Promise.resolve();\n ws.on(\"message\", (raw: string) => {\n pending = pending.then(() =>\n processMessage(\n raw,\n ws,\n fixtures,\n journal,\n defaults,\n session,\n conversationItems,\n isBeta,\n ).catch((err: unknown) => {\n const msg = err instanceof Error ? err.message : \"Internal error\";\n logger.error(`WebSocket realtime error: ${msg}`);\n try {\n buildErrorRealtimeEvent(ws, msg, isBeta, \"server_error\");\n } catch (sendErr) {\n defaults.logger.debug(\n `Failed to send error to client: ${sendErr instanceof Error ? sendErr.message : \"unknown\"}`,\n );\n }\n }),\n );\n });\n}\n\nasync function processMessage(\n raw: string,\n ws: WebSocketConnection,\n fixtures: Fixture[],\n journal: Journal,\n defaults: {\n latency: number;\n chunkSize: number;\n model: string;\n logger: Logger;\n strict?: boolean;\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest;\n testId?: string;\n upgradeHeaders?: import(\"node:http\").IncomingHttpHeaders;\n },\n session: SessionConfig,\n conversationItems: RealtimeItem[],\n isBeta: boolean,\n): Promise<void> {\n let parsed: RealtimeMessage;\n try {\n parsed = JSON.parse(raw) as RealtimeMessage;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n buildErrorRealtimeEvent(\n ws,\n `Malformed JSON: ${detail}`,\n isBeta,\n \"invalid_request_error\",\n \"invalid_json\",\n );\n return;\n }\n\n const msgType = parsed.type;\n\n // ── session.update ────────────────────────────────────────────────────\n if (msgType === \"session.update\") {\n if (parsed.session) {\n const s = parsed.session;\n\n // Validate session.type value before applying any mutations\n const validTypes = new Set([\"conversation\", \"transcription\", \"translation\"]);\n if ((s as Record<string, unknown>).type !== undefined) {\n if (!validTypes.has((s as Record<string, unknown>).type as string)) {\n sendEvent(\n ws,\n {\n type: \"error\",\n error: {\n message: `Invalid session type: ${(s as Record<string, unknown>).type}`,\n type: \"invalid_request_error\",\n code: \"invalid_session_config\",\n },\n },\n isBeta,\n );\n return;\n }\n }\n\n // Capture pre-mutation values for rollback on validation failure\n const prevModel = session.model;\n const prevType = session.type;\n\n if (s.instructions !== undefined) session.instructions = s.instructions;\n if (s.tools !== undefined) session.tools = s.tools;\n if (s.modalities !== undefined) session.modalities = s.modalities;\n if (s.model !== undefined) session.model = s.model;\n if (s.temperature !== undefined) session.temperature = s.temperature;\n if ((s as Record<string, unknown>).type !== undefined)\n session.type = (s as Record<string, unknown>).type as SessionConfig[\"type\"];\n // GA nested audio config\n if ((s as Record<string, unknown>).audio) {\n const audio = (s as Record<string, unknown>).audio as Record<string, unknown>;\n if (audio.voice !== undefined) session.voice = audio.voice as string | null;\n if (audio.input_audio_format !== undefined)\n session.input_audio_format = audio.input_audio_format as string | null;\n if (audio.output_audio_format !== undefined)\n session.output_audio_format = audio.output_audio_format as string | null;\n if (audio.input_audio_noise_reduction !== undefined)\n session.input_audio_noise_reduction = audio.input_audio_noise_reduction as {\n type: string;\n } | null;\n if (audio.input_audio_transcription !== undefined)\n session.input_audio_transcription = audio.input_audio_transcription as {\n model: string;\n } | null;\n }\n // Beta flat fields (backward compat)\n if (s.voice !== undefined) session.voice = s.voice;\n if (s.input_audio_format !== undefined) session.input_audio_format = s.input_audio_format;\n if (s.output_audio_format !== undefined) session.output_audio_format = s.output_audio_format;\n // reasoning config\n if ((s as Record<string, unknown>).reasoning !== undefined)\n session.reasoning = (s as Record<string, unknown>).reasoning as {\n effort: string;\n } | null;\n\n // Validate model+type combinations (rollback on failure)\n const transcriptionModels = new Set([\n \"gpt-4o-transcribe\",\n \"gpt-4o-mini-transcribe\",\n \"gpt-realtime-whisper\",\n \"whisper-1\",\n ]);\n const translationModels = new Set([\n \"gpt-4o-transcribe\",\n \"gpt-4o-mini-transcribe\",\n \"gpt-realtime-translate\",\n ]);\n\n if (session.type === \"transcription\" && !transcriptionModels.has(session.model)) {\n session.model = prevModel;\n session.type = prevType;\n sendEvent(\n ws,\n {\n type: \"error\",\n error: {\n message: `Model ${s.model ?? prevModel} does not support session type transcription`,\n type: \"invalid_request_error\",\n code: \"invalid_session_config\",\n },\n },\n isBeta,\n );\n return;\n }\n if (session.type === \"translation\" && !translationModels.has(session.model)) {\n session.model = prevModel;\n session.type = prevType;\n sendEvent(\n ws,\n {\n type: \"error\",\n error: {\n message: `Model ${s.model ?? prevModel} does not support session type translation`,\n type: \"invalid_request_error\",\n code: \"invalid_session_config\",\n },\n },\n isBeta,\n );\n return;\n }\n }\n\n sendEvent(\n ws,\n {\n type: \"session.updated\",\n session: {\n object: \"realtime.session\",\n model: session.model,\n expires_at: Math.floor(Date.now() / 1000) + 3600,\n modalities: session.modalities,\n instructions: session.instructions,\n tools: session.tools,\n tool_choice: \"auto\",\n temperature: session.temperature,\n max_response_output_tokens: \"inf\",\n audio: {\n voice: session.voice,\n input_audio_format: session.input_audio_format,\n output_audio_format: session.output_audio_format,\n input_audio_noise_reduction: session.input_audio_noise_reduction,\n input_audio_transcription: session.input_audio_transcription,\n },\n turn_detection: session.turn_detection,\n type: session.type,\n reasoning: session.reasoning,\n },\n },\n isBeta,\n );\n return;\n }\n\n // ── conversation.item.create ──────────────────────────────────────────\n if (msgType === \"conversation.item.create\") {\n if (!parsed.item) {\n buildErrorRealtimeEvent(\n ws,\n \"Missing 'item' in conversation.item.create\",\n isBeta,\n \"invalid_request_error\",\n );\n return;\n }\n const item = parsed.item;\n if (!item.id) {\n item.id = realtimeId(\"item\");\n }\n const previousId =\n conversationItems.length > 0\n ? (conversationItems[conversationItems.length - 1].id ?? null)\n : null;\n conversationItems.push(item);\n sendEvent(ws, { type: \"conversation.item.added\", previous_item_id: previousId, item }, isBeta);\n return;\n }\n\n // ── response.create ───────────────────────────────────────────────────\n if (msgType === \"response.create\") {\n await handleResponseCreate(\n ws,\n fixtures,\n journal,\n defaults,\n session,\n conversationItems,\n isBeta,\n parsed.response,\n );\n return;\n }\n\n // ── input_audio_buffer.append ────────────────────────────────────────\n if (msgType === \"input_audio_buffer.append\") {\n // Accept silently — aimock doesn't process actual audio\n return;\n }\n\n // ── input_audio_buffer.commit ──────────────────────────────────────\n if (msgType === \"input_audio_buffer.commit\") {\n sendEvent(ws, { type: \"input_audio_buffer.committed\" }, isBeta);\n // In transcription/translation mode, add a placeholder user item\n if (session.type === \"transcription\" || session.type === \"translation\") {\n const audioItem: RealtimeItem = {\n type: \"message\",\n id: realtimeId(\"item\"),\n role: \"user\",\n content: [{ type: \"input_audio\", transcript: null }],\n };\n conversationItems.push(audioItem);\n sendEvent(\n ws,\n {\n type: \"conversation.item.added\",\n item: audioItem,\n },\n isBeta,\n );\n }\n return;\n }\n\n // ── input_audio_buffer.clear ───────────────────────────────────────\n if (msgType === \"input_audio_buffer.clear\") {\n sendEvent(ws, { type: \"input_audio_buffer.cleared\" }, isBeta);\n return;\n }\n\n // ── response.cancel ────────────────────────────────────────────────\n if (msgType === \"response.cancel\") {\n sendEvent(ws, { type: \"response.cancelled\" }, isBeta);\n return;\n }\n\n // Unknown message type — ignore silently (matches OpenAI behavior)\n}\n\nasync function handleResponseCreate(\n ws: WebSocketConnection,\n fixtures: Fixture[],\n journal: Journal,\n defaults: {\n latency: number;\n chunkSize: number;\n model: string;\n logger: Logger;\n strict?: boolean;\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest;\n testId?: string;\n upgradeHeaders?: import(\"node:http\").IncomingHttpHeaders;\n },\n session: SessionConfig,\n conversationItems: RealtimeItem[],\n isBeta: boolean,\n responseOverrides?: { instructions?: string; [key: string]: unknown },\n): Promise<void> {\n const instructions = (responseOverrides?.instructions ?? session.instructions) || undefined;\n const messages = realtimeItemsToMessages(conversationItems, instructions, defaults.logger);\n\n const endpointTypeMap: Record<string, string> = {\n conversation: \"realtime\",\n transcription: \"realtime-transcription\",\n translation: \"realtime-translation\",\n };\n const endpointType = endpointTypeMap[session.type] ?? \"realtime\";\n\n const completionReq: ChatCompletionRequest = {\n model: session.model,\n messages,\n _endpointType: endpointType,\n };\n\n const testId = defaults.testId ?? DEFAULT_TEST_ID;\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n const responseId = realtimeId(\"resp\");\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (!fixture) {\n if (resolveStrictMode(defaults.strict, defaults.upgradeHeaders)) {\n defaults.logger.warn(`STRICT: No fixture matched for WebSocket message`);\n journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, defaults.upgradeHeaders),\n },\n });\n ws.close(1008, \"Strict mode: no fixture matched\");\n return;\n }\n journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, defaults.upgradeHeaders),\n },\n });\n // Send response.created with failed status then response.done with error\n sendEvent(\n ws,\n {\n type: \"response.created\",\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"failed\",\n status_details: null,\n output: [],\n usage: null,\n },\n },\n isBeta,\n );\n sendEvent(\n ws,\n {\n type: \"response.done\",\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"failed\",\n output: [],\n status_details: {\n type: \"error\",\n error: {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n },\n usage: { total_tokens: 0, input_tokens: 0, output_tokens: 0 },\n },\n },\n isBeta,\n );\n return;\n }\n\n const response = await resolveResponse(fixture, completionReq);\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n // ── Error fixture ───────────────────────────────────────────────────\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status, fixture },\n });\n sendEvent(\n ws,\n {\n type: \"response.created\",\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"failed\",\n status_details: null,\n output: [],\n usage: null,\n },\n },\n isBeta,\n );\n sendEvent(\n ws,\n {\n type: \"response.done\",\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"failed\",\n output: [],\n status_details: {\n type: \"error\",\n error: {\n message: response.error.message,\n type: response.error.type,\n code: response.error.code,\n },\n },\n usage: { total_tokens: 0, input_tokens: 0, output_tokens: 0 },\n },\n },\n isBeta,\n );\n return;\n }\n\n // ── Content + tool calls response ──────────────────────────────────\n if (isContentWithToolCallsResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 200, fixture },\n });\n\n // response.created\n sendEvent(\n ws,\n {\n type: \"response.created\",\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"in_progress\",\n status_details: null,\n output: [],\n usage: null,\n },\n },\n isBeta,\n );\n\n const interruption = createInterruptionSignal(fixture);\n let interrupted = false;\n const allOutputItems: unknown[] = [];\n\n // ── Text content part ──────────────────────────────────────────\n const textItemId = realtimeId(\"item\");\n const contentIndex = 0;\n const textOutputIndex = 0;\n\n const textOutputItem = {\n id: textItemId,\n type: \"message\",\n role: \"assistant\",\n status: \"completed\",\n content: [{ type: \"output_text\", text: response.content }],\n };\n\n // Determine phase: text is \"commentary\" when tool calls are also present\n const hasToolCalls = response.toolCalls && response.toolCalls.length > 0;\n const textPhase = hasToolCalls ? \"commentary\" : \"final_answer\";\n\n // response.output_item.added (text)\n sendEvent(\n ws,\n {\n type: \"response.output_item.added\",\n response_id: responseId,\n output_index: textOutputIndex,\n item: {\n id: textItemId,\n type: \"message\",\n role: \"assistant\",\n status: \"in_progress\",\n content: [],\n phase: textPhase,\n },\n },\n isBeta,\n );\n\n // response.content_part.added\n sendEvent(\n ws,\n {\n type: \"response.content_part.added\",\n response_id: responseId,\n item_id: textItemId,\n output_index: textOutputIndex,\n content_index: contentIndex,\n part: { type: \"output_text\", text: \"\" },\n },\n isBeta,\n );\n\n // response.output_text.delta (chunked) — GA name\n const content = response.content;\n for (let i = 0; i < content.length; i += chunkSize) {\n if (ws.isClosed) break;\n if (latency > 0) await delay(latency, interruption?.signal);\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n if (ws.isClosed) break;\n const chunk = content.slice(i, i + chunkSize);\n sendEvent(\n ws,\n {\n type: \"response.output_text.delta\",\n response_id: responseId,\n item_id: textItemId,\n output_index: textOutputIndex,\n content_index: contentIndex,\n delta: chunk,\n },\n isBeta,\n );\n interruption?.tick();\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n }\n\n if (interrupted) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n interruption?.cleanup();\n return;\n }\n\n if (ws.isClosed) {\n interruption?.cleanup();\n return;\n }\n\n // response.output_text.done\n sendEvent(\n ws,\n {\n type: \"response.output_text.done\",\n response_id: responseId,\n item_id: textItemId,\n output_index: textOutputIndex,\n content_index: contentIndex,\n text: content,\n },\n isBeta,\n );\n\n if (ws.isClosed) {\n interruption?.cleanup();\n return;\n }\n\n // response.content_part.done\n sendEvent(\n ws,\n {\n type: \"response.content_part.done\",\n response_id: responseId,\n item_id: textItemId,\n output_index: textOutputIndex,\n content_index: contentIndex,\n part: { type: \"output_text\", text: content },\n },\n isBeta,\n );\n\n if (ws.isClosed) {\n interruption?.cleanup();\n return;\n }\n\n // response.output_item.done (text)\n sendEvent(\n ws,\n {\n type: \"response.output_item.done\",\n response_id: responseId,\n output_index: textOutputIndex,\n item: { ...textOutputItem, phase: textPhase },\n },\n isBeta,\n );\n\n // conversation.item.done (text message)\n sendEvent(\n ws,\n {\n type: \"conversation.item.done\",\n item: {\n id: textItemId,\n object: \"realtime.item\",\n type: \"message\",\n role: \"assistant\",\n status: \"completed\",\n content: textOutputItem.content,\n },\n },\n isBeta,\n );\n\n if (ws.isClosed) {\n interruption?.cleanup();\n return;\n }\n\n allOutputItems.push(textOutputItem);\n\n // ── Tool call parts ────────────────────────────────────────────\n for (let tcIdx = 0; tcIdx < response.toolCalls.length; tcIdx++) {\n const tc = response.toolCalls[tcIdx];\n const callId = tc.id ?? generateToolCallId();\n const itemId = realtimeId(\"item\");\n const outputIndex = tcIdx + 1; // offset by 1 for the text item\n\n const toolOutputItem = {\n id: itemId,\n type: \"function_call\",\n status: \"completed\",\n call_id: callId,\n name: tc.name,\n arguments: tc.arguments,\n };\n\n // response.output_item.added\n sendEvent(\n ws,\n {\n type: \"response.output_item.added\",\n response_id: responseId,\n output_index: outputIndex,\n item: {\n id: itemId,\n type: \"function_call\",\n status: \"in_progress\",\n call_id: callId,\n name: tc.name,\n arguments: \"\",\n phase: \"final_answer\",\n },\n },\n isBeta,\n );\n\n // response.function_call_arguments.delta (chunked)\n const args = tc.arguments;\n for (let i = 0; i < args.length; i += chunkSize) {\n if (ws.isClosed) break;\n if (latency > 0) await delay(latency, interruption?.signal);\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n if (ws.isClosed) break;\n const chunk = args.slice(i, i + chunkSize);\n sendEvent(\n ws,\n {\n type: \"response.function_call_arguments.delta\",\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n call_id: callId,\n delta: chunk,\n },\n isBeta,\n );\n interruption?.tick();\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n }\n\n if (interrupted) break;\n\n if (ws.isClosed) break;\n\n // response.function_call_arguments.done\n sendEvent(\n ws,\n {\n type: \"response.function_call_arguments.done\",\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n call_id: callId,\n arguments: args,\n },\n isBeta,\n );\n\n if (ws.isClosed) break;\n\n // response.output_item.done\n sendEvent(\n ws,\n {\n type: \"response.output_item.done\",\n response_id: responseId,\n output_index: outputIndex,\n item: { ...toolOutputItem, phase: \"final_answer\" },\n },\n isBeta,\n );\n\n // conversation.item.done (tool call)\n sendEvent(\n ws,\n {\n type: \"conversation.item.done\",\n item: {\n id: itemId,\n object: \"realtime.item\",\n type: \"function_call\",\n status: \"completed\",\n call_id: callId,\n name: tc.name,\n arguments: args,\n },\n },\n isBeta,\n );\n\n if (ws.isClosed) break;\n\n allOutputItems.push(toolOutputItem);\n }\n\n if (interrupted) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n interruption?.cleanup();\n return;\n }\n\n interruption?.cleanup();\n\n if (ws.isClosed) return;\n\n // response.done\n sendEvent(\n ws,\n {\n type: \"response.done\",\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"completed\",\n output: allOutputItems,\n usage: { total_tokens: 0, input_tokens: 0, output_tokens: 0 },\n },\n },\n isBeta,\n );\n\n // Accumulate into conversation for multi-turn\n conversationItems.push({\n type: \"message\",\n id: textItemId,\n role: \"assistant\",\n content: [{ type: \"text\", text: content }],\n });\n for (const item of allOutputItems.slice(1)) {\n conversationItems.push(item as RealtimeItem);\n }\n return;\n }\n\n // ── Text response ───────────────────────────────────────────────────\n if (isTextResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 200, fixture },\n });\n\n const itemId = realtimeId(\"item\");\n const contentIndex = 0;\n const outputIndex = 0;\n\n const outputItem = {\n id: itemId,\n type: \"message\",\n role: \"assistant\",\n status: \"completed\",\n content: [{ type: \"output_text\", text: response.content }],\n };\n\n // response.created\n sendEvent(\n ws,\n {\n type: \"response.created\",\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"in_progress\",\n status_details: null,\n output: [],\n usage: null,\n },\n },\n isBeta,\n );\n\n // response.output_item.added\n sendEvent(\n ws,\n {\n type: \"response.output_item.added\",\n response_id: responseId,\n output_index: outputIndex,\n item: {\n id: itemId,\n type: \"message\",\n role: \"assistant\",\n status: \"in_progress\",\n content: [],\n phase: \"final_answer\",\n },\n },\n isBeta,\n );\n\n // response.content_part.added\n sendEvent(\n ws,\n {\n type: \"response.content_part.added\",\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n content_index: contentIndex,\n part: { type: \"output_text\", text: \"\" },\n },\n isBeta,\n );\n\n // response.output_text.delta (chunked) — GA name\n const content = response.content;\n const interruption = createInterruptionSignal(fixture);\n let interrupted = false;\n\n for (let i = 0; i < content.length; i += chunkSize) {\n if (ws.isClosed) break;\n if (latency > 0) await delay(latency, interruption?.signal);\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n if (ws.isClosed) break;\n const chunk = content.slice(i, i + chunkSize);\n sendEvent(\n ws,\n {\n type: \"response.output_text.delta\",\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n content_index: contentIndex,\n delta: chunk,\n },\n isBeta,\n );\n interruption?.tick();\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n }\n\n if (interrupted) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n interruption?.cleanup();\n return;\n }\n\n interruption?.cleanup();\n\n if (ws.isClosed) return;\n\n // response.output_text.done\n sendEvent(\n ws,\n {\n type: \"response.output_text.done\",\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n content_index: contentIndex,\n text: content,\n },\n isBeta,\n );\n\n // response.content_part.done\n sendEvent(\n ws,\n {\n type: \"response.content_part.done\",\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n content_index: contentIndex,\n part: { type: \"output_text\", text: content },\n },\n isBeta,\n );\n\n // response.output_item.done\n sendEvent(\n ws,\n {\n type: \"response.output_item.done\",\n response_id: responseId,\n output_index: outputIndex,\n item: { ...outputItem, phase: \"final_answer\" },\n },\n isBeta,\n );\n\n // conversation.item.done (text message)\n sendEvent(\n ws,\n {\n type: \"conversation.item.done\",\n item: {\n id: itemId,\n object: \"realtime.item\",\n type: \"message\",\n role: \"assistant\",\n status: \"completed\",\n content: outputItem.content,\n },\n },\n isBeta,\n );\n\n // response.done\n sendEvent(\n ws,\n {\n type: \"response.done\",\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"completed\",\n output: [outputItem],\n usage: { total_tokens: 0, input_tokens: 0, output_tokens: 0 },\n },\n },\n isBeta,\n );\n\n // Accumulate assistant response into conversation for multi-turn\n conversationItems.push({\n type: \"message\",\n id: itemId,\n role: \"assistant\",\n content: [{ type: \"text\", text: content }],\n });\n return;\n }\n\n // ── Tool call response ──────────────────────────────────────────────\n if (isToolCallResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 200, fixture },\n });\n\n // response.created\n sendEvent(\n ws,\n {\n type: \"response.created\",\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"in_progress\",\n status_details: null,\n output: [],\n usage: null,\n },\n },\n isBeta,\n );\n\n const outputItems: unknown[] = [];\n const interruption = createInterruptionSignal(fixture);\n let interrupted = false;\n\n for (let tcIdx = 0; tcIdx < response.toolCalls.length; tcIdx++) {\n const tc = response.toolCalls[tcIdx];\n const callId = tc.id ?? generateToolCallId();\n const itemId = realtimeId(\"item\");\n\n const outputItem = {\n id: itemId,\n type: \"function_call\",\n status: \"completed\",\n call_id: callId,\n name: tc.name,\n arguments: tc.arguments,\n };\n\n // response.output_item.added\n sendEvent(\n ws,\n {\n type: \"response.output_item.added\",\n response_id: responseId,\n output_index: tcIdx,\n item: {\n id: itemId,\n type: \"function_call\",\n status: \"in_progress\",\n call_id: callId,\n name: tc.name,\n arguments: \"\",\n phase: \"final_answer\",\n },\n },\n isBeta,\n );\n\n // response.function_call_arguments.delta (chunked)\n const args = tc.arguments;\n for (let i = 0; i < args.length; i += chunkSize) {\n if (ws.isClosed) break;\n if (latency > 0) await delay(latency, interruption?.signal);\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n if (ws.isClosed) break;\n const chunk = args.slice(i, i + chunkSize);\n sendEvent(\n ws,\n {\n type: \"response.function_call_arguments.delta\",\n response_id: responseId,\n item_id: itemId,\n output_index: tcIdx,\n call_id: callId,\n delta: chunk,\n },\n isBeta,\n );\n interruption?.tick();\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n }\n\n if (interrupted) break;\n\n if (ws.isClosed) break;\n\n // response.function_call_arguments.done\n sendEvent(\n ws,\n {\n type: \"response.function_call_arguments.done\",\n response_id: responseId,\n item_id: itemId,\n output_index: tcIdx,\n call_id: callId,\n arguments: args,\n },\n isBeta,\n );\n\n // response.output_item.done\n sendEvent(\n ws,\n {\n type: \"response.output_item.done\",\n response_id: responseId,\n output_index: tcIdx,\n item: { ...outputItem, phase: \"final_answer\" },\n },\n isBeta,\n );\n\n // conversation.item.done (tool call)\n sendEvent(\n ws,\n {\n type: \"conversation.item.done\",\n item: {\n id: itemId,\n object: \"realtime.item\",\n type: \"function_call\",\n status: \"completed\",\n call_id: callId,\n name: tc.name,\n arguments: args,\n },\n },\n isBeta,\n );\n\n outputItems.push(outputItem);\n }\n\n if (interrupted) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n interruption?.cleanup();\n return;\n }\n\n interruption?.cleanup();\n\n if (ws.isClosed) return;\n\n // response.done\n sendEvent(\n ws,\n {\n type: \"response.done\",\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"completed\",\n output: outputItems,\n usage: { total_tokens: 0, input_tokens: 0, output_tokens: 0 },\n },\n },\n isBeta,\n );\n\n // Accumulate assistant tool calls into conversation for multi-turn\n // Reuse outputItems (which already have the correct call_id) to avoid generating divergent IDs\n for (const item of outputItems) {\n conversationItems.push(item as RealtimeItem);\n }\n return;\n }\n\n // Unknown response type\n journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 500, fixture },\n });\n buildErrorRealtimeEvent(\n ws,\n \"Fixture response did not match any known type\",\n isBeta,\n \"server_error\",\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ws-realtime.cjs","names":["generateToolCallId","DEFAULT_TEST_ID","matchFixture","resolveStrictMode","flattenHeaders","strictOverrideField","resolveResponse","isErrorResponse","isContentWithToolCallsResponse","createInterruptionSignal","calculateDelay","delay","isTextResponse","isToolCallResponse"],"sources":["../src/ws-realtime.ts"],"sourcesContent":["/**\n * WebSocket handler for OpenAI Realtime API.\n *\n * Accepts Realtime API messages (session.update, conversation.item.create,\n * response.create) over WebSocket and sends back Realtime API events as\n * individual WebSocket text frames.\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport type { ChatCompletionRequest, ChatMessage, Fixture } from \"./types.js\";\nimport { matchFixture } from \"./router.js\";\nimport {\n generateToolCallId,\n flattenHeaders,\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n} from \"./helpers.js\";\nimport { createInterruptionSignal } from \"./interruption.js\";\nimport { delay, calculateDelay } from \"./sse-writer.js\";\nimport { DEFAULT_TEST_ID, type Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport type { WebSocketConnection } from \"./ws-framing.js\";\n\n/** Generate a Realtime-API-style ID with underscore separator (e.g. event_xxx, item_xxx). */\nfunction realtimeId(prefix: string): string {\n return `${prefix}_${randomBytes(12).toString(\"base64url\")}`;\n}\n\n// ─── Realtime protocol types ────────────────────────────────────────────────\n\ninterface RealtimeItem {\n type: \"message\" | \"function_call\" | \"function_call_output\";\n id?: string;\n role?: \"user\" | \"assistant\" | \"system\";\n content?: Array<{ type: string; text?: string; url?: string; transcript?: string | null }>;\n name?: string;\n call_id?: string;\n arguments?: string;\n output?: string;\n}\n\ninterface SessionConfig {\n model: string;\n modalities: string[];\n instructions: string;\n tools: unknown[];\n voice: string | null;\n input_audio_format: string | null;\n output_audio_format: string | null;\n input_audio_noise_reduction: { type: string } | null;\n input_audio_transcription: { model: string } | null;\n turn_detection: unknown | null;\n temperature: number;\n type: \"conversation\" | \"transcription\" | \"translation\";\n reasoning: { effort: string } | null;\n}\n\ninterface RealtimeMessage {\n type: string;\n event_id?: string;\n session?: Partial<SessionConfig>;\n item?: RealtimeItem;\n response?: {\n modalities?: string[];\n instructions?: string;\n [key: string]: unknown;\n };\n}\n\n// ─── Conversion helpers ─────────────────────────────────────────────────────\n\nexport function realtimeItemsToMessages(\n items: RealtimeItem[],\n instructions?: string,\n logger?: Logger,\n): ChatMessage[] {\n const messages: ChatMessage[] = [];\n\n if (instructions) {\n messages.push({ role: \"system\", content: instructions });\n }\n\n for (const item of items) {\n if (item.type === \"message\") {\n const role =\n item.role === \"assistant\" ? \"assistant\" : item.role === \"system\" ? \"system\" : \"user\";\n\n // Check if content contains multimodal input types (input_text, input_image, input_audio)\n const hasMultimodal = item.content?.some(\n (p) => p.type === \"input_text\" || p.type === \"input_image\" || p.type === \"input_audio\",\n );\n\n if (hasMultimodal && item.content) {\n // Map realtime input content types to ChatMessage content parts\n const mappedContent = item.content.map((part) => {\n if (part.type === \"input_text\") {\n return { type: \"text\" as const, text: part.text ?? \"\" };\n }\n if (part.type === \"input_image\") {\n return {\n type: \"image_url\" as const,\n image_url: { url: part.url ?? \"\" },\n };\n }\n if (part.type === \"input_audio\") {\n return { type: \"text\" as const, text: \"[audio input]\" };\n }\n // Pass through unknown content types as-is\n return part;\n });\n messages.push({ role, content: mappedContent });\n } else {\n // Existing behavior: extract text from first content element\n const text = item.content?.[0]?.text ?? \"\";\n messages.push({ role, content: text });\n }\n } else if (item.type === \"function_call\") {\n if (!item.name) {\n logger?.warn(\"Realtime function_call item missing 'name'\");\n }\n messages.push({\n role: \"assistant\",\n content: null,\n tool_calls: [\n {\n id: item.call_id ?? generateToolCallId(),\n type: \"function\",\n function: {\n name: item.name ?? \"\",\n arguments: item.arguments ?? \"\",\n },\n },\n ],\n });\n } else if (item.type === \"function_call_output\") {\n if (!item.output) {\n logger?.warn(\"Realtime function_call_output item missing 'output'\");\n }\n messages.push({\n role: \"tool\",\n content: item.output ?? \"\",\n tool_call_id: item.call_id,\n });\n }\n }\n\n return messages;\n}\n\n// ─── GA -> Beta translation ─────────────────────────────────────────────────\n\n/** GA -> Beta event name mapping */\nconst GA_TO_BETA_EVENT: Record<string, string> = {\n \"response.output_text.delta\": \"response.text.delta\",\n \"response.output_text.done\": \"response.text.done\",\n \"response.output_audio.delta\": \"response.audio.delta\",\n \"response.output_audio.done\": \"response.audio.done\",\n \"response.output_audio_transcript.delta\": \"response.audio_transcript.delta\",\n \"response.output_audio_transcript.done\": \"response.audio_transcript.done\",\n \"conversation.item.added\": \"conversation.item.created\",\n};\n\n/** GA -> Beta content type mapping */\nconst GA_TO_BETA_CONTENT_TYPE: Record<string, string> = {\n output_text: \"text\",\n output_audio: \"audio\",\n};\n\n/** Events suppressed in Beta mode (GA-only events) */\nconst BETA_SUPPRESSED_EVENTS = new Set([\"conversation.item.done\"]);\n\nfunction translateGAToBeta(event: Record<string, unknown>): Record<string, unknown> | null {\n const type = event.type as string;\n if (BETA_SUPPRESSED_EVENTS.has(type)) return null;\n\n const translated = { ...event };\n if (GA_TO_BETA_EVENT[type]) {\n translated.type = GA_TO_BETA_EVENT[type];\n }\n\n // Translate content types in nested structures\n if (translated.part && typeof translated.part === \"object\") {\n const part = { ...(translated.part as Record<string, unknown>) };\n if (typeof part.type === \"string\" && GA_TO_BETA_CONTENT_TYPE[part.type]) {\n part.type = GA_TO_BETA_CONTENT_TYPE[part.type];\n }\n translated.part = part;\n }\n if (translated.content_part && typeof translated.content_part === \"object\") {\n const cp = { ...(translated.content_part as Record<string, unknown>) };\n if (typeof cp.type === \"string\" && GA_TO_BETA_CONTENT_TYPE[cp.type]) {\n cp.type = GA_TO_BETA_CONTENT_TYPE[cp.type];\n }\n translated.content_part = cp;\n }\n // Translate content arrays\n if (Array.isArray(translated.content)) {\n translated.content = (translated.content as Record<string, unknown>[]).map((c) => {\n if (typeof c.type === \"string\" && GA_TO_BETA_CONTENT_TYPE[c.type]) {\n return { ...c, type: GA_TO_BETA_CONTENT_TYPE[c.type] };\n }\n return c;\n });\n }\n // Translate item.content arrays (response.output_item.added/done, conversation.item.added)\n if (translated.item && typeof translated.item === \"object\") {\n const item = { ...(translated.item as Record<string, unknown>) };\n delete item.phase; // GA-only field\n if (Array.isArray(item.content)) {\n item.content = (item.content as Record<string, unknown>[]).map((c) => {\n if (typeof c.type === \"string\" && GA_TO_BETA_CONTENT_TYPE[c.type]) {\n return { ...c, type: GA_TO_BETA_CONTENT_TYPE[c.type] };\n }\n return c;\n });\n }\n translated.item = item;\n }\n // Translate response.output[].content arrays (response.done)\n if (translated.response && typeof translated.response === \"object\") {\n const resp = { ...(translated.response as Record<string, unknown>) };\n if (Array.isArray(resp.output)) {\n resp.output = (resp.output as Record<string, unknown>[]).map((outItem) => {\n const o = { ...(outItem as Record<string, unknown>) };\n if (Array.isArray(o.content)) {\n o.content = (o.content as Record<string, unknown>[]).map((c) =>\n typeof c.type === \"string\" && GA_TO_BETA_CONTENT_TYPE[c.type]\n ? { ...c, type: GA_TO_BETA_CONTENT_TYPE[c.type] }\n : c,\n );\n }\n return o;\n });\n }\n translated.response = resp;\n }\n\n // Flatten GA session config for Beta (session.created / session.updated)\n if (type === \"session.created\" || type === \"session.updated\") {\n if (translated.session && typeof translated.session === \"object\") {\n const session = { ...(translated.session as Record<string, unknown>) };\n if (session.audio && typeof session.audio === \"object\") {\n const audio = session.audio as Record<string, unknown>;\n session.voice = audio.voice;\n session.input_audio_format = audio.input_audio_format;\n session.output_audio_format = audio.output_audio_format;\n session.input_audio_transcription = audio.input_audio_transcription;\n delete session.audio;\n }\n delete session.type;\n delete session.reasoning;\n translated.session = session;\n }\n }\n\n return translated;\n}\n\n// ─── Event sending ──────────────────────────────────────────────────────────\n\nfunction sendEvent(ws: WebSocketConnection, event: Record<string, unknown>, isBeta: boolean): void {\n const out = { ...event, event_id: event.event_id ?? realtimeId(\"event\") };\n if (isBeta) {\n const translated = translateGAToBeta(out);\n if (translated === null) return; // suppressed in Beta mode\n ws.send(JSON.stringify(translated));\n } else {\n ws.send(JSON.stringify(out));\n }\n}\n\nfunction buildErrorRealtimeEvent(\n ws: WebSocketConnection,\n message: string,\n isBeta: boolean,\n type = \"invalid_request_error\",\n code?: string,\n): void {\n sendEvent(ws, { type: \"error\", error: { message, type, code } }, isBeta);\n}\n\n// ─── Main handler ───────────────────────────────────────────────────────────\n\nexport function handleWebSocketRealtime(\n ws: WebSocketConnection,\n fixtures: Fixture[],\n journal: Journal,\n defaults: {\n latency: number;\n chunkSize: number;\n replaySpeed?: number;\n model: string;\n logger: Logger;\n strict?: boolean;\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest;\n testId?: string;\n upgradeHeaders?: import(\"node:http\").IncomingHttpHeaders;\n },\n): void {\n const { logger } = defaults;\n const sessionId = realtimeId(\"sess\");\n\n const isBeta = defaults.upgradeHeaders?.[\"openai-beta\"]\n ? String(defaults.upgradeHeaders[\"openai-beta\"]).includes(\"realtime=v1\")\n : false;\n\n const session: SessionConfig = {\n model: defaults.model,\n modalities: [\"text\"],\n instructions: \"\",\n tools: [],\n voice: null,\n input_audio_format: null,\n output_audio_format: null,\n input_audio_noise_reduction: null,\n input_audio_transcription: null,\n turn_detection: null,\n temperature: 0.8,\n type: \"conversation\",\n reasoning: null,\n };\n\n const conversationItems: RealtimeItem[] = [];\n\n // Send session.created immediately on connect (GA format — shim flattens for Beta)\n sendEvent(\n ws,\n {\n type: \"session.created\",\n session: {\n id: sessionId,\n object: \"realtime.session\",\n model: session.model,\n expires_at: Math.floor(Date.now() / 1000) + 3600,\n modalities: session.modalities,\n instructions: session.instructions,\n tools: session.tools,\n tool_choice: \"auto\",\n temperature: session.temperature,\n max_response_output_tokens: \"inf\",\n audio: {\n voice: session.voice,\n input_audio_format: session.input_audio_format,\n output_audio_format: session.output_audio_format,\n input_audio_noise_reduction: session.input_audio_noise_reduction,\n input_audio_transcription: session.input_audio_transcription,\n },\n turn_detection: session.turn_detection,\n type: session.type,\n reasoning: session.reasoning,\n },\n },\n isBeta,\n );\n\n // Serialize message processing to prevent event interleaving\n let pending = Promise.resolve();\n ws.on(\"message\", (raw: string) => {\n pending = pending.then(() =>\n processMessage(\n raw,\n ws,\n fixtures,\n journal,\n defaults,\n session,\n conversationItems,\n isBeta,\n ).catch((err: unknown) => {\n const msg = err instanceof Error ? err.message : \"Internal error\";\n logger.error(`WebSocket realtime error: ${msg}`);\n try {\n buildErrorRealtimeEvent(ws, msg, isBeta, \"server_error\");\n } catch (sendErr) {\n defaults.logger.debug(\n `Failed to send error to client: ${sendErr instanceof Error ? sendErr.message : \"unknown\"}`,\n );\n }\n }),\n );\n });\n}\n\nasync function processMessage(\n raw: string,\n ws: WebSocketConnection,\n fixtures: Fixture[],\n journal: Journal,\n defaults: {\n latency: number;\n chunkSize: number;\n replaySpeed?: number;\n model: string;\n logger: Logger;\n strict?: boolean;\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest;\n testId?: string;\n upgradeHeaders?: import(\"node:http\").IncomingHttpHeaders;\n },\n session: SessionConfig,\n conversationItems: RealtimeItem[],\n isBeta: boolean,\n): Promise<void> {\n let parsed: RealtimeMessage;\n try {\n parsed = JSON.parse(raw) as RealtimeMessage;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n buildErrorRealtimeEvent(\n ws,\n `Malformed JSON: ${detail}`,\n isBeta,\n \"invalid_request_error\",\n \"invalid_json\",\n );\n return;\n }\n\n const msgType = parsed.type;\n\n // ── session.update ────────────────────────────────────────────────────\n if (msgType === \"session.update\") {\n if (parsed.session) {\n const s = parsed.session;\n\n // Validate session.type value before applying any mutations\n const validTypes = new Set([\"conversation\", \"transcription\", \"translation\"]);\n if ((s as Record<string, unknown>).type !== undefined) {\n if (!validTypes.has((s as Record<string, unknown>).type as string)) {\n sendEvent(\n ws,\n {\n type: \"error\",\n error: {\n message: `Invalid session type: ${(s as Record<string, unknown>).type}`,\n type: \"invalid_request_error\",\n code: \"invalid_session_config\",\n },\n },\n isBeta,\n );\n return;\n }\n }\n\n // Capture pre-mutation values for rollback on validation failure\n const prevModel = session.model;\n const prevType = session.type;\n\n if (s.instructions !== undefined) session.instructions = s.instructions;\n if (s.tools !== undefined) session.tools = s.tools;\n if (s.modalities !== undefined) session.modalities = s.modalities;\n if (s.model !== undefined) session.model = s.model;\n if (s.temperature !== undefined) session.temperature = s.temperature;\n if ((s as Record<string, unknown>).type !== undefined)\n session.type = (s as Record<string, unknown>).type as SessionConfig[\"type\"];\n // GA nested audio config\n if ((s as Record<string, unknown>).audio) {\n const audio = (s as Record<string, unknown>).audio as Record<string, unknown>;\n if (audio.voice !== undefined) session.voice = audio.voice as string | null;\n if (audio.input_audio_format !== undefined)\n session.input_audio_format = audio.input_audio_format as string | null;\n if (audio.output_audio_format !== undefined)\n session.output_audio_format = audio.output_audio_format as string | null;\n if (audio.input_audio_noise_reduction !== undefined)\n session.input_audio_noise_reduction = audio.input_audio_noise_reduction as {\n type: string;\n } | null;\n if (audio.input_audio_transcription !== undefined)\n session.input_audio_transcription = audio.input_audio_transcription as {\n model: string;\n } | null;\n }\n // Beta flat fields (backward compat)\n if (s.voice !== undefined) session.voice = s.voice;\n if (s.input_audio_format !== undefined) session.input_audio_format = s.input_audio_format;\n if (s.output_audio_format !== undefined) session.output_audio_format = s.output_audio_format;\n // reasoning config\n if ((s as Record<string, unknown>).reasoning !== undefined)\n session.reasoning = (s as Record<string, unknown>).reasoning as {\n effort: string;\n } | null;\n\n // Validate model+type combinations (rollback on failure)\n const transcriptionModels = new Set([\n \"gpt-4o-transcribe\",\n \"gpt-4o-mini-transcribe\",\n \"gpt-realtime-whisper\",\n \"whisper-1\",\n ]);\n const translationModels = new Set([\n \"gpt-4o-transcribe\",\n \"gpt-4o-mini-transcribe\",\n \"gpt-realtime-translate\",\n ]);\n\n if (session.type === \"transcription\" && !transcriptionModels.has(session.model)) {\n session.model = prevModel;\n session.type = prevType;\n sendEvent(\n ws,\n {\n type: \"error\",\n error: {\n message: `Model ${s.model ?? prevModel} does not support session type transcription`,\n type: \"invalid_request_error\",\n code: \"invalid_session_config\",\n },\n },\n isBeta,\n );\n return;\n }\n if (session.type === \"translation\" && !translationModels.has(session.model)) {\n session.model = prevModel;\n session.type = prevType;\n sendEvent(\n ws,\n {\n type: \"error\",\n error: {\n message: `Model ${s.model ?? prevModel} does not support session type translation`,\n type: \"invalid_request_error\",\n code: \"invalid_session_config\",\n },\n },\n isBeta,\n );\n return;\n }\n }\n\n sendEvent(\n ws,\n {\n type: \"session.updated\",\n session: {\n object: \"realtime.session\",\n model: session.model,\n expires_at: Math.floor(Date.now() / 1000) + 3600,\n modalities: session.modalities,\n instructions: session.instructions,\n tools: session.tools,\n tool_choice: \"auto\",\n temperature: session.temperature,\n max_response_output_tokens: \"inf\",\n audio: {\n voice: session.voice,\n input_audio_format: session.input_audio_format,\n output_audio_format: session.output_audio_format,\n input_audio_noise_reduction: session.input_audio_noise_reduction,\n input_audio_transcription: session.input_audio_transcription,\n },\n turn_detection: session.turn_detection,\n type: session.type,\n reasoning: session.reasoning,\n },\n },\n isBeta,\n );\n return;\n }\n\n // ── conversation.item.create ──────────────────────────────────────────\n if (msgType === \"conversation.item.create\") {\n if (!parsed.item) {\n buildErrorRealtimeEvent(\n ws,\n \"Missing 'item' in conversation.item.create\",\n isBeta,\n \"invalid_request_error\",\n );\n return;\n }\n const item = parsed.item;\n if (!item.id) {\n item.id = realtimeId(\"item\");\n }\n const previousId =\n conversationItems.length > 0\n ? (conversationItems[conversationItems.length - 1].id ?? null)\n : null;\n conversationItems.push(item);\n sendEvent(ws, { type: \"conversation.item.added\", previous_item_id: previousId, item }, isBeta);\n return;\n }\n\n // ── response.create ───────────────────────────────────────────────────\n if (msgType === \"response.create\") {\n await handleResponseCreate(\n ws,\n fixtures,\n journal,\n defaults,\n session,\n conversationItems,\n isBeta,\n parsed.response,\n );\n return;\n }\n\n // ── input_audio_buffer.append ────────────────────────────────────────\n if (msgType === \"input_audio_buffer.append\") {\n // Accept silently — aimock doesn't process actual audio\n return;\n }\n\n // ── input_audio_buffer.commit ──────────────────────────────────────\n if (msgType === \"input_audio_buffer.commit\") {\n sendEvent(ws, { type: \"input_audio_buffer.committed\" }, isBeta);\n // In transcription/translation mode, add a placeholder user item\n if (session.type === \"transcription\" || session.type === \"translation\") {\n const audioItem: RealtimeItem = {\n type: \"message\",\n id: realtimeId(\"item\"),\n role: \"user\",\n content: [{ type: \"input_audio\", transcript: null }],\n };\n conversationItems.push(audioItem);\n sendEvent(\n ws,\n {\n type: \"conversation.item.added\",\n item: audioItem,\n },\n isBeta,\n );\n }\n return;\n }\n\n // ── input_audio_buffer.clear ───────────────────────────────────────\n if (msgType === \"input_audio_buffer.clear\") {\n sendEvent(ws, { type: \"input_audio_buffer.cleared\" }, isBeta);\n return;\n }\n\n // ── response.cancel ────────────────────────────────────────────────\n if (msgType === \"response.cancel\") {\n sendEvent(ws, { type: \"response.cancelled\" }, isBeta);\n return;\n }\n\n // Unknown message type — ignore silently (matches OpenAI behavior)\n}\n\nasync function handleResponseCreate(\n ws: WebSocketConnection,\n fixtures: Fixture[],\n journal: Journal,\n defaults: {\n latency: number;\n chunkSize: number;\n replaySpeed?: number;\n model: string;\n logger: Logger;\n strict?: boolean;\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest;\n testId?: string;\n upgradeHeaders?: import(\"node:http\").IncomingHttpHeaders;\n },\n session: SessionConfig,\n conversationItems: RealtimeItem[],\n isBeta: boolean,\n responseOverrides?: { instructions?: string; [key: string]: unknown },\n): Promise<void> {\n const instructions = (responseOverrides?.instructions ?? session.instructions) || undefined;\n const messages = realtimeItemsToMessages(conversationItems, instructions, defaults.logger);\n\n const endpointTypeMap: Record<string, string> = {\n conversation: \"realtime\",\n transcription: \"realtime-transcription\",\n translation: \"realtime-translation\",\n };\n const endpointType = endpointTypeMap[session.type] ?? \"realtime\";\n\n const completionReq: ChatCompletionRequest = {\n model: session.model,\n messages,\n _endpointType: endpointType,\n };\n\n const testId = defaults.testId ?? DEFAULT_TEST_ID;\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n const responseId = realtimeId(\"resp\");\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (!fixture) {\n if (resolveStrictMode(defaults.strict, defaults.upgradeHeaders)) {\n defaults.logger.warn(`STRICT: No fixture matched for WebSocket message`);\n journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, defaults.upgradeHeaders),\n },\n });\n ws.close(1008, \"Strict mode: no fixture matched\");\n return;\n }\n journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, defaults.upgradeHeaders),\n },\n });\n // Send response.created with failed status then response.done with error\n sendEvent(\n ws,\n {\n type: \"response.created\",\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"failed\",\n status_details: null,\n output: [],\n usage: null,\n },\n },\n isBeta,\n );\n sendEvent(\n ws,\n {\n type: \"response.done\",\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"failed\",\n output: [],\n status_details: {\n type: \"error\",\n error: {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n },\n usage: { total_tokens: 0, input_tokens: 0, output_tokens: 0 },\n },\n },\n isBeta,\n );\n return;\n }\n\n const response = await resolveResponse(fixture, completionReq);\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n // ── Error fixture ───────────────────────────────────────────────────\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status, fixture },\n });\n sendEvent(\n ws,\n {\n type: \"response.created\",\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"failed\",\n status_details: null,\n output: [],\n usage: null,\n },\n },\n isBeta,\n );\n sendEvent(\n ws,\n {\n type: \"response.done\",\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"failed\",\n output: [],\n status_details: {\n type: \"error\",\n error: {\n message: response.error.message,\n type: response.error.type,\n code: response.error.code,\n },\n },\n usage: { total_tokens: 0, input_tokens: 0, output_tokens: 0 },\n },\n },\n isBeta,\n );\n return;\n }\n\n // ── Content + tool calls response ──────────────────────────────────\n if (isContentWithToolCallsResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 200, fixture },\n });\n\n // response.created\n sendEvent(\n ws,\n {\n type: \"response.created\",\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"in_progress\",\n status_details: null,\n output: [],\n usage: null,\n },\n },\n isBeta,\n );\n\n const interruption = createInterruptionSignal(fixture);\n let interrupted = false;\n const allOutputItems: unknown[] = [];\n\n // ── Text content part ──────────────────────────────────────────\n const textItemId = realtimeId(\"item\");\n const contentIndex = 0;\n const textOutputIndex = 0;\n\n const textOutputItem = {\n id: textItemId,\n type: \"message\",\n role: \"assistant\",\n status: \"completed\",\n content: [{ type: \"output_text\", text: response.content }],\n };\n\n // Determine phase: text is \"commentary\" when tool calls are also present\n const hasToolCalls = response.toolCalls && response.toolCalls.length > 0;\n const textPhase = hasToolCalls ? \"commentary\" : \"final_answer\";\n\n // response.output_item.added (text)\n sendEvent(\n ws,\n {\n type: \"response.output_item.added\",\n response_id: responseId,\n output_index: textOutputIndex,\n item: {\n id: textItemId,\n type: \"message\",\n role: \"assistant\",\n status: \"in_progress\",\n content: [],\n phase: textPhase,\n },\n },\n isBeta,\n );\n\n // response.content_part.added\n sendEvent(\n ws,\n {\n type: \"response.content_part.added\",\n response_id: responseId,\n item_id: textItemId,\n output_index: textOutputIndex,\n content_index: contentIndex,\n part: { type: \"output_text\", text: \"\" },\n },\n isBeta,\n );\n\n // response.output_text.delta (chunked) — GA name\n const content = response.content;\n const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;\n const { recordedTimings } = fixture;\n let eventIndex = 0;\n for (let i = 0; i < content.length; i += chunkSize) {\n if (ws.isClosed) break;\n const chunkDelay = calculateDelay(\n eventIndex,\n undefined,\n latency,\n recordedTimings,\n replaySpeed,\n );\n if (chunkDelay > 0) await delay(chunkDelay, interruption?.signal);\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n if (ws.isClosed) break;\n const chunk = content.slice(i, i + chunkSize);\n sendEvent(\n ws,\n {\n type: \"response.output_text.delta\",\n response_id: responseId,\n item_id: textItemId,\n output_index: textOutputIndex,\n content_index: contentIndex,\n delta: chunk,\n },\n isBeta,\n );\n eventIndex++;\n interruption?.tick();\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n }\n\n if (interrupted) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n interruption?.cleanup();\n return;\n }\n\n if (ws.isClosed) {\n interruption?.cleanup();\n return;\n }\n\n // response.output_text.done\n sendEvent(\n ws,\n {\n type: \"response.output_text.done\",\n response_id: responseId,\n item_id: textItemId,\n output_index: textOutputIndex,\n content_index: contentIndex,\n text: content,\n },\n isBeta,\n );\n\n if (ws.isClosed) {\n interruption?.cleanup();\n return;\n }\n\n // response.content_part.done\n sendEvent(\n ws,\n {\n type: \"response.content_part.done\",\n response_id: responseId,\n item_id: textItemId,\n output_index: textOutputIndex,\n content_index: contentIndex,\n part: { type: \"output_text\", text: content },\n },\n isBeta,\n );\n\n if (ws.isClosed) {\n interruption?.cleanup();\n return;\n }\n\n // response.output_item.done (text)\n sendEvent(\n ws,\n {\n type: \"response.output_item.done\",\n response_id: responseId,\n output_index: textOutputIndex,\n item: { ...textOutputItem, phase: textPhase },\n },\n isBeta,\n );\n\n // conversation.item.done (text message)\n sendEvent(\n ws,\n {\n type: \"conversation.item.done\",\n item: {\n id: textItemId,\n object: \"realtime.item\",\n type: \"message\",\n role: \"assistant\",\n status: \"completed\",\n content: textOutputItem.content,\n },\n },\n isBeta,\n );\n\n if (ws.isClosed) {\n interruption?.cleanup();\n return;\n }\n\n allOutputItems.push(textOutputItem);\n\n // ── Tool call parts ────────────────────────────────────────────\n for (let tcIdx = 0; tcIdx < response.toolCalls.length; tcIdx++) {\n const tc = response.toolCalls[tcIdx];\n const callId = tc.id ?? generateToolCallId();\n const itemId = realtimeId(\"item\");\n const outputIndex = tcIdx + 1; // offset by 1 for the text item\n\n const toolOutputItem = {\n id: itemId,\n type: \"function_call\",\n status: \"completed\",\n call_id: callId,\n name: tc.name,\n arguments: tc.arguments,\n };\n\n // response.output_item.added\n sendEvent(\n ws,\n {\n type: \"response.output_item.added\",\n response_id: responseId,\n output_index: outputIndex,\n item: {\n id: itemId,\n type: \"function_call\",\n status: \"in_progress\",\n call_id: callId,\n name: tc.name,\n arguments: \"\",\n phase: \"final_answer\",\n },\n },\n isBeta,\n );\n\n // response.function_call_arguments.delta (chunked)\n // Continue eventIndex from content chunks to avoid re-triggering TTFT\n const args = tc.arguments;\n for (let i = 0; i < args.length; i += chunkSize) {\n if (ws.isClosed) break;\n const chunkDelay = calculateDelay(\n eventIndex,\n undefined,\n latency,\n recordedTimings,\n replaySpeed,\n );\n if (chunkDelay > 0) await delay(chunkDelay, interruption?.signal);\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n if (ws.isClosed) break;\n const chunk = args.slice(i, i + chunkSize);\n sendEvent(\n ws,\n {\n type: \"response.function_call_arguments.delta\",\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n call_id: callId,\n delta: chunk,\n },\n isBeta,\n );\n eventIndex++;\n interruption?.tick();\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n }\n\n if (interrupted) break;\n\n if (ws.isClosed) break;\n\n // response.function_call_arguments.done\n sendEvent(\n ws,\n {\n type: \"response.function_call_arguments.done\",\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n call_id: callId,\n arguments: args,\n },\n isBeta,\n );\n\n if (ws.isClosed) break;\n\n // response.output_item.done\n sendEvent(\n ws,\n {\n type: \"response.output_item.done\",\n response_id: responseId,\n output_index: outputIndex,\n item: { ...toolOutputItem, phase: \"final_answer\" },\n },\n isBeta,\n );\n\n // conversation.item.done (tool call)\n sendEvent(\n ws,\n {\n type: \"conversation.item.done\",\n item: {\n id: itemId,\n object: \"realtime.item\",\n type: \"function_call\",\n status: \"completed\",\n call_id: callId,\n name: tc.name,\n arguments: args,\n },\n },\n isBeta,\n );\n\n if (ws.isClosed) break;\n\n allOutputItems.push(toolOutputItem);\n }\n\n if (interrupted) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n interruption?.cleanup();\n return;\n }\n\n interruption?.cleanup();\n\n if (ws.isClosed) return;\n\n // response.done\n sendEvent(\n ws,\n {\n type: \"response.done\",\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"completed\",\n output: allOutputItems,\n usage: { total_tokens: 0, input_tokens: 0, output_tokens: 0 },\n },\n },\n isBeta,\n );\n\n // Accumulate into conversation for multi-turn\n conversationItems.push({\n type: \"message\",\n id: textItemId,\n role: \"assistant\",\n content: [{ type: \"text\", text: content }],\n });\n for (const item of allOutputItems.slice(1)) {\n conversationItems.push(item as RealtimeItem);\n }\n return;\n }\n\n // ── Text response ───────────────────────────────────────────────────\n if (isTextResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 200, fixture },\n });\n\n const itemId = realtimeId(\"item\");\n const contentIndex = 0;\n const outputIndex = 0;\n\n const outputItem = {\n id: itemId,\n type: \"message\",\n role: \"assistant\",\n status: \"completed\",\n content: [{ type: \"output_text\", text: response.content }],\n };\n\n // response.created\n sendEvent(\n ws,\n {\n type: \"response.created\",\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"in_progress\",\n status_details: null,\n output: [],\n usage: null,\n },\n },\n isBeta,\n );\n\n // response.output_item.added\n sendEvent(\n ws,\n {\n type: \"response.output_item.added\",\n response_id: responseId,\n output_index: outputIndex,\n item: {\n id: itemId,\n type: \"message\",\n role: \"assistant\",\n status: \"in_progress\",\n content: [],\n phase: \"final_answer\",\n },\n },\n isBeta,\n );\n\n // response.content_part.added\n sendEvent(\n ws,\n {\n type: \"response.content_part.added\",\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n content_index: contentIndex,\n part: { type: \"output_text\", text: \"\" },\n },\n isBeta,\n );\n\n // response.output_text.delta (chunked) — GA name\n const content = response.content;\n const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;\n const { recordedTimings } = fixture;\n const interruption = createInterruptionSignal(fixture);\n let interrupted = false;\n let eventIndex = 0;\n\n for (let i = 0; i < content.length; i += chunkSize) {\n if (ws.isClosed) break;\n const chunkDelay = calculateDelay(\n eventIndex,\n undefined,\n latency,\n recordedTimings,\n replaySpeed,\n );\n if (chunkDelay > 0) await delay(chunkDelay, interruption?.signal);\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n if (ws.isClosed) break;\n const chunk = content.slice(i, i + chunkSize);\n sendEvent(\n ws,\n {\n type: \"response.output_text.delta\",\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n content_index: contentIndex,\n delta: chunk,\n },\n isBeta,\n );\n eventIndex++;\n interruption?.tick();\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n }\n\n if (interrupted) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n interruption?.cleanup();\n return;\n }\n\n interruption?.cleanup();\n\n if (ws.isClosed) return;\n\n // response.output_text.done\n sendEvent(\n ws,\n {\n type: \"response.output_text.done\",\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n content_index: contentIndex,\n text: content,\n },\n isBeta,\n );\n\n // response.content_part.done\n sendEvent(\n ws,\n {\n type: \"response.content_part.done\",\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n content_index: contentIndex,\n part: { type: \"output_text\", text: content },\n },\n isBeta,\n );\n\n // response.output_item.done\n sendEvent(\n ws,\n {\n type: \"response.output_item.done\",\n response_id: responseId,\n output_index: outputIndex,\n item: { ...outputItem, phase: \"final_answer\" },\n },\n isBeta,\n );\n\n // conversation.item.done (text message)\n sendEvent(\n ws,\n {\n type: \"conversation.item.done\",\n item: {\n id: itemId,\n object: \"realtime.item\",\n type: \"message\",\n role: \"assistant\",\n status: \"completed\",\n content: outputItem.content,\n },\n },\n isBeta,\n );\n\n // response.done\n sendEvent(\n ws,\n {\n type: \"response.done\",\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"completed\",\n output: [outputItem],\n usage: { total_tokens: 0, input_tokens: 0, output_tokens: 0 },\n },\n },\n isBeta,\n );\n\n // Accumulate assistant response into conversation for multi-turn\n conversationItems.push({\n type: \"message\",\n id: itemId,\n role: \"assistant\",\n content: [{ type: \"text\", text: content }],\n });\n return;\n }\n\n // ── Tool call response ──────────────────────────────────────────────\n if (isToolCallResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 200, fixture },\n });\n\n // response.created\n sendEvent(\n ws,\n {\n type: \"response.created\",\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"in_progress\",\n status_details: null,\n output: [],\n usage: null,\n },\n },\n isBeta,\n );\n\n const outputItems: unknown[] = [];\n const interruption = createInterruptionSignal(fixture);\n const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;\n const { recordedTimings } = fixture;\n let interrupted = false;\n let eventIndex = 0;\n\n for (let tcIdx = 0; tcIdx < response.toolCalls.length; tcIdx++) {\n const tc = response.toolCalls[tcIdx];\n const callId = tc.id ?? generateToolCallId();\n const itemId = realtimeId(\"item\");\n\n const outputItem = {\n id: itemId,\n type: \"function_call\",\n status: \"completed\",\n call_id: callId,\n name: tc.name,\n arguments: tc.arguments,\n };\n\n // response.output_item.added\n sendEvent(\n ws,\n {\n type: \"response.output_item.added\",\n response_id: responseId,\n output_index: tcIdx,\n item: {\n id: itemId,\n type: \"function_call\",\n status: \"in_progress\",\n call_id: callId,\n name: tc.name,\n arguments: \"\",\n phase: \"final_answer\",\n },\n },\n isBeta,\n );\n\n // response.function_call_arguments.delta (chunked)\n // eventIndex is continuous across all tool calls to avoid re-triggering TTFT\n const args = tc.arguments;\n for (let i = 0; i < args.length; i += chunkSize) {\n if (ws.isClosed) break;\n const chunkDelay = calculateDelay(\n eventIndex,\n undefined,\n latency,\n recordedTimings,\n replaySpeed,\n );\n if (chunkDelay > 0) await delay(chunkDelay, interruption?.signal);\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n if (ws.isClosed) break;\n const chunk = args.slice(i, i + chunkSize);\n sendEvent(\n ws,\n {\n type: \"response.function_call_arguments.delta\",\n response_id: responseId,\n item_id: itemId,\n output_index: tcIdx,\n call_id: callId,\n delta: chunk,\n },\n isBeta,\n );\n eventIndex++;\n interruption?.tick();\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n }\n\n if (interrupted) break;\n\n if (ws.isClosed) break;\n\n // response.function_call_arguments.done\n sendEvent(\n ws,\n {\n type: \"response.function_call_arguments.done\",\n response_id: responseId,\n item_id: itemId,\n output_index: tcIdx,\n call_id: callId,\n arguments: args,\n },\n isBeta,\n );\n\n // response.output_item.done\n sendEvent(\n ws,\n {\n type: \"response.output_item.done\",\n response_id: responseId,\n output_index: tcIdx,\n item: { ...outputItem, phase: \"final_answer\" },\n },\n isBeta,\n );\n\n // conversation.item.done (tool call)\n sendEvent(\n ws,\n {\n type: \"conversation.item.done\",\n item: {\n id: itemId,\n object: \"realtime.item\",\n type: \"function_call\",\n status: \"completed\",\n call_id: callId,\n name: tc.name,\n arguments: args,\n },\n },\n isBeta,\n );\n\n outputItems.push(outputItem);\n }\n\n if (interrupted) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n interruption?.cleanup();\n return;\n }\n\n interruption?.cleanup();\n\n if (ws.isClosed) return;\n\n // response.done\n sendEvent(\n ws,\n {\n type: \"response.done\",\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"completed\",\n output: outputItems,\n usage: { total_tokens: 0, input_tokens: 0, output_tokens: 0 },\n },\n },\n isBeta,\n );\n\n // Accumulate assistant tool calls into conversation for multi-turn\n // Reuse outputItems (which already have the correct call_id) to avoid generating divergent IDs\n for (const item of outputItems) {\n conversationItems.push(item as RealtimeItem);\n }\n return;\n }\n\n // Unknown response type\n journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 500, fixture },\n });\n buildErrorRealtimeEvent(\n ws,\n \"Fixture response did not match any known type\",\n isBeta,\n \"server_error\",\n );\n}\n"],"mappings":"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"}
@@ -9,6 +9,7 @@ import * as node_http0 from "node:http";
9
9
  declare function handleWebSocketRealtime(ws: WebSocketConnection, fixtures: Fixture[], journal: Journal, defaults: {
10
10
  latency: number;
11
11
  chunkSize: number;
12
+ replaySpeed?: number;
12
13
  model: string;
13
14
  logger: Logger;
14
15
  strict?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"ws-realtime.d.cts","names":[],"sources":["../src/ws-realtime.ts"],"sourcesContent":[],"mappings":";;;;;;;;iBAgSgB,uBAAA,KACV,+BACM,oBACD;;;;UAKC;;2BAEiB,0BAA0B;;mBAAqB,UAAA,CAEnC"}
1
+ {"version":3,"file":"ws-realtime.d.cts","names":[],"sources":["../src/ws-realtime.ts"],"sourcesContent":[],"mappings":";;;;;;;;iBAgSgB,uBAAA,KACV,+BACM,oBACD;;;;;UAMC;;2BAEiB,0BAA0B;;mBAAqB,UAAA,CAEnC"}
@@ -9,6 +9,7 @@ import * as node_http0 from "node:http";
9
9
  declare function handleWebSocketRealtime(ws: WebSocketConnection, fixtures: Fixture[], journal: Journal, defaults: {
10
10
  latency: number;
11
11
  chunkSize: number;
12
+ replaySpeed?: number;
12
13
  model: string;
13
14
  logger: Logger;
14
15
  strict?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"ws-realtime.d.ts","names":[],"sources":["../src/ws-realtime.ts"],"sourcesContent":[],"mappings":";;;;;;;;iBAgSgB,uBAAA,KACV,+BACM,oBACD;;;;UAKC;;2BAEiB,0BAA0B;;mBAAqB,UAAA,CAEnC"}
1
+ {"version":3,"file":"ws-realtime.d.ts","names":[],"sources":["../src/ws-realtime.ts"],"sourcesContent":[],"mappings":";;;;;;;;iBAgSgB,uBAAA,KACV,+BACM,oBACD;;;;;UAMC;;2BAEiB,0BAA0B;;mBAAqB,UAAA,CAEnC"}
@@ -2,7 +2,7 @@ import { DEFAULT_TEST_ID } from "./constants.js";
2
2
  import { flattenHeaders, generateToolCallId, isContentWithToolCallsResponse, isErrorResponse, isTextResponse, isToolCallResponse, resolveResponse, resolveStrictMode, strictOverrideField } from "./helpers.js";
3
3
  import "./journal.js";
4
4
  import { matchFixture } from "./router.js";
5
- import { delay } from "./sse-writer.js";
5
+ import { calculateDelay, delay } from "./sse-writer.js";
6
6
  import { createInterruptionSignal } from "./interruption.js";
7
7
  import { randomBytes } from "node:crypto";
8
8
 
@@ -594,9 +594,13 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
594
594
  }
595
595
  }, isBeta);
596
596
  const content = response.content;
597
+ const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;
598
+ const { recordedTimings } = fixture;
599
+ let eventIndex = 0;
597
600
  for (let i = 0; i < content.length; i += chunkSize) {
598
601
  if (ws.isClosed) break;
599
- if (latency > 0) await delay(latency, interruption?.signal);
602
+ const chunkDelay = calculateDelay(eventIndex, void 0, latency, recordedTimings, replaySpeed);
603
+ if (chunkDelay > 0) await delay(chunkDelay, interruption?.signal);
600
604
  if (interruption?.signal.aborted) {
601
605
  interrupted = true;
602
606
  break;
@@ -610,6 +614,7 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
610
614
  content_index: contentIndex,
611
615
  delta: content.slice(i, i + chunkSize)
612
616
  }, isBeta);
617
+ eventIndex++;
613
618
  interruption?.tick();
614
619
  if (interruption?.signal.aborted) {
615
620
  interrupted = true;
@@ -709,7 +714,8 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
709
714
  const args = tc.arguments;
710
715
  for (let i = 0; i < args.length; i += chunkSize) {
711
716
  if (ws.isClosed) break;
712
- if (latency > 0) await delay(latency, interruption?.signal);
717
+ const chunkDelay = calculateDelay(eventIndex, void 0, latency, recordedTimings, replaySpeed);
718
+ if (chunkDelay > 0) await delay(chunkDelay, interruption?.signal);
713
719
  if (interruption?.signal.aborted) {
714
720
  interrupted = true;
715
721
  break;
@@ -723,6 +729,7 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
723
729
  call_id: callId,
724
730
  delta: args.slice(i, i + chunkSize)
725
731
  }, isBeta);
732
+ eventIndex++;
726
733
  interruption?.tick();
727
734
  if (interruption?.signal.aborted) {
728
735
  interrupted = true;
@@ -859,11 +866,15 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
859
866
  }
860
867
  }, isBeta);
861
868
  const content = response.content;
869
+ const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;
870
+ const { recordedTimings } = fixture;
862
871
  const interruption = createInterruptionSignal(fixture);
863
872
  let interrupted = false;
873
+ let eventIndex = 0;
864
874
  for (let i = 0; i < content.length; i += chunkSize) {
865
875
  if (ws.isClosed) break;
866
- if (latency > 0) await delay(latency, interruption?.signal);
876
+ const chunkDelay = calculateDelay(eventIndex, void 0, latency, recordedTimings, replaySpeed);
877
+ if (chunkDelay > 0) await delay(chunkDelay, interruption?.signal);
867
878
  if (interruption?.signal.aborted) {
868
879
  interrupted = true;
869
880
  break;
@@ -877,6 +888,7 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
877
888
  content_index: contentIndex,
878
889
  delta: content.slice(i, i + chunkSize)
879
890
  }, isBeta);
891
+ eventIndex++;
880
892
  interruption?.tick();
881
893
  if (interruption?.signal.aborted) {
882
894
  interrupted = true;
@@ -980,7 +992,10 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
980
992
  }, isBeta);
981
993
  const outputItems = [];
982
994
  const interruption = createInterruptionSignal(fixture);
995
+ const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;
996
+ const { recordedTimings } = fixture;
983
997
  let interrupted = false;
998
+ let eventIndex = 0;
984
999
  for (let tcIdx = 0; tcIdx < response.toolCalls.length; tcIdx++) {
985
1000
  const tc = response.toolCalls[tcIdx];
986
1001
  const callId = tc.id ?? generateToolCallId();
@@ -1010,7 +1025,8 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
1010
1025
  const args = tc.arguments;
1011
1026
  for (let i = 0; i < args.length; i += chunkSize) {
1012
1027
  if (ws.isClosed) break;
1013
- if (latency > 0) await delay(latency, interruption?.signal);
1028
+ const chunkDelay = calculateDelay(eventIndex, void 0, latency, recordedTimings, replaySpeed);
1029
+ if (chunkDelay > 0) await delay(chunkDelay, interruption?.signal);
1014
1030
  if (interruption?.signal.aborted) {
1015
1031
  interrupted = true;
1016
1032
  break;
@@ -1025,6 +1041,7 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
1025
1041
  call_id: callId,
1026
1042
  delta: chunk
1027
1043
  }, isBeta);
1044
+ eventIndex++;
1028
1045
  interruption?.tick();
1029
1046
  if (interruption?.signal.aborted) {
1030
1047
  interrupted = true;