@copilotkit/aimock 1.44.0 → 1.45.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 (272) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +31 -0
  4. package/README.md +6 -0
  5. package/dist/agui-types.d.ts.map +1 -1
  6. package/dist/aimock-cli.cjs +31 -5
  7. package/dist/aimock-cli.cjs.map +1 -1
  8. package/dist/aimock-cli.d.cts.map +1 -1
  9. package/dist/aimock-cli.d.ts.map +1 -1
  10. package/dist/aimock-cli.js +31 -5
  11. package/dist/aimock-cli.js.map +1 -1
  12. package/dist/bedrock-converse.cjs +312 -39
  13. package/dist/bedrock-converse.cjs.map +1 -1
  14. package/dist/bedrock-converse.d.cts.map +1 -1
  15. package/dist/bedrock-converse.d.ts.map +1 -1
  16. package/dist/bedrock-converse.js +313 -40
  17. package/dist/bedrock-converse.js.map +1 -1
  18. package/dist/bedrock.cjs +195 -42
  19. package/dist/bedrock.cjs.map +1 -1
  20. package/dist/bedrock.d.cts.map +1 -1
  21. package/dist/bedrock.d.ts.map +1 -1
  22. package/dist/bedrock.js +196 -43
  23. package/dist/bedrock.js.map +1 -1
  24. package/dist/chaos.cjs +1 -0
  25. package/dist/chaos.js +1 -1
  26. package/dist/cli.cjs +152 -5
  27. package/dist/cli.cjs.map +1 -1
  28. package/dist/cli.js +155 -8
  29. package/dist/cli.js.map +1 -1
  30. package/dist/cohere.cjs +132 -71
  31. package/dist/cohere.cjs.map +1 -1
  32. package/dist/cohere.d.cts.map +1 -1
  33. package/dist/cohere.d.ts.map +1 -1
  34. package/dist/cohere.js +133 -72
  35. package/dist/cohere.js.map +1 -1
  36. package/dist/config-loader.cjs +62 -4
  37. package/dist/config-loader.cjs.map +1 -1
  38. package/dist/config-loader.d.cts +37 -4
  39. package/dist/config-loader.d.cts.map +1 -1
  40. package/dist/config-loader.d.ts +37 -4
  41. package/dist/config-loader.d.ts.map +1 -1
  42. package/dist/config-loader.js +62 -4
  43. package/dist/config-loader.js.map +1 -1
  44. package/dist/fakes-plugin.cjs +131 -0
  45. package/dist/fakes-plugin.cjs.map +1 -0
  46. package/dist/fakes-plugin.d.cts +23 -0
  47. package/dist/fakes-plugin.d.cts.map +1 -0
  48. package/dist/fakes-plugin.d.ts +23 -0
  49. package/dist/fakes-plugin.d.ts.map +1 -0
  50. package/dist/fakes-plugin.js +127 -0
  51. package/dist/fakes-plugin.js.map +1 -0
  52. package/dist/fixture-loader-services.cjs +1 -1
  53. package/dist/fixture-loader-services.cjs.map +1 -1
  54. package/dist/fixture-loader-services.js +1 -1
  55. package/dist/fixture-loader-services.js.map +1 -1
  56. package/dist/fixture-loader.cjs +389 -27
  57. package/dist/fixture-loader.cjs.map +1 -1
  58. package/dist/fixture-loader.d.cts +47 -3
  59. package/dist/fixture-loader.d.cts.map +1 -1
  60. package/dist/fixture-loader.d.ts +47 -3
  61. package/dist/fixture-loader.d.ts.map +1 -1
  62. package/dist/fixture-loader.js +384 -29
  63. package/dist/fixture-loader.js.map +1 -1
  64. package/dist/gemini-interactions.cjs +250 -52
  65. package/dist/gemini-interactions.cjs.map +1 -1
  66. package/dist/gemini-interactions.d.cts.map +1 -1
  67. package/dist/gemini-interactions.d.ts.map +1 -1
  68. package/dist/gemini-interactions.js +251 -53
  69. package/dist/gemini-interactions.js.map +1 -1
  70. package/dist/gemini.cjs +226 -37
  71. package/dist/gemini.cjs.map +1 -1
  72. package/dist/gemini.d.cts.map +1 -1
  73. package/dist/gemini.d.ts.map +1 -1
  74. package/dist/gemini.js +227 -38
  75. package/dist/gemini.js.map +1 -1
  76. package/dist/helpers.cjs +881 -7
  77. package/dist/helpers.cjs.map +1 -1
  78. package/dist/helpers.d.cts +85 -4
  79. package/dist/helpers.d.cts.map +1 -1
  80. package/dist/helpers.d.ts +85 -4
  81. package/dist/helpers.d.ts.map +1 -1
  82. package/dist/helpers.js +839 -8
  83. package/dist/helpers.js.map +1 -1
  84. package/dist/index.cjs +9 -0
  85. package/dist/index.d.cts +7 -5
  86. package/dist/index.d.ts +7 -5
  87. package/dist/index.js +4 -3
  88. package/dist/jest.cjs +31 -7
  89. package/dist/jest.cjs.map +1 -1
  90. package/dist/jest.d.cts +43 -3
  91. package/dist/jest.d.cts.map +1 -1
  92. package/dist/jest.d.ts +43 -3
  93. package/dist/jest.d.ts.map +1 -1
  94. package/dist/jest.js +33 -9
  95. package/dist/jest.js.map +1 -1
  96. package/dist/journal.cjs +135 -13
  97. package/dist/journal.cjs.map +1 -1
  98. package/dist/journal.d.cts +38 -4
  99. package/dist/journal.d.cts.map +1 -1
  100. package/dist/journal.d.ts +38 -4
  101. package/dist/journal.d.ts.map +1 -1
  102. package/dist/journal.js +135 -13
  103. package/dist/journal.js.map +1 -1
  104. package/dist/live-sanitize.cjs +4 -32
  105. package/dist/live-sanitize.cjs.map +1 -1
  106. package/dist/live-sanitize.js +4 -32
  107. package/dist/live-sanitize.js.map +1 -1
  108. package/dist/llmock.cjs +123 -30
  109. package/dist/llmock.cjs.map +1 -1
  110. package/dist/llmock.d.cts +16 -1
  111. package/dist/llmock.d.cts.map +1 -1
  112. package/dist/llmock.d.ts +16 -1
  113. package/dist/llmock.d.ts.map +1 -1
  114. package/dist/llmock.js +125 -32
  115. package/dist/llmock.js.map +1 -1
  116. package/dist/mcp-fakes-mount.cjs +1 -0
  117. package/dist/mcp-fakes-mount.cjs.map +1 -1
  118. package/dist/mcp-fakes-mount.js +1 -0
  119. package/dist/mcp-fakes-mount.js.map +1 -1
  120. package/dist/mcp-fakes-report.cjs +111 -0
  121. package/dist/mcp-fakes-report.cjs.map +1 -0
  122. package/dist/mcp-fakes-report.d.cts +40 -0
  123. package/dist/mcp-fakes-report.d.cts.map +1 -0
  124. package/dist/mcp-fakes-report.d.ts +40 -0
  125. package/dist/mcp-fakes-report.d.ts.map +1 -0
  126. package/dist/mcp-fakes-report.js +107 -0
  127. package/dist/mcp-fakes-report.js.map +1 -0
  128. package/dist/mcp-fakes.cjs +331 -8
  129. package/dist/mcp-fakes.cjs.map +1 -1
  130. package/dist/mcp-fakes.d.cts +406 -3
  131. package/dist/mcp-fakes.d.cts.map +1 -1
  132. package/dist/mcp-fakes.d.ts +406 -3
  133. package/dist/mcp-fakes.d.ts.map +1 -1
  134. package/dist/mcp-fakes.js +331 -8
  135. package/dist/mcp-fakes.js.map +1 -1
  136. package/dist/mcp-handler.cjs +209 -16
  137. package/dist/mcp-handler.cjs.map +1 -1
  138. package/dist/mcp-handler.d.cts +49 -0
  139. package/dist/mcp-handler.d.cts.map +1 -0
  140. package/dist/mcp-handler.d.ts +49 -0
  141. package/dist/mcp-handler.d.ts.map +1 -0
  142. package/dist/mcp-handler.js +207 -17
  143. package/dist/mcp-handler.js.map +1 -1
  144. package/dist/mcp-mock.cjs +112 -1
  145. package/dist/mcp-mock.cjs.map +1 -1
  146. package/dist/mcp-mock.d.cts +33 -2
  147. package/dist/mcp-mock.d.cts.map +1 -1
  148. package/dist/mcp-mock.d.ts +33 -2
  149. package/dist/mcp-mock.d.ts.map +1 -1
  150. package/dist/mcp-mock.js +112 -1
  151. package/dist/mcp-mock.js.map +1 -1
  152. package/dist/mcp-recorder.cjs +1224 -0
  153. package/dist/mcp-recorder.cjs.map +1 -0
  154. package/dist/mcp-recorder.d.cts +108 -0
  155. package/dist/mcp-recorder.d.cts.map +1 -0
  156. package/dist/mcp-recorder.d.ts +108 -0
  157. package/dist/mcp-recorder.d.ts.map +1 -0
  158. package/dist/mcp-recorder.js +1215 -0
  159. package/dist/mcp-recorder.js.map +1 -0
  160. package/dist/messages.cjs +362 -56
  161. package/dist/messages.cjs.map +1 -1
  162. package/dist/messages.d.cts.map +1 -1
  163. package/dist/messages.d.ts.map +1 -1
  164. package/dist/messages.js +363 -58
  165. package/dist/messages.js.map +1 -1
  166. package/dist/misbehavior.cjs +1523 -0
  167. package/dist/misbehavior.cjs.map +1 -0
  168. package/dist/misbehavior.d.cts +43 -0
  169. package/dist/misbehavior.d.cts.map +1 -0
  170. package/dist/misbehavior.d.ts +43 -0
  171. package/dist/misbehavior.d.ts.map +1 -0
  172. package/dist/misbehavior.js +1511 -0
  173. package/dist/misbehavior.js.map +1 -0
  174. package/dist/ollama.cjs +310 -112
  175. package/dist/ollama.cjs.map +1 -1
  176. package/dist/ollama.d.cts.map +1 -1
  177. package/dist/ollama.d.ts.map +1 -1
  178. package/dist/ollama.js +311 -113
  179. package/dist/ollama.js.map +1 -1
  180. package/dist/record-sanitize.cjs +231 -0
  181. package/dist/record-sanitize.cjs.map +1 -0
  182. package/dist/record-sanitize.js +224 -0
  183. package/dist/record-sanitize.js.map +1 -0
  184. package/dist/recorder.cjs +106 -5
  185. package/dist/recorder.cjs.map +1 -1
  186. package/dist/recorder.d.cts +2 -1
  187. package/dist/recorder.d.cts.map +1 -1
  188. package/dist/recorder.d.ts +2 -1
  189. package/dist/recorder.d.ts.map +1 -1
  190. package/dist/recorder.js +107 -7
  191. package/dist/recorder.js.map +1 -1
  192. package/dist/responses.cjs +667 -171
  193. package/dist/responses.cjs.map +1 -1
  194. package/dist/responses.d.cts +5 -13
  195. package/dist/responses.d.cts.map +1 -1
  196. package/dist/responses.d.ts +5 -13
  197. package/dist/responses.d.ts.map +1 -1
  198. package/dist/responses.js +665 -174
  199. package/dist/responses.js.map +1 -1
  200. package/dist/router.cjs +24 -2
  201. package/dist/router.cjs.map +1 -1
  202. package/dist/router.d.cts.map +1 -1
  203. package/dist/router.d.ts.map +1 -1
  204. package/dist/router.js +25 -4
  205. package/dist/router.js.map +1 -1
  206. package/dist/server.cjs +572 -76
  207. package/dist/server.cjs.map +1 -1
  208. package/dist/server.d.cts.map +1 -1
  209. package/dist/server.d.ts.map +1 -1
  210. package/dist/server.js +574 -78
  211. package/dist/server.js.map +1 -1
  212. package/dist/stream-collapse.cjs +236 -34
  213. package/dist/stream-collapse.cjs.map +1 -1
  214. package/dist/stream-collapse.d.cts +39 -4
  215. package/dist/stream-collapse.d.cts.map +1 -1
  216. package/dist/stream-collapse.d.ts +39 -4
  217. package/dist/stream-collapse.d.ts.map +1 -1
  218. package/dist/stream-collapse.js +235 -35
  219. package/dist/stream-collapse.js.map +1 -1
  220. package/dist/types.cjs.map +1 -1
  221. package/dist/types.d.cts +315 -4
  222. package/dist/types.d.cts.map +1 -1
  223. package/dist/types.d.ts +315 -4
  224. package/dist/types.d.ts.map +1 -1
  225. package/dist/types.js.map +1 -1
  226. package/dist/validate-cli.cjs +36 -4
  227. package/dist/validate-cli.cjs.map +1 -1
  228. package/dist/validate-cli.js +37 -5
  229. package/dist/validate-cli.js.map +1 -1
  230. package/dist/vector-types.d.ts.map +1 -1
  231. package/dist/version.cjs +45 -0
  232. package/dist/version.cjs.map +1 -0
  233. package/dist/version.js +44 -0
  234. package/dist/version.js.map +1 -0
  235. package/dist/vitest.cjs +66 -10
  236. package/dist/vitest.cjs.map +1 -1
  237. package/dist/vitest.d.cts +35 -4
  238. package/dist/vitest.d.cts.map +1 -1
  239. package/dist/vitest.d.ts +35 -4
  240. package/dist/vitest.d.ts.map +1 -1
  241. package/dist/vitest.js +67 -11
  242. package/dist/vitest.js.map +1 -1
  243. package/dist/watcher.cjs +6 -2
  244. package/dist/watcher.cjs.map +1 -1
  245. package/dist/watcher.js +6 -2
  246. package/dist/watcher.js.map +1 -1
  247. package/dist/ws-gemini-live.cjs +210 -101
  248. package/dist/ws-gemini-live.cjs.map +1 -1
  249. package/dist/ws-gemini-live.d.cts +9 -3
  250. package/dist/ws-gemini-live.d.cts.map +1 -1
  251. package/dist/ws-gemini-live.d.ts +9 -3
  252. package/dist/ws-gemini-live.d.ts.map +1 -1
  253. package/dist/ws-gemini-live.js +211 -102
  254. package/dist/ws-gemini-live.js.map +1 -1
  255. package/dist/ws-realtime.cjs +175 -62
  256. package/dist/ws-realtime.cjs.map +1 -1
  257. package/dist/ws-realtime.d.cts +8 -3
  258. package/dist/ws-realtime.d.cts.map +1 -1
  259. package/dist/ws-realtime.d.ts +8 -3
  260. package/dist/ws-realtime.d.ts.map +1 -1
  261. package/dist/ws-realtime.js +176 -63
  262. package/dist/ws-realtime.js.map +1 -1
  263. package/dist/ws-responses.cjs +109 -16
  264. package/dist/ws-responses.cjs.map +1 -1
  265. package/dist/ws-responses.d.cts +8 -12
  266. package/dist/ws-responses.d.cts.map +1 -1
  267. package/dist/ws-responses.d.ts +8 -12
  268. package/dist/ws-responses.d.ts.map +1 -1
  269. package/dist/ws-responses.js +111 -18
  270. package/dist/ws-responses.js.map +1 -1
  271. package/package.json +10 -2
  272. package/skills/write-fixtures/SKILL.md +179 -21
