{"version":3,"file":"recorder.cjs","names":["resolveUpstreamUrl","collapseStreamingResponse","crypto","path","https","http","getLastMessageByRole","getTextContent"],"sources":["../src/recorder.ts"],"sourcesContent":["import * as http from \"node:http\";\nimport * as https from \"node:https\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as crypto from \"node:crypto\";\nimport type {\n  ChatCompletionRequest,\n  Fixture,\n  FixtureResponse,\n  RecordConfig,\n  RecordProviderKey,\n  ToolCall,\n} from \"./types.js\";\nimport { getLastMessageByRole, getTextContent } from \"./router.js\";\nimport type { Logger } from \"./logger.js\";\nimport { collapseStreamingResponse } from \"./stream-collapse.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport { resolveUpstreamUrl } from \"./url.js\";\n\n/** Headers to strip when proxying — hop-by-hop (RFC 2616 §13.5.1) + client-set. */\nconst STRIP_HEADERS = new Set([\n  // Hop-by-hop (RFC 2616 §13.5.1)\n  \"connection\",\n  \"keep-alive\",\n  \"transfer-encoding\",\n  \"te\",\n  \"trailer\",\n  \"upgrade\",\n  \"proxy-authorization\",\n  \"proxy-authenticate\",\n  // Set by HTTP client from the target URL / body\n  \"host\",\n  \"content-length\",\n  // Not relevant for LLM APIs; avoid leaking or mismatched encoding\n  \"cookie\",\n  \"accept-encoding\",\n]);\n\n/**\n * Proxy an unmatched request to the real upstream provider, record the\n * response as a fixture on disk and in memory, then relay the response\n * back to the original client.\n *\n * Returns `true` if the request was proxied (provider configured),\n * `false` if no upstream URL is configured for the given provider key.\n */\nexport async function proxyAndRecord(\n  req: http.IncomingMessage,\n  res: http.ServerResponse,\n  request: ChatCompletionRequest,\n  providerKey: RecordProviderKey,\n  pathname: string,\n  fixtures: Fixture[],\n  defaults: {\n    record?: RecordConfig;\n    logger: Logger;\n    requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest;\n  },\n  rawBody?: string,\n): Promise<boolean> {\n  const record = defaults.record;\n  if (!record) return false;\n\n  const providers = record.providers;\n  // gemini-interactions shares the same upstream config as gemini\n  const lookupKey = providerKey === \"gemini-interactions\" ? \"gemini\" : providerKey;\n  const upstreamUrl = providers[lookupKey];\n\n  if (!upstreamUrl) {\n    defaults.logger.warn(`No upstream URL configured for provider \"${providerKey}\" — cannot proxy`);\n    return false;\n  }\n\n  const fixturePath = record.fixturePath ?? \"./fixtures/recorded\";\n  let target: URL;\n  try {\n    target = resolveUpstreamUrl(upstreamUrl, pathname);\n  } catch {\n    defaults.logger.error(`Invalid upstream URL for provider \"${providerKey}\": ${upstreamUrl}`);\n    writeErrorResponse(\n      res,\n      502,\n      JSON.stringify({\n        error: { message: `Invalid upstream URL: ${upstreamUrl}`, type: \"proxy_error\" },\n      }),\n    );\n    return true;\n  }\n\n  defaults.logger.warn(`NO FIXTURE MATCH — proxying to ${upstreamUrl}${pathname}`);\n\n  // Forward all request headers except hop-by-hop and client-set ones.\n  const forwardHeaders: Record<string, string> = {};\n  for (const [name, val] of Object.entries(req.headers)) {\n    if (val !== undefined && !STRIP_HEADERS.has(name)) {\n      forwardHeaders[name] = Array.isArray(val) ? val.join(\", \") : val;\n    }\n  }\n\n  const requestBody = rawBody ?? JSON.stringify(request);\n\n  // Make upstream request\n  let upstreamStatus: number;\n  let upstreamHeaders: http.IncomingHttpHeaders;\n  let upstreamBody: string;\n  let rawBuffer: Buffer;\n\n  // Track whether we streamed SSE progressively to the client; if so,\n  // skip the final res.writeHead/res.end relay at the bottom of this fn.\n  let streamedToClient = false;\n  let clientDisconnected = false;\n  try {\n    const result = await makeUpstreamRequest(target, forwardHeaders, requestBody, res);\n    upstreamStatus = result.status;\n    upstreamHeaders = result.headers;\n    upstreamBody = result.body;\n    rawBuffer = result.rawBuffer;\n    streamedToClient = result.streamedToClient;\n    clientDisconnected = result.clientDisconnected;\n  } catch (err) {\n    const msg = err instanceof Error ? err.message : \"Unknown proxy error\";\n    defaults.logger.error(`Proxy request failed: ${msg}`);\n    if (!res.headersSent) {\n      res.writeHead(502, { \"Content-Type\": \"application/json\" });\n      res.end(\n        JSON.stringify({\n          error: { message: `Proxy to upstream failed: ${msg}`, type: \"proxy_error\" },\n        }),\n      );\n    } else {\n      // SSE headers already sent — gracefully close the connection\n      res.end();\n    }\n    return true;\n  }\n\n  // Detect streaming response and collapse if necessary\n  const contentType = upstreamHeaders[\"content-type\"];\n  const ctString = Array.isArray(contentType) ? contentType.join(\", \") : (contentType ?? \"\");\n  const isBinaryStream = ctString.toLowerCase().includes(\"application/vnd.amazon.eventstream\");\n  const collapsed = collapseStreamingResponse(\n    ctString,\n    providerKey,\n    isBinaryStream ? rawBuffer : upstreamBody,\n    defaults.logger,\n  );\n\n  let fixtureResponse: FixtureResponse;\n\n  // TTS response — binary audio, not JSON\n  const isAudioResponse = ctString.toLowerCase().startsWith(\"audio/\");\n  if (isAudioResponse && rawBuffer.length > 0) {\n    // Derive format from Content-Type (audio/mpeg→mp3, audio/opus→opus, etc.)\n    const audioFormat = ctString\n      .toLowerCase()\n      .replace(\"audio/\", \"\")\n      .replace(\"mpeg\", \"mp3\")\n      .split(\";\")[0]\n      .trim();\n    fixtureResponse = {\n      audio: rawBuffer.toString(\"base64\"),\n      ...(audioFormat && audioFormat !== \"mp3\" ? { format: audioFormat } : {}),\n    };\n  } else if (collapsed) {\n    // Streaming response — use collapsed result\n    defaults.logger.warn(`Streaming response detected (${ctString}) — collapsing to fixture`);\n    if (collapsed.truncated) {\n      defaults.logger.warn(\"Bedrock EventStream: CRC mismatch — response may be truncated\");\n    }\n    if (collapsed.droppedChunks && collapsed.droppedChunks > 0) {\n      defaults.logger.warn(`${collapsed.droppedChunks} chunk(s) dropped during stream collapse`);\n    }\n    // Audio from streamed inlineData (e.g. Gemini SSE with audio parts)\n    if (collapsed.audioB64) {\n      fixtureResponse = {\n        audio: {\n          b64Json: collapsed.audioB64,\n          contentType: collapsed.audioMimeType ?? \"audio/mpeg\",\n        },\n      };\n    } else if (\n      collapsed.content === \"\" &&\n      (!collapsed.toolCalls || collapsed.toolCalls.length === 0)\n    ) {\n      defaults.logger.warn(\"Stream collapse produced empty content — fixture may be incomplete\");\n      const reasoningSpread = collapsed.reasoning ? { reasoning: collapsed.reasoning } : {};\n      fixtureResponse = { content: collapsed.content ?? \"\", ...reasoningSpread };\n    } else {\n      const reasoningSpread = collapsed.reasoning ? { reasoning: collapsed.reasoning } : {};\n      if (collapsed.toolCalls && collapsed.toolCalls.length > 0) {\n        if (collapsed.content) {\n          // Both content and toolCalls present — save as ContentWithToolCallsResponse\n          fixtureResponse = {\n            content: collapsed.content,\n            toolCalls: collapsed.toolCalls,\n            ...reasoningSpread,\n          };\n        } else {\n          fixtureResponse = { toolCalls: collapsed.toolCalls, ...reasoningSpread };\n        }\n      } else {\n        fixtureResponse = { content: collapsed.content ?? \"\", ...reasoningSpread };\n      }\n    }\n  } else {\n    // Non-streaming — try to parse as JSON\n    let parsedResponse: unknown = null;\n    try {\n      parsedResponse = JSON.parse(upstreamBody);\n    } catch {\n      // Not JSON — could be an unknown format\n      defaults.logger.warn(\"Upstream response is not valid JSON — saving as error fixture\");\n    }\n    let encodingFormat: string | undefined;\n    try {\n      encodingFormat = rawBody ? JSON.parse(rawBody).encoding_format : undefined;\n    } catch (err) {\n      defaults.logger.debug(\n        `Could not parse encoding_format from raw body: ${err instanceof Error ? err.message : \"unknown error\"}`,\n      );\n    }\n    fixtureResponse = buildFixtureResponse(parsedResponse, upstreamStatus, encodingFormat);\n  }\n\n  // If the client disconnected mid-stream, the collected data is likely\n  // truncated.  Saving a partial fixture is worse than saving none — skip\n  // fixture persistence entirely.\n  if (clientDisconnected) {\n    defaults.logger.warn(\n      \"Client disconnected mid-stream — skipping fixture save to avoid truncated data\",\n    );\n    return true;\n  }\n\n  // Build the match criteria from the (optionally transformed) request\n  const matchRequest = defaults.requestTransform ? defaults.requestTransform(request) : request;\n  const fixtureMatch = buildFixtureMatch(matchRequest);\n\n  // Build and save the fixture\n  const fixture: Fixture = {\n    match: fixtureMatch,\n    response: fixtureResponse,\n  };\n\n  // Check if the match is empty (all undefined values) — warn but still save to disk\n  const matchValues = Object.values(fixtureMatch);\n  const isEmptyMatch = matchValues.length === 0 || matchValues.every((v) => v === undefined);\n  if (isEmptyMatch) {\n    defaults.logger.warn(\n      \"Recorded fixture has empty match criteria — skipping in-memory registration\",\n    );\n  }\n\n  // In proxy-only mode, skip recording to disk and in-memory caching\n  if (!defaults.record?.proxyOnly) {\n    const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n    const filename = `${providerKey}-${timestamp}-${crypto.randomUUID().slice(0, 8)}.json`;\n    const filepath = path.join(fixturePath, filename);\n\n    let writtenToDisk = false;\n    try {\n      // Ensure fixture directory exists\n      fs.mkdirSync(fixturePath, { recursive: true });\n\n      // Collect warnings for the fixture file\n      const warnings: string[] = [];\n      if (isEmptyMatch) {\n        warnings.push(\"Empty match criteria — this fixture will not match any request\");\n      }\n      if (collapsed?.truncated) {\n        warnings.push(\"Stream response was truncated — fixture may be incomplete\");\n      }\n\n      // Auth headers are forwarded to upstream but excluded from saved fixtures for security\n      const fileContent: Record<string, unknown> = { fixtures: [fixture] };\n      if (warnings.length > 0) {\n        fileContent._warning = warnings.join(\"; \");\n      }\n      fs.writeFileSync(filepath, JSON.stringify(fileContent, null, 2), \"utf-8\");\n      writtenToDisk = true;\n    } catch (err) {\n      const msg = err instanceof Error ? err.message : \"Unknown filesystem error\";\n      defaults.logger.error(`Failed to save fixture to disk: ${msg}`);\n      if (!res.headersSent) {\n        res.setHeader(\"X-LLMock-Record-Error\", msg);\n      } else {\n        defaults.logger.warn(`Cannot set X-LLMock-Record-Error header — headers already sent`);\n      }\n    }\n\n    if (writtenToDisk) {\n      // Register in memory so subsequent identical requests match (skip if empty match)\n      if (!isEmptyMatch) {\n        fixtures.push(fixture);\n      }\n      defaults.logger.warn(`Response recorded → ${filepath}`);\n    } else {\n      defaults.logger.warn(`Response relayed but NOT saved to disk — see error above`);\n    }\n  } else {\n    defaults.logger.info(`Proxied ${providerKey} request (proxy-only mode)`);\n  }\n\n  // Relay upstream response to client (skip when SSE was already streamed\n  // progressively by makeUpstreamRequest — headers and body are already on\n  // the wire).\n  if (!streamedToClient) {\n    const relayHeaders: Record<string, string> = {};\n    if (ctString) {\n      relayHeaders[\"Content-Type\"] = ctString;\n    }\n    res.writeHead(upstreamStatus, relayHeaders);\n    const isAudioRelay = ctString.toLowerCase().startsWith(\"audio/\");\n    res.end(isBinaryStream || isAudioRelay ? rawBuffer : upstreamBody);\n  }\n\n  return true;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction makeUpstreamRequest(\n  target: URL,\n  headers: Record<string, string>,\n  body: string,\n  clientRes?: http.ServerResponse,\n): Promise<{\n  status: number;\n  headers: http.IncomingHttpHeaders;\n  body: string;\n  rawBuffer: Buffer;\n  streamedToClient: boolean;\n  clientDisconnected: boolean;\n}> {\n  return new Promise((resolve, reject) => {\n    const transport = target.protocol === \"https:\" ? https : http;\n    const UPSTREAM_TIMEOUT_MS = 30_000;\n    const BODY_TIMEOUT_MS = 30_000;\n    const req = transport.request(\n      target,\n      {\n        method: \"POST\",\n        timeout: UPSTREAM_TIMEOUT_MS,\n        headers: {\n          ...headers,\n          \"Content-Length\": Buffer.byteLength(body).toString(),\n        },\n      },\n      (res) => {\n        res.setTimeout(BODY_TIMEOUT_MS, () => {\n          req.destroy(new Error(`Upstream response timed out after ${BODY_TIMEOUT_MS / 1000}s`));\n        });\n        // Detect Server-Sent Events so we can tee upstream chunks to the\n        // client as they arrive rather than buffering the entire stream and\n        // replaying it in a single res.end() at the bottom of proxyAndRecord.\n        // Buffering collapses every SSE frame into one client-visible write,\n        // which defeats progressive rendering in downstream consumers.\n        const ct = res.headers[\"content-type\"];\n        const ctStr = Array.isArray(ct) ? ct.join(\", \") : (ct ?? \"\");\n        const isSSE = ctStr.toLowerCase().includes(\"text/event-stream\");\n        let streamedToClient = false;\n        let clientDisconnected = false;\n        if (isSSE && clientRes && !clientRes.headersSent) {\n          const relayHeaders: Record<string, string> = {};\n          if (ctStr) relayHeaders[\"Content-Type\"] = ctStr;\n          clientRes.writeHead(res.statusCode ?? 200, relayHeaders);\n          // Flush headers immediately so the client starts parsing frames\n          // before the first data chunk arrives.\n          if (typeof clientRes.flushHeaders === \"function\") clientRes.flushHeaders();\n          streamedToClient = true;\n          // Stop relaying if the client disconnects mid-stream\n          clientRes.on(\"close\", () => {\n            clientDisconnected = true;\n            req.destroy();\n          });\n        }\n        const chunks: Buffer[] = [];\n        res.on(\"data\", (chunk: Buffer) => {\n          chunks.push(chunk);\n          if (\n            streamedToClient &&\n            clientRes &&\n            !clientDisconnected &&\n            !clientRes.destroyed &&\n            !clientRes.writableEnded\n          ) {\n            clientRes.write(chunk);\n          }\n        });\n        res.on(\"error\", reject);\n        res.on(\"end\", () => {\n          const rawBuffer = Buffer.concat(chunks);\n          if (\n            streamedToClient &&\n            clientRes &&\n            !clientDisconnected &&\n            !clientRes.destroyed &&\n            !clientRes.writableEnded\n          ) {\n            clientRes.end();\n          }\n          resolve({\n            status: res.statusCode ?? 500,\n            headers: res.headers,\n            body: rawBuffer.toString(),\n            rawBuffer,\n            streamedToClient,\n            clientDisconnected,\n          });\n        });\n      },\n    );\n    req.on(\"timeout\", () => {\n      req.destroy(\n        new Error(\n          `Upstream request timed out after ${UPSTREAM_TIMEOUT_MS / 1000}s: ${target.href}`,\n        ),\n      );\n    });\n    req.on(\"error\", reject);\n    req.write(body);\n    req.end();\n  });\n}\n\n/**\n * Detect the response format from the parsed upstream JSON and convert\n * it into an aimock FixtureResponse.\n */\nfunction buildFixtureResponse(\n  parsed: unknown,\n  status: number,\n  encodingFormat?: string,\n): FixtureResponse {\n  if (parsed === null || parsed === undefined) {\n    // Raw / unparseable response — save as error\n    return {\n      error: { message: \"Upstream returned non-JSON response\", type: \"proxy_error\" },\n      status,\n    };\n  }\n\n  const obj = parsed as Record<string, unknown>;\n\n  // Error response — only match the actual { error: { message: \"...\" } } shape\n  // used by OpenAI/Anthropic/etc., not arbitrary truthy `.error` fields.\n  if (\n    typeof obj.error === \"object\" &&\n    obj.error !== null &&\n    typeof (obj.error as Record<string, unknown>).message === \"string\"\n  ) {\n    const err = obj.error as Record<string, unknown>;\n    return {\n      error: {\n        message: String(err.message ?? \"Unknown error\"),\n        type: String(err.type ?? \"api_error\"),\n        code: err.code ? String(err.code) : undefined,\n      },\n      status,\n    };\n  }\n\n  // OpenAI embeddings: { data: [{ embedding: [...] }] }\n  if (Array.isArray(obj.data) && obj.data.length > 0) {\n    const first = obj.data[0] as Record<string, unknown>;\n    if (Array.isArray(first.embedding)) {\n      return { embedding: first.embedding as number[] };\n    }\n    if (typeof first.embedding === \"string\" && encodingFormat === \"base64\") {\n      const buf = Buffer.from(first.embedding, \"base64\");\n      if (buf.byteLength % 4 !== 0) {\n        // Malformed embedding — return a zero-dimension embedding fixture\n        return { embedding: [] };\n      }\n      const aligned = new Uint8Array(buf).buffer; // Always offset 0\n      const floats = new Float32Array(aligned, 0, buf.byteLength / 4);\n      return { embedding: Array.from(floats) };\n    }\n    // OpenAI image generation: { created, data: [{ url, b64_json, revised_prompt }] }\n    if (first.url || first.b64_json) {\n      const images = (obj.data as Array<Record<string, unknown>>).map((item) => ({\n        ...(item.url ? { url: String(item.url) } : {}),\n        ...(item.b64_json ? { b64Json: String(item.b64_json) } : {}),\n        ...(item.revised_prompt ? { revisedPrompt: String(item.revised_prompt) } : {}),\n      }));\n      if (images.length === 1) {\n        return { image: images[0] };\n      }\n      return { images };\n    }\n  }\n\n  // Gemini Imagen: { predictions: [...] }\n  if (Array.isArray(obj.predictions)) {\n    const images = (obj.predictions as Array<Record<string, unknown>>).map((p) => ({\n      ...(p.bytesBase64Encoded ? { b64Json: String(p.bytesBase64Encoded) } : {}),\n      ...(p.mimeType ? { mimeType: String(p.mimeType) } : {}),\n    }));\n    if (images.length === 1) {\n      return { image: images[0] };\n    }\n    return { images };\n  }\n\n  // OpenAI transcription: { text: \"...\", ... }\n  if (\n    typeof obj.text === \"string\" &&\n    (obj.task === \"transcribe\" || obj.language !== undefined || obj.duration !== undefined)\n  ) {\n    return {\n      transcription: {\n        text: obj.text as string,\n        ...(obj.language ? { language: String(obj.language) } : {}),\n        ...(obj.duration !== undefined ? { duration: Number(obj.duration) } : {}),\n        ...(Array.isArray(obj.words) ? { words: obj.words } : {}),\n        ...(Array.isArray(obj.segments) ? { segments: obj.segments } : {}),\n      },\n    };\n  }\n\n  // Gemini Interactions: { id, status, outputs: [{ type: \"text\", text }, { type: \"function_call\", name, arguments }] }\n  if (Array.isArray(obj.outputs) && obj.outputs.length > 0) {\n    const outputs = obj.outputs as Array<Record<string, unknown>>;\n    const fnCallOutputs = outputs.filter((o) => o.type === \"function_call\");\n    const textOutputs = outputs.filter((o) => o.type === \"text\" && typeof o.text === \"string\");\n    const hasToolCalls = fnCallOutputs.length > 0;\n    const joinedText = textOutputs.map((o) => String(o.text ?? \"\")).join(\"\");\n    const hasContent = joinedText.length > 0;\n\n    if (hasToolCalls) {\n      const toolCalls: ToolCall[] = fnCallOutputs.map((o) => ({\n        name: String(o.name),\n        arguments: typeof o.arguments === \"string\" ? o.arguments : JSON.stringify(o.arguments),\n        ...(o.id ? { id: String(o.id) } : {}),\n      }));\n      if (hasContent) {\n        return { content: joinedText, toolCalls };\n      }\n      return { toolCalls };\n    }\n    if (hasContent) {\n      return { content: joinedText };\n    }\n    // Recognized Gemini Interactions shape but empty content\n    return { content: \"\" };\n  }\n\n  // OpenAI video generation: { id, status, ... }\n  // Guard against false positives: many API responses have `id` + `status` fields\n  // (e.g. chat completions, Anthropic messages). Reject if the response has fields\n  // that indicate a known non-video format.\n  if (\n    typeof obj.id === \"string\" &&\n    typeof obj.status === \"string\" &&\n    (obj.status === \"completed\" || obj.status === \"in_progress\" || obj.status === \"failed\") &&\n    !(\"choices\" in obj) &&\n    !(\"content\" in obj) &&\n    !(\"candidates\" in obj) &&\n    !(\"message\" in obj) &&\n    !(\"data\" in obj) &&\n    !(\"object\" in obj) &&\n    !(\"outputs\" in obj)\n  ) {\n    if (obj.status === \"completed\" && obj.url) {\n      return {\n        video: {\n          id: String(obj.id),\n          status: \"completed\" as const,\n          url: String(obj.url),\n        },\n      };\n    }\n    return {\n      video: {\n        id: String(obj.id),\n        status: obj.status === \"failed\" ? (\"failed\" as const) : (\"processing\" as const),\n      },\n    };\n  }\n\n  // Direct embedding: { embedding: [...] }\n  if (Array.isArray(obj.embedding)) {\n    return { embedding: obj.embedding as number[] };\n  }\n\n  // OpenAI chat completion: { choices: [{ message: { content, tool_calls } }] }\n  if (Array.isArray(obj.choices) && obj.choices.length > 0) {\n    const choice = obj.choices[0] as Record<string, unknown>;\n    const message = choice.message as Record<string, unknown> | undefined;\n    if (message) {\n      const hasToolCalls = Array.isArray(message.tool_calls) && message.tool_calls.length > 0;\n      const hasContent = typeof message.content === \"string\" && message.content.length > 0;\n\n      const openaiReasoning =\n        typeof message.reasoning_content === \"string\" && message.reasoning_content.length > 0\n          ? message.reasoning_content\n          : undefined;\n\n      if (hasToolCalls) {\n        const toolCalls: ToolCall[] = (message.tool_calls as Array<Record<string, unknown>>).map(\n          (tc) => {\n            const fn = tc.function as Record<string, unknown>;\n            return {\n              name: String(fn.name),\n              arguments: String(fn.arguments),\n              ...(tc.id ? { id: String(tc.id) } : {}),\n            };\n          },\n        );\n        if (hasContent) {\n          return {\n            content: message.content as string,\n            toolCalls,\n            ...(openaiReasoning ? { reasoning: openaiReasoning } : {}),\n          };\n        }\n        return { toolCalls, ...(openaiReasoning ? { reasoning: openaiReasoning } : {}) };\n      }\n      // Text content only\n      if (hasContent) {\n        return {\n          content: message.content as string,\n          ...(openaiReasoning ? { reasoning: openaiReasoning } : {}),\n        };\n      }\n      // Recognized OpenAI shape but empty content (e.g. content filtering, zero max_tokens)\n      return { content: \"\", ...(openaiReasoning ? { reasoning: openaiReasoning } : {}) };\n    }\n  }\n\n  // Anthropic: { content: [{ type: \"text\", text: \"...\" }] } or tool_use\n  if (Array.isArray(obj.content) && obj.content.length > 0) {\n    const blocks = obj.content as Array<Record<string, unknown>>;\n    const toolUseBlocks = blocks.filter((b) => b.type === \"tool_use\");\n    const textBlocks = blocks.filter((b) => b.type === \"text\" && typeof b.text === \"string\");\n    const thinkingBlocks = blocks.filter((b) => b.type === \"thinking\");\n    const hasToolCalls = toolUseBlocks.length > 0;\n    const joinedText = textBlocks.map((b) => String(b.text ?? \"\")).join(\"\");\n    const hasContent = joinedText.length > 0;\n    const anthropicReasoning =\n      thinkingBlocks.length > 0\n        ? thinkingBlocks.map((b) => String(b.thinking ?? \"\")).join(\"\")\n        : undefined;\n\n    if (hasToolCalls) {\n      const toolCalls: ToolCall[] = toolUseBlocks.map((b) => ({\n        name: String(b.name),\n        arguments: typeof b.input === \"string\" ? b.input : JSON.stringify(b.input),\n        ...(b.id ? { id: String(b.id) } : {}),\n      }));\n      if (hasContent) {\n        return {\n          content: joinedText,\n          toolCalls,\n          ...(anthropicReasoning ? { reasoning: anthropicReasoning } : {}),\n        };\n      }\n      return { toolCalls, ...(anthropicReasoning ? { reasoning: anthropicReasoning } : {}) };\n    }\n    if (hasContent) {\n      return {\n        content: joinedText,\n        ...(anthropicReasoning ? { reasoning: anthropicReasoning } : {}),\n      };\n    }\n    // Thinking-only response (no text, no tool calls)\n    if (anthropicReasoning) {\n      return { content: \"\", reasoning: anthropicReasoning };\n    }\n  }\n\n  // Gemini: { candidates: [{ content: { parts: [{ text: \"...\" }] } }] }\n  if (Array.isArray(obj.candidates) && obj.candidates.length > 0) {\n    const candidate = obj.candidates[0] as Record<string, unknown>;\n    const content = candidate.content as Record<string, unknown> | undefined;\n    if (content && Array.isArray(content.parts)) {\n      const parts = content.parts as Array<Record<string, unknown>>;\n\n      // Audio inlineData parts take priority over text\n      const audioParts = parts.filter(\n        (p: Record<string, unknown>) =>\n          p.inlineData &&\n          typeof (p.inlineData as Record<string, unknown>).mimeType === \"string\" &&\n          ((p.inlineData as Record<string, unknown>).mimeType as string).startsWith(\"audio/\"),\n      );\n      if (audioParts.length > 0) {\n        const inlineData = audioParts[0].inlineData as Record<string, unknown>;\n        return {\n          audio: {\n            b64Json: String(inlineData.data ?? \"\"),\n            contentType: String(inlineData.mimeType),\n          },\n        };\n      }\n\n      const fnCallParts = parts.filter((p) => p.functionCall);\n      const textParts = parts.filter((p) => typeof p.text === \"string\" && !p.thought);\n      const thoughtParts = parts.filter((p) => p.thought === true && typeof p.text === \"string\");\n      const hasToolCalls = fnCallParts.length > 0;\n      const joinedText = textParts.map((p) => String(p.text ?? \"\")).join(\"\");\n      const hasContent = joinedText.length > 0;\n      const geminiReasoning =\n        thoughtParts.length > 0\n          ? thoughtParts.map((p) => String(p.text ?? \"\")).join(\"\")\n          : undefined;\n\n      if (hasToolCalls) {\n        const toolCalls: ToolCall[] = fnCallParts.map((p) => {\n          const fc = p.functionCall as Record<string, unknown>;\n          return {\n            name: String(fc.name),\n            arguments: typeof fc.args === \"string\" ? fc.args : JSON.stringify(fc.args),\n          };\n        });\n        if (hasContent) {\n          return {\n            content: joinedText,\n            toolCalls,\n            ...(geminiReasoning ? { reasoning: geminiReasoning } : {}),\n          };\n        }\n        return { toolCalls, ...(geminiReasoning ? { reasoning: geminiReasoning } : {}) };\n      }\n      if (hasContent) {\n        return {\n          content: joinedText,\n          ...(geminiReasoning ? { reasoning: geminiReasoning } : {}),\n        };\n      }\n      // Recognized Gemini shape but empty content\n      return { content: \"\", ...(geminiReasoning ? { reasoning: geminiReasoning } : {}) };\n    }\n  }\n\n  // Bedrock Converse: { output: { message: { role, content: [{ text }, { toolUse }] } } }\n  if (obj.output && typeof obj.output === \"object\") {\n    const output = obj.output as Record<string, unknown>;\n    const msg = output.message as Record<string, unknown> | undefined;\n    if (msg && Array.isArray(msg.content)) {\n      const blocks = msg.content as Array<Record<string, unknown>>;\n      const toolUseBlocks = blocks.filter((b) => b.toolUse);\n      const textBlocks = blocks.filter((b) => typeof b.text === \"string\");\n      const reasoningBlocks = blocks.filter((b) => b.reasoningContent);\n      const hasToolCalls = toolUseBlocks.length > 0;\n      const joinedText = textBlocks.map((b) => String(b.text ?? \"\")).join(\"\");\n      const hasContent = joinedText.length > 0;\n      const bedrockReasoning =\n        reasoningBlocks.length > 0\n          ? reasoningBlocks\n              .map((b) => {\n                const rc = b.reasoningContent as Record<string, unknown>;\n                const rt = rc?.reasoningText as Record<string, unknown> | undefined;\n                return String(rt?.text ?? \"\");\n              })\n              .join(\"\")\n          : undefined;\n\n      if (hasToolCalls) {\n        const toolCalls: ToolCall[] = toolUseBlocks.map((b) => {\n          const tu = b.toolUse as Record<string, unknown>;\n          return {\n            name: String(tu.name ?? \"\"),\n            arguments: typeof tu.input === \"string\" ? tu.input : JSON.stringify(tu.input),\n            ...(tu.toolUseId ? { id: String(tu.toolUseId) } : {}),\n          };\n        });\n        if (hasContent) {\n          return {\n            content: joinedText,\n            toolCalls,\n            ...(bedrockReasoning ? { reasoning: bedrockReasoning } : {}),\n          };\n        }\n        return { toolCalls, ...(bedrockReasoning ? { reasoning: bedrockReasoning } : {}) };\n      }\n      if (hasContent) {\n        return {\n          content: joinedText,\n          ...(bedrockReasoning ? { reasoning: bedrockReasoning } : {}),\n        };\n      }\n      // Recognized Bedrock Converse shape but empty content\n      return { content: \"\", ...(bedrockReasoning ? { reasoning: bedrockReasoning } : {}) };\n    }\n  }\n\n  // Cohere v2 chat: { finish_reason: \"...\", message: { content: [{ type: \"text\", text: \"...\" }] } }\n  // Must come before Ollama since both have `message`, but Cohere has `finish_reason` at top level\n  // (not nested in `choices`) and `message.content` as an array of typed objects.\n  if (\n    typeof obj.finish_reason === \"string\" &&\n    obj.message &&\n    typeof obj.message === \"object\" &&\n    Array.isArray((obj.message as Record<string, unknown>).content)\n  ) {\n    const msg = obj.message as Record<string, unknown>;\n    const contentBlocks = msg.content as Array<Record<string, unknown>>;\n    const textBlock = contentBlocks.find((b) => b.type === \"text\" && typeof b.text === \"string\");\n    const hasContent = textBlock && typeof textBlock.text === \"string\" && textBlock.text.length > 0;\n    const toolCallBlocks = contentBlocks.filter((b) => b.type === \"tool_call\");\n\n    // Also check message-level tool_calls (Cohere v2 puts tool calls here, not in content blocks)\n    const msgToolCalls = Array.isArray(msg.tool_calls)\n      ? (msg.tool_calls as Array<Record<string, unknown>>)\n      : [];\n\n    if (toolCallBlocks.length > 0) {\n      const toolCalls: ToolCall[] = toolCallBlocks.map((b) => ({\n        name: String(b.name ?? (b.function as Record<string, unknown>)?.name ?? \"\"),\n        arguments:\n          typeof b.parameters === \"string\"\n            ? b.parameters\n            : typeof b.parameters === \"object\"\n              ? JSON.stringify(b.parameters)\n              : typeof (b.function as Record<string, unknown>)?.arguments === \"string\"\n                ? String((b.function as Record<string, unknown>).arguments)\n                : JSON.stringify((b.function as Record<string, unknown>)?.arguments),\n        ...(b.id ? { id: String(b.id) } : {}),\n      }));\n      if (hasContent) {\n        return { content: textBlock.text as string, toolCalls };\n      }\n      return { toolCalls };\n    }\n    if (msgToolCalls.length > 0) {\n      const toolCalls: ToolCall[] = msgToolCalls.map((tc) => {\n        const fn = tc.function as Record<string, unknown> | undefined;\n        return {\n          name: String(tc.name ?? fn?.name ?? \"\"),\n          arguments:\n            typeof tc.parameters === \"string\"\n              ? tc.parameters\n              : typeof tc.parameters === \"object\"\n                ? JSON.stringify(tc.parameters)\n                : typeof fn?.arguments === \"string\"\n                  ? String(fn.arguments)\n                  : JSON.stringify(fn?.arguments),\n          ...(tc.id ? { id: String(tc.id) } : {}),\n        };\n      });\n      if (hasContent) {\n        return { content: textBlock.text as string, toolCalls };\n      }\n      return { toolCalls };\n    }\n    if (hasContent) {\n      return { content: textBlock.text as string };\n    }\n  }\n\n  // Ollama: { message: { content: \"...\", tool_calls: [...] } }\n  if (obj.message && typeof obj.message === \"object\") {\n    const msg = obj.message as Record<string, unknown>;\n    const hasOllamaToolCalls = Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0;\n    const hasOllamaContent = typeof msg.content === \"string\" && msg.content.length > 0;\n\n    if (hasOllamaToolCalls) {\n      const toolCalls: ToolCall[] = (msg.tool_calls as Array<Record<string, unknown>>)\n        .filter((tc) => tc.function != null)\n        .map((tc) => {\n          const fn = tc.function as Record<string, unknown>;\n          return {\n            name: String(fn.name ?? \"\"),\n            arguments:\n              typeof fn.arguments === \"string\" ? fn.arguments : JSON.stringify(fn.arguments),\n          };\n        });\n      if (hasOllamaContent) {\n        return { content: msg.content as string, toolCalls };\n      }\n      return { toolCalls };\n    }\n    if (hasOllamaContent) {\n      return { content: msg.content as string };\n    }\n    // Ollama message with content array (like Cohere)\n    if (Array.isArray(msg.content) && msg.content.length > 0) {\n      const first = msg.content[0] as Record<string, unknown>;\n      if (typeof first.text === \"string\") {\n        return { content: first.text };\n      }\n    }\n  }\n\n  // Ollama /api/generate: { response: \"...\", done: true/false }\n  if (typeof obj.response === \"string\" && \"done\" in obj) {\n    return { content: obj.response };\n  }\n\n  // Fallback: unknown format — save as error\n  return {\n    error: {\n      message: \"Could not detect response format from upstream\",\n      type: \"proxy_error\",\n    },\n    status,\n  };\n}\n\n/**\n * Derive fixture match criteria from the original request.\n */\ntype EndpointType =\n  | \"chat\"\n  | \"image\"\n  | \"speech\"\n  | \"transcription\"\n  | \"video\"\n  | \"embedding\"\n  | \"audio-gen\"\n  | \"fal-audio\";\n\nfunction buildFixtureMatch(request: ChatCompletionRequest): {\n  userMessage?: string;\n  inputText?: string;\n  endpoint?: EndpointType;\n} {\n  const match: { userMessage?: string; inputText?: string; endpoint?: EndpointType } = {};\n\n  // Include endpoint type for multimedia fixtures\n  if (request._endpointType && request._endpointType !== \"chat\") {\n    match.endpoint = request._endpointType as EndpointType;\n  }\n\n  // Embedding request\n  if (request.embeddingInput) {\n    match.inputText = request.embeddingInput;\n    return match;\n  }\n\n  // Chat/multimedia request — match on the last user message\n  const lastUser = getLastMessageByRole(request.messages ?? [], \"user\");\n  if (lastUser) {\n    const text = getTextContent(lastUser.content);\n    if (text) {\n      match.userMessage = text;\n    }\n  }\n\n  return match;\n}\n"],"mappings":"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"}