@copilotkit/aimock 1.25.0 → 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 (130) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +9 -0
  4. package/README.md +4 -0
  5. package/dist/aws-event-stream.cjs +2 -1
  6. package/dist/aws-event-stream.cjs.map +1 -1
  7. package/dist/aws-event-stream.d.cts +3 -1
  8. package/dist/aws-event-stream.d.cts.map +1 -1
  9. package/dist/aws-event-stream.d.ts +3 -1
  10. package/dist/aws-event-stream.d.ts.map +1 -1
  11. package/dist/aws-event-stream.js +2 -1
  12. package/dist/aws-event-stream.js.map +1 -1
  13. package/dist/bedrock-converse.cjs +6 -0
  14. package/dist/bedrock-converse.cjs.map +1 -1
  15. package/dist/bedrock-converse.js +6 -0
  16. package/dist/bedrock-converse.js.map +1 -1
  17. package/dist/bedrock.cjs +6 -0
  18. package/dist/bedrock.cjs.map +1 -1
  19. package/dist/bedrock.js +6 -0
  20. package/dist/bedrock.js.map +1 -1
  21. package/dist/cli.cjs +11 -0
  22. package/dist/cli.cjs.map +1 -1
  23. package/dist/cli.js +11 -0
  24. package/dist/cli.js.map +1 -1
  25. package/dist/cohere.cjs +8 -1
  26. package/dist/cohere.cjs.map +1 -1
  27. package/dist/cohere.d.cts.map +1 -1
  28. package/dist/cohere.d.ts.map +1 -1
  29. package/dist/cohere.js +8 -1
  30. package/dist/cohere.js.map +1 -1
  31. package/dist/config-loader.d.cts.map +1 -1
  32. package/dist/fixture-loader.cjs +16 -3
  33. package/dist/fixture-loader.cjs.map +1 -1
  34. package/dist/fixture-loader.d.cts.map +1 -1
  35. package/dist/fixture-loader.d.ts.map +1 -1
  36. package/dist/fixture-loader.js +16 -3
  37. package/dist/fixture-loader.js.map +1 -1
  38. package/dist/gemini-interactions.cjs +9 -1
  39. package/dist/gemini-interactions.cjs.map +1 -1
  40. package/dist/gemini-interactions.d.cts.map +1 -1
  41. package/dist/gemini-interactions.d.ts.map +1 -1
  42. package/dist/gemini-interactions.js +9 -1
  43. package/dist/gemini-interactions.js.map +1 -1
  44. package/dist/gemini.cjs +11 -1
  45. package/dist/gemini.cjs.map +1 -1
  46. package/dist/gemini.d.cts.map +1 -1
  47. package/dist/gemini.d.ts.map +1 -1
  48. package/dist/gemini.js +11 -1
  49. package/dist/gemini.js.map +1 -1
  50. package/dist/llmock.cjs +1 -1
  51. package/dist/llmock.cjs.map +1 -1
  52. package/dist/llmock.js +1 -1
  53. package/dist/llmock.js.map +1 -1
  54. package/dist/messages.cjs +8 -1
  55. package/dist/messages.cjs.map +1 -1
  56. package/dist/messages.d.cts.map +1 -1
  57. package/dist/messages.d.ts.map +1 -1
  58. package/dist/messages.js +8 -1
  59. package/dist/messages.js.map +1 -1
  60. package/dist/ndjson-writer.cjs +2 -1
  61. package/dist/ndjson-writer.cjs.map +1 -1
  62. package/dist/ndjson-writer.d.cts +3 -2
  63. package/dist/ndjson-writer.d.cts.map +1 -1
  64. package/dist/ndjson-writer.d.ts +3 -2
  65. package/dist/ndjson-writer.d.ts.map +1 -1
  66. package/dist/ndjson-writer.js +2 -1
  67. package/dist/ndjson-writer.js.map +1 -1
  68. package/dist/ollama.cjs +8 -0
  69. package/dist/ollama.cjs.map +1 -1
  70. package/dist/ollama.d.cts.map +1 -1
  71. package/dist/ollama.d.ts.map +1 -1
  72. package/dist/ollama.js +8 -0
  73. package/dist/ollama.js.map +1 -1
  74. package/dist/recorder.cjs +38 -1
  75. package/dist/recorder.cjs.map +1 -1
  76. package/dist/recorder.d.cts.map +1 -1
  77. package/dist/recorder.d.ts.map +1 -1
  78. package/dist/recorder.js +38 -1
  79. package/dist/recorder.js.map +1 -1
  80. package/dist/responses.cjs +10 -1
  81. package/dist/responses.cjs.map +1 -1
  82. package/dist/responses.d.cts.map +1 -1
  83. package/dist/responses.d.ts.map +1 -1
  84. package/dist/responses.js +10 -1
  85. package/dist/responses.js.map +1 -1
  86. package/dist/server.cjs +11 -4
  87. package/dist/server.cjs.map +1 -1
  88. package/dist/server.d.cts.map +1 -1
  89. package/dist/server.d.ts.map +1 -1
  90. package/dist/server.js +11 -4
  91. package/dist/server.js.map +1 -1
  92. package/dist/sse-writer.cjs +28 -8
  93. package/dist/sse-writer.cjs.map +1 -1
  94. package/dist/sse-writer.d.cts +4 -2
  95. package/dist/sse-writer.d.cts.map +1 -1
  96. package/dist/sse-writer.d.ts +4 -2
  97. package/dist/sse-writer.d.ts.map +1 -1
  98. package/dist/sse-writer.js +28 -8
  99. package/dist/sse-writer.js.map +1 -1
  100. package/dist/types.d.cts +17 -1
  101. package/dist/types.d.cts.map +1 -1
  102. package/dist/types.d.ts +17 -1
  103. package/dist/types.d.ts.map +1 -1
  104. package/dist/vector-types.d.cts.map +1 -1
  105. package/dist/vector-types.d.ts.map +1 -1
  106. package/dist/ws-gemini-live.cjs +14 -4
  107. package/dist/ws-gemini-live.cjs.map +1 -1
  108. package/dist/ws-gemini-live.d.cts +1 -0
  109. package/dist/ws-gemini-live.d.cts.map +1 -1
  110. package/dist/ws-gemini-live.d.ts +1 -0
  111. package/dist/ws-gemini-live.d.ts.map +1 -1
  112. package/dist/ws-gemini-live.js +15 -5
  113. package/dist/ws-gemini-live.js.map +1 -1
  114. package/dist/ws-realtime.cjs +21 -4
  115. package/dist/ws-realtime.cjs.map +1 -1
  116. package/dist/ws-realtime.d.cts +1 -0
  117. package/dist/ws-realtime.d.cts.map +1 -1
  118. package/dist/ws-realtime.d.ts +1 -0
  119. package/dist/ws-realtime.d.ts.map +1 -1
  120. package/dist/ws-realtime.js +22 -5
  121. package/dist/ws-realtime.js.map +1 -1
  122. package/dist/ws-responses.cjs +8 -5
  123. package/dist/ws-responses.cjs.map +1 -1
  124. package/dist/ws-responses.d.cts +1 -0
  125. package/dist/ws-responses.d.cts.map +1 -1
  126. package/dist/ws-responses.d.ts +1 -0
  127. package/dist/ws-responses.d.ts.map +1 -1
  128. package/dist/ws-responses.js +9 -6
  129. package/dist/ws-responses.js.map +1 -1
  130. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ws-realtime.js","names":[],"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.js","names":[],"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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"}
