{"version":3,"file":"ws-gemini-live.cjs","names":["generateToolCallId","DEFAULT_TEST_ID","matchFixtureDiagnostic","resolveStrictMode","strictNoMatchMessage","strictNoMatchLogLine","flattenHeaders","strictOverrideField","resolveResponse","isErrorResponse","isAudioResponse","formatToMime","isContentWithToolCallsResponse","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  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    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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"}