@@ -1 +1 @@
1
- {"version":3,"file":"ws-gemini-live.js","names":[],"sources":["../src/ws-gemini-live.ts"],"sourcesContent":["/**\n * WebSocket handler for Gemini Live BidiGenerateContent API.\n *\n * Accepts setup, clientContent, and toolResponse messages over WebSocket\n * and responds with setupComplete, serverContent, toolCall, and error\n * messages in the Gemini Live streaming format.\n */\n\nimport type {\n Fixture,\n ChatMessage,\n ChatCompletionRequest,\n ToolDefinition,\n AudioResponse,\n} from \"./types.js\";\nimport { matchFixtureDiagnostic } from \"./router.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n isAudioResponse,\n flattenHeaders,\n formatToMime,\n generateToolCallId,\n resolveFixtureBlocks,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n strictNoMatchMessage,\n strictNoMatchLogLine,\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// ─── Gemini Live protocol types ─────────────────────────────────────────────\n\ninterface GeminiLivePart {\n text?: string;\n thought?: boolean;\n functionCall?: { name: string; args: Record<string, unknown> };\n functionResponse?: { name: string; response: unknown; id?: string };\n inlineData?: { mimeType: string; data: string };\n}\n\ninterface GeminiLiveTurn {\n role: string;\n parts: GeminiLivePart[];\n}\n\ninterface GeminiLiveFunctionDeclaration {\n name: string;\n description?: string;\n parameters?: object;\n}\n\ninterface GeminiLiveToolDef {\n functionDeclarations?: GeminiLiveFunctionDeclaration[];\n}\n\ninterface GeminiLiveSetup {\n model?: string;\n generationConfig?: Record<string, unknown>;\n tools?: GeminiLiveToolDef[];\n}\n\ninterface GeminiLiveClientContent {\n turns: GeminiLiveTurn[];\n turnComplete?: boolean;\n}\n\ninterface GeminiLiveFunctionResponse {\n id?: string;\n name: string;\n response: unknown;\n}\n\ninterface GeminiLiveToolResponse {\n functionResponses: GeminiLiveFunctionResponse[];\n}\n\ninterface GeminiLiveMessage {\n setup?: GeminiLiveSetup;\n config?: GeminiLiveSetup;\n clientContent?: GeminiLiveClientContent;\n toolResponse?: GeminiLiveToolResponse;\n}\n\n// ─── Session state ──────────────────────────────────────────────────────────\n\ninterface SessionState {\n setupDone: boolean;\n model: string;\n tools: ToolDefinition[];\n conversationHistory: ChatMessage[];\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────────\n\nconst WS_PATH = \"/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent\";\n\n/**\n * Map HTTP status codes to gRPC error codes.\n * Gemini Live uses gRPC codes, not HTTP status codes.\n */\nfunction httpToGrpc(httpCode: number): number {\n switch (httpCode) {\n case 400:\n return 3; // INVALID_ARGUMENT\n case 401:\n return 16; // UNAUTHENTICATED\n case 403:\n return 7; // PERMISSION_DENIED\n case 404:\n return 5; // NOT_FOUND\n case 409:\n return 10; // ABORTED\n case 429:\n return 8; // RESOURCE_EXHAUSTED\n case 501:\n return 12; // UNIMPLEMENTED\n case 503:\n return 14; // UNAVAILABLE\n default:\n return 13; // INTERNAL\n }\n}\n\n/**\n * Convert Gemini Live turns into ChatMessage[] for fixture matching.\n */\nfunction geminiTurnsToMessages(turns: GeminiLiveTurn[], logger?: Logger): ChatMessage[] {\n const messages: ChatMessage[] = [];\n\n for (const turn of turns) {\n const role = turn.role ?? \"user\";\n\n if (role === \"user\") {\n const funcResponses = turn.parts.filter((p) => p.functionResponse);\n // inlineData parts (e.g. client audio input) are silently skipped —\n // only text and functionResponse parts are relevant for fixture matching.\n const textParts = turn.parts.filter((p) => p.text !== undefined && !p.thought);\n\n if (funcResponses.length > 0) {\n for (let i = 0; i < funcResponses.length; i++) {\n const part = funcResponses[i];\n const fr = part.functionResponse!;\n messages.push({\n role: \"tool\",\n content: typeof fr.response === \"string\" ? fr.response : JSON.stringify(fr.response),\n tool_call_id: fr.id ?? generateToolCallId(),\n });\n }\n if (textParts.length > 0) {\n messages.push({\n role: \"user\",\n content: textParts.map((p) => p.text!).join(\"\"),\n });\n }\n } else {\n const text = textParts.map((p) => p.text!).join(\"\");\n messages.push({ role: \"user\", content: text });\n }\n } else if (role === \"model\") {\n const funcCalls = turn.parts.filter((p) => p.functionCall);\n const textParts = turn.parts.filter((p) => p.text !== undefined && !p.thought);\n\n if (funcCalls.length > 0) {\n const text = textParts.map((p) => p.text!).join(\"\");\n messages.push({\n role: \"assistant\",\n content: text || null,\n tool_calls: funcCalls.map((p) => ({\n id: generateToolCallId(),\n type: \"function\" as const,\n function: {\n name: p.functionCall!.name,\n arguments: JSON.stringify(p.functionCall!.args ?? {}),\n },\n })),\n });\n } else {\n const text = textParts.map((p) => p.text!).join(\"\");\n messages.push({ role: \"assistant\", content: text });\n }\n } else {\n logger?.warn(`[gemini-live] skipping turn with unrecognized role: ${role}`);\n }\n }\n\n return messages;\n}\n\n/**\n * Convert toolResponse messages into ChatMessage[] for fixture matching.\n */\nfunction toolResponseToMessages(toolResponse: GeminiLiveToolResponse): ChatMessage[] {\n return toolResponse.functionResponses.map((fr) => ({\n role: \"tool\" as const,\n content: typeof fr.response === \"string\" ? fr.response : JSON.stringify(fr.response),\n tool_call_id: fr.id ?? generateToolCallId(),\n }));\n}\n\n/**\n * Convert Gemini tool definitions to ChatCompletion ToolDefinition[].\n */\nfunction convertTools(geminiTools?: GeminiLiveToolDef[]): ToolDefinition[] {\n if (!geminiTools || geminiTools.length === 0) return [];\n const decls = geminiTools.flatMap((t) => t.functionDeclarations ?? []);\n return decls.map((d) => ({\n type: \"function\" as const,\n function: {\n name: d.name,\n description: d.description,\n parameters: d.parameters,\n },\n }));\n}\n\n// ─── Main handler ───────────────────────────────────────────────────────────\n\nexport function handleWebSocketGeminiLive(\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 session: SessionState = {\n setupDone: false,\n model: defaults.model,\n tools: [],\n conversationHistory: [],\n };\n\n let pending = Promise.resolve();\n ws.on(\"message\", (raw: string) => {\n pending = pending.then(() =>\n processMessage(raw, ws, fixtures, journal, defaults, session).catch((err: unknown) => {\n const msg = err instanceof Error ? err.message : \"Internal error\";\n logger.error(`WebSocket Gemini Live error: ${msg}`);\n try {\n ws.send(\n JSON.stringify({\n error: { code: 13, message: msg, status: \"INTERNAL\" },\n }),\n );\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: SessionState,\n): Promise<void> {\n let parsed: GeminiLiveMessage;\n try {\n parsed = JSON.parse(raw) as GeminiLiveMessage;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n ws.send(\n JSON.stringify({\n error: { code: 3, message: `Malformed JSON: ${detail}`, status: \"INVALID_ARGUMENT\" },\n }),\n );\n return;\n }\n\n // Handle setup message (accept both `setup` and `config` as aliases)\n const setupMsg = parsed.setup ?? parsed.config;\n if (setupMsg) {\n session.setupDone = true;\n session.model = setupMsg.model ?? defaults.model;\n session.tools = convertTools(setupMsg.tools);\n ws.send(JSON.stringify({ setupComplete: {} }));\n return;\n }\n\n // Reject messages before setup\n if (!session.setupDone) {\n ws.send(\n JSON.stringify({\n error: { code: 9, message: \"Setup required\", status: \"FAILED_PRECONDITION\" },\n }),\n );\n return;\n }\n\n // Build messages from this interaction\n let newMessages: ChatMessage[];\n\n if (parsed.clientContent) {\n if (!parsed.clientContent.turns || !Array.isArray(parsed.clientContent.turns)) {\n ws.send(\n JSON.stringify({\n error: {\n code: 3,\n message: \"Missing 'turns' in clientContent\",\n status: \"INVALID_ARGUMENT\",\n },\n }),\n );\n return;\n }\n newMessages = geminiTurnsToMessages(parsed.clientContent.turns, defaults.logger);\n } else if (parsed.toolResponse) {\n if (\n !parsed.toolResponse.functionResponses ||\n !Array.isArray(parsed.toolResponse.functionResponses)\n ) {\n ws.send(\n JSON.stringify({\n error: {\n code: 3,\n message: \"Missing 'functionResponses' in toolResponse\",\n status: \"INVALID_ARGUMENT\",\n },\n }),\n );\n return;\n }\n newMessages = toolResponseToMessages(parsed.toolResponse);\n } else {\n ws.send(\n JSON.stringify({\n error: {\n code: 3,\n message: \"Expected clientContent or toolResponse\",\n status: \"INVALID_ARGUMENT\",\n },\n }),\n );\n return;\n }\n\n // Build completion request for fixture matching (include new messages speculatively)\n const geminiContextHeader = defaults.upgradeHeaders?.[\"x-aimock-context\"];\n const geminiContext =\n typeof geminiContextHeader === \"string\"\n ? geminiContextHeader\n : Array.isArray(geminiContextHeader) && geminiContextHeader.length > 0\n ? geminiContextHeader[0]\n : undefined;\n\n const completionReq: ChatCompletionRequest = {\n model: session.model,\n messages: [...session.conversationHistory, ...newMessages],\n stream: true,\n tools: session.tools.length > 0 ? session.tools : undefined,\n _endpointType: \"chat\",\n _context: geminiContext,\n };\n\n const testId = defaults.testId ?? DEFAULT_TEST_ID;\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n const path = WS_PATH;\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (!fixture) {\n if (resolveStrictMode(defaults.strict, defaults.upgradeHeaders)) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n defaults.logger.error(strictNoMatchLogLine(\"WS\", path, skippedBySequenceOrTurn));\n journal.add({\n method: \"WS\",\n path,\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, strictMessage);\n return;\n }\n journal.add({\n method: \"WS\",\n path,\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 error: { code: 5, message: \"No fixture matched\", status: \"NOT_FOUND\" },\n }),\n );\n return;\n }\n\n // Commit messages to conversation history only after successful fixture match\n session.conversationHistory.push(...newMessages);\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,\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status, fixture },\n });\n ws.send(\n JSON.stringify({\n error: {\n code: httpToGrpc(status),\n message: response.error.message,\n status: response.error.type ?? \"INTERNAL\",\n },\n }),\n );\n return;\n }\n\n // Audio response — an AUDIO-modality model turn, plus the companion\n // modalities `AudioResponse` documents (types.ts) as preserved alongside it.\n //\n // COMPANION PLACEMENT is protocol-specific, and the Live protocol does NOT\n // place a tool call where the HTTP protocol does. On `generateContent` a\n // functionCall is a PART, so gemini.ts's `buildGeminiAudioParts` appends it to\n // the same `content.parts` array as the audio. On Live, `serverContent` and\n // `toolCall` are alternatives of ONE union: `@google/genai`'s\n // `LiveServerMessage` declares them as sibling optional fields and the\n // BidiGenerateContent reference states the response's `messageType` union\n // \"can be only one of the following\". A turn that both speaks and calls a\n // function is therefore TWO messages, not one part list —\n //\n // serverContent{modelTurn.parts} → toolCall{functionCalls} → serverContent{turnComplete}\n //\n // — which is also the order every other branch of this handler already emits\n // and the order the live provider was observed to use (its tool turn sent a\n // `serverContent` strictly before the `toolCall`).\n //\n // Emitting only the audio and returning — the previous behavior — silently\n // discarded the tool call and the text content of a recorded audio turn, the\n // exact loss types.ts's AudioResponse companions exist to prevent.\n //\n // NOT handled here: `AudioResponse.reasoning`. This handler has no thought\n // channel in ANY branch (nothing in it emits `thought: true`), so replaying\n // reasoning from the audio branch alone would invent a Live-wide capability\n // out of one fixture field. That gap is real but separate.\n if (isAudioResponse(response)) {\n journal.add({\n method: \"WS\",\n path,\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 200, fixture },\n });\n\n const audioResp = response as AudioResponse;\n let mimeType: string;\n let data: string;\n\n if (typeof audioResp.audio === \"string\") {\n mimeType = formatToMime(audioResp.format ?? \"mp3\");\n data = audioResp.audio;\n } else {\n mimeType = audioResp.audio.contentType ?? \"audio/mpeg\";\n data = audioResp.audio.b64Json;\n }\n\n // Part order mirrors gemini.ts's `buildGeminiAudioParts`: audio first, then\n // the text companion.\n const parts: GeminiLivePart[] = [{ inlineData: { mimeType, data } }];\n if (audioResp.content) {\n parts.push({ text: audioResp.content });\n }\n\n // Stable IDs resolved once, so the wire message and conversation history\n // agree (same contract as the tool-call branches below).\n const resolvedToolCalls = (audioResp.toolCalls ?? []).map((tc) => ({\n ...tc,\n resolvedId: tc.id ?? generateToolCallId(),\n }));\n\n if (resolvedToolCalls.length === 0) {\n ws.send(\n JSON.stringify({\n serverContent: { modelTurn: { parts }, turnComplete: true },\n }),\n );\n } else {\n ws.send(JSON.stringify({ serverContent: { modelTurn: { parts } } }));\n\n if (!ws.isClosed) {\n const functionCalls = resolvedToolCalls.map((tc) => {\n let argsObj: Record<string, unknown>;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\") as Record<string, unknown>;\n } catch {\n defaults.logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n return { name: tc.name, args: argsObj, id: tc.resolvedId };\n });\n ws.send(JSON.stringify({ toolCall: { functionCalls } }));\n }\n\n if (!ws.isClosed) {\n ws.send(JSON.stringify({ serverContent: { turnComplete: true } }));\n }\n }\n\n session.conversationHistory.push({\n role: \"assistant\",\n content: audioResp.content ?? \"[audio]\",\n ...(resolvedToolCalls.length > 0\n ? {\n tool_calls: resolvedToolCalls.map((tc) => ({\n id: tc.resolvedId,\n type: \"function\" as const,\n function: { name: tc.name, arguments: tc.arguments },\n })),\n }\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,\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 200, fixture },\n });\n\n // BLOCKS path (#274): when the fixture carries an ordered `blocks` array,\n // honor it instead of the legacy `content ?? \"\"` / `toolCalls ?? []` path.\n // The Gemini Live WS protocol expresses ordering via SEQUENTIAL messages —\n // a text block becomes one-or-more `serverContent.modelTurn.parts[{text}]`\n // messages and a toolCall block becomes a `toolCall.functionCalls` message,\n // so emitting in array order faithfully reproduces tool-before-text (or any\n // interleaving), matching the HTTP gemini.ts blocks branch. Without this, a\n // blocks-only fixture (post-F0 it matches this guard) would stream an EMPTY\n // payload — a silent drop. Legacy fixtures (no `blocks`) skip this entirely.\n if (response.blocks && response.blocks.length > 0) {\n const resolvedBlocks = resolveFixtureBlocks(response.blocks);\n const interruption = createInterruptionSignal(fixture);\n const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;\n const { recordedTimings } = fixture;\n let chunkIndex = 0;\n let interrupted = false;\n\n // Accumulate the equivalent assistant turn for conversation history, so a\n // follow-up turn sees the same content + tool_calls as the legacy path.\n let historyContent = \"\";\n const historyToolCalls: {\n id: string;\n type: \"function\";\n function: { name: string; arguments: string };\n }[] = [];\n\n outer: for (const block of resolvedBlocks) {\n if (block.type === \"toolCall\") {\n if (ws.isClosed) break;\n const tcDelay = calculateDelay(\n chunkIndex,\n undefined,\n latency,\n recordedTimings,\n replaySpeed,\n );\n if (tcDelay > 0) await delay(tcDelay, interruption?.signal);\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n if (ws.isClosed) break;\n\n const resolvedId = block.id ?? generateToolCallId();\n let argsObj: Record<string, unknown>;\n try {\n argsObj = JSON.parse(block.arguments || \"{}\") as Record<string, unknown>;\n } catch {\n defaults.logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${block.name}\": ${block.arguments}`,\n );\n argsObj = {};\n }\n\n try {\n ws.send(\n JSON.stringify({\n toolCall: { functionCalls: [{ name: block.name, args: argsObj, id: resolvedId }] },\n }),\n );\n } catch (err) {\n defaults.logger.debug(\n \"[gemini-live] send failed during blocks streaming, closing\",\n err,\n );\n break;\n }\n chunkIndex++;\n historyToolCalls.push({\n id: resolvedId,\n type: \"function\" as const,\n function: { name: block.name, arguments: block.arguments },\n });\n interruption?.tick();\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n } else {\n const text = block.text;\n historyContent += text;\n if (text.length === 0) {\n // An empty text block carries no wire content, so emit nothing and\n // spend no chunk: the old guard sent a useless empty `modelTurn`\n // message and `continue`d WITHOUT `chunkIndex++`/`interruption.tick()`,\n // which leaked the next block past `truncateAfterChunks` and shifted\n // `recordedTimings` indexing for every following block. Skipping\n // keeps non-empty-block output byte-identical and the chunk/timing\n // accounting correct.\n continue;\n }\n for (let i = 0; i < text.length; i += chunkSize) {\n if (ws.isClosed) break outer;\n const chunkDelay = calculateDelay(\n chunkIndex,\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 outer;\n }\n if (ws.isClosed) break outer;\n try {\n ws.send(\n JSON.stringify({\n serverContent: { modelTurn: { parts: [{ text: text.slice(i, i + chunkSize) }] } },\n }),\n );\n } catch (err) {\n defaults.logger.debug(\n \"[gemini-live] send failed during blocks streaming, closing\",\n err,\n );\n break outer;\n }\n chunkIndex++;\n interruption?.tick();\n if (interruption?.signal.aborted) {\n interrupted = true;\n break outer;\n }\n }\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 // Send turnComplete\n if (!ws.isClosed) {\n ws.send(JSON.stringify({ serverContent: { turnComplete: true } }));\n }\n\n session.conversationHistory.push({\n role: \"assistant\",\n content: historyContent || null,\n ...(historyToolCalls.length > 0 ? { tool_calls: historyToolCalls } : {}),\n });\n return;\n }\n\n const content = response.content ?? \"\";\n const chunkList: string[] = [];\n for (let i = 0; i < content.length; i += chunkSize) {\n chunkList.push(content.slice(i, i + chunkSize));\n }\n\n const interruption = createInterruptionSignal(fixture);\n const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;\n const { recordedTimings } = fixture;\n let interrupted = false;\n\n // Stream text content chunks (turnComplete omitted — sent as a separate message later)\n if (content.length === 0) {\n if (!ws.isClosed) {\n ws.send(\n JSON.stringify({\n serverContent: {\n modelTurn: { parts: [{ text: \"\" }] },\n },\n }),\n );\n }\n } else {\n for (let i = 0; i < chunkList.length; i++) {\n if (ws.isClosed) break;\n const chunkDelay = calculateDelay(i, undefined, latency, recordedTimings, replaySpeed);\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\n try {\n ws.send(\n JSON.stringify({\n serverContent: {\n modelTurn: { parts: [{ text: chunkList[i] }] },\n },\n }),\n );\n } catch (err) {\n defaults.logger.debug(\"[gemini-live] send failed during text streaming, closing\", err);\n break;\n }\n interruption?.tick();\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\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 // Pre-compute tool calls with stable IDs so wire message and history match\n const resolvedToolCalls = (response.toolCalls ?? []).map((tc) => ({\n ...tc,\n resolvedId: tc.id ?? generateToolCallId(),\n }));\n\n // Send tool calls\n if (!ws.isClosed) {\n const tcDelay = calculateDelay(\n chunkList.length,\n undefined,\n latency,\n recordedTimings,\n replaySpeed,\n );\n if (tcDelay > 0) await delay(tcDelay, interruption?.signal);\n if (interruption?.signal.aborted) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n interruption?.cleanup();\n return;\n }\n\n const functionCalls = resolvedToolCalls.map((tc) => {\n let argsObj: Record<string, unknown>;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\") as Record<string, unknown>;\n } catch {\n defaults.logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n return {\n name: tc.name,\n args: argsObj,\n id: tc.resolvedId,\n };\n });\n\n ws.send(JSON.stringify({ toolCall: { functionCalls } }));\n interruption?.tick();\n }\n\n if (interruption?.signal.aborted) {\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 // Send turnComplete\n if (!ws.isClosed) {\n ws.send(\n JSON.stringify({\n serverContent: { turnComplete: true },\n }),\n );\n }\n\n // Add to conversation history using the same resolved IDs from the wire message\n session.conversationHistory.push({\n role: \"assistant\",\n content: content || null,\n tool_calls: resolvedToolCalls.map((tc) => ({\n id: tc.resolvedId,\n type: \"function\" as const,\n function: {\n name: tc.name,\n arguments: tc.arguments,\n },\n })),\n });\n return;\n }\n\n // Text response — stream chunks with serverContent\n if (isTextResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path,\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 200, fixture },\n });\n\n const content = response.content;\n\n if (content.length === 0) {\n if (ws.isClosed) return;\n // Empty content: send empty modelTurn, then separate turnComplete\n ws.send(\n JSON.stringify({\n serverContent: {\n modelTurn: { parts: [{ text: \"\" }] },\n },\n }),\n );\n ws.send(\n JSON.stringify({\n serverContent: { turnComplete: true },\n }),\n );\n return;\n }\n\n // Chunk the content\n const chunks: string[] = [];\n for (let i = 0; i < content.length; i += chunkSize) {\n chunks.push(content.slice(i, i + chunkSize));\n }\n\n const interruption = createInterruptionSignal(fixture);\n const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;\n const { recordedTimings } = fixture;\n let interrupted = false;\n\n // Stream content chunks without turnComplete (sent separately after)\n for (let i = 0; i < chunks.length; i++) {\n if (ws.isClosed) break;\n const chunkDelay = calculateDelay(i, undefined, latency, recordedTimings, replaySpeed);\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\n try {\n ws.send(\n JSON.stringify({\n serverContent: {\n modelTurn: { parts: [{ text: chunks[i] }] },\n },\n }),\n );\n } catch (err) {\n defaults.logger.debug(\"[gemini-live] send failed during text streaming, closing\", err);\n break;\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 // Send separate turnComplete message\n if (!ws.isClosed) {\n ws.send(\n JSON.stringify({\n serverContent: { turnComplete: true },\n }),\n );\n }\n\n // Add assistant response to conversation history\n session.conversationHistory.push({ role: \"assistant\", content });\n return;\n }\n\n // Tool call response\n if (isToolCallResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path,\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 200, fixture },\n });\n\n const interruption = createInterruptionSignal(fixture);\n const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;\n const { recordedTimings } = fixture;\n\n if (ws.isClosed) {\n interruption?.cleanup();\n return;\n }\n const tcDelay = calculateDelay(0, undefined, latency, recordedTimings, replaySpeed);\n if (tcDelay > 0) await delay(tcDelay, interruption?.signal);\n if (interruption?.signal.aborted) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n interruption?.cleanup();\n return;\n }\n if (ws.isClosed) {\n interruption?.cleanup();\n return;\n }\n\n // Pre-compute tool calls with stable IDs so wire message and history match\n const resolvedToolCalls = (response.toolCalls ?? []).map((tc) => ({\n ...tc,\n resolvedId: tc.id ?? generateToolCallId(),\n }));\n\n const functionCalls = resolvedToolCalls.map((tc) => {\n let argsObj: Record<string, unknown>;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\") as Record<string, unknown>;\n } catch {\n defaults.logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n return {\n name: tc.name,\n args: argsObj,\n id: tc.resolvedId,\n };\n });\n\n ws.send(JSON.stringify({ toolCall: { functionCalls } }));\n interruption?.tick();\n\n if (interruption?.signal.aborted) {\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 // Send turnComplete after tool call\n if (!ws.isClosed) {\n ws.send(\n JSON.stringify({\n serverContent: { turnComplete: true },\n }),\n );\n }\n\n // Add assistant tool_calls to conversation history using the same resolved IDs\n session.conversationHistory.push({\n role: \"assistant\",\n content: null,\n tool_calls: resolvedToolCalls.map((tc) => ({\n id: tc.resolvedId,\n type: \"function\" as const,\n function: {\n name: tc.name,\n arguments: tc.arguments,\n },\n })),\n });\n return;\n }\n\n // Unknown response type\n journal.add({\n method: \"WS\",\n path,\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status: 500, fixture },\n });\n ws.send(\n JSON.stringify({\n error: {\n code: 13,\n message: \"Fixture response did not match any known type\",\n status: \"INTERNAL\",\n },\n }),\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ws-gemini-live.js","names":[],"sources":["../src/ws-gemini-live.ts"],"sourcesContent":["/**\n * WebSocket handler for Gemini Live BidiGenerateContent API.\n *\n * Accepts setup, clientContent, and toolResponse messages over WebSocket\n * and responds with setupComplete, serverContent, toolCall, and error\n * messages in the Gemini Live streaming format.\n */\n\nimport type {\n Fixture,\n ChatMessage,\n ChatCompletionRequest,\n ToolDefinition,\n AudioResponse,\n ToolCall,\n FixtureFileBlock,\n JournalEntry,\n HandlerDefaults,\n} from \"./types.js\";\nimport { planMisbehavior, recordMisbehaviorOutcome, type MisbehaviorPlan } from \"./misbehavior.js\";\nimport { matchFixtureDiagnostic } from \"./router.js\";\nimport {\n fixtureToolCallErrorCode,\n googleFixtureToolCallErrorDetails,\n isFixtureToolCallError,\n journalFixtureToolCallError,\n rejectResponsesOnlyToolCalls,\n requireServedFunctionToolCalls,\n toolArgsForWire,\n runWithToolArgumentsScope,\n servedToolArgs,\n strictToolArgumentsEnabled,\n isTextResponse,\n isToolCallResponse,\n isCombinedFixtureResponse,\n isErrorResponse,\n isAudioResponse,\n flattenHeaders,\n formatToMime,\n generateToolCallId,\n resolveFixtureBlocks,\n resolveFixtureBlockOutcome,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n strictNoMatchMessage,\n strictNoMatchLogLine,\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// ─── Gemini Live protocol types ─────────────────────────────────────────────\n\ninterface GeminiLivePart {\n text?: string;\n thought?: boolean;\n functionCall?: { name: string; args: Record<string, unknown> };\n functionResponse?: { name: string; response: unknown; id?: string };\n inlineData?: { mimeType: string; data: string };\n}\n\ninterface GeminiLiveTurn {\n role: string;\n parts: GeminiLivePart[];\n}\n\ninterface GeminiLiveFunctionDeclaration {\n name: string;\n description?: string;\n parameters?: object;\n parametersJsonSchema?: object;\n}\n\ninterface GeminiLiveToolDef {\n functionDeclarations?: GeminiLiveFunctionDeclaration[];\n}\n\ninterface GeminiLiveSetup {\n model?: string;\n generationConfig?: Record<string, unknown>;\n tools?: GeminiLiveToolDef[];\n}\n\ninterface GeminiLiveClientContent {\n turns: GeminiLiveTurn[];\n turnComplete?: boolean;\n}\n\ninterface GeminiLiveFunctionResponse {\n id?: string;\n name: string;\n response: unknown;\n}\n\ninterface GeminiLiveToolResponse {\n functionResponses: GeminiLiveFunctionResponse[];\n}\n\ninterface GeminiLiveMessage {\n setup?: GeminiLiveSetup;\n config?: GeminiLiveSetup;\n clientContent?: GeminiLiveClientContent;\n toolResponse?: GeminiLiveToolResponse;\n}\n\n// ─── Session state ──────────────────────────────────────────────────────────\n\ninterface SessionState {\n setupDone: boolean;\n model: string;\n tools: ToolDefinition[];\n conversationHistory: ChatMessage[];\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────────\n\nconst WS_PATH = \"/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent\";\n\n/**\n * Map HTTP status codes to gRPC error codes.\n * Gemini Live uses gRPC codes, not HTTP status codes.\n */\nfunction httpToGrpc(httpCode: number): number {\n switch (httpCode) {\n case 400:\n return 3; // INVALID_ARGUMENT\n case 401:\n return 16; // UNAUTHENTICATED\n case 403:\n return 7; // PERMISSION_DENIED\n case 404:\n return 5; // NOT_FOUND\n case 409:\n return 10; // ABORTED\n case 429:\n return 8; // RESOURCE_EXHAUSTED\n case 501:\n return 12; // UNIMPLEMENTED\n case 503:\n return 14; // UNAVAILABLE\n default:\n return 13; // INTERNAL\n }\n}\n\n/**\n * Convert Gemini Live turns into ChatMessage[] for fixture matching.\n */\nfunction geminiTurnsToMessages(turns: GeminiLiveTurn[], logger?: Logger): ChatMessage[] {\n const messages: ChatMessage[] = [];\n\n for (const turn of turns) {\n const role = turn.role ?? \"user\";\n\n if (role === \"user\") {\n const funcResponses = turn.parts.filter((p) => p.functionResponse);\n // inlineData parts (e.g. client audio input) are silently skipped —\n // only text and functionResponse parts are relevant for fixture matching.\n const textParts = turn.parts.filter((p) => p.text !== undefined && !p.thought);\n\n if (funcResponses.length > 0) {\n for (let i = 0; i < funcResponses.length; i++) {\n const part = funcResponses[i];\n const fr = part.functionResponse!;\n messages.push({\n role: \"tool\",\n content: typeof fr.response === \"string\" ? fr.response : JSON.stringify(fr.response),\n tool_call_id: fr.id ?? generateToolCallId(),\n });\n }\n if (textParts.length > 0) {\n messages.push({\n role: \"user\",\n content: textParts.map((p) => p.text!).join(\"\"),\n });\n }\n } else {\n const text = textParts.map((p) => p.text!).join(\"\");\n messages.push({ role: \"user\", content: text });\n }\n } else if (role === \"model\") {\n const funcCalls = turn.parts.filter((p) => p.functionCall);\n const textParts = turn.parts.filter((p) => p.text !== undefined && !p.thought);\n\n if (funcCalls.length > 0) {\n const text = textParts.map((p) => p.text!).join(\"\");\n messages.push({\n role: \"assistant\",\n content: text || null,\n tool_calls: funcCalls.map((p) => ({\n id: generateToolCallId(),\n type: \"function\" as const,\n function: {\n name: p.functionCall!.name,\n arguments: JSON.stringify(p.functionCall!.args ?? {}),\n },\n })),\n });\n } else {\n const text = textParts.map((p) => p.text!).join(\"\");\n messages.push({ role: \"assistant\", content: text });\n }\n } else {\n logger?.warn(`[gemini-live] skipping turn with unrecognized role: ${role}`);\n }\n }\n\n return messages;\n}\n\n/**\n * Convert toolResponse messages into ChatMessage[] for fixture matching.\n */\nfunction toolResponseToMessages(toolResponse: GeminiLiveToolResponse): ChatMessage[] {\n return toolResponse.functionResponses.map((fr) => ({\n role: \"tool\" as const,\n content: typeof fr.response === \"string\" ? fr.response : JSON.stringify(fr.response),\n tool_call_id: fr.id ?? generateToolCallId(),\n }));\n}\n\n/**\n * Convert Gemini tool definitions to ChatCompletion ToolDefinition[].\n */\nfunction convertTools(geminiTools?: GeminiLiveToolDef[]): ToolDefinition[] {\n if (!geminiTools || geminiTools.length === 0) return [];\n const decls = geminiTools.flatMap((t) => t.functionDeclarations ?? []);\n return decls.map((d) => ({\n type: \"function\" as const,\n function: {\n name: d.name,\n description: d.description,\n parameters: d.parameters ?? d.parametersJsonSchema,\n },\n }));\n}\n\nfunction liveToolArguments(tc: Pick<ToolCall, \"name\" | \"arguments\">) {\n return servedToolArgs(tc, \"object\").value;\n}\n\nconst GEMINI_LIVE_WIRE = \"Gemini Live\";\n\n/** Prepare native object arguments and IDs once for both output and the journal. */\nfunction prepareLiveMisbehavior(plan: MisbehaviorPlan): MisbehaviorPlan {\n if (plan.summary.fault === \"tool-args-invalid-json\" || plan.summary.fault === \"empty-response\") {\n return { ...plan, summary: { ...plan.summary, servedToolCalls: [] } };\n }\n const response = plan.response;\n const combined = isCombinedFixtureResponse(response);\n const outcome =\n combined && response.blocks?.length ? resolveFixtureBlockOutcome(response.blocks) : undefined;\n const calls =\n outcome?.toolCalls ??\n (combined || isToolCallResponse(response) ? (response.toolCalls ?? []) : []);\n const duplicate = plan.duplicateId;\n const toolCalls = calls.map((call, index) => {\n const args = toolArgsForWire(call);\n // Preserve authored-invalid arguments for the existing journaled preflight error.\n return {\n ...call,\n arguments: args.kind === \"parsed\" ? args.text : args.raw,\n ...(index === duplicate?.destinationIndex ? {} : { id: call.id ?? generateToolCallId() }),\n };\n });\n if (duplicate) toolCalls[duplicate.destinationIndex].id = toolCalls[duplicate.sourceIndex].id;\n let preparedResponse = { ...response };\n if (\"usage\" in preparedResponse) delete preparedResponse.usage;\n if (outcome) {\n let index = 0;\n const blocks = outcome.ordered.map((block) =>\n block.type === \"text\" ? { ...block } : { ...block, ...toolCalls[index++] },\n );\n preparedResponse = { ...preparedResponse, content: outcome.content, toolCalls, blocks };\n } else if (combined || isToolCallResponse(response)) {\n preparedResponse = { ...preparedResponse, toolCalls };\n }\n return {\n ...plan,\n response: preparedResponse,\n summary: {\n ...plan.summary,\n servedToolCalls: toolCalls.map(({ name, arguments: args, id }) => ({\n name,\n arguments: args,\n id,\n })),\n },\n };\n}\n\n/**\n * Validate the whole selected response before audio, text, or any tool is\n * sent: every entry is validated, custom tool calls are rejected, and every\n * argument string must parse. Returns the calls narrowed to function calls. On\n * failure the journal entry becomes a 500, and the outer message handler sends\n * code 13 with `google.rpc.ErrorInfo` details carrying the aimock code, drops\n * this turn from the history, and leaves the socket open.\n */\nfunction preflightToolArguments(\n response: { toolCalls?: ToolCall[] },\n journalEntry: JournalEntry,\n): ToolCall[] {\n return journalFixtureToolCallError(journalEntry, () => {\n const functionCalls = requireServedFunctionToolCalls(response, GEMINI_LIVE_WIRE);\n if (strictToolArgumentsEnabled()) for (const tc of functionCalls) liveToolArguments(tc);\n return functionCalls;\n });\n}\n\n/** Resolve ordered blocks (rejecting custom tool calls) and preflight their arguments. */\nfunction preflightBlocks(\n response: { blocks?: FixtureFileBlock[]; customToolCalls?: unknown },\n journalEntry: JournalEntry,\n) {\n return journalFixtureToolCallError(journalEntry, () => {\n rejectResponsesOnlyToolCalls(response, GEMINI_LIVE_WIRE);\n const resolved = resolveFixtureBlocks(response.blocks ?? []);\n if (strictToolArgumentsEnabled())\n for (const block of resolved) if (block.type === \"toolCall\") liveToolArguments(block);\n return resolved;\n });\n}\n\n// ─── Main handler ───────────────────────────────────────────────────────────\n\nexport function handleWebSocketGeminiLive(\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 strictToolArguments?: boolean;\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest;\n testId?: string;\n upgradeHeaders?: import(\"node:http\").IncomingHttpHeaders;\n misbehavior?: HandlerDefaults[\"misbehavior\"];\n misbehaviorCounters?: HandlerDefaults[\"misbehaviorCounters\"];\n registry?: HandlerDefaults[\"registry\"];\n /** @internal Called before each message is processed (the server assigns fixture positions). */\n beforeProcessMessage?: () => void;\n },\n): void {\n const { logger } = defaults;\n const session: SessionState = {\n setupDone: false,\n model: defaults.model,\n tools: [],\n conversationHistory: [],\n };\n\n let pending = Promise.resolve();\n ws.on(\"message\", (raw: string) => {\n pending = pending.then(async () => {\n // Messages are processed one at a time, so this marks where this\n // message's turn begins in the history.\n const historyMark = session.conversationHistory.length;\n try {\n defaults.beforeProcessMessage?.();\n await runWithToolArgumentsScope(\n { strict: defaults.strictToolArguments === true, logger },\n () => processMessage(raw, ws, fixtures, journal, defaults, session),\n );\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : \"Internal error\";\n logger.error(`WebSocket Gemini Live error: ${msg}`);\n const fixtureToolCallError = isFixtureToolCallError(err);\n // A fixture rejected by a tool-call check serves nothing, so its turn\n // must not stay in the history as a user turn with no reply. Only\n // fixture tool-call errors roll back: any other error (for example a\n // malformed text block) leaves the user turn in the history.\n if (fixtureToolCallError) session.conversationHistory.length = historyMark;\n try {\n ws.send(\n JSON.stringify({\n error: {\n code: 13,\n message: msg,\n status: \"INTERNAL\",\n ...(fixtureToolCallError\n ? { details: googleFixtureToolCallErrorDetails(fixtureToolCallErrorCode(err)) }\n : {}),\n },\n }),\n );\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 misbehavior?: HandlerDefaults[\"misbehavior\"];\n misbehaviorCounters?: HandlerDefaults[\"misbehaviorCounters\"];\n registry?: HandlerDefaults[\"registry\"];\n },\n session: SessionState,\n): Promise<void> {\n let parsed: GeminiLiveMessage;\n try {\n parsed = JSON.parse(raw) as GeminiLiveMessage;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n ws.send(\n JSON.stringify({\n error: { code: 3, message: `Malformed JSON: ${detail}`, status: \"INVALID_ARGUMENT\" },\n }),\n );\n return;\n }\n\n // Handle setup message (accept both `setup` and `config` as aliases)\n const setupMsg = parsed.setup ?? parsed.config;\n if (setupMsg) {\n session.setupDone = true;\n session.model = setupMsg.model ?? defaults.model;\n session.tools = convertTools(setupMsg.tools);\n ws.send(JSON.stringify({ setupComplete: {} }));\n return;\n }\n\n // Reject messages before setup\n if (!session.setupDone) {\n ws.send(\n JSON.stringify({\n error: { code: 9, message: \"Setup required\", status: \"FAILED_PRECONDITION\" },\n }),\n );\n return;\n }\n\n // Build messages from this interaction\n let newMessages: ChatMessage[];\n\n if (parsed.clientContent) {\n if (!parsed.clientContent.turns || !Array.isArray(parsed.clientContent.turns)) {\n ws.send(\n JSON.stringify({\n error: {\n code: 3,\n message: \"Missing 'turns' in clientContent\",\n status: \"INVALID_ARGUMENT\",\n },\n }),\n );\n return;\n }\n newMessages = geminiTurnsToMessages(parsed.clientContent.turns, defaults.logger);\n } else if (parsed.toolResponse) {\n if (\n !parsed.toolResponse.functionResponses ||\n !Array.isArray(parsed.toolResponse.functionResponses)\n ) {\n ws.send(\n JSON.stringify({\n error: {\n code: 3,\n message: \"Missing 'functionResponses' in toolResponse\",\n status: \"INVALID_ARGUMENT\",\n },\n }),\n );\n return;\n }\n newMessages = toolResponseToMessages(parsed.toolResponse);\n } else {\n ws.send(\n JSON.stringify({\n error: {\n code: 3,\n message: \"Expected clientContent or toolResponse\",\n status: \"INVALID_ARGUMENT\",\n },\n }),\n );\n return;\n }\n\n // Build completion request for fixture matching (include new messages speculatively)\n const geminiContextHeader = defaults.upgradeHeaders?.[\"x-aimock-context\"];\n const geminiContext =\n typeof geminiContextHeader === \"string\"\n ? geminiContextHeader\n : Array.isArray(geminiContextHeader) && geminiContextHeader.length > 0\n ? geminiContextHeader[0]\n : undefined;\n\n const completionReq: ChatCompletionRequest = {\n model: session.model,\n messages: [...session.conversationHistory, ...newMessages],\n stream: true,\n tools: session.tools.length > 0 ? session.tools : undefined,\n _endpointType: \"chat\",\n _context: geminiContext,\n };\n\n const testId = defaults.testId ?? DEFAULT_TEST_ID;\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n const path = WS_PATH;\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (!fixture) {\n if (resolveStrictMode(defaults.strict, defaults.upgradeHeaders)) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n defaults.logger.error(strictNoMatchLogLine(\"WS\", path, skippedBySequenceOrTurn));\n journal.add({\n method: \"WS\",\n path,\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, strictMessage);\n return;\n }\n journal.add({\n method: \"WS\",\n path,\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 error: { code: 5, message: \"No fixture matched\", status: \"NOT_FOUND\" },\n }),\n );\n return;\n }\n\n // Commit messages to conversation history only after successful fixture match\n session.conversationHistory.push(...newMessages);\n\n let response = await resolveResponse(fixture, completionReq);\n const plannerDefaults = { ...defaults, replaySpeed: defaults.replaySpeed ?? 1 };\n let misbehavior = planMisbehavior({\n wire: \"gemini-live\",\n emitsToolCallIds: true,\n fixture,\n response,\n request: completionReq,\n stream: true,\n defaults: plannerDefaults,\n rawHeaders: defaults.upgradeHeaders ?? {},\n // The server resolves both header and query-string session scopes.\n url: `${path}?testId=${encodeURIComponent(testId)}`,\n });\n if (misbehavior.kind === \"applied\") {\n misbehavior = prepareLiveMisbehavior(misbehavior);\n response = misbehavior.response;\n }\n const addResponseEntry = (status: number) => {\n const entry = journal.add({\n method: \"WS\",\n path,\n headers: flattenHeaders(defaults.upgradeHeaders ?? {}),\n body: completionReq,\n response: { status, fixture },\n });\n recordMisbehaviorOutcome({\n entry,\n summary: misbehavior.summary,\n defaults: plannerDefaults,\n testId,\n });\n return entry;\n };\n if (misbehavior.kind === \"error\") {\n addResponseEntry(misbehavior.status);\n if (!misbehavior.summary?.evaluations.length) {\n defaults.logger.error(`[misbehavior] ${misbehavior.message} (testId=${testId})`);\n }\n ws.send(\n JSON.stringify({\n error: {\n code: httpToGrpc(misbehavior.status),\n message: misbehavior.message,\n status: misbehavior.status === 400 ? \"INVALID_ARGUMENT\" : \"UNIMPLEMENTED\",\n },\n }),\n );\n return;\n }\n const latency = fixture.latency ?? defaults.latency;\n if (\n misbehavior.kind === \"applied\" &&\n (misbehavior.summary.fault === \"tool-args-invalid-json\" ||\n misbehavior.summary.fault === \"empty-response\")\n ) {\n const journalEntry = addResponseEntry(200);\n const interruption = createInterruptionSignal(fixture);\n const markInterrupted = () => {\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n ws.destroy();\n };\n try {\n if (ws.isClosed) return;\n const chunkDelay = calculateDelay(\n 0,\n undefined,\n latency,\n fixture.recordedTimings,\n fixture.replaySpeed ?? defaults.replaySpeed,\n );\n if (chunkDelay > 0) await delay(chunkDelay, interruption?.signal);\n if (interruption?.signal.aborted) {\n markInterrupted();\n return;\n }\n if (ws.isClosed) return;\n const malformed = misbehavior.summary.fault === \"tool-args-invalid-json\";\n ws.send(\n JSON.stringify({\n serverContent: malformed\n ? { turnComplete: true, turnCompleteReason: \"MALFORMED_FUNCTION_CALL\" }\n : { modelTurn: { parts: [{ text: \"\" }] } },\n }),\n );\n interruption?.tick();\n if (interruption?.signal.aborted) {\n markInterrupted();\n return;\n }\n // Like ordinary text streaming, completion follows the counted content chunk.\n if (!malformed && !ws.isClosed) {\n ws.send(JSON.stringify({ serverContent: { turnComplete: true } }));\n session.conversationHistory.push({ role: \"assistant\", content: \"\" });\n }\n } finally {\n interruption?.cleanup();\n }\n return;\n }\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 addResponseEntry(status);\n ws.send(\n JSON.stringify({\n error: {\n code: httpToGrpc(status),\n message: response.error.message,\n status: response.error.type ?? \"INTERNAL\",\n },\n }),\n );\n return;\n }\n\n // Audio response — an AUDIO-modality model turn, plus the companion\n // modalities `AudioResponse` documents (types.ts) as preserved alongside it.\n //\n // COMPANION PLACEMENT is protocol-specific, and the Live protocol does NOT\n // place a tool call where the HTTP protocol does. On `generateContent` a\n // functionCall is a PART, so gemini.ts's `buildGeminiAudioParts` appends it to\n // the same `content.parts` array as the audio. On Live, `serverContent` and\n // `toolCall` are alternatives of ONE union: `@google/genai`'s\n // `LiveServerMessage` declares them as sibling optional fields and the\n // BidiGenerateContent reference states the response's `messageType` union\n // \"can be only one of the following\". A turn that both speaks and calls a\n // function is therefore TWO messages, not one part list —\n //\n // serverContent{modelTurn.parts} → toolCall{functionCalls} → serverContent{turnComplete}\n //\n // — which is also the order every other branch of this handler already emits\n // and the order the live provider was observed to use (its tool turn sent a\n // `serverContent` strictly before the `toolCall`).\n //\n // Emitting only the audio and returning — the previous behavior — silently\n // discarded the tool call and the text content of a recorded audio turn, the\n // exact loss types.ts's AudioResponse companions exist to prevent.\n //\n // NOT handled here: `AudioResponse.reasoning`. This handler has no thought\n // channel in ANY branch (nothing in it emits `thought: true`), so replaying\n // reasoning from the audio branch alone would invent a Live-wide capability\n // out of one fixture field. That gap is real but separate.\n if (isAudioResponse(response)) {\n const journalEntry = addResponseEntry(200);\n\n const audioResp = response as AudioResponse;\n const audioToolCalls = preflightToolArguments(audioResp, journalEntry);\n let mimeType: string;\n let data: string;\n\n if (typeof audioResp.audio === \"string\") {\n mimeType = formatToMime(audioResp.format ?? \"mp3\");\n data = audioResp.audio;\n } else {\n mimeType = audioResp.audio.contentType ?? \"audio/mpeg\";\n data = audioResp.audio.b64Json;\n }\n\n // Part order mirrors gemini.ts's `buildGeminiAudioParts`: audio first, then\n // the text companion.\n const parts: GeminiLivePart[] = [{ inlineData: { mimeType, data } }];\n if (audioResp.content) {\n parts.push({ text: audioResp.content });\n }\n\n // Stable IDs resolved once, so the wire message and conversation history\n // agree (same contract as the tool-call branches below).\n const resolvedToolCalls = audioToolCalls.map((tc) => ({\n ...tc,\n resolvedId: tc.id ?? generateToolCallId(),\n }));\n\n if (resolvedToolCalls.length === 0) {\n ws.send(\n JSON.stringify({\n serverContent: { modelTurn: { parts }, turnComplete: true },\n }),\n );\n } else {\n ws.send(JSON.stringify({ serverContent: { modelTurn: { parts } } }));\n\n if (!ws.isClosed) {\n const functionCalls = resolvedToolCalls.map((tc) => {\n const argsObj = liveToolArguments(tc);\n return { name: tc.name, args: argsObj, id: tc.resolvedId };\n });\n ws.send(JSON.stringify({ toolCall: { functionCalls } }));\n }\n\n if (!ws.isClosed) {\n ws.send(JSON.stringify({ serverContent: { turnComplete: true } }));\n }\n }\n\n session.conversationHistory.push({\n role: \"assistant\",\n content: audioResp.content ?? \"[audio]\",\n ...(resolvedToolCalls.length > 0\n ? {\n tool_calls: resolvedToolCalls.map((tc) => ({\n id: tc.resolvedId,\n type: \"function\" as const,\n function: { name: tc.name, arguments: tc.arguments },\n })),\n }\n : {}),\n });\n return;\n }\n\n // Content + tool calls response (must be checked before isTextResponse / isToolCallResponse)\n if (isCombinedFixtureResponse(response)) {\n const journalEntry = addResponseEntry(200);\n\n // BLOCKS path (#274): when the fixture carries an ordered `blocks` array,\n // honor it instead of the legacy `content ?? \"\"` / `toolCalls ?? []` path.\n // The Gemini Live WS protocol expresses ordering via SEQUENTIAL messages —\n // a text block becomes one-or-more `serverContent.modelTurn.parts[{text}]`\n // messages and a toolCall block becomes a `toolCall.functionCalls` message,\n // so emitting in array order faithfully reproduces tool-before-text (or any\n // interleaving), matching the HTTP gemini.ts blocks branch. Without this, a\n // blocks-only fixture (post-F0 it matches this guard) would stream an EMPTY\n // payload — a silent drop. Legacy fixtures (no `blocks`) skip this entirely.\n if (response.blocks && response.blocks.length > 0) {\n const resolvedBlocks = preflightBlocks(response, journalEntry);\n const interruption = createInterruptionSignal(fixture);\n const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;\n const { recordedTimings } = fixture;\n let chunkIndex = 0;\n let interrupted = false;\n\n // Accumulate the equivalent assistant turn for conversation history, so a\n // follow-up turn sees the same content + tool_calls as the legacy path.\n let historyContent = \"\";\n const historyToolCalls: {\n id: string;\n type: \"function\";\n function: { name: string; arguments: string };\n }[] = [];\n\n outer: for (const block of resolvedBlocks) {\n if (block.type === \"toolCall\") {\n if (ws.isClosed) break;\n const tcDelay = calculateDelay(\n chunkIndex,\n undefined,\n latency,\n recordedTimings,\n replaySpeed,\n );\n if (tcDelay > 0) await delay(tcDelay, interruption?.signal);\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n if (ws.isClosed) break;\n\n const resolvedId = block.id ?? generateToolCallId();\n const argsObj = liveToolArguments(block);\n\n try {\n ws.send(\n JSON.stringify({\n toolCall: { functionCalls: [{ name: block.name, args: argsObj, id: resolvedId }] },\n }),\n );\n } catch (err) {\n defaults.logger.debug(\n \"[gemini-live] send failed during blocks streaming, closing\",\n err,\n );\n break;\n }\n chunkIndex++;\n historyToolCalls.push({\n id: resolvedId,\n type: \"function\" as const,\n function: { name: block.name, arguments: block.arguments },\n });\n interruption?.tick();\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n } else {\n const text = block.text;\n historyContent += text;\n if (text.length === 0) {\n // An empty text block carries no wire content, so emit nothing and\n // spend no chunk: the old guard sent a useless empty `modelTurn`\n // message and `continue`d WITHOUT `chunkIndex++`/`interruption.tick()`,\n // which leaked the next block past `truncateAfterChunks` and shifted\n // `recordedTimings` indexing for every following block. Skipping\n // keeps non-empty-block output byte-identical and the chunk/timing\n // accounting correct.\n continue;\n }\n for (let i = 0; i < text.length; i += chunkSize) {\n if (ws.isClosed) break outer;\n const chunkDelay = calculateDelay(\n chunkIndex,\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 outer;\n }\n if (ws.isClosed) break outer;\n try {\n ws.send(\n JSON.stringify({\n serverContent: { modelTurn: { parts: [{ text: text.slice(i, i + chunkSize) }] } },\n }),\n );\n } catch (err) {\n defaults.logger.debug(\n \"[gemini-live] send failed during blocks streaming, closing\",\n err,\n );\n break outer;\n }\n chunkIndex++;\n interruption?.tick();\n if (interruption?.signal.aborted) {\n interrupted = true;\n break outer;\n }\n }\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 // Send turnComplete\n if (!ws.isClosed) {\n ws.send(JSON.stringify({ serverContent: { turnComplete: true } }));\n }\n\n session.conversationHistory.push({\n role: \"assistant\",\n content: historyContent || null,\n ...(historyToolCalls.length > 0 ? { tool_calls: historyToolCalls } : {}),\n });\n return;\n }\n\n const functionToolCalls = preflightToolArguments(response, journalEntry);\n const content = response.content ?? \"\";\n const chunkList: string[] = [];\n for (let i = 0; i < content.length; i += chunkSize) {\n chunkList.push(content.slice(i, i + chunkSize));\n }\n\n const interruption = createInterruptionSignal(fixture);\n const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;\n const { recordedTimings } = fixture;\n let interrupted = false;\n\n // Stream text content chunks (turnComplete omitted — sent as a separate message later)\n if (content.length === 0) {\n if (!ws.isClosed) {\n ws.send(\n JSON.stringify({\n serverContent: {\n modelTurn: { parts: [{ text: \"\" }] },\n },\n }),\n );\n }\n } else {\n for (let i = 0; i < chunkList.length; i++) {\n if (ws.isClosed) break;\n const chunkDelay = calculateDelay(i, undefined, latency, recordedTimings, replaySpeed);\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\n try {\n ws.send(\n JSON.stringify({\n serverContent: {\n modelTurn: { parts: [{ text: chunkList[i] }] },\n },\n }),\n );\n } catch (err) {\n defaults.logger.debug(\"[gemini-live] send failed during text streaming, closing\", err);\n break;\n }\n interruption?.tick();\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\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 // Pre-compute tool calls with stable IDs so wire message and history match\n const resolvedToolCalls = functionToolCalls.map((tc) => ({\n ...tc,\n resolvedId: tc.id ?? generateToolCallId(),\n }));\n\n // Send tool calls\n if (!ws.isClosed) {\n const tcDelay = calculateDelay(\n chunkList.length,\n undefined,\n latency,\n recordedTimings,\n replaySpeed,\n );\n if (tcDelay > 0) await delay(tcDelay, interruption?.signal);\n if (interruption?.signal.aborted) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n interruption?.cleanup();\n return;\n }\n\n const functionCalls = resolvedToolCalls.map((tc) => {\n const argsObj = liveToolArguments(tc);\n return {\n name: tc.name,\n args: argsObj,\n id: tc.resolvedId,\n };\n });\n\n ws.send(JSON.stringify({ toolCall: { functionCalls } }));\n interruption?.tick();\n }\n\n if (interruption?.signal.aborted) {\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 // Send turnComplete\n if (!ws.isClosed) {\n ws.send(\n JSON.stringify({\n serverContent: { turnComplete: true },\n }),\n );\n }\n\n // Add to conversation history using the same resolved IDs from the wire message\n session.conversationHistory.push({\n role: \"assistant\",\n content: content || null,\n tool_calls: resolvedToolCalls.map((tc) => ({\n id: tc.resolvedId,\n type: \"function\" as const,\n function: {\n name: tc.name,\n arguments: tc.arguments,\n },\n })),\n });\n return;\n }\n\n // Text response — stream chunks with serverContent\n if (isTextResponse(response)) {\n const journalEntry = addResponseEntry(200);\n\n const content = response.content;\n\n if (content.length === 0) {\n if (ws.isClosed) return;\n // Empty content: send empty modelTurn, then separate turnComplete\n ws.send(\n JSON.stringify({\n serverContent: {\n modelTurn: { parts: [{ text: \"\" }] },\n },\n }),\n );\n ws.send(\n JSON.stringify({\n serverContent: { turnComplete: true },\n }),\n );\n return;\n }\n\n // Chunk the content\n const chunks: string[] = [];\n for (let i = 0; i < content.length; i += chunkSize) {\n chunks.push(content.slice(i, i + chunkSize));\n }\n\n const interruption = createInterruptionSignal(fixture);\n const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;\n const { recordedTimings } = fixture;\n let interrupted = false;\n\n // Stream content chunks without turnComplete (sent separately after)\n for (let i = 0; i < chunks.length; i++) {\n if (ws.isClosed) break;\n const chunkDelay = calculateDelay(i, undefined, latency, recordedTimings, replaySpeed);\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\n try {\n ws.send(\n JSON.stringify({\n serverContent: {\n modelTurn: { parts: [{ text: chunks[i] }] },\n },\n }),\n );\n } catch (err) {\n defaults.logger.debug(\"[gemini-live] send failed during text streaming, closing\", err);\n break;\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 // Send separate turnComplete message\n if (!ws.isClosed) {\n ws.send(\n JSON.stringify({\n serverContent: { turnComplete: true },\n }),\n );\n }\n\n // Add assistant response to conversation history\n session.conversationHistory.push({ role: \"assistant\", content });\n return;\n }\n\n // Tool call response\n if (isToolCallResponse(response)) {\n const journalEntry = addResponseEntry(200);\n\n const functionToolCalls = preflightToolArguments(response, journalEntry);\n\n const interruption = createInterruptionSignal(fixture);\n const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;\n const { recordedTimings } = fixture;\n\n if (ws.isClosed) {\n interruption?.cleanup();\n return;\n }\n const tcDelay = calculateDelay(0, undefined, latency, recordedTimings, replaySpeed);\n if (tcDelay > 0) await delay(tcDelay, interruption?.signal);\n if (interruption?.signal.aborted) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n interruption?.cleanup();\n return;\n }\n if (ws.isClosed) {\n interruption?.cleanup();\n return;\n }\n\n // Pre-compute tool calls with stable IDs so wire message and history match\n const resolvedToolCalls = functionToolCalls.map((tc) => ({\n ...tc,\n resolvedId: tc.id ?? generateToolCallId(),\n }));\n\n const functionCalls = resolvedToolCalls.map((tc) => {\n const argsObj = liveToolArguments(tc);\n return {\n name: tc.name,\n args: argsObj,\n id: tc.resolvedId,\n };\n });\n\n ws.send(JSON.stringify({ toolCall: { functionCalls } }));\n interruption?.tick();\n\n if (interruption?.signal.aborted) {\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 // Send turnComplete after tool call\n if (!ws.isClosed) {\n ws.send(\n JSON.stringify({\n serverContent: { turnComplete: true },\n }),\n );\n }\n\n // Add assistant tool_calls to conversation history using the same resolved IDs\n session.conversationHistory.push({\n role: \"assistant\",\n content: null,\n tool_calls: resolvedToolCalls.map((tc) => ({\n id: tc.resolvedId,\n type: \"function\" as const,\n function: {\n name: tc.name,\n arguments: tc.arguments,\n },\n })),\n });\n return;\n }\n\n // Unknown response type\n addResponseEntry(500);\n ws.send(\n JSON.stringify({\n error: {\n code: 13,\n message: \"Fixture response did not match any known type\",\n status: \"INTERNAL\",\n },\n }),\n );\n}\n"],"mappings":"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"}
@@ -2,6 +2,7 @@ const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
2
2
  const require_constants = require('./constants.cjs');
3
3
  const require_helpers = require('./helpers.cjs');
4
4
  const require_sse_writer = require('./sse-writer.cjs');
5
+ const require_misbehavior = require('./misbehavior.cjs');
5
6
  const require_router = require('./router.cjs');
6
7
  require('./journal.cjs');
7
8
  const require_interruption = require('./interruption.cjs');
@@ -15,6 +16,11 @@ let node_crypto = require("node:crypto");
15
16
  * response.create) over WebSocket and sends back Realtime API events as