@@ -124,7 +124,7 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
124
124
  });
125
125
  const events = require_responses.buildContentWithToolCallsStreamEvents(response.content, response.toolCalls, completionReq.model, chunkSize, response.reasoning, response.webSearches, require_helpers.extractOverrides(response));
126
126
  const interruption = require_interruption.createInterruptionSignal(fixture);
127
- if (!await sendEvents(ws, events, latency, interruption?.signal, interruption?.tick)) {
127
+ if (!await sendEvents(ws, events, latency, interruption?.signal, interruption?.tick, fixture.recordedTimings, fixture.replaySpeed ?? defaults.replaySpeed)) {
128
128
  ws.destroy();
129
129
  journalEntry.response.interrupted = true;
130
130
  journalEntry.response.interruptReason = interruption?.reason();
@@ -145,7 +145,7 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
145
145
  });
146
146
  const events = require_responses.buildTextStreamEvents(response.content, completionReq.model, chunkSize, response.reasoning, response.webSearches, require_helpers.extractOverrides(response));
147
147
  const interruption = require_interruption.createInterruptionSignal(fixture);
148
- if (!await sendEvents(ws, events, latency, interruption?.signal, interruption?.tick)) {
148
+ if (!await sendEvents(ws, events, latency, interruption?.signal, interruption?.tick, fixture.recordedTimings, fixture.replaySpeed ?? defaults.replaySpeed)) {
149
149
  ws.destroy();
150
150
  journalEntry.response.interrupted = true;
151
151
  journalEntry.response.interruptReason = interruption?.reason();
@@ -166,7 +166,7 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
166
166
  });
