{"version":3,"file":"stream-collapse.cjs","names":["isHarmonyContent","parseHarmonyContent"],"sources":["../src/stream-collapse.ts"],"sourcesContent":["/**\n * Stream collapsing functions for record-and-replay.\n *\n * Each function takes a raw streaming response body (SSE, NDJSON, or binary\n * EventStream) and collapses it into a non-streaming fixture response\n * containing `{ content }`, `{ toolCalls }`, or both when the stream includes\n * text followed by tool calls.\n */\n\nimport { crc32 } from \"node:zlib\";\nimport type { RecordProviderKey, ToolCall } from \"./types.js\";\nimport type { Logger } from \"./logger.js\";\nimport { isHarmonyContent, parseHarmonyContent } from \"./harmony.js\";\n\n// ---------------------------------------------------------------------------\n// Result type shared by all collapse functions\n// ---------------------------------------------------------------------------\n\nexport interface CollapseResult {\n  content?: string;\n  reasoning?: string;\n  /**\n   * The real cryptographic `signature` value captured from an Anthropic\n   * `signature_delta`. Carried so a recorded real-provider thinking turn can\n   * replay its ACTUAL signature instead of aimock's placeholder. Absent when the\n   * stream carried no signature. Single-signature assumption: a turn with\n   * MULTIPLE thinking blocks collapses to one merged `reasoning` string carrying\n   * only the FINAL block's signature (last-signature-wins) — per-block fidelity\n   * is not preserved. The recorder persists this only alongside a non-empty\n   * `reasoning` (a bare signature has nothing to attach to on replay); see\n   * `TextResponse.reasoningSignature` in types.ts.\n   */\n  reasoningSignature?: string;\n  /**\n   * The opaque `data` payload(s) of any Anthropic `redacted_thinking` blocks, in\n   * stream order. Captured so a recorded redacted-thinking turn round-trips its\n   * encrypted reasoning faithfully. Absent when none present.\n   */\n  redactedThinking?: string[];\n  webSearches?: string[];\n  toolCalls?: ToolCall[];\n  droppedChunks?: number;\n  firstDroppedSample?: string;\n  truncated?: boolean;\n  audioB64?: string;\n  audioMimeType?: string;\n  /**\n   * Set when harmony channel tokens were present in the accumulated content but\n   * could NOT be parsed into a complete, valid harmony structure. The content\n   * is preserved VERBATIM, so this is NOT transport loss — it is distinct from\n   * `droppedChunks` / `truncated`, which are reserved for genuine transport loss\n   * (malformed SSE/NDJSON frames, CRC mismatch). The caller surfaces this as a\n   * dedicated warning rather than a dropped/truncated-chunk warning.\n   */\n  harmonyUnparsed?: true;\n  /** Short human-readable note accompanying {@link harmonyUnparsed}. */\n  harmonyNote?: string;\n}\n\n/**\n * The opaque `data` of a non-empty Anthropic `redacted_thinking` block, or\n * `undefined` if `block` is not a redacted_thinking block or carries empty/no\n * data. NON-EMPTY is required: the replay-side validator rejects a leading\n * empty-data redacted_thinking block, so recording `data: \"\"` would yield a\n * fixture that 400s under strict replay. Shared by every capture site (SSE,\n * Anthropic-native binary, non-streaming recorder) so the rule stays in one\n * place.\n */\nexport function capturedRedactedData(\n  block: Record<string, unknown> | undefined,\n): string | undefined {\n  if (\n    block?.type === \"redacted_thinking\" &&\n    typeof block.data === \"string\" &&\n    block.data.length > 0\n  ) {\n    return block.data;\n  }\n  return undefined;\n}\n\n/**\n * Slice the first `max` UTF-16 code units of `s` for a diagnostic sample,\n * trimming a trailing lone high-surrogate so the resulting sample never ends on\n * a lone high surrogate (i.e. never mid-surrogate-pair).\n */\nfunction surrogateSafeSlice(s: string, max: number): string {\n  let out = s.slice(0, max);\n  if (out.length > 0) {\n    const last = out.charCodeAt(out.length - 1);\n    // A high surrogate (U+D800..U+DBFF) at the end is the lead of a split pair.\n    if (last >= 0xd800 && last <= 0xdbff) {\n      out = out.slice(0, -1);\n    }\n  }\n  return out;\n}\n\n/**\n * Split a raw SSE body into per-event blocks.\n *\n * Events are delimited by a blank line. Real HTTP/SSE transports use CRLF\n * (`\\r\\n`) line endings, so the inter-event delimiter is `\\r\\n\\r\\n` (which\n * contains no `\\n\\n` substring) and each line ends with a trailing `\\r`.\n * Splitting on `/\\r?\\n\\r?\\n/` handles LF, CRLF, and mixed streams; per-line\n * `\\r` trimming happens in {@link splitSSELines}. Blank blocks are dropped.\n */\nfunction splitSSEEvents(body: string): string[] {\n  return body.split(/\\r?\\n\\r?\\n/).filter((block) => block.trim().length > 0);\n}\n\n/**\n * Split a single SSE event block into its lines, trimming a trailing `\\r` so\n * CRLF streams parse identically to LF streams.\n */\nfunction splitSSELines(block: string): string[] {\n  return block.split(\"\\n\").map((line) => (line.endsWith(\"\\r\") ? line.slice(0, -1) : line));\n}\n\n/**\n * Extract the SSE `data` field from a single event block's lines.\n *\n * Per the SSE spec a single event may carry MULTIPLE `data:` lines; the field\n * value is every data line's content joined with \"\\n\". Collecting only the\n * first `data:` line (e.g. via `.find`) corrupts payloads that a server split\n * across lines. Callers MUST pass lines produced by {@link splitSSELines} so\n * any trailing `\\r` is already stripped. Returns the joined payload (with the\n * leading \"data:\" prefix and one optional leading space stripped per line), or\n * `undefined` when the block contains no `data:` line.\n */\nfunction extractSSEData(lines: string[]): string | undefined {\n  const dataParts: string[] = [];\n  for (const line of lines) {\n    if (!line.startsWith(\"data:\")) continue;\n    // Strip \"data:\" then a single optional leading space, per the SSE spec.\n    let part = line.slice(5);\n    if (part.startsWith(\" \")) part = part.slice(1);\n    dataParts.push(part);\n  }\n  if (dataParts.length === 0) return undefined;\n  return dataParts.join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// 1. OpenAI SSE\n// ---------------------------------------------------------------------------\n\n/**\n * Collapse OpenAI Chat Completions SSE stream into a single response.\n *\n * Format:\n *   data: {\"id\":\"chatcmpl-123\",\"choices\":[{\"delta\":{\"content\":\"Hello\"}}]}\\n\\n\n *   data: [DONE]\\n\\n\n */\nexport function collapseOpenAISSE(body: string): CollapseResult {\n  const lines = splitSSEEvents(body);\n  let content = \"\";\n  let reasoning = \"\";\n  const webSearchQueries: string[] = [];\n  let droppedChunks = 0;\n  let firstDroppedSample: string | undefined;\n  let harmonyUnparsed = false;\n  let harmonyNote: string | undefined;\n  const toolCallMap = new Map<number, { id: string; name: string; arguments: string }>();\n  // Fallback keying for deltas that OMIT `index`. Without this, every\n  // index-less delta collapses under one `undefined`/NaN key, merging distinct\n  // tool calls and corrupting arguments. Index-less fragments that share an\n  // `id` correlate via `idKeyMap`; otherwise each gets a fresh synthetic key\n  // assigned from a counter kept above any real index so sort order is stable.\n  // The 1_000_000 sentinel assumes real provider tool-call indices stay below\n  // it (they are small per-stream counters), so synthetic keys never collide.\n  let nextSyntheticIndex = 1_000_000;\n  const idKeyMap = new Map<string, number>();\n\n  for (const line of lines) {\n    const data = extractSSEData(splitSSELines(line));\n    if (data === undefined) continue;\n\n    const payload = data.trim();\n    if (payload === \"[DONE]\") continue;\n\n    let parsed: Record<string, unknown>;\n    try {\n      parsed = JSON.parse(payload) as Record<string, unknown>;\n    } catch (err) {\n      droppedChunks++;\n      if (droppedChunks === 1) {\n        const msg = err instanceof Error ? err.message : \"unknown\";\n        firstDroppedSample = `parse failed (${msg}): ${surrogateSafeSlice(payload, 200)}`;\n      }\n      continue;\n    }\n\n    // Responses API reasoning events\n    if (\n      parsed.type === \"response.reasoning_summary_text.delta\" &&\n      typeof parsed.delta === \"string\"\n    ) {\n      reasoning += parsed.delta;\n      continue;\n    }\n\n    // Responses API web search events\n    if (parsed.type === \"response.output_item.done\") {\n      const item = parsed.item as Record<string, unknown> | undefined;\n      if (item?.type === \"web_search_call\") {\n        const action = item.action as Record<string, unknown> | undefined;\n        if (action && typeof action.query === \"string\") {\n          webSearchQueries.push(action.query);\n          continue;\n        }\n      }\n    }\n\n    // Responses API text content events\n    if (parsed.type === \"response.output_text.delta\" && typeof parsed.delta === \"string\") {\n      content += parsed.delta;\n      continue;\n    }\n\n    // Skip other Responses API structural events\n    if (typeof parsed.type === \"string\" && parsed.type.startsWith(\"response.\")) {\n      continue;\n    }\n\n    const choices = parsed.choices as Array<Record<string, unknown>> | undefined;\n    if (!choices || choices.length === 0) continue;\n\n    const delta = choices[0].delta as Record<string, unknown> | undefined;\n    if (!delta) continue;\n\n    // Reasoning content (OpenRouter / chat completions format)\n    if (typeof delta.reasoning_content === \"string\") {\n      reasoning += delta.reasoning_content;\n    }\n\n    // Text content\n    if (typeof delta.content === \"string\") {\n      content += delta.content;\n    }\n\n    // Tool calls\n    const toolCalls = delta.tool_calls as Array<Record<string, unknown>> | undefined;\n    if (toolCalls) {\n      for (const tc of toolCalls) {\n        const fn = tc.function as Record<string, unknown> | undefined;\n        const rawId = typeof tc.id === \"string\" ? tc.id : undefined;\n\n        // Resolve a stable map key. Prefer the streamed `index`; when it is\n        // absent, correlate by `id` if present, else mint a fresh synthetic\n        // key so distinct index-less calls never merge.\n        let index: number;\n        if (typeof tc.index === \"number\") {\n          index = tc.index;\n        } else if (rawId !== undefined) {\n          const existing = idKeyMap.get(rawId);\n          if (existing !== undefined) {\n            index = existing;\n          } else {\n            index = nextSyntheticIndex++;\n            idKeyMap.set(rawId, index);\n          }\n        } else {\n          index = nextSyntheticIndex++;\n        }\n\n        if (!toolCallMap.has(index)) {\n          toolCallMap.set(index, {\n            id: rawId ?? \"\",\n            name: (fn?.name as string) ?? \"\",\n            arguments: \"\",\n          });\n        }\n\n        const entry = toolCallMap.get(index)!;\n        if (fn?.name && typeof fn.name === \"string\" && !entry.name) {\n          entry.name = fn.name;\n        }\n        if (tc.id && typeof tc.id === \"string\" && !entry.id) {\n          entry.id = tc.id;\n        }\n        if (fn?.arguments && typeof fn.arguments === \"string\") {\n          entry.arguments += fn.arguments;\n        }\n      }\n    }\n  }\n\n  // Open-weight gpt-oss models (Ollama / vLLM / OpenRouter) stream tool calls\n  // as raw harmony channel tokens inside delta.content rather than structured\n  // delta.tool_calls. Harmony parsing is FALLBACK-ONLY: attempt it ONLY when\n  // there are NO structured delta.tool_calls. If structured tool calls exist,\n  // any harmony-looking content is prose — never merged (no phantom tool call),\n  // never stamped as truncated/dropped. When harmony IS the only source, a\n  // successful parse routes channels (content/reasoning/toolCalls); a failure\n  // preserves content VERBATIM and surfaces the distinct `harmonyUnparsed`\n  // signal (NOT droppedChunks/truncated — the bytes are not lost).\n  const harmonyToolCalls: ToolCall[] = [];\n  if (toolCallMap.size === 0 && isHarmonyContent(content)) {\n    const parsed = parseHarmonyContent(content);\n    if (parsed.failed) {\n      harmonyUnparsed = true;\n      harmonyNote = `harmony tokens present but unparseable; content preserved verbatim: ${surrogateSafeSlice(content, 200)}`;\n    } else {\n      content = parsed.content;\n      if (parsed.reasoning) {\n        reasoning += parsed.reasoning;\n      }\n      harmonyToolCalls.push(...parsed.toolCalls);\n    }\n  }\n\n  if (toolCallMap.size > 0 || harmonyToolCalls.length > 0) {\n    const sorted = Array.from(toolCallMap.entries()).sort(([a], [b]) => a - b);\n    return {\n      ...(content ? { content } : {}),\n      // Fallback-only: harmonyToolCalls are populated ONLY in the\n      // no-structured-calls branch, so this is never a merge of both sources.\n      toolCalls: [\n        ...sorted.map(([, tc]) => ({\n          name: tc.name,\n          arguments: tc.arguments,\n          ...(tc.id ? { id: tc.id } : {}),\n        })),\n        ...harmonyToolCalls,\n      ],\n      // Reasoning is preserved alongside tool calls for ALL structured streams\n      // (DeepSeek/OpenRouter reasoning_content, harmony analysis channel), at\n      // parity with every other collapser and the non-streaming path.\n      ...(reasoning ? { reasoning } : {}),\n      // webSearches parity with the text-only return branch.\n      ...(webSearchQueries.length > 0 ? { webSearches: webSearchQueries } : {}),\n      ...(droppedChunks > 0 ? { droppedChunks } : {}),\n      ...(firstDroppedSample ? { firstDroppedSample } : {}),\n      ...(harmonyUnparsed ? { harmonyUnparsed: true } : {}),\n      ...(harmonyNote ? { harmonyNote } : {}),\n    };\n  }\n\n  return {\n    content,\n    ...(reasoning ? { reasoning } : {}),\n    ...(webSearchQueries.length > 0 ? { webSearches: webSearchQueries } : {}),\n    ...(droppedChunks > 0 ? { droppedChunks } : {}),\n    ...(firstDroppedSample ? { firstDroppedSample } : {}),\n    ...(harmonyUnparsed ? { harmonyUnparsed: true } : {}),\n    ...(harmonyNote ? { harmonyNote } : {}),\n  };\n}\n\n// ---------------------------------------------------------------------------\n// 2. Anthropic SSE\n// ---------------------------------------------------------------------------\n\n/**\n * Collapse Anthropic Claude Messages SSE stream into a single response.\n *\n * Format:\n *   event: message_start\\ndata: {...}\\n\\n\n *   event: content_block_delta\\ndata: {\"delta\":{\"type\":\"text_delta\",\"text\":\"Hello\"}}\\n\\n\n */\nexport function collapseAnthropicSSE(body: string): CollapseResult {\n  const blocks = splitSSEEvents(body);\n  let content = \"\";\n  let reasoning = \"\";\n  // Real cryptographic signature captured from a `signature_delta`; stays\n  // undefined when the stream carried none (e.g. aimock's own placeholder turns\n  // or non-thinking turns). Carried so a recorded real-provider thinking turn\n  // can replay its ACTUAL signature instead of aimock's placeholder.\n  let reasoningSignature: string | undefined;\n  // Opaque `data` payloads of any `redacted_thinking` content blocks, in stream\n  // order. Stays empty when none present. Carried so a recorded redacted-thinking\n  // turn round-trips its encrypted reasoning faithfully on replay.\n  const redactedThinking: string[] = [];\n  let droppedChunks = 0;\n  let firstDroppedSample: string | undefined;\n  const toolCallMap = new Map<number, { id: string; name: string; arguments: string }>();\n  // Fallback keying for content blocks that OMIT `index` (mirrors the OpenAI /\n  // Cohere / Bedrock guards). Without it, every index-less block collapses\n  // under one `undefined` key, merging distinct tool_use blocks. Index-less\n  // starts mint a fresh synthetic key (kept above any real index so sort order\n  // is stable). Despite its name, `lastSyntheticIndex` tracks whichever\n  // tool_use start most recently opened REGARDLESS of whether its index was\n  // real or synthetic (it is set on every tool_use start; thinking /\n  // redacted_thinking starts do not touch it), so an index-less delta\n  // correlates to the most-recent tool_use start — not just to the last\n  // synthetic one. The 1_000_000 sentinel assumes real provider indices stay\n  // below it.\n  let nextSyntheticIndex = 1_000_000;\n  let lastSyntheticIndex: number | undefined;\n\n  for (const block of blocks) {\n    const lines = splitSSELines(block);\n    const eventLine = lines.find((l) => l.startsWith(\"event:\"));\n    const data = extractSSEData(lines);\n    if (data === undefined) continue;\n\n    const eventType = eventLine ? eventLine.slice(6).trim() : \"\";\n    const payload = data.trim();\n\n    let parsed: Record<string, unknown>;\n    try {\n      parsed = JSON.parse(payload) as Record<string, unknown>;\n    } catch (err) {\n      droppedChunks++;\n      if (droppedChunks === 1) {\n        const msg = err instanceof Error ? err.message : \"unknown\";\n        firstDroppedSample = `parse failed (${msg}): ${surrogateSafeSlice(payload, 200)}`;\n      }\n      continue;\n    }\n\n    if (eventType === \"content_block_start\") {\n      const contentBlock = parsed.content_block as Record<string, unknown> | undefined;\n      // A `redacted_thinking` block carries its encrypted reasoning in an opaque\n      // `data` string on the start event (no deltas follow). Capture it so the\n      // recorded turn can replay the redacted block faithfully\n      // (see capturedRedactedData for the non-empty rule).\n      const redactedData = capturedRedactedData(contentBlock);\n      if (redactedData !== undefined) {\n        redactedThinking.push(redactedData);\n      }\n      if (contentBlock?.type === \"tool_use\") {\n        // Prefer the streamed `index`; when absent, mint a fresh synthetic key\n        // so distinct index-less tool_use blocks never merge.\n        let index: number;\n        if (typeof parsed.index === \"number\") {\n          index = parsed.index;\n        } else {\n          index = nextSyntheticIndex++;\n        }\n        lastSyntheticIndex = index;\n        toolCallMap.set(index, {\n          id: (contentBlock.id as string) ?? \"\",\n          name: (contentBlock.name as string) ?? \"\",\n          arguments: \"\",\n        });\n      }\n    }\n\n    if (eventType === \"content_block_delta\") {\n      const delta = parsed.delta as Record<string, unknown> | undefined;\n      if (!delta) continue;\n\n      if (delta.type === \"text_delta\" && typeof delta.text === \"string\") {\n        content += delta.text;\n      }\n\n      if (delta.type === \"thinking_delta\" && typeof delta.thinking === \"string\") {\n        reasoning += delta.thinking;\n      }\n\n      // The real cryptographic signature arrives via a trailing\n      // `signature_delta` (the `content_block_start` carried \"\"). Capture the\n      // last one seen so a recorded thinking turn replays its actual signature.\n      // Last-signature-wins: a turn with MULTIPLE thinking blocks overwrites this\n      // on each block, so the merged `reasoning` string ends up bound only to the\n      // FINAL block's signature — per-block signatures are not preserved.\n      if (delta.type === \"signature_delta\" && typeof delta.signature === \"string\") {\n        reasoningSignature = delta.signature;\n      }\n\n      if (delta.type === \"input_json_delta\" && typeof delta.partial_json === \"string\") {\n        // Use the streamed `index` when present; otherwise correlate to the\n        // most recent tool_use start (mirrors the start-side fallback).\n        const index = typeof parsed.index === \"number\" ? parsed.index : lastSyntheticIndex;\n        // A delta that cannot correlate to any known start (no streamed index\n        // AND no prior start, or a stale index with no entry) would otherwise\n        // silently lose its args. Account for it as a dropped chunk instead of\n        // vanishing (mirrors the Cohere uncorrelated-delta path).\n        const entry = index !== undefined ? toolCallMap.get(index) : undefined;\n        if (entry) {\n          entry.arguments += delta.partial_json;\n        } else {\n          droppedChunks++;\n          if (droppedChunks === 1) {\n            firstDroppedSample = `input_json_delta with no correlating tool_use start: ${surrogateSafeSlice(\n              payload,\n              200,\n            )}`;\n          }\n        }\n      }\n    }\n  }\n\n  if (toolCallMap.size > 0) {\n    const sorted = Array.from(toolCallMap.entries()).sort(([a], [b]) => a - b);\n    return {\n      ...(content ? { content } : {}),\n      toolCalls: sorted.map(([, tc]) => ({\n        name: tc.name,\n        arguments: tc.arguments,\n        ...(tc.id ? { id: tc.id } : {}),\n      })),\n      ...(reasoning ? { reasoning } : {}),\n      ...(reasoningSignature ? { reasoningSignature } : {}),\n      ...(redactedThinking.length > 0 ? { redactedThinking } : {}),\n      ...(droppedChunks > 0 ? { droppedChunks } : {}),\n      ...(firstDroppedSample ? { firstDroppedSample } : {}),\n    };\n  }\n\n  return {\n    content,\n    ...(reasoning ? { reasoning } : {}),\n    ...(reasoningSignature ? { reasoningSignature } : {}),\n    ...(redactedThinking.length > 0 ? { redactedThinking } : {}),\n    ...(droppedChunks > 0 ? { droppedChunks } : {}),\n    ...(firstDroppedSample ? { firstDroppedSample } : {}),\n  };\n}\n\n// ---------------------------------------------------------------------------\n// 3. Gemini SSE\n// ---------------------------------------------------------------------------\n\n/**\n * Collapse Gemini SSE stream into a single response.\n *\n * Format (data-only, no event prefix, no [DONE]):\n *   data: {\"candidates\":[{\"content\":{\"parts\":[{\"text\":\"Hello\"}]}}]}\\n\\n\n */\nexport function collapseGeminiSSE(body: string): CollapseResult {\n  const lines = splitSSEEvents(body);\n  let content = \"\";\n  let reasoning = \"\";\n  let droppedChunks = 0;\n  let firstDroppedSample: string | undefined;\n  let audioB64 = \"\";\n  let audioMimeType: string | undefined;\n  const toolCalls: ToolCall[] = [];\n\n  for (const line of lines) {\n    const data = extractSSEData(splitSSELines(line));\n    if (data === undefined) continue;\n\n    const payload = data.trim();\n\n    let parsed: Record<string, unknown>;\n    try {\n      parsed = JSON.parse(payload) as Record<string, unknown>;\n    } catch (err) {\n      droppedChunks++;\n      if (droppedChunks === 1) {\n        const msg = err instanceof Error ? err.message : \"unknown\";\n        firstDroppedSample = `parse failed (${msg}): ${surrogateSafeSlice(payload, 200)}`;\n      }\n      continue;\n    }\n\n    const candidates = parsed.candidates as Array<Record<string, unknown>> | undefined;\n    if (!candidates || candidates.length === 0) continue;\n\n    const candidateContent = candidates[0].content as Record<string, unknown> | undefined;\n    if (!candidateContent) continue;\n\n    const parts = candidateContent.parts as Array<Record<string, unknown>> | undefined;\n    if (!parts || parts.length === 0) continue;\n\n    for (const part of parts) {\n      if (part.functionCall) {\n        const fc = part.functionCall as Record<string, unknown>;\n        toolCalls.push({\n          name: String(fc.name ?? \"\"),\n          // Default undefined/object args to a JSON object string (matches\n          // collapseGeminiInteractionsSSE / Ollama). JSON.stringify(undefined)\n          // would otherwise yield the VALUE undefined, violating the\n          // ToolCall.arguments:string contract.\n          arguments:\n            typeof fc.args === \"string\" ? (fc.args as string) : JSON.stringify(fc.args ?? {}),\n        });\n      } else if (\n        part.inlineData &&\n        typeof (part.inlineData as Record<string, unknown>).mimeType === \"string\" &&\n        ((part.inlineData as Record<string, unknown>).mimeType as string).startsWith(\"audio/\")\n      ) {\n        const inlineData = part.inlineData as Record<string, unknown>;\n        if (!audioMimeType) {\n          audioMimeType = inlineData.mimeType as string;\n        }\n        if (typeof inlineData.data === \"string\") {\n          audioB64 += inlineData.data;\n        }\n      } else if (typeof part.text === \"string\") {\n        if (part.thought) {\n          reasoning += part.text;\n        } else {\n          content += part.text;\n        }\n      }\n    }\n  }\n\n  if (audioB64) {\n    // Preserve any content / reasoning / tool calls accumulated in the same\n    // stream — a Gemini turn can interleave audio with text and functionCall\n    // parts, and the early return must not silently drop them.\n    return {\n      audioB64,\n      audioMimeType,\n      ...(content ? { content } : {}),\n      ...(reasoning ? { reasoning } : {}),\n      ...(toolCalls.length > 0 ? { toolCalls } : {}),\n      ...(droppedChunks > 0 ? { droppedChunks } : {}),\n      ...(firstDroppedSample ? { firstDroppedSample } : {}),\n    };\n  }\n\n  if (toolCalls.length > 0) {\n    return {\n      ...(content ? { content } : {}),\n      toolCalls,\n      ...(reasoning ? { reasoning } : {}),\n      ...(droppedChunks > 0 ? { droppedChunks } : {}),\n      ...(firstDroppedSample ? { firstDroppedSample } : {}),\n    };\n  }\n\n  return {\n    content,\n    ...(reasoning ? { reasoning } : {}),\n    ...(droppedChunks > 0 ? { droppedChunks } : {}),\n    ...(firstDroppedSample ? { firstDroppedSample } : {}),\n  };\n}\n\n// ---------------------------------------------------------------------------\n// 4. Ollama NDJSON\n// ---------------------------------------------------------------------------\n\n/**\n * Collapse Ollama NDJSON stream into a single response.\n *\n * /api/chat format:\n *   {\"model\":\"llama3\",\"message\":{\"role\":\"assistant\",\"content\":\"Hello\"},\"done\":false}\\n\n *\n * /api/generate format:\n *   {\"model\":\"llama3\",\"response\":\"Hello\",\"done\":false}\\n\n *\n * Open-weight gpt-oss served via Ollama streams harmony channel tokens inside\n * `message.content` (just like the OpenAI SSE path), so after accumulation the\n * content is run through the same fail-safe {@link parseHarmonyContent} gate to\n * capture structured tool calls / reasoning instead of leaking raw tokens.\n */\nexport function collapseOllamaNDJSON(body: string): CollapseResult {\n  const lines = body.split(\"\\n\").filter((l) => l.trim().length > 0);\n  let content = \"\";\n  let reasoning = \"\";\n  let droppedChunks = 0;\n  let firstDroppedSample: string | undefined;\n  let harmonyUnparsed = false;\n  let harmonyNote: string | undefined;\n  const toolCalls: ToolCall[] = [];\n\n  for (const line of lines) {\n    let parsed: Record<string, unknown>;\n    try {\n      parsed = JSON.parse(line.trim()) as Record<string, unknown>;\n    } catch (err) {\n      droppedChunks++;\n      if (droppedChunks === 1) {\n        const msg = err instanceof Error ? err.message : \"unknown\";\n        firstDroppedSample = `parse failed (${msg}): ${surrogateSafeSlice(line.trim(), 200)}`;\n      }\n      continue;\n    }\n\n    // /api/chat format\n    const message = parsed.message as Record<string, unknown> | undefined;\n    if (message) {\n      if (typeof message.content === \"string\") {\n        content += message.content;\n      }\n\n      // Tool calls\n      if (Array.isArray(message.tool_calls)) {\n        for (const tc of message.tool_calls as Array<Record<string, unknown>>) {\n          const fn = tc.function as Record<string, unknown> | undefined;\n          if (fn) {\n            toolCalls.push({\n              name: String(fn.name ?? \"\"),\n              // Default undefined/object args to a JSON object (matching\n              // collapseGeminiInteractionsSSE) — JSON.stringify(undefined)\n              // would otherwise yield the literal string \"undefined\".\n              arguments:\n                typeof fn.arguments === \"string\"\n                  ? fn.arguments\n                  : JSON.stringify(fn.arguments ?? {}),\n            });\n          }\n        }\n      }\n    }\n\n    // /api/generate format\n    else if (typeof parsed.response === \"string\") {\n      content += parsed.response;\n    }\n  }\n\n  // Open-weight gpt-oss served via Ollama streams harmony channel tokens inside\n  // message.content (same as the OpenAI SSE path). Harmony parsing is\n  // FALLBACK-ONLY: attempt it ONLY when there are NO structured message\n  // tool_calls. If structured tool calls exist, harmony-looking content is\n  // prose — never merged (no phantom), never stamped truncated/dropped. On a\n  // harmony failure the content is preserved VERBATIM and surfaced via the\n  // distinct `harmonyUnparsed` signal (NOT droppedChunks/truncated).\n  if (toolCalls.length === 0 && isHarmonyContent(content)) {\n    const parsedHarmony = parseHarmonyContent(content);\n    if (parsedHarmony.failed) {\n      harmonyUnparsed = true;\n      harmonyNote = `harmony tokens present but unparseable; content preserved verbatim: ${surrogateSafeSlice(content, 200)}`;\n    } else {\n      content = parsedHarmony.content;\n      if (parsedHarmony.reasoning) {\n        reasoning += parsedHarmony.reasoning;\n      }\n      toolCalls.push(...parsedHarmony.toolCalls);\n    }\n  }\n\n  if (toolCalls.length > 0) {\n    return {\n      ...(content ? { content } : {}),\n      toolCalls,\n      ...(reasoning ? { reasoning } : {}),\n      ...(droppedChunks > 0 ? { droppedChunks } : {}),\n      ...(firstDroppedSample ? { firstDroppedSample } : {}),\n      ...(harmonyUnparsed ? { harmonyUnparsed: true } : {}),\n      ...(harmonyNote ? { harmonyNote } : {}),\n    };\n  }\n\n  return {\n    content,\n    ...(reasoning ? { reasoning } : {}),\n    ...(droppedChunks > 0 ? { droppedChunks } : {}),\n    ...(firstDroppedSample ? { firstDroppedSample } : {}),\n    ...(harmonyUnparsed ? { harmonyUnparsed: true } : {}),\n    ...(harmonyNote ? { harmonyNote } : {}),\n  };\n}\n\n// ---------------------------------------------------------------------------\n// 5. Cohere SSE\n// ---------------------------------------------------------------------------\n\n/**\n * Collapse Cohere SSE stream into a single response.\n *\n * Format:\n *   event: content-delta\\ndata: {\"type\":\"content-delta\",\"delta\":{\"message\":{\"content\":{\"text\":\"Hello\"}}}}\\n\\n\n */\nexport function collapseCohereSSE(body: string): CollapseResult {\n  const blocks = splitSSEEvents(body);\n  let content = \"\";\n  // Reasoning text assembled from `thinking` content-delta blocks. Cohere's\n  // reasoning models stream a `content.type === \"thinking\"` block carrying a\n  // `thinking` string before the `text` block; capture it so a recorded\n  // reasoning turn round-trips its reasoning instead of dropping it.\n  let reasoning = \"\";\n  let droppedChunks = 0;\n  let firstDroppedSample: string | undefined;\n  const toolCallMap = new Map<number, { id: string; name: string; arguments: string }>();\n  // Fallback keying for tool-call events that OMIT `index` (mirrors the\n  // OpenAI guard). Without it, every index-less tool-call-start collapses\n  // under one `undefined`/NaN key, merging distinct calls. Index-less starts\n  // mint a fresh synthetic key. `lastStartKey` tracks the most-recent\n  // tool-call-start key REGARDLESS of whether it was real or synthetic, so an\n  // index-less tool-call-delta correlates to whichever start most recently\n  // opened — not just to the last synthetic one. The 1_000_000 sentinel\n  // assumes real provider indices stay below it.\n  let nextSyntheticIndex = 1_000_000;\n  let lastStartKey: number | undefined;\n\n  for (const block of blocks) {\n    const lines = splitSSELines(block);\n    const eventLine = lines.find((l) => l.startsWith(\"event:\"));\n    const data = extractSSEData(lines);\n    if (data === undefined) continue;\n\n    const eventType = eventLine ? eventLine.slice(6).trim() : \"\";\n    const payload = data.trim();\n\n    let parsed: Record<string, unknown>;\n    try {\n      parsed = JSON.parse(payload) as Record<string, unknown>;\n    } catch (err) {\n      droppedChunks++;\n      if (droppedChunks === 1) {\n        const msg = err instanceof Error ? err.message : \"unknown\";\n        firstDroppedSample = `parse failed (${msg}): ${surrogateSafeSlice(payload, 200)}`;\n      }\n      continue;\n    }\n\n    if (eventType === \"content-delta\") {\n      const delta = parsed.delta as Record<string, unknown> | undefined;\n      const message = delta?.message as Record<string, unknown> | undefined;\n      const contentObj = message?.content as Record<string, unknown> | undefined;\n      if (contentObj && contentObj.type === \"thinking\" && typeof contentObj.thinking === \"string\") {\n        reasoning += contentObj.thinking;\n      } else if (contentObj && typeof contentObj.text === \"string\") {\n        content += contentObj.text;\n      }\n    }\n\n    if (eventType === \"tool-call-start\") {\n      let index: number;\n      if (typeof parsed.index === \"number\") {\n        index = parsed.index;\n      } else {\n        index = nextSyntheticIndex++;\n      }\n      // Track the most-recent start key (real OR synthetic) so a following\n      // index-less delta correlates to whichever call just opened.\n      lastStartKey = index;\n      const delta = parsed.delta as Record<string, unknown> | undefined;\n      const message = delta?.message as Record<string, unknown> | undefined;\n      const toolCalls = message?.tool_calls as Record<string, unknown> | undefined;\n      if (toolCalls) {\n        const fn = toolCalls.function as Record<string, unknown> | undefined;\n        toolCallMap.set(index, {\n          id: (toolCalls.id as string) ?? \"\",\n          name: (fn?.name as string) ?? \"\",\n          arguments: \"\",\n        });\n      }\n    }\n\n    if (eventType === \"tool-call-delta\") {\n      // Use the streamed `index` when present; otherwise correlate to the most\n      // recent tool-call-start (real or synthetic key).\n      const index = typeof parsed.index === \"number\" ? parsed.index : lastStartKey;\n      const delta = parsed.delta as Record<string, unknown> | undefined;\n      const message = delta?.message as Record<string, unknown> | undefined;\n      const toolCalls = message?.tool_calls as Record<string, unknown> | undefined;\n      if (toolCalls) {\n        const fn = toolCalls.function as Record<string, unknown> | undefined;\n        if (fn && typeof fn.arguments === \"string\") {\n          // A delta that cannot correlate to any known start (no streamed\n          // index AND no prior start) would otherwise silently lose its args.\n          // Account for it as a dropped chunk instead of vanishing.\n          const entry = index !== undefined ? toolCallMap.get(index) : undefined;\n          if (entry) {\n            entry.arguments += fn.arguments;\n          } else {\n            droppedChunks++;\n            if (droppedChunks === 1) {\n              firstDroppedSample = `tool-call-delta with no correlating start: ${surrogateSafeSlice(\n                payload,\n                200,\n              )}`;\n            }\n          }\n        }\n      }\n    }\n  }\n\n  if (toolCallMap.size > 0) {\n    const sorted = Array.from(toolCallMap.entries()).sort(([a], [b]) => a - b);\n    return {\n      ...(content ? { content } : {}),\n      toolCalls: sorted.map(([, tc]) => ({\n        name: tc.name,\n        arguments: tc.arguments,\n        ...(tc.id ? { id: tc.id } : {}),\n      })),\n      ...(reasoning ? { reasoning } : {}),\n      ...(droppedChunks > 0 ? { droppedChunks } : {}),\n      ...(firstDroppedSample ? { firstDroppedSample } : {}),\n    };\n  }\n\n  return {\n    content,\n    ...(reasoning ? { reasoning } : {}),\n    ...(droppedChunks > 0 ? { droppedChunks } : {}),\n    ...(firstDroppedSample ? { firstDroppedSample } : {}),\n  };\n}\n\n// ---------------------------------------------------------------------------\n// 6. Bedrock EventStream (binary)\n// ---------------------------------------------------------------------------\n\n/**\n * Decode AWS Event Stream binary frames and extract JSON payloads.\n *\n * Binary frame layout:\n *   [total_length: 4B uint32-BE]\n *   [headers_length: 4B uint32-BE]\n *   [prelude_crc32: 4B]\n *   [headers: variable]\n *   [payload: variable]\n *   [message_crc32: 4B]\n */\nfunction decodeEventStreamFrames(buf: Buffer): {\n  frames: Array<{ headers: Record<string, string>; payload: Buffer }>;\n  truncated: boolean;\n} {\n  const frames: Array<{ headers: Record<string, string>; payload: Buffer }> = [];\n  let offset = 0;\n\n  while (offset < buf.length) {\n    if (offset + 12 > buf.length) break;\n\n    const totalLength = buf.readUInt32BE(offset);\n    const headersLength = buf.readUInt32BE(offset + 4);\n\n    // Validate bounds: ensure the full frame is within the buffer\n    if (totalLength < 12 || offset + totalLength > buf.length) {\n      return { frames, truncated: true };\n    }\n\n    // Validate prelude CRC\n    const preludeCrc = buf.readUInt32BE(offset + 8);\n    const computedPreludeCrc = crc32(buf.subarray(offset, offset + 8));\n    if (preludeCrc >>> 0 !== computedPreludeCrc >>> 0) {\n      return { frames, truncated: true }; // Prelude CRC mismatch — stop parsing\n    }\n\n    // Parse headers\n    const headersStart = offset + 12;\n    const headersEnd = headersStart + headersLength;\n    const payloadEnd = offset + totalLength - 4; // minus message CRC\n\n    // Validate the headers region fits inside the frame. A frame can carry a\n    // valid prelude CRC yet declare a `headersLength` that overruns the payload\n    // region (the prelude CRC only covers total/headers length, not the body).\n    // Without this guard a per-header read walks off the buffer and throws an\n    // uncaught RangeError; treat it as truncation instead.\n    if (headersEnd > payloadEnd || headersEnd > buf.length) {\n      return { frames, truncated: true };\n    }\n\n    const headers: Record<string, string> = {};\n    let hOffset = headersStart;\n    let headerOverrun = false;\n\n    while (hOffset < headersEnd) {\n      // Each read must stay within the declared headers region. Bail out\n      // (truncated) on any overrun rather than reading past the boundary.\n      if (hOffset + 1 > headersEnd) {\n        headerOverrun = true;\n        break;\n      }\n      const nameLen = buf.readUInt8(hOffset);\n      hOffset += 1;\n      if (hOffset + nameLen + 1 + 2 > headersEnd) {\n        headerOverrun = true;\n        break;\n      }\n      const name = buf.subarray(hOffset, hOffset + nameLen).toString(\"utf8\");\n      hOffset += nameLen;\n      // Skip header type byte (type 7 = STRING)\n      hOffset += 1;\n      const valueLen = buf.readUInt16BE(hOffset);\n      hOffset += 2;\n      if (hOffset + valueLen > headersEnd) {\n        headerOverrun = true;\n        break;\n      }\n      const value = buf.subarray(hOffset, hOffset + valueLen).toString(\"utf8\");\n      hOffset += valueLen;\n      headers[name] = value;\n    }\n\n    if (headerOverrun) {\n      return { frames, truncated: true };\n    }\n\n    // Extract payload\n    const payloadStart = headersEnd;\n    const payload = buf.subarray(payloadStart, payloadEnd);\n\n    // Validate message CRC (covers entire frame minus last 4 bytes)\n    const messageCrc = buf.readUInt32BE(offset + totalLength - 4);\n    const computedMessageCrc = crc32(buf.subarray(offset, offset + totalLength - 4));\n    if (messageCrc >>> 0 !== computedMessageCrc >>> 0) {\n      return { frames, truncated: true }; // Message CRC mismatch — stop parsing\n    }\n\n    frames.push({ headers, payload });\n    offset += totalLength;\n  }\n\n  return { frames, truncated: false };\n}\n\n/**\n * Collapse Bedrock binary Event Stream into a single response.\n *\n * Each frame contains a JSON payload with event types like:\n *   contentBlockDelta, contentBlockStart, etc.\n */\nexport function collapseBedrockEventStream(body: Buffer): CollapseResult {\n  const { frames, truncated } = decodeEventStreamFrames(body);\n  let content = \"\";\n  // Reasoning text assembled from Converse `reasoningContent.text` deltas. The\n  // Bedrock Converse stream interleaves a `delta.reasoningContent` block carrying\n  // the model's reasoning; capture it so a recorded reasoning turn round-trips\n  // its reasoning instead of dropping it.\n  let reasoning = \"\";\n  // Anthropic-native extended-thinking fields (invoke-with-response-stream).\n  // The native binary branch carries the same thinking/signature/redacted\n  // channel as the Anthropic SSE collapser; it mirrors that path's\n  // thinking/signature/redacted capture rules so a recorded reasoning turn\n  // round-trips instead of silently dropping it. Unlike the SSE path, binary\n  // tool_use correlation has no index-less fallback and relies on an explicit\n  // `index`.\n  let reasoningSignature: string | undefined;\n  const redactedThinking: string[] = [];\n  let droppedChunks = 0;\n  let firstDroppedSample: string | undefined;\n  const toolCallMap = new Map<number, { id: string; name: string; arguments: string }>();\n\n  for (const frame of frames) {\n    const frameStr = frame.payload.toString(\"utf8\");\n    let parsed: Record<string, unknown>;\n    try {\n      parsed = JSON.parse(frameStr) as Record<string, unknown>;\n    } catch (err) {\n      droppedChunks++;\n      if (droppedChunks === 1) {\n        const msg = err instanceof Error ? err.message : \"unknown\";\n        firstDroppedSample = `parse failed (${msg}): ${surrogateSafeSlice(frameStr, 200)}`;\n      }\n      continue;\n    }\n\n    // Anthropic Messages format (invoke-with-response-stream): flat payload with \"type\" field\n    if (parsed.type === \"content_block_delta\") {\n      const delta = parsed.delta as Record<string, unknown> | undefined;\n      if (delta?.type === \"text_delta\" && typeof delta.text === \"string\") {\n        content += delta.text;\n      }\n      if (delta?.type === \"thinking_delta\" && typeof delta.thinking === \"string\") {\n        reasoning += delta.thinking;\n      }\n      // Last-signature-wins: a turn with MULTIPLE thinking blocks overwrites\n      // this on each block, so the merged `reasoning` ends up bound only to the\n      // FINAL block's signature (mirrors collapseAnthropicSSE).\n      if (delta?.type === \"signature_delta\" && typeof delta.signature === \"string\") {\n        reasoningSignature = delta.signature;\n      }\n      if (delta?.type === \"input_json_delta\" && typeof delta.partial_json === \"string\") {\n        const index = parsed.index as number | undefined;\n        // An arg delta that cannot correlate to a known tool_use start would\n        // otherwise silently lose its args. Account for it as a dropped chunk\n        // instead of vanishing (mirrors the Cohere uncorrelated-delta path).\n        const entry = index !== undefined ? toolCallMap.get(index) : undefined;\n        if (entry) {\n          entry.arguments += delta.partial_json;\n        } else {\n          droppedChunks++;\n          if (droppedChunks === 1) {\n            firstDroppedSample = `input_json_delta with no correlating tool_use start: ${surrogateSafeSlice(\n              frameStr,\n              200,\n            )}`;\n          }\n        }\n      }\n      continue;\n    }\n    if (parsed.type === \"content_block_start\") {\n      const block = parsed.content_block as Record<string, unknown> | undefined;\n      const index = parsed.index as number | undefined;\n      // A `redacted_thinking` block carries its encrypted reasoning in an\n      // opaque `data` string on the start event (no deltas follow). Capture it\n      // so the recorded turn replays the redacted block faithfully\n      // (mirrors collapseAnthropicSSE; see capturedRedactedData).\n      const redactedData = capturedRedactedData(block);\n      if (redactedData !== undefined) {\n        redactedThinking.push(redactedData);\n      }\n      if (block?.type === \"tool_use\" && index !== undefined) {\n        toolCallMap.set(index, {\n          id: (block.id as string) ?? \"\",\n          name: (block.name as string) ?? \"\",\n          arguments: \"\",\n        });\n      }\n      continue;\n    }\n\n    // Converse format (converse-stream): camelCase wrapper keys\n    // contentBlockStart — may initiate a tool_use block\n    if (parsed.contentBlockStart) {\n      const blockStart = parsed.contentBlockStart as Record<string, unknown>;\n      const index = (parsed.contentBlockIndex ?? blockStart.contentBlockIndex) as\n        | number\n        | undefined;\n      const start = blockStart.start as Record<string, unknown> | undefined;\n      if (start?.toolUse && index !== undefined) {\n        const toolUse = start.toolUse as Record<string, unknown>;\n        toolCallMap.set(index, {\n          id: (toolUse.toolUseId as string) ?? \"\",\n          name: (toolUse.name as string) ?? \"\",\n          arguments: \"\",\n        });\n      }\n    }\n\n    // contentBlockDelta\n    if (parsed.contentBlockDelta) {\n      const blockDelta = parsed.contentBlockDelta as Record<string, unknown>;\n      const index = (parsed.contentBlockIndex ?? blockDelta.contentBlockIndex) as\n        | number\n        | undefined;\n      const delta = blockDelta.delta as Record<string, unknown> | undefined;\n      if (!delta) continue;\n\n      // Text delta\n      if (typeof delta.text === \"string\") {\n        content += delta.text;\n      }\n\n      // Reasoning delta — Converse carries reasoning in `reasoningContent.text`.\n      // The Converse branch intentionally captures no signature/redacted channel:\n      // Converse has no `signature_delta`/`redacted_thinking` wire shape, so the\n      // asymmetry with the Anthropic-native branch above is by design, not a gap\n      // to be \"fixed\" later.\n      if (typeof delta.reasoningContent === \"object\" && delta.reasoningContent !== null) {\n        const reasoningDelta = delta.reasoningContent as Record<string, unknown>;\n        if (typeof reasoningDelta.text === \"string\") {\n          reasoning += reasoningDelta.text;\n        }\n      }\n\n      // Tool use input JSON delta\n      if (typeof delta.toolUse === \"object\" && delta.toolUse !== null) {\n        const toolUseDelta = delta.toolUse as Record<string, unknown>;\n        if (typeof toolUseDelta.input === \"string\") {\n          // An arg delta that cannot correlate to a known tool_use start would\n          // otherwise silently lose its args. Account for it as a dropped chunk\n          // instead of vanishing (mirrors the Cohere uncorrelated-delta path).\n          const entry = index !== undefined ? toolCallMap.get(index) : undefined;\n          if (entry) {\n            entry.arguments += toolUseDelta.input;\n          } else {\n            droppedChunks++;\n            if (droppedChunks === 1) {\n              firstDroppedSample = `toolUse.input delta with no correlating tool_use start: ${surrogateSafeSlice(\n                frameStr,\n                200,\n              )}`;\n            }\n          }\n        }\n      }\n    }\n  }\n\n  if (toolCallMap.size > 0) {\n    const sorted = Array.from(toolCallMap.entries()).sort(([a], [b]) => a - b);\n    return {\n      toolCalls: sorted.map(([, tc]) => ({\n        name: tc.name,\n        arguments: tc.arguments,\n        ...(tc.id ? { id: tc.id } : {}),\n      })),\n      ...(reasoning ? { reasoning } : {}),\n      ...(reasoningSignature ? { reasoningSignature } : {}),\n      ...(redactedThinking.length > 0 ? { redactedThinking } : {}),\n      ...(droppedChunks > 0 ? { droppedChunks } : {}),\n      ...(firstDroppedSample ? { firstDroppedSample } : {}),\n      ...(truncated ? { truncated } : {}),\n    };\n  }\n\n  return {\n    content,\n    ...(reasoning ? { reasoning } : {}),\n    ...(reasoningSignature ? { reasoningSignature } : {}),\n    ...(redactedThinking.length > 0 ? { redactedThinking } : {}),\n    ...(droppedChunks > 0 ? { droppedChunks } : {}),\n    ...(firstDroppedSample ? { firstDroppedSample } : {}),\n    ...(truncated ? { truncated } : {}),\n  };\n}\n\n// ---------------------------------------------------------------------------\n// 7. Gemini Interactions SSE\n// ---------------------------------------------------------------------------\n\n/**\n * Collapse Gemini Interactions SSE stream into a single response.\n *\n * Handles the SDK 2.x event protocol (the \"Interactions breaking changes,\n * May 2026\" shapes):\n *   data: {\"event_type\":\"step.start\",\"index\":1,\"step\":{\"type\":\"function_call\",\"id\":\"call_1\",\"name\":\"fn\",\"arguments\":{}}}\n *   data: {\"event_type\":\"step.delta\",\"index\":0,\"delta\":{\"type\":\"text\",\"text\":\"Hello\"}}\n *   data: {\"event_type\":\"step.delta\",\"index\":1,\"delta\":{\"type\":\"arguments_delta\",\"arguments\":\"{\\\"x\\\":1}\"}}\n *   data: {\"event_type\":\"interaction.completed\",\"interaction\":{\"id\":\"...\",\"usage\":{...}}}\n *\n * The legacy SDK 1.x shapes (`content.delta` with an inline `function_call`\n * delta) are still accepted for backward compatibility with previously\n * recorded fixtures.\n */\nexport function collapseGeminiInteractionsSSE(body: string): CollapseResult {\n  const lines = splitSSEEvents(body);\n  let content = \"\";\n  let reasoning = \"\";\n  let droppedChunks = 0;\n  let firstDroppedSample: string | undefined;\n  // Legacy 1.x tool calls arrive fully formed in a single content.delta.\n  const toolCalls: ToolCall[] = [];\n  // 2.x tool calls are assembled across step.start (identity) + arguments_delta\n  // (string fragments), keyed by step index.\n  const stepToolCalls = new Map<\n    number,\n    { id?: string; name: string; argsObj?: unknown; argsStr: string }\n  >();\n  // Synthetic keys for function_call step.start events that arrive without an\n  // index (matches the sibling collapsers); seeded high to avoid colliding with\n  // real step indices.\n  let nextSyntheticStepIndex = 1_000_000;\n\n  for (const line of lines) {\n    const data = extractSSEData(splitSSELines(line));\n    if (data === undefined) continue;\n\n    const payload = data.trim();\n\n    let parsed: Record<string, unknown>;\n    try {\n      parsed = JSON.parse(payload) as Record<string, unknown>;\n    } catch (err) {\n      droppedChunks++;\n      if (droppedChunks === 1) {\n        const msg = err instanceof Error ? err.message : \"unknown\";\n        firstDroppedSample = `parse failed (${msg}): ${surrogateSafeSlice(payload, 200)}`;\n      }\n      continue;\n    }\n\n    const eventType = parsed.event_type as string | undefined;\n    if (!eventType) continue;\n\n    const index = typeof parsed.index === \"number\" ? parsed.index : undefined;\n\n    if (eventType === \"step.start\") {\n      // 2.x — tool-call identity lives on step.start, not in a delta.\n      const step = parsed.step as Record<string, unknown> | undefined;\n      if (step && step.type === \"function_call\") {\n        // An index-less start can't correlate later arguments_delta fragments,\n        // but minting a synthetic key preserves the call's identity instead of\n        // dropping it silently.\n        const key = index ?? nextSyntheticStepIndex++;\n        stepToolCalls.set(key, {\n          id: step.id ? String(step.id) : undefined,\n          name: String(step.name ?? \"\"),\n          // step.start may carry a fully-populated `arguments` object (non-\n          // streamed calls) or an empty `{}` placeholder (streamed calls).\n          argsObj: step.arguments,\n          argsStr: \"\",\n        });\n      }\n    } else if (eventType === \"step.delta\" || eventType === \"content.delta\") {\n      const delta = parsed.delta as Record<string, unknown> | undefined;\n      if (!delta) continue;\n\n      if (delta.type === \"text\" && typeof delta.text === \"string\") {\n        content += delta.text;\n      } else if (delta.type === \"arguments_delta\") {\n        // 2.x — argument fragment (a JSON string) keyed by step index.\n        const entry = index !== undefined ? stepToolCalls.get(index) : undefined;\n        if (entry) {\n          if (typeof delta.arguments === \"string\") {\n            entry.argsStr += delta.arguments;\n          }\n        } else {\n          droppedChunks++;\n          if (droppedChunks === 1) {\n            firstDroppedSample = `arguments_delta with no correlating step.start: ${surrogateSafeSlice(\n              payload,\n              200,\n            )}`;\n          }\n        }\n      } else if (delta.type === \"function_call\") {\n        // Legacy 1.x — full tool call inline in a content.delta.\n        toolCalls.push({\n          name: String(delta.name ?? \"\"),\n          arguments:\n            typeof delta.arguments === \"string\"\n              ? delta.arguments\n              : JSON.stringify(delta.arguments ?? {}),\n          ...(delta.id ? { id: String(delta.id) } : {}),\n        });\n      } else if (delta.type === \"thought_summary\") {\n        // 2.x nests the text under `content.text`; 1.x used a flat `text`.\n        const summaryContent = delta.content as Record<string, unknown> | undefined;\n        if (summaryContent && typeof summaryContent.text === \"string\") {\n          reasoning += summaryContent.text;\n        } else if (typeof delta.text === \"string\") {\n          reasoning += delta.text;\n        }\n      }\n    }\n  }\n\n  // Finalize 2.x tool calls in step-index order.\n  for (const [, tc] of Array.from(stepToolCalls.entries()).sort(([a], [b]) => a - b)) {\n    let args: string;\n    if (tc.argsStr !== \"\") {\n      args = tc.argsStr;\n      // The arguments_delta fragments must concatenate into valid JSON by\n      // step.stop. A truncated/interrupted stream can leave them malformed;\n      // surface that via droppedChunks rather than writing a corrupt fixture\n      // silently (mirrors the per-chunk parse guard above).\n      try {\n        JSON.parse(args);\n      } catch {\n        droppedChunks++;\n        if (droppedChunks === 1) {\n          firstDroppedSample = `assembled arguments_delta not valid JSON for \"${tc.name}\": ${surrogateSafeSlice(\n            args,\n            200,\n          )}`;\n        }\n      }\n    } else {\n      args = typeof tc.argsObj === \"string\" ? tc.argsObj : JSON.stringify(tc.argsObj ?? {});\n    }\n    toolCalls.push({ name: tc.name, arguments: args, ...(tc.id ? { id: tc.id } : {}) });\n  }\n\n  if (toolCalls.length > 0) {\n    return {\n      ...(content ? { content } : {}),\n      toolCalls,\n      ...(reasoning ? { reasoning } : {}),\n      ...(droppedChunks > 0 ? { droppedChunks } : {}),\n      ...(firstDroppedSample ? { firstDroppedSample } : {}),\n    };\n  }\n\n  return {\n    content,\n    ...(reasoning ? { reasoning } : {}),\n    ...(droppedChunks > 0 ? { droppedChunks } : {}),\n    ...(firstDroppedSample ? { firstDroppedSample } : {}),\n  };\n}\n\n// ---------------------------------------------------------------------------\n// Dispatch helper — pick the right collapse function by provider\n// ---------------------------------------------------------------------------\n\n/**\n * Collapse a streaming response body into a non-streaming fixture response.\n * Returns null if the content type is not a known streaming format.\n * Falls back to OpenAI SSE parsing for unrecognized provider keys with text/event-stream.\n */\nexport function collapseStreamingResponse(\n  contentType: string,\n  providerKey: RecordProviderKey,\n  body: string | Buffer,\n  logger?: Logger,\n): CollapseResult | null {\n  const ct = contentType.toLowerCase();\n\n  if (ct.includes(\"application/vnd.amazon.eventstream\")) {\n    const buf = typeof body === \"string\" ? Buffer.from(body, \"binary\") : body;\n    return collapseBedrockEventStream(buf);\n  }\n\n  if (ct.includes(\"application/x-ndjson\")) {\n    const str = typeof body === \"string\" ? body : body.toString(\"utf8\");\n    return collapseOllamaNDJSON(str);\n  }\n\n  if (ct.includes(\"text/event-stream\")) {\n    const str = typeof body === \"string\" ? body : body.toString(\"utf8\");\n    switch (providerKey) {\n      case \"openai\":\n      case \"azure\":\n        return collapseOpenAISSE(str);\n      case \"anthropic\":\n        return collapseAnthropicSSE(str);\n      case \"gemini\":\n      case \"vertexai\":\n        return collapseGeminiSSE(str);\n      case \"gemini-interactions\":\n        return collapseGeminiInteractionsSSE(str);\n      case \"cohere\":\n        return collapseCohereSSE(str);\n      case \"bedrock\":\n        return collapseAnthropicSSE(str);\n      default:\n        logger?.warn(\n          `[stream-collapse] unknown SSE provider \"${providerKey}\", falling back to OpenAI SSE format`,\n        );\n        return collapseOpenAISSE(str);\n    }\n  }\n\n  return null;\n}\n"],"mappings":"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"}