16
17
  * individual WebSocket text frames.
17
18
  */
19
+ /**
20
+ * Wire label for custom-tool-call rejections on this transport, on both the
21
+ * `toolCalls` and the ordered `blocks` path.
22
+ */
23
+ const REALTIME_WIRE = "OpenAI Realtime";
18
24
  /** Generate a Realtime-API-style ID with underscore separator (e.g. event_xxx, item_xxx). */
19
25
  function realtimeId(prefix) {
20
26
  return `${prefix}_${(0, node_crypto.randomBytes)(12).toString("base64url")}`;
@@ -136,6 +142,22 @@ function serializeSession(session, sessionId) {
136
142
  reasoning: session.reasoning
137
143
  };
138
144
  }
145
+ /** Realtime declares functions directly, rather than under Chat's `function` key. */
146
+ function realtimeToolDefinitions(tools) {
147
+ return tools.flatMap((value) => {
148
+ if (!value || typeof value !== "object") return [];
149
+ const tool = value;
150
+ if (tool.type !== "function" || typeof tool.name !== "string") return [];
151
+ return [{
152
+ type: "function",
153
+ function: {
154
+ name: tool.name,
155
+ ...typeof tool.description === "string" ? { description: tool.description } : {},
156
+ ...tool.parameters && typeof tool.parameters === "object" && !Array.isArray(tool.parameters) ? { parameters: tool.parameters } : {}
157
+ }
158
+ }];
159
+ });
160
+ }
139
161
  function realtimeItemsToMessages(items, instructions, logger) {
140
162
  const messages = [];
141
163
  if (instructions) messages.push({
@@ -371,15 +393,20 @@ function handleWebSocketRealtime(ws, fixtures, journal, defaults) {
371
393
  }, isBeta);
372
394
  let pending = Promise.resolve();
373
395
  ws.on("message", (raw) => {
374
- pending = pending.then(() => processMessage(raw, ws, fixtures, journal, defaults, session, conversationItems, isBeta).catch((err) => {
375
- const msg = err instanceof Error ? err.message : "Internal error";
376
- logger.error(`WebSocket realtime error: ${msg}`);
396
+ pending = pending.then(async () => {
377
397
  try {
378
- buildErrorRealtimeEvent(ws, msg, isBeta, "server_error");
379
- } catch (sendErr) {
380
- defaults.logger.debug(`Failed to send error to client: ${sendErr instanceof Error ? sendErr.message : "unknown"}`);
398
+ defaults.beforeProcessMessage?.();
399
+ await processMessage(raw, ws, fixtures, journal, defaults, session, conversationItems, isBeta);
400
+ } catch (err) {
401
+ const msg = err instanceof Error ? err.message : "Internal error";
402
+ logger.error(`WebSocket realtime error: ${msg}`);
403
+ try {
404
+ buildErrorRealtimeEvent(ws, msg, isBeta, "server_error");
405
+ } catch (sendErr) {
406
+ defaults.logger.debug(`Failed to send error to client: ${sendErr instanceof Error ? sendErr.message : "unknown"}`);
407
+ }
381
408
  }
382
- }));
409
+ });
383
410
  });
384
411
  }
385
412
  async function processMessage(raw, ws, fixtures, journal, defaults, session, conversationItems, isBeta) {
@@ -695,6 +722,57 @@ function sendLiveTranscriptionFailure(ws, itemId, error, isBeta) {
695
722
  error
696
723
  }, isBeta);
697
724
  }
