@copilotkit/aimock 1.37.4 → 1.39.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 (175) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +48 -0
  4. package/README.md +50 -11
  5. package/dist/agui-handler.cjs +20 -18
  6. package/dist/agui-handler.cjs.map +1 -1
  7. package/dist/agui-handler.d.cts +8 -1
  8. package/dist/agui-handler.d.cts.map +1 -1
  9. package/dist/agui-handler.d.ts +8 -1
  10. package/dist/agui-handler.d.ts.map +1 -1
  11. package/dist/agui-handler.js +20 -18
  12. package/dist/agui-handler.js.map +1 -1
  13. package/dist/agui-recorder.cjs +6 -0
  14. package/dist/agui-recorder.cjs.map +1 -1
  15. package/dist/agui-recorder.d.cts.map +1 -1
  16. package/dist/agui-recorder.d.ts.map +1 -1
  17. package/dist/agui-recorder.js +6 -0
  18. package/dist/agui-recorder.js.map +1 -1
  19. package/dist/agui-stub.d.cts +2 -2
  20. package/dist/agui-stub.d.ts +2 -2
  21. package/dist/agui-types.d.cts +19 -1
  22. package/dist/agui-types.d.cts.map +1 -1
  23. package/dist/agui-types.d.ts +19 -1
  24. package/dist/agui-types.d.ts.map +1 -1
  25. package/dist/aimock-cli.cjs +1 -1
  26. package/dist/aimock-cli.cjs.map +1 -1
  27. package/dist/aimock-cli.js +1 -1
  28. package/dist/aimock-cli.js.map +1 -1
  29. package/dist/api-key-auth.cjs +166 -0
  30. package/dist/api-key-auth.cjs.map +1 -0
  31. package/dist/api-key-auth.d.cts +17 -0
  32. package/dist/api-key-auth.d.cts.map +1 -0
  33. package/dist/api-key-auth.d.ts +17 -0
  34. package/dist/api-key-auth.d.ts.map +1 -0
  35. package/dist/api-key-auth.js +158 -0
  36. package/dist/api-key-auth.js.map +1 -0
  37. package/dist/cli.cjs +14 -11
  38. package/dist/cli.cjs.map +1 -1
  39. package/dist/cli.js +14 -11
  40. package/dist/cli.js.map +1 -1
  41. package/dist/config-loader.cjs +4 -2
  42. package/dist/config-loader.cjs.map +1 -1
  43. package/dist/config-loader.d.cts +2 -1
  44. package/dist/config-loader.d.cts.map +1 -1
  45. package/dist/config-loader.d.ts +2 -1
  46. package/dist/config-loader.d.ts.map +1 -1
  47. package/dist/config-loader.js +5 -3
  48. package/dist/config-loader.js.map +1 -1
  49. package/dist/fal-audio.cjs +2 -0
  50. package/dist/fal-audio.cjs.map +1 -1
  51. package/dist/fal-audio.d.cts.map +1 -1
  52. package/dist/fal-audio.d.ts.map +1 -1
  53. package/dist/fal-audio.js +2 -0
  54. package/dist/fal-audio.js.map +1 -1
  55. package/dist/fal.cjs +4 -1
  56. package/dist/fal.cjs.map +1 -1
  57. package/dist/fal.d.cts.map +1 -1
  58. package/dist/fal.d.ts.map +1 -1
  59. package/dist/fal.js +4 -1
  60. package/dist/fal.js.map +1 -1
  61. package/dist/fixture-loader.cjs +0 -0
  62. package/dist/fixture-loader.cjs.map +1 -1
  63. package/dist/fixture-loader.d.cts +3 -1
  64. package/dist/fixture-loader.d.cts.map +1 -1
  65. package/dist/fixture-loader.d.ts +3 -1
  66. package/dist/fixture-loader.d.ts.map +1 -1
  67. package/dist/fixture-loader.js +0 -0
  68. package/dist/fixture-loader.js.map +1 -1
  69. package/dist/grok-video.cjs +9 -2
  70. package/dist/grok-video.cjs.map +1 -1
  71. package/dist/grok-video.js +10 -3
  72. package/dist/grok-video.js.map +1 -1
  73. package/dist/helpers.cjs +16 -22
  74. package/dist/helpers.cjs.map +1 -1
  75. package/dist/helpers.d.cts.map +1 -1
  76. package/dist/helpers.d.ts.map +1 -1
  77. package/dist/helpers.js +16 -21
  78. package/dist/helpers.js.map +1 -1
  79. package/dist/index.d.cts +3 -3
  80. package/dist/index.d.ts +3 -3
  81. package/dist/llmock.cjs +37 -12
  82. package/dist/llmock.cjs.map +1 -1
  83. package/dist/llmock.d.cts +26 -2
  84. package/dist/llmock.d.cts.map +1 -1
  85. package/dist/llmock.d.ts +26 -2
  86. package/dist/llmock.d.ts.map +1 -1
  87. package/dist/llmock.js +39 -15
  88. package/dist/llmock.js.map +1 -1
  89. package/dist/openrouter-chat.cjs +333 -0
  90. package/dist/openrouter-chat.cjs.map +1 -0
  91. package/dist/openrouter-chat.js +325 -0
  92. package/dist/openrouter-chat.js.map +1 -0
  93. package/dist/openrouter-video.cjs +24 -7
  94. package/dist/openrouter-video.cjs.map +1 -1
  95. package/dist/openrouter-video.d.cts.map +1 -1
  96. package/dist/openrouter-video.d.ts.map +1 -1
  97. package/dist/openrouter-video.js +25 -8
  98. package/dist/openrouter-video.js.map +1 -1
  99. package/dist/provider-auth.cjs +20 -0
  100. package/dist/provider-auth.cjs.map +1 -1
  101. package/dist/provider-auth.js +20 -1
  102. package/dist/provider-auth.js.map +1 -1
  103. package/dist/recorder.cjs +50 -8
  104. package/dist/recorder.cjs.map +1 -1
  105. package/dist/recorder.d.cts.map +1 -1
  106. package/dist/recorder.d.ts.map +1 -1
  107. package/dist/recorder.js +50 -10
  108. package/dist/recorder.js.map +1 -1
  109. package/dist/responses.cjs +200 -89
  110. package/dist/responses.cjs.map +1 -1
  111. package/dist/responses.d.cts +18 -3
  112. package/dist/responses.d.cts.map +1 -1
  113. package/dist/responses.d.ts +18 -3
  114. package/dist/responses.d.ts.map +1 -1
  115. package/dist/responses.js +199 -90
  116. package/dist/responses.js.map +1 -1
  117. package/dist/server.cjs +190 -83
  118. package/dist/server.cjs.map +1 -1
  119. package/dist/server.d.cts +7 -2
  120. package/dist/server.d.cts.map +1 -1
  121. package/dist/server.d.ts +7 -2
  122. package/dist/server.d.ts.map +1 -1
  123. package/dist/server.js +190 -85
  124. package/dist/server.js.map +1 -1
  125. package/dist/sse-writer.cjs +3 -0
  126. package/dist/sse-writer.cjs.map +1 -1
  127. package/dist/sse-writer.d.cts +8 -0
  128. package/dist/sse-writer.d.cts.map +1 -1
  129. package/dist/sse-writer.d.ts +8 -0
  130. package/dist/sse-writer.d.ts.map +1 -1
  131. package/dist/sse-writer.js +3 -0
  132. package/dist/sse-writer.js.map +1 -1
  133. package/dist/stream-collapse.cjs +103 -0
  134. package/dist/stream-collapse.cjs.map +1 -1
  135. package/dist/stream-collapse.d.cts +7 -0
  136. package/dist/stream-collapse.d.cts.map +1 -1
  137. package/dist/stream-collapse.d.ts +7 -0
  138. package/dist/stream-collapse.d.ts.map +1 -1
  139. package/dist/stream-collapse.js +103 -0
  140. package/dist/stream-collapse.js.map +1 -1
  141. package/dist/transcription.cjs +49 -2
  142. package/dist/transcription.cjs.map +1 -1
  143. package/dist/transcription.d.cts.map +1 -1
  144. package/dist/transcription.d.ts.map +1 -1
  145. package/dist/transcription.js +50 -3
  146. package/dist/transcription.js.map +1 -1
  147. package/dist/types.d.cts +187 -5
  148. package/dist/types.d.cts.map +1 -1
  149. package/dist/types.d.ts +187 -5
  150. package/dist/types.d.ts.map +1 -1
  151. package/dist/vector-types.d.ts.map +1 -1
  152. package/dist/veo-video.cjs +9 -2
  153. package/dist/veo-video.cjs.map +1 -1
  154. package/dist/veo-video.js +10 -3
  155. package/dist/veo-video.js.map +1 -1
  156. package/dist/ws-gemini-live.cjs +41 -6
  157. package/dist/ws-gemini-live.cjs.map +1 -1
  158. package/dist/ws-gemini-live.js +41 -6
  159. package/dist/ws-gemini-live.js.map +1 -1
  160. package/dist/ws-realtime.cjs +306 -57
  161. package/dist/ws-realtime.cjs.map +1 -1
  162. package/dist/ws-realtime.d.cts +1 -0
  163. package/dist/ws-realtime.d.cts.map +1 -1
  164. package/dist/ws-realtime.d.ts +1 -0
  165. package/dist/ws-realtime.d.ts.map +1 -1
  166. package/dist/ws-realtime.js +307 -58
  167. package/dist/ws-realtime.js.map +1 -1
  168. package/dist/ws-responses.cjs +11 -6
  169. package/dist/ws-responses.cjs.map +1 -1
  170. package/dist/ws-responses.d.cts.map +1 -1
  171. package/dist/ws-responses.d.ts.map +1 -1
  172. package/dist/ws-responses.js +12 -7
  173. package/dist/ws-responses.js.map +1 -1
  174. package/package.json +3 -2
  175. package/skills/write-fixtures/SKILL.md +53 -19