167
167
  const events = require_responses.buildToolCallStreamEvents(response.toolCalls, completionReq.model, chunkSize, response.webSearches, require_helpers.extractOverrides(response));
168
168
  const interruption = require_interruption.createInterruptionSignal(fixture);
169
- if (!await sendEvents(ws, events, latency, interruption?.signal, interruption?.tick)) {
169
+ if (!await sendEvents(ws, events, latency, interruption?.signal, interruption?.tick, fixture.recordedTimings, fixture.replaySpeed ?? defaults.replaySpeed)) {
170
170
  ws.destroy();
171
171
  journalEntry.response.interrupted = true;
172
172
  journalEntry.response.interruptReason = interruption?.reason();
@@ -186,13 +186,16 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
186
186
  });
187
187
  ws.send(JSON.stringify(buildErrorEvent("Fixture response did not match any known type", "server_error")));
188
188
  }
189
- async function sendEvents(ws, events, latency, signal, onChunkSent) {
189
+ async function sendEvents(ws, events, latency, signal, onChunkSent, recordedTimings, replaySpeed) {
190
+ let eventIndex = 0;
190
191
  for (const event of events) {
191
192
  if (ws.isClosed) return false;
192
- if (latency > 0) await require_sse_writer.delay(latency, signal);
193
+ const chunkDelay = require_sse_writer.calculateDelay(eventIndex, void 0, latency, recordedTimings, replaySpeed);
194
+ if (chunkDelay > 0) await require_sse_writer.delay(chunkDelay, signal);
193
195
  if (signal?.aborted) return false;
194
196
  if (ws.isClosed) return false;
195
197
  ws.send(JSON.stringify(event));
198
+ eventIndex++;
196
199
  onChunkSent?.();
197
200
  if (signal?.aborted) return false;
198
201
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ws-responses.cjs","names":["responsesToCompletionRequest","DEFAULT_TEST_ID","matchFixture","resolveStrictMode","flattenHeaders","strictOverrideField","resolveResponse","isErrorResponse","isContentWithToolCallsResponse","buildContentWithToolCallsStreamEvents","extractOverrides","createInterruptionSignal","isTextResponse","buildTextStreamEvents","isToolCallResponse","buildToolCallStreamEvents","delay"],"sources":["../src/ws-responses.ts"],"sourcesContent":["/**\n * WebSocket handler for OpenAI Responses API.\n *\n * Accepts `{ type: \"response.create\", model: \"...\", input: [...] }` messages over\n * WebSocket and sends back the same Responses API SSE events as the HTTP\n * handler, but as individual WebSocket text frames.\n */\n\nimport type { ChatCompletionRequest, Fixture } from \"./types.js\";\nimport { matchFixture } from \"./router.js\";\nimport {\n responsesToCompletionRequest,\n buildTextStreamEvents,\n buildToolCallStreamEvents,\n buildContentWithToolCallsStreamEvents,\n type ResponsesSSEEvent,\n} from \"./responses.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n extractOverrides,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n flattenHeaders,\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\ninterface ResponseCreateMessage {\n type: \"response.create\";\n model?: string;\n input?: unknown[];\n instructions?: string;\n tools?: unknown[];\n tool_choice?: string | object;\n stream?: boolean;\n temperature?: number;\n max_output_tokens?: number;\n [key: string]: unknown;\n}\n\nfunction isResponseCreateMessage(msg: unknown): msg is ResponseCreateMessage {\n return (\n typeof msg === \"object\" &&\n msg !== null &&\n (msg as ResponseCreateMessage).type === \"response.create\"\n );\n}\n\nfunction buildErrorEvent(\n message: string,\n type = \"invalid_request_error\",\n code?: string,\n): ResponsesSSEEvent {\n return {\n type: \"error\",\n error: { message, type, code },\n };\n}\n\nexport function handleWebSocketResponses(\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 // Serialize message processing to prevent event interleaving\n let pending = Promise.resolve();\n ws.on(\"message\", (raw: string) => {\n pending = pending.then(() =>\n processMessage(raw, ws, fixtures, journal, defaults).catch((err: unknown) => {\n const msg = err instanceof Error ? err.message : \"Internal error\";\n logger.error(`WebSocket responses error: ${msg}`);\n try {\n ws.send(JSON.stringify(buildErrorEvent(msg, \"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): Promise<void> {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n ws.send(\n JSON.stringify(\n buildErrorEvent(`Malformed JSON: ${detail}`, \"invalid_request_error\", \"invalid_json\"),\n ),\n );\n return;\n }\n\n if (!isResponseCreateMessage(parsed)) {\n ws.send(\n JSON.stringify(\n buildErrorEvent(\n 'Expected message type \"response.create\"',\n \"invalid_request_error\",\n \"invalid_message_type\",\n ),\n ),\n );\n return;\n }\n\n const responsesReq = {\n model: parsed.model ?? defaults.model,\n input: (parsed.input ?? []) as {\n role?: string;\n type?: string;\n content?: string | { type: string; text?: string }[];\n call_id?: string;\n name?: string;\n arguments?: string;\n output?: string;\n id?: string;\n }[],\n instructions: parsed.instructions,\n tools: parsed.tools as\n | {\n type: \"function\";\n name: string;\n description?: string;\n parameters?: object;\n strict?: boolean;\n }[]\n | undefined,\n tool_choice: parsed.tool_choice,\n stream: parsed.stream,\n temperature: parsed.temperature,\n max_output_tokens: parsed.max_output_tokens,\n };\n\n const completionReq = responsesToCompletionRequest(responsesReq);\n completionReq._endpointType = \"chat\";\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\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/responses\",\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/responses\",\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 ws.send(\n JSON.stringify(\n buildErrorEvent(\"No fixture matched\", \"invalid_request_error\", \"no_fixture_match\"),\n ),\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 response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status, fixture },\n });\n ws.send(\n JSON.stringify(\n buildErrorEvent(response.error.message, response.error.type, response.error.code),\n ),\n );\n return;\n }\n\n // Content + tool calls response (must be checked before isTextResponse / isToolCallResponse)\n if (isContentWithToolCallsResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 200, fixture },\n });\n\n const events = buildContentWithToolCallsStreamEvents(\n response.content,\n response.toolCalls,\n completionReq.model,\n chunkSize,\n response.reasoning,\n response.webSearches,\n extractOverrides(response),\n );\n\n const interruption = createInterruptionSignal(fixture);\n const completed = await sendEvents(\n ws,\n events,\n latency,\n interruption?.signal,\n interruption?.tick,\n );\n if (!completed) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n return;\n }\n\n // Text response\n if (isTextResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 200, fixture },\n });\n\n const events = buildTextStreamEvents(\n response.content,\n completionReq.model,\n chunkSize,\n response.reasoning,\n response.webSearches,\n extractOverrides(response),\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await sendEvents(\n ws,\n events,\n latency,\n interruption?.signal,\n interruption?.tick,\n );\n if (!completed) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n return;\n }\n\n // Tool call response\n if (isToolCallResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 200, fixture },\n });\n const events = buildToolCallStreamEvents(\n response.toolCalls,\n completionReq.model,\n chunkSize,\n response.webSearches,\n extractOverrides(response),\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await sendEvents(\n ws,\n events,\n latency,\n interruption?.signal,\n interruption?.tick,\n );\n if (!completed) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n return;\n }\n\n // Unknown response type\n journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 500, fixture },\n });\n ws.send(\n JSON.stringify(\n buildErrorEvent(\"Fixture response did not match any known type\", \"server_error\"),\n ),\n );\n}\n\nasync function sendEvents(\n ws: WebSocketConnection,\n events: ResponsesSSEEvent[],\n latency: number,\n signal?: AbortSignal,\n onChunkSent?: () => void,\n): Promise<boolean> {\n for (const event of events) {\n if (ws.isClosed) return false;\n if (latency > 0) await delay(latency, signal);\n if (signal?.aborted) return false;\n if (ws.isClosed) return false;\n ws.send(JSON.stringify(event));\n onChunkSent?.();\n if (signal?.aborted) return false;\n }\n return true;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ws-responses.cjs","names":["responsesToCompletionRequest","DEFAULT_TEST_ID","matchFixture","resolveStrictMode","flattenHeaders","strictOverrideField","resolveResponse","isErrorResponse","isContentWithToolCallsResponse","buildContentWithToolCallsStreamEvents","extractOverrides","createInterruptionSignal","isTextResponse","buildTextStreamEvents","isToolCallResponse","buildToolCallStreamEvents","calculateDelay","delay"],"sources":["../src/ws-responses.ts"],"sourcesContent":["/**\n * WebSocket handler for OpenAI Responses API.\n *\n * Accepts `{ type: \"response.create\", model: \"...\", input: [...] }` messages over\n * WebSocket and sends back the same Responses API SSE events as the HTTP\n * handler, but as individual WebSocket text frames.\n */\n\nimport type { ChatCompletionRequest, Fixture } from \"./types.js\";\nimport { matchFixture } from \"./router.js\";\nimport {\n responsesToCompletionRequest,\n buildTextStreamEvents,\n buildToolCallStreamEvents,\n buildContentWithToolCallsStreamEvents,\n type ResponsesSSEEvent,\n} from \"./responses.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n extractOverrides,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n flattenHeaders,\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\ninterface ResponseCreateMessage {\n type: \"response.create\";\n model?: string;\n input?: unknown[];\n instructions?: string;\n tools?: unknown[];\n tool_choice?: string | object;\n stream?: boolean;\n temperature?: number;\n max_output_tokens?: number;\n [key: string]: unknown;\n}\n\nfunction isResponseCreateMessage(msg: unknown): msg is ResponseCreateMessage {\n return (\n typeof msg === \"object\" &&\n msg !== null &&\n (msg as ResponseCreateMessage).type === \"response.create\"\n );\n}\n\nfunction buildErrorEvent(\n message: string,\n type = \"invalid_request_error\",\n code?: string,\n): ResponsesSSEEvent {\n return {\n type: \"error\",\n error: { message, type, code },\n };\n}\n\nexport function handleWebSocketResponses(\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 // Serialize message processing to prevent event interleaving\n let pending = Promise.resolve();\n ws.on(\"message\", (raw: string) => {\n pending = pending.then(() =>\n processMessage(raw, ws, fixtures, journal, defaults).catch((err: unknown) => {\n const msg = err instanceof Error ? err.message : \"Internal error\";\n logger.error(`WebSocket responses error: ${msg}`);\n try {\n ws.send(JSON.stringify(buildErrorEvent(msg, \"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): Promise<void> {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n ws.send(\n JSON.stringify(\n buildErrorEvent(`Malformed JSON: ${detail}`, \"invalid_request_error\", \"invalid_json\"),\n ),\n );\n return;\n }\n\n if (!isResponseCreateMessage(parsed)) {\n ws.send(\n JSON.stringify(\n buildErrorEvent(\n 'Expected message type \"response.create\"',\n \"invalid_request_error\",\n \"invalid_message_type\",\n ),\n ),\n );\n return;\n }\n\n const responsesReq = {\n model: parsed.model ?? defaults.model,\n input: (parsed.input ?? []) as {\n role?: string;\n type?: string;\n content?: string | { type: string; text?: string }[];\n call_id?: string;\n name?: string;\n arguments?: string;\n output?: string;\n id?: string;\n }[],\n instructions: parsed.instructions,\n tools: parsed.tools as\n | {\n type: \"function\";\n name: string;\n description?: string;\n parameters?: object;\n strict?: boolean;\n }[]\n | undefined,\n tool_choice: parsed.tool_choice,\n stream: parsed.stream,\n temperature: parsed.temperature,\n max_output_tokens: parsed.max_output_tokens,\n };\n\n const completionReq = responsesToCompletionRequest(responsesReq);\n completionReq._endpointType = \"chat\";\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\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/responses\",\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/responses\",\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 ws.send(\n JSON.stringify(\n buildErrorEvent(\"No fixture matched\", \"invalid_request_error\", \"no_fixture_match\"),\n ),\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 response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status, fixture },\n });\n ws.send(\n JSON.stringify(\n buildErrorEvent(response.error.message, response.error.type, response.error.code),\n ),\n );\n return;\n }\n\n // Content + tool calls response (must be checked before isTextResponse / isToolCallResponse)\n if (isContentWithToolCallsResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 200, fixture },\n });\n\n const events = buildContentWithToolCallsStreamEvents(\n response.content,\n response.toolCalls,\n completionReq.model,\n chunkSize,\n response.reasoning,\n response.webSearches,\n extractOverrides(response),\n );\n\n const interruption = createInterruptionSignal(fixture);\n const completed = await sendEvents(\n ws,\n events,\n latency,\n interruption?.signal,\n interruption?.tick,\n fixture.recordedTimings,\n fixture.replaySpeed ?? defaults.replaySpeed,\n );\n if (!completed) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n return;\n }\n\n // Text response\n if (isTextResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 200, fixture },\n });\n\n const events = buildTextStreamEvents(\n response.content,\n completionReq.model,\n chunkSize,\n response.reasoning,\n response.webSearches,\n extractOverrides(response),\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await sendEvents(\n ws,\n events,\n latency,\n interruption?.signal,\n interruption?.tick,\n fixture.recordedTimings,\n fixture.replaySpeed ?? defaults.replaySpeed,\n );\n if (!completed) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n return;\n }\n\n // Tool call response\n if (isToolCallResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 200, fixture },\n });\n const events = buildToolCallStreamEvents(\n response.toolCalls,\n completionReq.model,\n chunkSize,\n response.webSearches,\n extractOverrides(response),\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await sendEvents(\n ws,\n events,\n latency,\n interruption?.signal,\n interruption?.tick,\n fixture.recordedTimings,\n fixture.replaySpeed ?? defaults.replaySpeed,\n );\n if (!completed) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n return;\n }\n\n // Unknown response type\n journal.add({\n method: \"WS\",\n path: \"/v1/responses\",\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 500, fixture },\n });\n ws.send(\n JSON.stringify(\n buildErrorEvent(\"Fixture response did not match any known type\", \"server_error\"),\n ),\n );\n}\n\nasync function sendEvents(\n ws: WebSocketConnection,\n events: ResponsesSSEEvent[],\n latency: number,\n signal?: AbortSignal,\n onChunkSent?: () => void,\n recordedTimings?: import(\"./types.js\").RecordedTimings,\n replaySpeed?: number,\n): Promise<boolean> {\n let eventIndex = 0;\n for (const event of events) {\n if (ws.isClosed) return false;\n const chunkDelay = calculateDelay(eventIndex, undefined, latency, recordedTimings, replaySpeed);\n if (chunkDelay > 0) await delay(chunkDelay, signal);\n if (signal?.aborted) return false;\n if (ws.isClosed) return false;\n ws.send(JSON.stringify(event));\n eventIndex++;\n onChunkSent?.();\n if (signal?.aborted) return false;\n }\n return true;\n}\n"],"mappings":"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"}
@@ -9,6 +9,7 @@ import * as node_http0 from "node:http";
9
9
  declare function handleWebSocketResponses(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-responses.d.cts","names":[],"sources":["../src/ws-responses.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAqEW,iBAHK,wBAAA,CAGL,EAAA,EAFL,mBAEK,EAAA,QAAA,EADC,OACD,EAAA,EAAA,OAAA,EAAA,OAAA,EAAA,QAAA,EAAA;SAKC,EAAA,MAAA;WAEiB,EAAA,MAAA;OAA0B,EAAA,MAAA;QAAqB,EAFhE,MAI6B;EAAmB,MAAA,CAAA,EAAA,OAAA;2BAF/B,0BAA0B;;mBAAqB,UAAA,CAEnC"}
1
+ {"version":3,"file":"ws-responses.d.cts","names":[],"sources":["../src/ws-responses.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAqEW,iBAHK,wBAAA,CAGL,EAAA,EAFL,mBAEK,EAAA,QAAA,EADC,OACD,EAAA,EAAA,OAAA,EAAA,OAAA,EAAA,QAAA,EAAA;SAMC,EAAA,MAAA;WAEiB,EAAA,MAAA;aAA0B,CAAA,EAAA,MAAA;OAAqB,EAAA,MAAA;EAEhB,MAAA,EAJhD,MAIgD;;2BAF/B,0BAA0B;;mBAAqB,UAAA,CAEnC"}
@@ -9,6 +9,7 @@ import * as node_http0 from "node:http";
9
9
  declare function handleWebSocketResponses(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-responses.d.ts","names":[],"sources":["../src/ws-responses.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAqEW,iBAHK,wBAAA,CAGL,EAAA,EAFL,mBAEK,EAAA,QAAA,EADC,OACD,EAAA,EAAA,OAAA,EAAA,OAAA,EAAA,QAAA,EAAA;SAKC,EAAA,MAAA;WAEiB,EAAA,MAAA;OAA0B,EAAA,MAAA;QAAqB,EAFhE,MAI6B;EAAmB,MAAA,CAAA,EAAA,OAAA;2BAF/B,0BAA0B;;mBAAqB,UAAA,CAEnC"}
1
+ {"version":3,"file":"ws-responses.d.ts","names":[],"sources":["../src/ws-responses.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAqEW,iBAHK,wBAAA,CAGL,EAAA,EAFL,mBAEK,EAAA,QAAA,EADC,OACD,EAAA,EAAA,OAAA,EAAA,OAAA,EAAA,QAAA,EAAA;SAMC,EAAA,MAAA;WAEiB,EAAA,MAAA;aAA0B,CAAA,EAAA,MAAA;OAAqB,EAAA,MAAA;EAEhB,MAAA,EAJhD,MAIgD;;2BAF/B,0BAA0B;;mBAAqB,UAAA,CAEnC"}
@@ -2,7 +2,7 @@ import { DEFAULT_TEST_ID } from "./constants.js";
2
2
  import { extractOverrides, flattenHeaders, 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 { buildContentWithToolCallsStreamEvents, buildTextStreamEvents, buildToolCallStreamEvents, responsesToCompletionRequest } from "./responses.js";
8
8
 
@@ -124,7 +124,7 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
124
124
  });
125
125
  const events = buildContentWithToolCallsStreamEvents(response.content, response.toolCalls, completionReq.model, chunkSize, response.reasoning, response.webSearches, extractOverrides(response));
126
126
  const interruption = createInterruptionSignal(fixture);
127
- if (!await sendEvents(ws, events, latency, interruption?.signal, interruption?.tick)) {
127
+ if (!await sendEvents(ws, events, latency, interruption?.signal, interruption?.tick, fixture.recordedTimings, fixture.replaySpeed ?? defaults.replaySpeed)) {
128
128
  ws.destroy();
129
129
  journalEntry.response.interrupted = true;
130
130
  journalEntry.response.interruptReason = interruption?.reason();
@@ -145,7 +145,7 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
145
145
  });
146
146
  const events = buildTextStreamEvents(response.content, completionReq.model, chunkSize, response.reasoning, response.webSearches, extractOverrides(response));
147
147
  const interruption = createInterruptionSignal(fixture);
148
- if (!await sendEvents(ws, events, latency, interruption?.signal, interruption?.tick)) {
148
+ if (!await sendEvents(ws, events, latency, interruption?.signal, interruption?.tick, fixture.recordedTimings, fixture.replaySpeed ?? defaults.replaySpeed)) {
149
149
  ws.destroy();
150
150
  journalEntry.response.interrupted = true;
151
151
  journalEntry.response.interruptReason = interruption?.reason();
@@ -166,7 +166,7 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
166
166
  });
167
167
  const events = buildToolCallStreamEvents(response.toolCalls, completionReq.model, chunkSize, response.webSearches, extractOverrides(response));
168
168
  const interruption = createInterruptionSignal(fixture);
169
- if (!await sendEvents(ws, events, latency, interruption?.signal, interruption?.tick)) {
169
+ if (!await sendEvents(ws, events, latency, interruption?.signal, interruption?.tick, fixture.recordedTimings, fixture.replaySpeed ?? defaults.replaySpeed)) {
170
170
  ws.destroy();
171
171
  journalEntry.response.interrupted = true;
172
172
  journalEntry.response.interruptReason = interruption?.reason();
@@ -186,13 +186,16 @@ async function processMessage(raw, ws, fixtures, journal, defaults) {
186
186
  });
187
187
  ws.send(JSON.stringify(buildErrorEvent("Fixture response did not match any known type", "server_error")));
188
188
  }
189
- async function sendEvents(ws, events, latency, signal, onChunkSent) {
189
+ async function sendEvents(ws, events, latency, signal, onChunkSent, recordedTimings, replaySpeed) {
190
+ let eventIndex = 0;
190
191
  for (const event of events) {
191
192
  if (ws.isClosed) return false;
192
- if (latency > 0) await delay(latency, signal);
193
+ const chunkDelay = calculateDelay(eventIndex, void 0, latency, recordedTimings, replaySpeed);
194
+ if (chunkDelay > 0) await delay(chunkDelay, signal);
193
195
  if (signal?.aborted) return false;
194
196
  if (ws.isClosed) return false;
195
197
  ws.send(JSON.stringify(event));
198
+ eventIndex++;
196
199
  onChunkSent?.();
197
200
  if (signal?.aborted) return false;
198
201
  }