725
+ /**
726
+ * Run a fixture tool-call `check` before any output item is sent. A rejected
727
+ * fixture (a custom tool call, which Realtime cannot carry, or a malformed
728
+ * `toolCalls` entry or tool-call block) marks the journal entry as a 500 and
729
+ * answers with a failed response — `response.created` then
730
+ * `response.done` with `status: "failed"` and the error, including its aimock
731
+ * `code`, in `status_details` — the same pair the error-fixture branch sends.
732
+ * Returns undefined when the response was rejected.
733
+ */
734
+ function checkRealtimeFixtureToolCalls(ws, journalEntry, responseId, isBeta, logger, check) {
735
+ try {
736
+ return require_helpers.journalFixtureToolCallError(journalEntry, check);
737
+ } catch (err) {
738
+ if (!require_helpers.isFixtureToolCallError(err)) throw err;
739
+ logger.error(`WebSocket realtime error: ${err.message}`);
740
+ sendEvent(ws, {
741
+ type: "response.created",
742
+ response: {
743
+ id: responseId,
744
+ object: "realtime.response",
745
+ status: "failed",
746
+ status_details: null,
747
+ output: [],
748
+ usage: null
749
+ }
750
+ }, isBeta);
751
+ sendEvent(ws, {
752
+ type: "response.done",
753
+ response: {
754
+ id: responseId,
755
+ object: "realtime.response",
756
+ status: "failed",
757
+ output: [],
758
+ status_details: {
759
+ type: "error",
760
+ error: {
761
+ message: err.message,
762
+ type: "server_error",
763
+ code: require_helpers.fixtureToolCallErrorCode(err)
764
+ }
765
+ },
766
+ usage: {
767
+ total_tokens: 0,
768
+ input_tokens: 0,
769
+ output_tokens: 0
770
+ }
771
+ }
772
+ }, isBeta);
773
+ return;
774
+ }
775
+ }
698
776
  async function handleResponseCreate(ws, fixtures, journal, defaults, session, conversationItems, isBeta, responseOverrides) {
699
777
  const messages = realtimeItemsToMessages(conversationItems, (responseOverrides?.instructions ?? session.instructions) || void 0, defaults.logger);
700
778
  const endpointType = {
@@ -704,10 +782,12 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
704
782
  }[session.type] ?? "realtime";
705
783
  const realtimeContextHeader = defaults.upgradeHeaders?.["x-aimock-context"];
706
784
  const realtimeContext = typeof realtimeContextHeader === "string" ? realtimeContextHeader : Array.isArray(realtimeContextHeader) && realtimeContextHeader.length > 0 ? realtimeContextHeader[0] : void 0;
785
+ const tools = realtimeToolDefinitions(Array.isArray(responseOverrides?.tools) ? responseOverrides.tools : session.tools);
707
786
  const completionReq = {
708
787
  model: session.model,
709
788
  messages,
710
789
  _endpointType: endpointType,
790
+ ...tools.length > 0 ? { tools } : {},
711
791
  _context: realtimeContext
712
792
  };
713
793
  const testId = defaults.testId ?? require_constants.DEFAULT_TEST_ID;
@@ -779,11 +859,24 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
779
859
  return;
780
860
  }