@@ -283,16 +283,51 @@ async function processMessage(raw, ws, fixtures, journal, defaults, session) {
283
283
  mimeType = audioResp.audio.contentType ?? "audio/mpeg";
284
284
  data = audioResp.audio.b64Json;
285
285
  }
286
- ws.send(JSON.stringify({ serverContent: {
287
- modelTurn: { parts: [{ inlineData: {
288
- mimeType,
289
- data
290
- } }] },
286
+ const parts = [{ inlineData: {
287
+ mimeType,
288
+ data
289
+ } }];
290
+ if (audioResp.content) parts.push({ text: audioResp.content });
291
+ const resolvedToolCalls = (audioResp.toolCalls ?? []).map((tc) => ({
292
+ ...tc,
293
+ resolvedId: tc.id ?? require_helpers.generateToolCallId()
294
+ }));
295
+ if (resolvedToolCalls.length === 0) ws.send(JSON.stringify({ serverContent: {
296
+ modelTurn: { parts },
291
297
  turnComplete: true
292
298
  } }));
299
+ else {
300
+ ws.send(JSON.stringify({ serverContent: { modelTurn: { parts } } }));
301
+ if (!ws.isClosed) {
302
+ const functionCalls = resolvedToolCalls.map((tc) => {
303
+ let argsObj;
304
+ try {
305
+ argsObj = JSON.parse(tc.arguments || "{}");
306
+ } catch {
307
+ defaults.logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
308
+ argsObj = {};
309
+ }
310
+ return {
311
+ name: tc.name,
312
+ args: argsObj,
313
+ id: tc.resolvedId
314
+ };
315
+ });
316
+ ws.send(JSON.stringify({ toolCall: { functionCalls } }));
317
+ }
318
+ if (!ws.isClosed) ws.send(JSON.stringify({ serverContent: { turnComplete: true } }));
319
+ }
293
320
  session.conversationHistory.push({
294
321
  role: "assistant",
295
- content: "[audio]"
322
+ content: audioResp.content ?? "[audio]",
323
+ ...resolvedToolCalls.length > 0 ? { tool_calls: resolvedToolCalls.map((tc) => ({
324
+ id: tc.resolvedId,
325
+ type: "function",
326
+ function: {
327
+ name: tc.name,
328
+ arguments: tc.arguments
329
+ }
330
+ })) } : {}
296
331
  });
297
332
  return;
298
333
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ws-gemini-live.cjs","names":["generateToolCallId","DEFAULT_TEST_ID","matchFixtureDiagnostic","resolveStrictMode","strictNoMatchMessage","strictNoMatchLogLine","flattenHeaders","strictOverrideField","resolveResponse","isErrorResponse","isAudioResponse","formatToMime","isContentWithToolCallsResponse","resolveFixtureBlocks","createInterruptionSignal","calculateDelay","delay","isTextResponse","isToolCallResponse"],"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 — single frame with inlineData and turnComplete: true\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 ws.send(\n JSON.stringify({\n serverContent: {\n modelTurn: {\n parts: [{ inlineData: { mimeType, data } }],\n },\n turnComplete: true,\n },\n }),\n );\n\n session.conversationHistory.push({\n role: \"assistant\",\n content: \"[audio]\",\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":";;;;;;;;AAsGA,MAAM,UAAU;;;;;AAMhB,SAAS,WAAW,UAA0B;AAC5C,SAAQ,UAAR;EACE,KAAK,IACH,QAAO;EACT,KAAK,IACH,QAAO;EACT,KAAK,IACH,QAAO;EACT,KAAK,IACH,QAAO;EACT,KAAK,IACH,QAAO;EACT,KAAK,IACH,QAAO;EACT,KAAK,IACH,QAAO;EACT,KAAK,IACH,QAAO;EACT,QACE,QAAO;;;;;;AAOb,SAAS,sBAAsB,OAAyB,QAAgC;CACtF,MAAM,WAA0B,EAAE;AAElC,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,OAAO,KAAK,QAAQ;AAE1B,MAAI,SAAS,QAAQ;GACnB,MAAM,gBAAgB,KAAK,MAAM,QAAQ,MAAM,EAAE,iBAAiB;GAGlE,MAAM,YAAY,KAAK,MAAM,QAAQ,MAAM,EAAE,SAAS,UAAa,CAAC,EAAE,QAAQ;AAE9E,OAAI,cAAc,SAAS,GAAG;AAC5B,SAAK,IAAI,IAAI,GAAG,IAAI,cAAc,QAAQ,KAAK;KAE7C,MAAM,KADO,cAAc,GACX;AAChB,cAAS,KAAK;MACZ,MAAM;MACN,SAAS,OAAO,GAAG,aAAa,WAAW,GAAG,WAAW,KAAK,UAAU,GAAG,SAAS;MACpF,cAAc,GAAG,MAAMA,oCAAoB;MAC5C,CAAC;;AAEJ,QAAI,UAAU,SAAS,EACrB,UAAS,KAAK;KACZ,MAAM;KACN,SAAS,UAAU,KAAK,MAAM,EAAE,KAAM,CAAC,KAAK,GAAG;KAChD,CAAC;UAEC;IACL,MAAM,OAAO,UAAU,KAAK,MAAM,EAAE,KAAM,CAAC,KAAK,GAAG;AACnD,aAAS,KAAK;KAAE,MAAM;KAAQ,SAAS;KAAM,CAAC;;aAEvC,SAAS,SAAS;GAC3B,MAAM,YAAY,KAAK,MAAM,QAAQ,MAAM,EAAE,aAAa;GAC1D,MAAM,YAAY,KAAK,MAAM,QAAQ,MAAM,EAAE,SAAS,UAAa,CAAC,EAAE,QAAQ;AAE9E,OAAI,UAAU,SAAS,GAAG;IACxB,MAAM,OAAO,UAAU,KAAK,MAAM,EAAE,KAAM,CAAC,KAAK,GAAG;AACnD,aAAS,KAAK;KACZ,MAAM;KACN,SAAS,QAAQ;KACjB,YAAY,UAAU,KAAK,OAAO;MAChC,IAAIA,oCAAoB;MACxB,MAAM;MACN,UAAU;OACR,MAAM,EAAE,aAAc;OACtB,WAAW,KAAK,UAAU,EAAE,aAAc,QAAQ,EAAE,CAAC;OACtD;MACF,EAAE;KACJ,CAAC;UACG;IACL,MAAM,OAAO,UAAU,KAAK,MAAM,EAAE,KAAM,CAAC,KAAK,GAAG;AACnD,aAAS,KAAK;KAAE,MAAM;KAAa,SAAS;KAAM,CAAC;;QAGrD,SAAQ,KAAK,uDAAuD,OAAO;;AAI/E,QAAO;;;;;AAMT,SAAS,uBAAuB,cAAqD;AACnF,QAAO,aAAa,kBAAkB,KAAK,QAAQ;EACjD,MAAM;EACN,SAAS,OAAO,GAAG,aAAa,WAAW,GAAG,WAAW,KAAK,UAAU,GAAG,SAAS;EACpF,cAAc,GAAG,MAAMA,oCAAoB;EAC5C,EAAE;;;;;AAML,SAAS,aAAa,aAAqD;AACzE,KAAI,CAAC,eAAe,YAAY,WAAW,EAAG,QAAO,EAAE;AAEvD,QADc,YAAY,SAAS,MAAM,EAAE,wBAAwB,EAAE,CAAC,CACzD,KAAK,OAAO;EACvB,MAAM;EACN,UAAU;GACR,MAAM,EAAE;GACR,aAAa,EAAE;GACf,YAAY,EAAE;GACf;EACF,EAAE;;AAKL,SAAgB,0BACd,IACA,UACA,SACA,UAWM;CACN,MAAM,EAAE,WAAW;CACnB,MAAM,UAAwB;EAC5B,WAAW;EACX,OAAO,SAAS;EAChB,OAAO,EAAE;EACT,qBAAqB,EAAE;EACxB;CAED,IAAI,UAAU,QAAQ,SAAS;AAC/B,IAAG,GAAG,YAAY,QAAgB;AAChC,YAAU,QAAQ,WAChB,eAAe,KAAK,IAAI,UAAU,SAAS,UAAU,QAAQ,CAAC,OAAO,QAAiB;GACpF,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU;AACjD,UAAO,MAAM,gCAAgC,MAAM;AACnD,OAAI;AACF,OAAG,KACD,KAAK,UAAU,EACb,OAAO;KAAE,MAAM;KAAI,SAAS;KAAK,QAAQ;KAAY,EACtD,CAAC,CACH;YACM,SAAS;AAChB,aAAS,OAAO,MACd,mCAAmC,mBAAmB,QAAQ,QAAQ,UAAU,YACjF;;IAEH,CACH;GACD;;AAGJ,eAAe,eACb,KACA,IACA,UACA,SACA,UAWA,SACe;CACf,IAAI;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,IAAI;UACjB,UAAU;EACjB,MAAM,SAAS,oBAAoB,QAAQ,SAAS,UAAU;AAC9D,KAAG,KACD,KAAK,UAAU,EACb,OAAO;GAAE,MAAM;GAAG,SAAS,mBAAmB;GAAU,QAAQ;GAAoB,EACrF,CAAC,CACH;AACD;;CAIF,MAAM,WAAW,OAAO,SAAS,OAAO;AACxC,KAAI,UAAU;AACZ,UAAQ,YAAY;AACpB,UAAQ,QAAQ,SAAS,SAAS,SAAS;AAC3C,UAAQ,QAAQ,aAAa,SAAS,MAAM;AAC5C,KAAG,KAAK,KAAK,UAAU,EAAE,eAAe,EAAE,EAAE,CAAC,CAAC;AAC9C;;AAIF,KAAI,CAAC,QAAQ,WAAW;AACtB,KAAG,KACD,KAAK,UAAU,EACb,OAAO;GAAE,MAAM;GAAG,SAAS;GAAkB,QAAQ;GAAuB,EAC7E,CAAC,CACH;AACD;;CAIF,IAAI;AAEJ,KAAI,OAAO,eAAe;AACxB,MAAI,CAAC,OAAO,cAAc,SAAS,CAAC,MAAM,QAAQ,OAAO,cAAc,MAAM,EAAE;AAC7E,MAAG,KACD,KAAK,UAAU,EACb,OAAO;IACL,MAAM;IACN,SAAS;IACT,QAAQ;IACT,EACF,CAAC,CACH;AACD;;AAEF,gBAAc,sBAAsB,OAAO,cAAc,OAAO,SAAS,OAAO;YACvE,OAAO,cAAc;AAC9B,MACE,CAAC,OAAO,aAAa,qBACrB,CAAC,MAAM,QAAQ,OAAO,aAAa,kBAAkB,EACrD;AACA,MAAG,KACD,KAAK,UAAU,EACb,OAAO;IACL,MAAM;IACN,SAAS;IACT,QAAQ;IACT,EACF,CAAC,CACH;AACD;;AAEF,gBAAc,uBAAuB,OAAO,aAAa;QACpD;AACL,KAAG,KACD,KAAK,UAAU,EACb,OAAO;GACL,MAAM;GACN,SAAS;GACT,QAAQ;GACT,EACF,CAAC,CACH;AACD;;CAIF,MAAM,sBAAsB,SAAS,iBAAiB;CACtD,MAAM,gBACJ,OAAO,wBAAwB,WAC3B,sBACA,MAAM,QAAQ,oBAAoB,IAAI,oBAAoB,SAAS,IACjE,oBAAoB,KACpB;CAER,MAAM,gBAAuC;EAC3C,OAAO,QAAQ;EACf,UAAU,CAAC,GAAG,QAAQ,qBAAqB,GAAG,YAAY;EAC1D,QAAQ;EACR,OAAO,QAAQ,MAAM,SAAS,IAAI,QAAQ,QAAQ;EAClD,eAAe;EACf,UAAU;EACX;CAED,MAAM,SAAS,SAAS,UAAUC;CAClC,MAAM,EAAE,SAAS,4BAA4BC,sCAC3C,UACA,eACA,QAAQ,6BAA6B,OAAO,EAC5C,SAAS,iBACV;CACD,MAAM,OAAO;AAEb,KAAI,QACF,SAAQ,2BAA2B,SAAS,UAAU,OAAO;AAG/D,KAAI,CAAC,SAAS;AACZ,MAAIC,kCAAkB,SAAS,QAAQ,SAAS,eAAe,EAAE;GAC/D,MAAM,gBAAgBC,qCAAqB,wBAAwB;AACnE,YAAS,OAAO,MAAMC,qCAAqB,MAAM,MAAM,wBAAwB,CAAC;AAChF,WAAQ,IAAI;IACV,QAAQ;IACR;IACA,SAASC,+BAAe,SAAS,kBAAkB,EAAE,CAAC;IACtD,MAAM;IACN,UAAU;KACR,QAAQ;KACR,SAAS;KACT,GAAGC,oCAAoB,SAAS,QAAQ,SAAS,eAAe;KACjE;IACF,CAAC;AACF,MAAG,MAAM,MAAM,cAAc;AAC7B;;AAEF,UAAQ,IAAI;GACV,QAAQ;GACR;GACA,SAASD,+BAAe,SAAS,kBAAkB,EAAE,CAAC;GACtD,MAAM;GACN,UAAU;IACR,QAAQ;IACR,SAAS;IACT,GAAGC,oCAAoB,SAAS,QAAQ,SAAS,eAAe;IACjE;GACF,CAAC;AACF,KAAG,KACD,KAAK,UAAU,EACb,OAAO;GAAE,MAAM;GAAG,SAAS;GAAsB,QAAQ;GAAa,EACvE,CAAC,CACH;AACD;;AAIF,SAAQ,oBAAoB,KAAK,GAAG,YAAY;CAEhD,MAAM,WAAW,MAAMC,gCAAgB,SAAS,cAAc;CAC9D,MAAM,UAAU,QAAQ,WAAW,SAAS;CAC5C,MAAM,YAAY,KAAK,IAAI,GAAG,QAAQ,aAAa,SAAS,UAAU;AAGtE,KAAIC,gCAAgB,SAAS,EAAE;EAC7B,MAAM,SAAS,SAAS,UAAU;AAClC,UAAQ,IAAI;GACV,QAAQ;GACR;GACA,SAASH,+BAAe,SAAS,kBAAkB,EAAE,CAAC;GACtD,MAAM;GACN,UAAU;IAAE;IAAQ;IAAS;GAC9B,CAAC;AACF,KAAG,KACD,KAAK,UAAU,EACb,OAAO;GACL,MAAM,WAAW,OAAO;GACxB,SAAS,SAAS,MAAM;GACxB,QAAQ,SAAS,MAAM,QAAQ;GAChC,EACF,CAAC,CACH;AACD;;AAIF,KAAII,gCAAgB,SAAS,EAAE;AAC7B,UAAQ,IAAI;GACV,QAAQ;GACR;GACA,SAASJ,+BAAe,SAAS,kBAAkB,EAAE,CAAC;GACtD,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;EAEF,MAAM,YAAY;EAClB,IAAI;EACJ,IAAI;AAEJ,MAAI,OAAO,UAAU,UAAU,UAAU;AACvC,cAAWK,6BAAa,UAAU,UAAU,MAAM;AAClD,UAAO,UAAU;SACZ;AACL,cAAW,UAAU,MAAM,eAAe;AAC1C,UAAO,UAAU,MAAM;;AAGzB,KAAG,KACD,KAAK,UAAU,EACb,eAAe;GACb,WAAW,EACT,OAAO,CAAC,EAAE,YAAY;IAAE;IAAU;IAAM,EAAE,CAAC,EAC5C;GACD,cAAc;GACf,EACF,CAAC,CACH;AAED,UAAQ,oBAAoB,KAAK;GAC/B,MAAM;GACN,SAAS;GACV,CAAC;AACF;;AAIF,KAAIC,+CAA+B,SAAS,EAAE;EAC5C,MAAM,eAAe,QAAQ,IAAI;GAC/B,QAAQ;GACR;GACA,SAASN,+BAAe,SAAS,kBAAkB,EAAE,CAAC;GACtD,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;AAWF,MAAI,SAAS,UAAU,SAAS,OAAO,SAAS,GAAG;GACjD,MAAM,iBAAiBO,qCAAqB,SAAS,OAAO;GAC5D,MAAM,eAAeC,8CAAyB,QAAQ;GACtD,MAAM,cAAc,QAAQ,eAAe,SAAS;GACpD,MAAM,EAAE,oBAAoB;GAC5B,IAAI,aAAa;GACjB,IAAI,cAAc;GAIlB,IAAI,iBAAiB;GACrB,MAAM,mBAIA,EAAE;AAER,SAAO,MAAK,MAAM,SAAS,eACzB,KAAI,MAAM,SAAS,YAAY;AAC7B,QAAI,GAAG,SAAU;IACjB,MAAM,UAAUC,kCACd,YACA,QACA,SACA,iBACA,YACD;AACD,QAAI,UAAU,EAAG,OAAMC,yBAAM,SAAS,cAAc,OAAO;AAC3D,QAAI,cAAc,OAAO,SAAS;AAChC,mBAAc;AACd;;AAEF,QAAI,GAAG,SAAU;IAEjB,MAAM,aAAa,MAAM,MAAMhB,oCAAoB;IACnD,IAAI;AACJ,QAAI;AACF,eAAU,KAAK,MAAM,MAAM,aAAa,KAAK;YACvC;AACN,cAAS,OAAO,KACd,sDAAsD,MAAM,KAAK,KAAK,MAAM,YAC7E;AACD,eAAU,EAAE;;AAGd,QAAI;AACF,QAAG,KACD,KAAK,UAAU,EACb,UAAU,EAAE,eAAe,CAAC;MAAE,MAAM,MAAM;MAAM,MAAM;MAAS,IAAI;MAAY,CAAC,EAAE,EACnF,CAAC,CACH;aACM,KAAK;AACZ,cAAS,OAAO,MACd,8DACA,IACD;AACD;;AAEF;AACA,qBAAiB,KAAK;KACpB,IAAI;KACJ,MAAM;KACN,UAAU;MAAE,MAAM,MAAM;MAAM,WAAW,MAAM;MAAW;KAC3D,CAAC;AACF,kBAAc,MAAM;AACpB,QAAI,cAAc,OAAO,SAAS;AAChC,mBAAc;AACd;;UAEG;IACL,MAAM,OAAO,MAAM;AACnB,sBAAkB;AAClB,QAAI,KAAK,WAAW,EAQlB;AAEF,SAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,WAAW;AAC/C,SAAI,GAAG,SAAU,OAAM;KACvB,MAAM,aAAae,kCACjB,YACA,QACA,SACA,iBACA,YACD;AACD,SAAI,aAAa,EAAG,OAAMC,yBAAM,YAAY,cAAc,OAAO;AACjE,SAAI,cAAc,OAAO,SAAS;AAChC,oBAAc;AACd,YAAM;;AAER,SAAI,GAAG,SAAU,OAAM;AACvB,SAAI;AACF,SAAG,KACD,KAAK,UAAU,EACb,eAAe,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC,EAAE,EAAE,EAClF,CAAC,CACH;cACM,KAAK;AACZ,eAAS,OAAO,MACd,8DACA,IACD;AACD,YAAM;;AAER;AACA,mBAAc,MAAM;AACpB,SAAI,cAAc,OAAO,SAAS;AAChC,oBAAc;AACd,YAAM;;;;AAMd,OAAI,aAAa;AACf,OAAG,SAAS;AACZ,iBAAa,SAAS,cAAc;AACpC,iBAAa,SAAS,kBAAkB,cAAc,QAAQ;AAC9D,kBAAc,SAAS;AACvB;;AAGF,iBAAc,SAAS;AAGvB,OAAI,CAAC,GAAG,SACN,IAAG,KAAK,KAAK,UAAU,EAAE,eAAe,EAAE,cAAc,MAAM,EAAE,CAAC,CAAC;AAGpE,WAAQ,oBAAoB,KAAK;IAC/B,MAAM;IACN,SAAS,kBAAkB;IAC3B,GAAI,iBAAiB,SAAS,IAAI,EAAE,YAAY,kBAAkB,GAAG,EAAE;IACxE,CAAC;AACF;;EAGF,MAAM,UAAU,SAAS,WAAW;EACpC,MAAM,YAAsB,EAAE;AAC9B,OAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,UACvC,WAAU,KAAK,QAAQ,MAAM,GAAG,IAAI,UAAU,CAAC;EAGjD,MAAM,eAAeF,8CAAyB,QAAQ;EACtD,MAAM,cAAc,QAAQ,eAAe,SAAS;EACpD,MAAM,EAAE,oBAAoB;EAC5B,IAAI,cAAc;AAGlB,MAAI,QAAQ,WAAW,GACrB;OAAI,CAAC,GAAG,SACN,IAAG,KACD,KAAK,UAAU,EACb,eAAe,EACb,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC,EAAE,EACrC,EACF,CAAC,CACH;QAGH,MAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;AACzC,OAAI,GAAG,SAAU;GACjB,MAAM,aAAaC,kCAAe,GAAG,QAAW,SAAS,iBAAiB,YAAY;AACtF,OAAI,aAAa,EAAG,OAAMC,yBAAM,YAAY,cAAc,OAAO;AACjE,OAAI,cAAc,OAAO,SAAS;AAChC,kBAAc;AACd;;AAEF,OAAI,GAAG,SAAU;AAEjB,OAAI;AACF,OAAG,KACD,KAAK,UAAU,EACb,eAAe,EACb,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,UAAU,IAAI,CAAC,EAAE,EAC/C,EACF,CAAC,CACH;YACM,KAAK;AACZ,aAAS,OAAO,MAAM,4DAA4D,IAAI;AACtF;;AAEF,iBAAc,MAAM;AACpB,OAAI,cAAc,OAAO,SAAS;AAChC,kBAAc;AACd;;;AAKN,MAAI,aAAa;AACf,MAAG,SAAS;AACZ,gBAAa,SAAS,cAAc;AACpC,gBAAa,SAAS,kBAAkB,cAAc,QAAQ;AAC9D,iBAAc,SAAS;AACvB;;EAIF,MAAM,qBAAqB,SAAS,aAAa,EAAE,EAAE,KAAK,QAAQ;GAChE,GAAG;GACH,YAAY,GAAG,MAAMhB,oCAAoB;GAC1C,EAAE;AAGH,MAAI,CAAC,GAAG,UAAU;GAChB,MAAM,UAAUe,kCACd,UAAU,QACV,QACA,SACA,iBACA,YACD;AACD,OAAI,UAAU,EAAG,OAAMC,yBAAM,SAAS,cAAc,OAAO;AAC3D,OAAI,cAAc,OAAO,SAAS;AAChC,OAAG,SAAS;AACZ,iBAAa,SAAS,cAAc;AACpC,iBAAa,SAAS,kBAAkB,cAAc,QAAQ;AAC9D,kBAAc,SAAS;AACvB;;GAGF,MAAM,gBAAgB,kBAAkB,KAAK,OAAO;IAClD,IAAI;AACJ,QAAI;AACF,eAAU,KAAK,MAAM,GAAG,aAAa,KAAK;YACpC;AACN,cAAS,OAAO,KACd,sDAAsD,GAAG,KAAK,KAAK,GAAG,YACvE;AACD,eAAU,EAAE;;AAEd,WAAO;KACL,MAAM,GAAG;KACT,MAAM;KACN,IAAI,GAAG;KACR;KACD;AAEF,MAAG,KAAK,KAAK,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC,CAAC;AACxD,iBAAc,MAAM;;AAGtB,MAAI,cAAc,OAAO,SAAS;AAChC,MAAG,SAAS;AACZ,gBAAa,SAAS,cAAc;AACpC,gBAAa,SAAS,kBAAkB,cAAc,QAAQ;AAC9D,iBAAc,SAAS;AACvB;;AAGF,gBAAc,SAAS;AAGvB,MAAI,CAAC,GAAG,SACN,IAAG,KACD,KAAK,UAAU,EACb,eAAe,EAAE,cAAc,MAAM,EACtC,CAAC,CACH;AAIH,UAAQ,oBAAoB,KAAK;GAC/B,MAAM;GACN,SAAS,WAAW;GACpB,YAAY,kBAAkB,KAAK,QAAQ;IACzC,IAAI,GAAG;IACP,MAAM;IACN,UAAU;KACR,MAAM,GAAG;KACT,WAAW,GAAG;KACf;IACF,EAAE;GACJ,CAAC;AACF;;AAIF,KAAIC,+BAAe,SAAS,EAAE;EAC5B,MAAM,eAAe,QAAQ,IAAI;GAC/B,QAAQ;GACR;GACA,SAASX,+BAAe,SAAS,kBAAkB,EAAE,CAAC;GACtD,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;EAEF,MAAM,UAAU,SAAS;AAEzB,MAAI,QAAQ,WAAW,GAAG;AACxB,OAAI,GAAG,SAAU;AAEjB,MAAG,KACD,KAAK,UAAU,EACb,eAAe,EACb,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC,EAAE,EACrC,EACF,CAAC,CACH;AACD,MAAG,KACD,KAAK,UAAU,EACb,eAAe,EAAE,cAAc,MAAM,EACtC,CAAC,CACH;AACD;;EAIF,MAAM,SAAmB,EAAE;AAC3B,OAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,UACvC,QAAO,KAAK,QAAQ,MAAM,GAAG,IAAI,UAAU,CAAC;EAG9C,MAAM,eAAeQ,8CAAyB,QAAQ;EACtD,MAAM,cAAc,QAAQ,eAAe,SAAS;EACpD,MAAM,EAAE,oBAAoB;EAC5B,IAAI,cAAc;AAGlB,OAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,OAAI,GAAG,SAAU;GACjB,MAAM,aAAaC,kCAAe,GAAG,QAAW,SAAS,iBAAiB,YAAY;AACtF,OAAI,aAAa,EAAG,OAAMC,yBAAM,YAAY,cAAc,OAAO;AACjE,OAAI,cAAc,OAAO,SAAS;AAChC,kBAAc;AACd;;AAEF,OAAI,GAAG,SAAU;AAEjB,OAAI;AACF,OAAG,KACD,KAAK,UAAU,EACb,eAAe,EACb,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,OAAO,IAAI,CAAC,EAAE,EAC5C,EACF,CAAC,CACH;YACM,KAAK;AACZ,aAAS,OAAO,MAAM,4DAA4D,IAAI;AACtF;;AAEF,iBAAc,MAAM;AACpB,OAAI,cAAc,OAAO,SAAS;AAChC,kBAAc;AACd;;;AAIJ,MAAI,aAAa;AACf,MAAG,SAAS;AACZ,gBAAa,SAAS,cAAc;AACpC,gBAAa,SAAS,kBAAkB,cAAc,QAAQ;AAC9D,iBAAc,SAAS;AACvB;;AAGF,gBAAc,SAAS;AAGvB,MAAI,CAAC,GAAG,SACN,IAAG,KACD,KAAK,UAAU,EACb,eAAe,EAAE,cAAc,MAAM,EACtC,CAAC,CACH;AAIH,UAAQ,oBAAoB,KAAK;GAAE,MAAM;GAAa;GAAS,CAAC;AAChE;;AAIF,KAAIE,mCAAmB,SAAS,EAAE;EAChC,MAAM,eAAe,QAAQ,IAAI;GAC/B,QAAQ;GACR;GACA,SAASZ,+BAAe,SAAS,kBAAkB,EAAE,CAAC;GACtD,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;EAEF,MAAM,eAAeQ,8CAAyB,QAAQ;EACtD,MAAM,cAAc,QAAQ,eAAe,SAAS;EACpD,MAAM,EAAE,oBAAoB;AAE5B,MAAI,GAAG,UAAU;AACf,iBAAc,SAAS;AACvB;;EAEF,MAAM,UAAUC,kCAAe,GAAG,QAAW,SAAS,iBAAiB,YAAY;AACnF,MAAI,UAAU,EAAG,OAAMC,yBAAM,SAAS,cAAc,OAAO;AAC3D,MAAI,cAAc,OAAO,SAAS;AAChC,MAAG,SAAS;AACZ,gBAAa,SAAS,cAAc;AACpC,gBAAa,SAAS,kBAAkB,cAAc,QAAQ;AAC9D,iBAAc,SAAS;AACvB;;AAEF,MAAI,GAAG,UAAU;AACf,iBAAc,SAAS;AACvB;;EAIF,MAAM,qBAAqB,SAAS,aAAa,EAAE,EAAE,KAAK,QAAQ;GAChE,GAAG;GACH,YAAY,GAAG,MAAMhB,oCAAoB;GAC1C,EAAE;EAEH,MAAM,gBAAgB,kBAAkB,KAAK,OAAO;GAClD,IAAI;AACJ,OAAI;AACF,cAAU,KAAK,MAAM,GAAG,aAAa,KAAK;WACpC;AACN,aAAS,OAAO,KACd,sDAAsD,GAAG,KAAK,KAAK,GAAG,YACvE;AACD,cAAU,EAAE;;AAEd,UAAO;IACL,MAAM,GAAG;IACT,MAAM;IACN,IAAI,GAAG;IACR;IACD;AAEF,KAAG,KAAK,KAAK,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC,CAAC;AACxD,gBAAc,MAAM;AAEpB,MAAI,cAAc,OAAO,SAAS;AAChC,MAAG,SAAS;AACZ,gBAAa,SAAS,cAAc;AACpC,gBAAa,SAAS,kBAAkB,cAAc,QAAQ;AAC9D,iBAAc,SAAS;AACvB;;AAGF,gBAAc,SAAS;AAGvB,MAAI,CAAC,GAAG,SACN,IAAG,KACD,KAAK,UAAU,EACb,eAAe,EAAE,cAAc,MAAM,EACtC,CAAC,CACH;AAIH,UAAQ,oBAAoB,KAAK;GAC/B,MAAM;GACN,SAAS;GACT,YAAY,kBAAkB,KAAK,QAAQ;IACzC,IAAI,GAAG;IACP,MAAM;IACN,UAAU;KACR,MAAM,GAAG;KACT,WAAW,GAAG;KACf;IACF,EAAE;GACJ,CAAC;AACF;;AAIF,SAAQ,IAAI;EACV,QAAQ;EACR;EACA,SAASM,+BAAe,SAAS,kBAAkB,EAAE,CAAC;EACtD,MAAM;EACN,UAAU;GAAE,QAAQ;GAAK;GAAS;EACnC,CAAC;AACF,IAAG,KACD,KAAK,UAAU,EACb,OAAO;EACL,MAAM;EACN,SAAS;EACT,QAAQ;EACT,EACF,CAAC,CACH"}
1
+ {"version":3,"file":"ws-gemini-live.cjs","names":["generateToolCallId","DEFAULT_TEST_ID","matchFixtureDiagnostic","resolveStrictMode","strictNoMatchMessage","strictNoMatchLogLine","flattenHeaders","strictOverrideField","resolveResponse","isErrorResponse","isAudioResponse","formatToMime","isContentWithToolCallsResponse","resolveFixtureBlocks","createInterruptionSignal","calculateDelay","delay","isTextResponse","isToolCallResponse"],"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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"}
@@ -283,16 +283,51 @@ async function processMessage(raw, ws, fixtures, journal, defaults, session) {
283
283
  mimeType = audioResp.audio.contentType ?? "audio/mpeg";
284
284
  data = audioResp.audio.b64Json;
285
285
  }
286
- ws.send(JSON.stringify({ serverContent: {
287
- modelTurn: { parts: [{ inlineData: {
288
- mimeType,
289
- data
290
- } }] },
286
+ const parts = [{ inlineData: {
287
+ mimeType,
288
+ data
289
+ } }];
290
+ if (audioResp.content) parts.push({ text: audioResp.content });
291
+ const resolvedToolCalls = (audioResp.toolCalls ?? []).map((tc) => ({
292
+ ...tc,
293
+ resolvedId: tc.id ?? generateToolCallId()
294
+ }));
295
+ if (resolvedToolCalls.length === 0) ws.send(JSON.stringify({ serverContent: {
296
+ modelTurn: { parts },
291
297
  turnComplete: true
292
298
  } }));
299
+ else {
300
+ ws.send(JSON.stringify({ serverContent: { modelTurn: { parts } } }));
301
+ if (!ws.isClosed) {
302
+ const functionCalls = resolvedToolCalls.map((tc) => {
303
+ let argsObj;
304
+ try {
305
+ argsObj = JSON.parse(tc.arguments || "{}");
306
+ } catch {
307
+ defaults.logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
308
+ argsObj = {};
309
+ }
310
+ return {
311
+ name: tc.name,
312
+ args: argsObj,
313
+ id: tc.resolvedId
314
+ };
315
+ });
316
+ ws.send(JSON.stringify({ toolCall: { functionCalls } }));
317
+ }
318
+ if (!ws.isClosed) ws.send(JSON.stringify({ serverContent: { turnComplete: true } }));
319
+ }
293
320
  session.conversationHistory.push({
294
321
  role: "assistant",
295
- content: "[audio]"
322
+ content: audioResp.content ?? "[audio]",
323
+ ...resolvedToolCalls.length > 0 ? { tool_calls: resolvedToolCalls.map((tc) => ({
324
+ id: tc.resolvedId,
325
+ type: "function",
326
+ function: {
327
+ name: tc.name,
328
+ arguments: tc.arguments
329
+ }
330
+ })) } : {}
296
331
  });
297
332
  return;
298
333
  }
@@ -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 — single frame with inlineData and turnComplete: true\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 ws.send(\n JSON.stringify({\n serverContent: {\n modelTurn: {\n parts: [{ inlineData: { mimeType, data } }],\n },\n turnComplete: true,\n },\n }),\n );\n\n session.conversationHistory.push({\n role: \"assistant\",\n content: \"[audio]\",\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":";;;;;;;;AAsGA,MAAM,UAAU;;;;;AAMhB,SAAS,WAAW,UAA0B;AAC5C,SAAQ,UAAR;EACE,KAAK,IACH,QAAO;EACT,KAAK,IACH,QAAO;EACT,KAAK,IACH,QAAO;EACT,KAAK,IACH,QAAO;EACT,KAAK,IACH,QAAO;EACT,KAAK,IACH,QAAO;EACT,KAAK,IACH,QAAO;EACT,KAAK,IACH,QAAO;EACT,QACE,QAAO;;;;;;AAOb,SAAS,sBAAsB,OAAyB,QAAgC;CACtF,MAAM,WAA0B,EAAE;AAElC,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,OAAO,KAAK,QAAQ;AAE1B,MAAI,SAAS,QAAQ;GACnB,MAAM,gBAAgB,KAAK,MAAM,QAAQ,MAAM,EAAE,iBAAiB;GAGlE,MAAM,YAAY,KAAK,MAAM,QAAQ,MAAM,EAAE,SAAS,UAAa,CAAC,EAAE,QAAQ;AAE9E,OAAI,cAAc,SAAS,GAAG;AAC5B,SAAK,IAAI,IAAI,GAAG,IAAI,cAAc,QAAQ,KAAK;KAE7C,MAAM,KADO,cAAc,GACX;AAChB,cAAS,KAAK;MACZ,MAAM;MACN,SAAS,OAAO,GAAG,aAAa,WAAW,GAAG,WAAW,KAAK,UAAU,GAAG,SAAS;MACpF,cAAc,GAAG,MAAM,oBAAoB;MAC5C,CAAC;;AAEJ,QAAI,UAAU,SAAS,EACrB,UAAS,KAAK;KACZ,MAAM;KACN,SAAS,UAAU,KAAK,MAAM,EAAE,KAAM,CAAC,KAAK,GAAG;KAChD,CAAC;UAEC;IACL,MAAM,OAAO,UAAU,KAAK,MAAM,EAAE,KAAM,CAAC,KAAK,GAAG;AACnD,aAAS,KAAK;KAAE,MAAM;KAAQ,SAAS;KAAM,CAAC;;aAEvC,SAAS,SAAS;GAC3B,MAAM,YAAY,KAAK,MAAM,QAAQ,MAAM,EAAE,aAAa;GAC1D,MAAM,YAAY,KAAK,MAAM,QAAQ,MAAM,EAAE,SAAS,UAAa,CAAC,EAAE,QAAQ;AAE9E,OAAI,UAAU,SAAS,GAAG;IACxB,MAAM,OAAO,UAAU,KAAK,MAAM,EAAE,KAAM,CAAC,KAAK,GAAG;AACnD,aAAS,KAAK;KACZ,MAAM;KACN,SAAS,QAAQ;KACjB,YAAY,UAAU,KAAK,OAAO;MAChC,IAAI,oBAAoB;MACxB,MAAM;MACN,UAAU;OACR,MAAM,EAAE,aAAc;OACtB,WAAW,KAAK,UAAU,EAAE,aAAc,QAAQ,EAAE,CAAC;OACtD;MACF,EAAE;KACJ,CAAC;UACG;IACL,MAAM,OAAO,UAAU,KAAK,MAAM,EAAE,KAAM,CAAC,KAAK,GAAG;AACnD,aAAS,KAAK;KAAE,MAAM;KAAa,SAAS;KAAM,CAAC;;QAGrD,SAAQ,KAAK,uDAAuD,OAAO;;AAI/E,QAAO;;;;;AAMT,SAAS,uBAAuB,cAAqD;AACnF,QAAO,aAAa,kBAAkB,KAAK,QAAQ;EACjD,MAAM;EACN,SAAS,OAAO,GAAG,aAAa,WAAW,GAAG,WAAW,KAAK,UAAU,GAAG,SAAS;EACpF,cAAc,GAAG,MAAM,oBAAoB;EAC5C,EAAE;;;;;AAML,SAAS,aAAa,aAAqD;AACzE,KAAI,CAAC,eAAe,YAAY,WAAW,EAAG,QAAO,EAAE;AAEvD,QADc,YAAY,SAAS,MAAM,EAAE,wBAAwB,EAAE,CAAC,CACzD,KAAK,OAAO;EACvB,MAAM;EACN,UAAU;GACR,MAAM,EAAE;GACR,aAAa,EAAE;GACf,YAAY,EAAE;GACf;EACF,EAAE;;AAKL,SAAgB,0BACd,IACA,UACA,SACA,UAWM;CACN,MAAM,EAAE,WAAW;CACnB,MAAM,UAAwB;EAC5B,WAAW;EACX,OAAO,SAAS;EAChB,OAAO,EAAE;EACT,qBAAqB,EAAE;EACxB;CAED,IAAI,UAAU,QAAQ,SAAS;AAC/B,IAAG,GAAG,YAAY,QAAgB;AAChC,YAAU,QAAQ,WAChB,eAAe,KAAK,IAAI,UAAU,SAAS,UAAU,QAAQ,CAAC,OAAO,QAAiB;GACpF,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU;AACjD,UAAO,MAAM,gCAAgC,MAAM;AACnD,OAAI;AACF,OAAG,KACD,KAAK,UAAU,EACb,OAAO;KAAE,MAAM;KAAI,SAAS;KAAK,QAAQ;KAAY,EACtD,CAAC,CACH;YACM,SAAS;AAChB,aAAS,OAAO,MACd,mCAAmC,mBAAmB,QAAQ,QAAQ,UAAU,YACjF;;IAEH,CACH;GACD;;AAGJ,eAAe,eACb,KACA,IACA,UACA,SACA,UAWA,SACe;CACf,IAAI;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,IAAI;UACjB,UAAU;EACjB,MAAM,SAAS,oBAAoB,QAAQ,SAAS,UAAU;AAC9D,KAAG,KACD,KAAK,UAAU,EACb,OAAO;GAAE,MAAM;GAAG,SAAS,mBAAmB;GAAU,QAAQ;GAAoB,EACrF,CAAC,CACH;AACD;;CAIF,MAAM,WAAW,OAAO,SAAS,OAAO;AACxC,KAAI,UAAU;AACZ,UAAQ,YAAY;AACpB,UAAQ,QAAQ,SAAS,SAAS,SAAS;AAC3C,UAAQ,QAAQ,aAAa,SAAS,MAAM;AAC5C,KAAG,KAAK,KAAK,UAAU,EAAE,eAAe,EAAE,EAAE,CAAC,CAAC;AAC9C;;AAIF,KAAI,CAAC,QAAQ,WAAW;AACtB,KAAG,KACD,KAAK,UAAU,EACb,OAAO;GAAE,MAAM;GAAG,SAAS;GAAkB,QAAQ;GAAuB,EAC7E,CAAC,CACH;AACD;;CAIF,IAAI;AAEJ,KAAI,OAAO,eAAe;AACxB,MAAI,CAAC,OAAO,cAAc,SAAS,CAAC,MAAM,QAAQ,OAAO,cAAc,MAAM,EAAE;AAC7E,MAAG,KACD,KAAK,UAAU,EACb,OAAO;IACL,MAAM;IACN,SAAS;IACT,QAAQ;IACT,EACF,CAAC,CACH;AACD;;AAEF,gBAAc,sBAAsB,OAAO,cAAc,OAAO,SAAS,OAAO;YACvE,OAAO,cAAc;AAC9B,MACE,CAAC,OAAO,aAAa,qBACrB,CAAC,MAAM,QAAQ,OAAO,aAAa,kBAAkB,EACrD;AACA,MAAG,KACD,KAAK,UAAU,EACb,OAAO;IACL,MAAM;IACN,SAAS;IACT,QAAQ;IACT,EACF,CAAC,CACH;AACD;;AAEF,gBAAc,uBAAuB,OAAO,aAAa;QACpD;AACL,KAAG,KACD,KAAK,UAAU,EACb,OAAO;GACL,MAAM;GACN,SAAS;GACT,QAAQ;GACT,EACF,CAAC,CACH;AACD;;CAIF,MAAM,sBAAsB,SAAS,iBAAiB;CACtD,MAAM,gBACJ,OAAO,wBAAwB,WAC3B,sBACA,MAAM,QAAQ,oBAAoB,IAAI,oBAAoB,SAAS,IACjE,oBAAoB,KACpB;CAER,MAAM,gBAAuC;EAC3C,OAAO,QAAQ;EACf,UAAU,CAAC,GAAG,QAAQ,qBAAqB,GAAG,YAAY;EAC1D,QAAQ;EACR,OAAO,QAAQ,MAAM,SAAS,IAAI,QAAQ,QAAQ;EAClD,eAAe;EACf,UAAU;EACX;CAED,MAAM,SAAS,SAAS,UAAU;CAClC,MAAM,EAAE,SAAS,4BAA4B,uBAC3C,UACA,eACA,QAAQ,6BAA6B,OAAO,EAC5C,SAAS,iBACV;CACD,MAAM,OAAO;AAEb,KAAI,QACF,SAAQ,2BAA2B,SAAS,UAAU,OAAO;AAG/D,KAAI,CAAC,SAAS;AACZ,MAAI,kBAAkB,SAAS,QAAQ,SAAS,eAAe,EAAE;GAC/D,MAAM,gBAAgB,qBAAqB,wBAAwB;AACnE,YAAS,OAAO,MAAM,qBAAqB,MAAM,MAAM,wBAAwB,CAAC;AAChF,WAAQ,IAAI;IACV,QAAQ;IACR;IACA,SAAS,eAAe,SAAS,kBAAkB,EAAE,CAAC;IACtD,MAAM;IACN,UAAU;KACR,QAAQ;KACR,SAAS;KACT,GAAG,oBAAoB,SAAS,QAAQ,SAAS,eAAe;KACjE;IACF,CAAC;AACF,MAAG,MAAM,MAAM,cAAc;AAC7B;;AAEF,UAAQ,IAAI;GACV,QAAQ;GACR;GACA,SAAS,eAAe,SAAS,kBAAkB,EAAE,CAAC;GACtD,MAAM;GACN,UAAU;IACR,QAAQ;IACR,SAAS;IACT,GAAG,oBAAoB,SAAS,QAAQ,SAAS,eAAe;IACjE;GACF,CAAC;AACF,KAAG,KACD,KAAK,UAAU,EACb,OAAO;GAAE,MAAM;GAAG,SAAS;GAAsB,QAAQ;GAAa,EACvE,CAAC,CACH;AACD;;AAIF,SAAQ,oBAAoB,KAAK,GAAG,YAAY;CAEhD,MAAM,WAAW,MAAM,gBAAgB,SAAS,cAAc;CAC9D,MAAM,UAAU,QAAQ,WAAW,SAAS;CAC5C,MAAM,YAAY,KAAK,IAAI,GAAG,QAAQ,aAAa,SAAS,UAAU;AAGtE,KAAI,gBAAgB,SAAS,EAAE;EAC7B,MAAM,SAAS,SAAS,UAAU;AAClC,UAAQ,IAAI;GACV,QAAQ;GACR;GACA,SAAS,eAAe,SAAS,kBAAkB,EAAE,CAAC;GACtD,MAAM;GACN,UAAU;IAAE;IAAQ;IAAS;GAC9B,CAAC;AACF,KAAG,KACD,KAAK,UAAU,EACb,OAAO;GACL,MAAM,WAAW,OAAO;GACxB,SAAS,SAAS,MAAM;GACxB,QAAQ,SAAS,MAAM,QAAQ;GAChC,EACF,CAAC,CACH;AACD;;AAIF,KAAI,gBAAgB,SAAS,EAAE;AAC7B,UAAQ,IAAI;GACV,QAAQ;GACR;GACA,SAAS,eAAe,SAAS,kBAAkB,EAAE,CAAC;GACtD,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;EAEF,MAAM,YAAY;EAClB,IAAI;EACJ,IAAI;AAEJ,MAAI,OAAO,UAAU,UAAU,UAAU;AACvC,cAAW,aAAa,UAAU,UAAU,MAAM;AAClD,UAAO,UAAU;SACZ;AACL,cAAW,UAAU,MAAM,eAAe;AAC1C,UAAO,UAAU,MAAM;;AAGzB,KAAG,KACD,KAAK,UAAU,EACb,eAAe;GACb,WAAW,EACT,OAAO,CAAC,EAAE,YAAY;IAAE;IAAU;IAAM,EAAE,CAAC,EAC5C;GACD,cAAc;GACf,EACF,CAAC,CACH;AAED,UAAQ,oBAAoB,KAAK;GAC/B,MAAM;GACN,SAAS;GACV,CAAC;AACF;;AAIF,KAAI,+BAA+B,SAAS,EAAE;EAC5C,MAAM,eAAe,QAAQ,IAAI;GAC/B,QAAQ;GACR;GACA,SAAS,eAAe,SAAS,kBAAkB,EAAE,CAAC;GACtD,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;AAWF,MAAI,SAAS,UAAU,SAAS,OAAO,SAAS,GAAG;GACjD,MAAM,iBAAiB,qBAAqB,SAAS,OAAO;GAC5D,MAAM,eAAe,yBAAyB,QAAQ;GACtD,MAAM,cAAc,QAAQ,eAAe,SAAS;GACpD,MAAM,EAAE,oBAAoB;GAC5B,IAAI,aAAa;GACjB,IAAI,cAAc;GAIlB,IAAI,iBAAiB;GACrB,MAAM,mBAIA,EAAE;AAER,SAAO,MAAK,MAAM,SAAS,eACzB,KAAI,MAAM,SAAS,YAAY;AAC7B,QAAI,GAAG,SAAU;IACjB,MAAM,UAAU,eACd,YACA,QACA,SACA,iBACA,YACD;AACD,QAAI,UAAU,EAAG,OAAM,MAAM,SAAS,cAAc,OAAO;AAC3D,QAAI,cAAc,OAAO,SAAS;AAChC,mBAAc;AACd;;AAEF,QAAI,GAAG,SAAU;IAEjB,MAAM,aAAa,MAAM,MAAM,oBAAoB;IACnD,IAAI;AACJ,QAAI;AACF,eAAU,KAAK,MAAM,MAAM,aAAa,KAAK;YACvC;AACN,cAAS,OAAO,KACd,sDAAsD,MAAM,KAAK,KAAK,MAAM,YAC7E;AACD,eAAU,EAAE;;AAGd,QAAI;AACF,QAAG,KACD,KAAK,UAAU,EACb,UAAU,EAAE,eAAe,CAAC;MAAE,MAAM,MAAM;MAAM,MAAM;MAAS,IAAI;MAAY,CAAC,EAAE,EACnF,CAAC,CACH;aACM,KAAK;AACZ,cAAS,OAAO,MACd,8DACA,IACD;AACD;;AAEF;AACA,qBAAiB,KAAK;KACpB,IAAI;KACJ,MAAM;KACN,UAAU;MAAE,MAAM,MAAM;MAAM,WAAW,MAAM;MAAW;KAC3D,CAAC;AACF,kBAAc,MAAM;AACpB,QAAI,cAAc,OAAO,SAAS;AAChC,mBAAc;AACd;;UAEG;IACL,MAAM,OAAO,MAAM;AACnB,sBAAkB;AAClB,QAAI,KAAK,WAAW,EAQlB;AAEF,SAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,WAAW;AAC/C,SAAI,GAAG,SAAU,OAAM;KACvB,MAAM,aAAa,eACjB,YACA,QACA,SACA,iBACA,YACD;AACD,SAAI,aAAa,EAAG,OAAM,MAAM,YAAY,cAAc,OAAO;AACjE,SAAI,cAAc,OAAO,SAAS;AAChC,oBAAc;AACd,YAAM;;AAER,SAAI,GAAG,SAAU,OAAM;AACvB,SAAI;AACF,SAAG,KACD,KAAK,UAAU,EACb,eAAe,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC,EAAE,EAAE,EAClF,CAAC,CACH;cACM,KAAK;AACZ,eAAS,OAAO,MACd,8DACA,IACD;AACD,YAAM;;AAER;AACA,mBAAc,MAAM;AACpB,SAAI,cAAc,OAAO,SAAS;AAChC,oBAAc;AACd,YAAM;;;;AAMd,OAAI,aAAa;AACf,OAAG,SAAS;AACZ,iBAAa,SAAS,cAAc;AACpC,iBAAa,SAAS,kBAAkB,cAAc,QAAQ;AAC9D,kBAAc,SAAS;AACvB;;AAGF,iBAAc,SAAS;AAGvB,OAAI,CAAC,GAAG,SACN,IAAG,KAAK,KAAK,UAAU,EAAE,eAAe,EAAE,cAAc,MAAM,EAAE,CAAC,CAAC;AAGpE,WAAQ,oBAAoB,KAAK;IAC/B,MAAM;IACN,SAAS,kBAAkB;IAC3B,GAAI,iBAAiB,SAAS,IAAI,EAAE,YAAY,kBAAkB,GAAG,EAAE;IACxE,CAAC;AACF;;EAGF,MAAM,UAAU,SAAS,WAAW;EACpC,MAAM,YAAsB,EAAE;AAC9B,OAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,UACvC,WAAU,KAAK,QAAQ,MAAM,GAAG,IAAI,UAAU,CAAC;EAGjD,MAAM,eAAe,yBAAyB,QAAQ;EACtD,MAAM,cAAc,QAAQ,eAAe,SAAS;EACpD,MAAM,EAAE,oBAAoB;EAC5B,IAAI,cAAc;AAGlB,MAAI,QAAQ,WAAW,GACrB;OAAI,CAAC,GAAG,SACN,IAAG,KACD,KAAK,UAAU,EACb,eAAe,EACb,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC,EAAE,EACrC,EACF,CAAC,CACH;QAGH,MAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;AACzC,OAAI,GAAG,SAAU;GACjB,MAAM,aAAa,eAAe,GAAG,QAAW,SAAS,iBAAiB,YAAY;AACtF,OAAI,aAAa,EAAG,OAAM,MAAM,YAAY,cAAc,OAAO;AACjE,OAAI,cAAc,OAAO,SAAS;AAChC,kBAAc;AACd;;AAEF,OAAI,GAAG,SAAU;AAEjB,OAAI;AACF,OAAG,KACD,KAAK,UAAU,EACb,eAAe,EACb,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,UAAU,IAAI,CAAC,EAAE,EAC/C,EACF,CAAC,CACH;YACM,KAAK;AACZ,aAAS,OAAO,MAAM,4DAA4D,IAAI;AACtF;;AAEF,iBAAc,MAAM;AACpB,OAAI,cAAc,OAAO,SAAS;AAChC,kBAAc;AACd;;;AAKN,MAAI,aAAa;AACf,MAAG,SAAS;AACZ,gBAAa,SAAS,cAAc;AACpC,gBAAa,SAAS,kBAAkB,cAAc,QAAQ;AAC9D,iBAAc,SAAS;AACvB;;EAIF,MAAM,qBAAqB,SAAS,aAAa,EAAE,EAAE,KAAK,QAAQ;GAChE,GAAG;GACH,YAAY,GAAG,MAAM,oBAAoB;GAC1C,EAAE;AAGH,MAAI,CAAC,GAAG,UAAU;GAChB,MAAM,UAAU,eACd,UAAU,QACV,QACA,SACA,iBACA,YACD;AACD,OAAI,UAAU,EAAG,OAAM,MAAM,SAAS,cAAc,OAAO;AAC3D,OAAI,cAAc,OAAO,SAAS;AAChC,OAAG,SAAS;AACZ,iBAAa,SAAS,cAAc;AACpC,iBAAa,SAAS,kBAAkB,cAAc,QAAQ;AAC9D,kBAAc,SAAS;AACvB;;GAGF,MAAM,gBAAgB,kBAAkB,KAAK,OAAO;IAClD,IAAI;AACJ,QAAI;AACF,eAAU,KAAK,MAAM,GAAG,aAAa,KAAK;YACpC;AACN,cAAS,OAAO,KACd,sDAAsD,GAAG,KAAK,KAAK,GAAG,YACvE;AACD,eAAU,EAAE;;AAEd,WAAO;KACL,MAAM,GAAG;KACT,MAAM;KACN,IAAI,GAAG;KACR;KACD;AAEF,MAAG,KAAK,KAAK,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC,CAAC;AACxD,iBAAc,MAAM;;AAGtB,MAAI,cAAc,OAAO,SAAS;AAChC,MAAG,SAAS;AACZ,gBAAa,SAAS,cAAc;AACpC,gBAAa,SAAS,kBAAkB,cAAc,QAAQ;AAC9D,iBAAc,SAAS;AACvB;;AAGF,gBAAc,SAAS;AAGvB,MAAI,CAAC,GAAG,SACN,IAAG,KACD,KAAK,UAAU,EACb,eAAe,EAAE,cAAc,MAAM,EACtC,CAAC,CACH;AAIH,UAAQ,oBAAoB,KAAK;GAC/B,MAAM;GACN,SAAS,WAAW;GACpB,YAAY,kBAAkB,KAAK,QAAQ;IACzC,IAAI,GAAG;IACP,MAAM;IACN,UAAU;KACR,MAAM,GAAG;KACT,WAAW,GAAG;KACf;IACF,EAAE;GACJ,CAAC;AACF;;AAIF,KAAI,eAAe,SAAS,EAAE;EAC5B,MAAM,eAAe,QAAQ,IAAI;GAC/B,QAAQ;GACR;GACA,SAAS,eAAe,SAAS,kBAAkB,EAAE,CAAC;GACtD,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;EAEF,MAAM,UAAU,SAAS;AAEzB,MAAI,QAAQ,WAAW,GAAG;AACxB,OAAI,GAAG,SAAU;AAEjB,MAAG,KACD,KAAK,UAAU,EACb,eAAe,EACb,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC,EAAE,EACrC,EACF,CAAC,CACH;AACD,MAAG,KACD,KAAK,UAAU,EACb,eAAe,EAAE,cAAc,MAAM,EACtC,CAAC,CACH;AACD;;EAIF,MAAM,SAAmB,EAAE;AAC3B,OAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,UACvC,QAAO,KAAK,QAAQ,MAAM,GAAG,IAAI,UAAU,CAAC;EAG9C,MAAM,eAAe,yBAAyB,QAAQ;EACtD,MAAM,cAAc,QAAQ,eAAe,SAAS;EACpD,MAAM,EAAE,oBAAoB;EAC5B,IAAI,cAAc;AAGlB,OAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,OAAI,GAAG,SAAU;GACjB,MAAM,aAAa,eAAe,GAAG,QAAW,SAAS,iBAAiB,YAAY;AACtF,OAAI,aAAa,EAAG,OAAM,MAAM,YAAY,cAAc,OAAO;AACjE,OAAI,cAAc,OAAO,SAAS;AAChC,kBAAc;AACd;;AAEF,OAAI,GAAG,SAAU;AAEjB,OAAI;AACF,OAAG,KACD,KAAK,UAAU,EACb,eAAe,EACb,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,OAAO,IAAI,CAAC,EAAE,EAC5C,EACF,CAAC,CACH;YACM,KAAK;AACZ,aAAS,OAAO,MAAM,4DAA4D,IAAI;AACtF;;AAEF,iBAAc,MAAM;AACpB,OAAI,cAAc,OAAO,SAAS;AAChC,kBAAc;AACd;;;AAIJ,MAAI,aAAa;AACf,MAAG,SAAS;AACZ,gBAAa,SAAS,cAAc;AACpC,gBAAa,SAAS,kBAAkB,cAAc,QAAQ;AAC9D,iBAAc,SAAS;AACvB;;AAGF,gBAAc,SAAS;AAGvB,MAAI,CAAC,GAAG,SACN,IAAG,KACD,KAAK,UAAU,EACb,eAAe,EAAE,cAAc,MAAM,EACtC,CAAC,CACH;AAIH,UAAQ,oBAAoB,KAAK;GAAE,MAAM;GAAa;GAAS,CAAC;AAChE;;AAIF,KAAI,mBAAmB,SAAS,EAAE;EAChC,MAAM,eAAe,QAAQ,IAAI;GAC/B,QAAQ;GACR;GACA,SAAS,eAAe,SAAS,kBAAkB,EAAE,CAAC;GACtD,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;EAEF,MAAM,eAAe,yBAAyB,QAAQ;EACtD,MAAM,cAAc,QAAQ,eAAe,SAAS;EACpD,MAAM,EAAE,oBAAoB;AAE5B,MAAI,GAAG,UAAU;AACf,iBAAc,SAAS;AACvB;;EAEF,MAAM,UAAU,eAAe,GAAG,QAAW,SAAS,iBAAiB,YAAY;AACnF,MAAI,UAAU,EAAG,OAAM,MAAM,SAAS,cAAc,OAAO;AAC3D,MAAI,cAAc,OAAO,SAAS;AAChC,MAAG,SAAS;AACZ,gBAAa,SAAS,cAAc;AACpC,gBAAa,SAAS,kBAAkB,cAAc,QAAQ;AAC9D,iBAAc,SAAS;AACvB;;AAEF,MAAI,GAAG,UAAU;AACf,iBAAc,SAAS;AACvB;;EAIF,MAAM,qBAAqB,SAAS,aAAa,EAAE,EAAE,KAAK,QAAQ;GAChE,GAAG;GACH,YAAY,GAAG,MAAM,oBAAoB;GAC1C,EAAE;EAEH,MAAM,gBAAgB,kBAAkB,KAAK,OAAO;GAClD,IAAI;AACJ,OAAI;AACF,cAAU,KAAK,MAAM,GAAG,aAAa,KAAK;WACpC;AACN,aAAS,OAAO,KACd,sDAAsD,GAAG,KAAK,KAAK,GAAG,YACvE;AACD,cAAU,EAAE;;AAEd,UAAO;IACL,MAAM,GAAG;IACT,MAAM;IACN,IAAI,GAAG;IACR;IACD;AAEF,KAAG,KAAK,KAAK,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC,CAAC;AACxD,gBAAc,MAAM;AAEpB,MAAI,cAAc,OAAO,SAAS;AAChC,MAAG,SAAS;AACZ,gBAAa,SAAS,cAAc;AACpC,gBAAa,SAAS,kBAAkB,cAAc,QAAQ;AAC9D,iBAAc,SAAS;AACvB;;AAGF,gBAAc,SAAS;AAGvB,MAAI,CAAC,GAAG,SACN,IAAG,KACD,KAAK,UAAU,EACb,eAAe,EAAE,cAAc,MAAM,EACtC,CAAC,CACH;AAIH,UAAQ,oBAAoB,KAAK;GAC/B,MAAM;GACN,SAAS;GACT,YAAY,kBAAkB,KAAK,QAAQ;IACzC,IAAI,GAAG;IACP,MAAM;IACN,UAAU;KACR,MAAM,GAAG;KACT,WAAW,GAAG;KACf;IACF,EAAE;GACJ,CAAC;AACF;;AAIF,SAAQ,IAAI;EACV,QAAQ;EACR;EACA,SAAS,eAAe,SAAS,kBAAkB,EAAE,CAAC;EACtD,MAAM;EACN,UAAU;GAAE,QAAQ;GAAK;GAAS;EACnC,CAAC;AACF,IAAG,KACD,KAAK,UAAU,EACb,OAAO;EACL,MAAM;EACN,SAAS;EACT,QAAQ;EACT,EACF,CAAC,CACH"}
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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"}