781
861
  const response = await require_helpers.resolveResponse(fixture, completionReq);
782
- const latency = fixture.latency ?? defaults.latency;
783
- const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
784
- if (require_helpers.isErrorResponse(response)) {
785
- const status = response.status ?? 500;
786
- journal.add({
862
+ const plannerDefaults = {
863
+ ...defaults,
864
+ replaySpeed: defaults.replaySpeed ?? 1
865
+ };
866
+ let misbehavior = require_misbehavior.planMisbehavior({
867
+ wire: "openai-realtime",
868
+ emitsToolCallIds: true,
869
+ fixture,
870
+ response,
871
+ request: completionReq,
872
+ stream: true,
873
+ defaults: plannerDefaults,
874
+ rawHeaders: defaults.upgradeHeaders ?? {},
875
+ url: `/v1/realtime?testId=${encodeURIComponent(testId)}`
876
+ });
877
+ if (misbehavior.kind === "applied") misbehavior = require_helpers.prepareOpenAIChatMisbehavior(misbehavior);
878
+ const addResponseEntry = (status) => {
879
+ const entry = journal.add({
787
880
  method: "WS",
788
881
  path: "/v1/realtime",
789
882
  headers: require_helpers.flattenHeaders(defaults.upgradeHeaders ?? {}),
@@ -793,6 +886,47 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
793
886
  fixture
794
887
  }
795
888
  });
889
+ require_misbehavior.recordMisbehaviorOutcome({
890
+ entry,
891
+ summary: misbehavior.summary,
892
+ defaults: plannerDefaults,
893
+ testId
894
+ });
895
+ return entry;
896
+ };
897
+ if (misbehavior.kind === "error") {
898
+ addResponseEntry(misbehavior.status);
899
+ if (!misbehavior.summary?.evaluations.length) defaults.logger.error(`[misbehavior] ${misbehavior.message} (testId=${testId})`);
900
+ buildErrorRealtimeEvent(ws, misbehavior.message, isBeta, "invalid_request_error", misbehavior.code);
901
+ return;
902
+ }
903
+ const latency = fixture.latency ?? defaults.latency;
904
+ const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
905
+ if (misbehavior.kind === "applied") {
906
+ const prepared = misbehavior.response;
907
+ const combined = require_helpers.isCombinedFixtureResponse(prepared);
908
+ const journalEntry = addResponseEntry(200);
909
+ const blocks = checkRealtimeFixtureToolCalls(ws, journalEntry, responseId, isBeta, defaults.logger, () => combined && prepared.blocks?.length ? require_helpers.resolveFixtureBlocks(prepared.blocks) : [...(require_helpers.isTextResponse(prepared) || combined) && prepared.content ? [{
910
+ type: "text",
911
+ text: prepared.content
912
+ }] : [], ...require_helpers.isToolCallResponse(prepared) || combined ? (prepared.toolCalls ?? []).map((call) => ({
913
+ type: "toolCall",
914
+ ...call
915
+ })) : []]);
916
+ if (!blocks) return;
917
+ const usage = require_helpers.resolveOpenAIChatMisbehaviorUsage(misbehavior, completionReq, false);
918
+ await streamRealtimeBlocks(ws, blocks, responseId, fixture, defaults, latency, chunkSize, isBeta, conversationItems, journalEntry, {
919
+ stop: misbehavior.stop,
920
+ usage: {
921
+ input_tokens: usage.prompt_tokens,
922
+ output_tokens: usage.completion_tokens,
923
+ total_tokens: usage.total_tokens
924
+ }
925
+ });
926
+ return;
927
+ }
928
+ if (require_helpers.isErrorResponse(response)) {
929
+ addResponseEntry(response.status ?? 500);
796
930
  sendEvent(ws, {
797
931
  type: "response.created",
798
932
  response: {
@@ -828,21 +962,20 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
828
962
  }, isBeta);
829
963
  return;
830
964
  }
831
- if (require_helpers.isContentWithToolCallsResponse(response)) {
832
- const journalEntry = journal.add({
833
- method: "WS",
834
- path: "/v1/realtime",
835
- headers: require_helpers.flattenHeaders(defaults.upgradeHeaders ?? {}),
836
- body: completionReq,
837
- response: {
838
- status: 200,
839
- fixture
840
- }
841
- });
965
+ if (require_helpers.isCombinedFixtureResponse(response)) {
966
+ const journalEntry = addResponseEntry(200);
842
967
  if (response.blocks && response.blocks.length > 0) {
843
- await streamRealtimeBlocks(ws, require_helpers.resolveFixtureBlocks(response.blocks), responseId, fixture, defaults, latency, chunkSize, isBeta, conversationItems, journalEntry);
968
+ const blocks = response.blocks;
969
+ const resolvedBlocks = checkRealtimeFixtureToolCalls(ws, journalEntry, responseId, isBeta, defaults.logger, () => {
970
+ require_helpers.rejectResponsesOnlyToolCalls(response, REALTIME_WIRE);
971
+ return require_helpers.resolveFixtureBlocks(blocks);
972
+ });
973
+ if (!resolvedBlocks) return;
974
+ await streamRealtimeBlocks(ws, resolvedBlocks, responseId, fixture, defaults, latency, chunkSize, isBeta, conversationItems, journalEntry);
844
975
  return;
845
976
  }
977
+ const functionToolCalls = checkRealtimeFixtureToolCalls(ws, journalEntry, responseId, isBeta, defaults.logger, () => require_helpers.requireServedFunctionToolCalls(response, REALTIME_WIRE));
978
+ if (!functionToolCalls) return;
846
979
  sendEvent(ws, {
847
980
  type: "response.created",
848
981
  response: {
@@ -992,7 +1125,7 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
992
1125
  return;
993
1126
  }
994
1127
  allOutputItems.push(textOutputItem);
995
- const toolCalls = response.toolCalls ?? [];
1128
+ const toolCalls = functionToolCalls;
996
1129
  for (let tcIdx = 0; tcIdx < toolCalls.length; tcIdx++) {
997
1130
  const tc = toolCalls[tcIdx];
998
1131
  const callId = tc.id ?? require_helpers.generateToolCallId();
@@ -1122,16 +1255,7 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
1122
1255
  return;
1123
1256
  }
1124
1257
  if (require_helpers.isTextResponse(response)) {
1125
- const journalEntry = journal.add({
1126
- method: "WS",
1127
- path: "/v1/realtime",
1128
- headers: require_helpers.flattenHeaders(defaults.upgradeHeaders ?? {}),
1129
- body: completionReq,
1130
- response: {
1131
- status: 200,
1132
- fixture
1133
- }
1134
- });
1258
+ const journalEntry = addResponseEntry(200);
1135
1259
  const itemId = realtimeId("item");
1136
1260
  const contentIndex = 0;
1137
1261
  const outputIndex = 0;
@@ -1290,16 +1414,9 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
1290
1414
  return;
1291
1415
  }
1292
1416
  if (require_helpers.isToolCallResponse(response)) {
1293
- const journalEntry = journal.add({
1294
- method: "WS",
1295
- path: "/v1/realtime",
1296
- headers: require_helpers.flattenHeaders(defaults.upgradeHeaders ?? {}),
1297
- body: completionReq,
1298
- response: {
1299
- status: 200,
1300
- fixture
1301
- }
1302
- });
1417
+ const journalEntry = addResponseEntry(200);
1418
+ const toolCalls = checkRealtimeFixtureToolCalls(ws, journalEntry, responseId, isBeta, defaults.logger, () => require_helpers.requireServedFunctionToolCalls(response, REALTIME_WIRE));
1419
+ if (!toolCalls) return;
1303
1420
  sendEvent(ws, {
1304
1421
  type: "response.created",
1305
1422
  response: {
@@ -1317,8 +1434,8 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
1317
1434
  const { recordedTimings } = fixture;
1318
1435
  let interrupted = false;
1319
1436
  let eventIndex = 0;
1320
- for (let tcIdx = 0; tcIdx < response.toolCalls.length; tcIdx++) {
1321
- const tc = response.toolCalls[tcIdx];
1437
+ for (let tcIdx = 0; tcIdx < toolCalls.length; tcIdx++) {
1438
+ const tc = toolCalls[tcIdx];
1322
1439
  const callId = tc.id ?? require_helpers.generateToolCallId();
1323
1440
  const itemId = realtimeId("item");
1324
1441
  const outputItem = {
@@ -1433,16 +1550,7 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
1433
1550
  for (const item of outputItems) conversationItems.push(item);
1434
1551
  return;
1435
1552
  }
1436
- journal.add({
1437
- method: "WS",
1438
- path: "/v1/realtime",
1439
- headers: require_helpers.flattenHeaders(defaults.upgradeHeaders ?? {}),
1440
- body: completionReq,
1441
- response: {
1442
- status: 500,
1443
- fixture
1444
- }
1445
- });
1553
+ addResponseEntry(500);
1446
1554
  buildErrorRealtimeEvent(ws, "Fixture response did not match any known type", isBeta, "server_error");
1447
1555
  }
1448
1556
  /**
@@ -1454,7 +1562,7 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
1454
1562
  * as the legacy text/tool branches; a blocks-only fixture therefore never
1455
1563
  * produces an empty payload.
1456
1564
  */
1457
- async function streamRealtimeBlocks(ws, blocks, responseId, fixture, defaults, latency, chunkSize, isBeta, conversationItems, journalEntry) {
1565
+ async function streamRealtimeBlocks(ws, blocks, responseId, fixture, defaults, latency, chunkSize, isBeta, conversationItems, journalEntry, fault) {
1458
1566
  sendEvent(ws, {
1459
1567
  type: "response.created",
1460
1568
  response: {
@@ -1593,10 +1701,11 @@ async function streamRealtimeBlocks(ws, blocks, responseId, fixture, defaults, l
1593
1701
  const callId = block.id ?? require_helpers.generateToolCallId();
1594
1702
  const itemId = realtimeId("item");
1595
1703
  const args = block.arguments;
1704
+ const itemStatus = fault?.stop === "length" && outputIndex === blocks.length - 1 ? "incomplete" : "completed";
1596
1705
  const toolOutputItem = {
1597
1706
  id: itemId,
1598
1707
  type: "function_call",
1599
- status: "completed",
1708
+ status: itemStatus,
1600
1709
  call_id: callId,
1601
1710
  name: block.name,
1602
1711
  arguments: args
@@ -1670,7 +1779,7 @@ async function streamRealtimeBlocks(ws, blocks, responseId, fixture, defaults, l
1670
1779
  id: itemId,
1671
1780
  object: "realtime.item",
1672
1781
  type: "function_call",
1673
- status: "completed",
1782
+ status: itemStatus,
1674
1783
  call_id: callId,
1675
1784
  name: block.name,
1676
1785
  arguments: args
@@ -1694,9 +1803,13 @@ async function streamRealtimeBlocks(ws, blocks, responseId, fixture, defaults, l
1694
1803
  response: {
1695
1804
  id: responseId,
1696
1805
  object: "realtime.response",
1697
- status: "completed",
1806
+ status: fault?.stop === "length" || fault?.stop === "content_filter" ? "incomplete" : "completed",
1807
+ ...fault?.stop === "length" || fault?.stop === "content_filter" ? { status_details: {
1808
+ type: "incomplete",
1809
+ reason: fault.stop === "length" ? "max_output_tokens" : "content_filter"
1810
+ } } : {},
1698
1811
  output: allOutputItems,
1699
- usage: {
1812
+ usage: fault?.usage ?? {
1700
1813
  total_tokens: 0,
1701
1814
  input_tokens: 0,
1702
1815
  output_tokens: 0