{"version":3,"file":"recorder.cjs","names":["DEFAULT_TEST_ID","slugifyTestId","path","crypto","slugifyContext","fs","resolveUpstreamUrl","collapseStreamingResponse","getTestId","StringDecoder","https","http","capturedRedactedData","getLastMessageByRole","getTextContent","normalizeModelName"],"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 { StringDecoder } from \"node:string_decoder\";\nimport type {\n  ChatCompletionRequest,\n  Fixture,\n  FixtureMatch,\n  FixtureResponse,\n  RecordConfig,\n  RecordedTimings,\n  RecordProviderKey,\n  ToolCall,\n} from \"./types.js\";\nimport { getLastMessageByRole, getTextContent } from \"./router.js\";\nimport { normalizeModelName } from \"./model-utils.js\";\nimport type { Logger } from \"./logger.js\";\nimport { collapseStreamingResponse, capturedRedactedData } from \"./stream-collapse.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport { resolveUpstreamUrl } from \"./url.js\";\nimport { getTestId, slugifyTestId, slugifyContext } from \"./helpers.js\";\nimport { DEFAULT_TEST_ID } from \"./constants.js\";\n\n/** Headers to strip when proxying — hop-by-hop (RFC 2616 §13.5.1) + client-set. */\n/**\n * Default ceiling (bytes) for the in-memory proxy-path buffer. Chosen well\n * under V8's ~512 MiB max string length so `rawBuffer.toString()` /\n * stream-collapse never throws `RangeError: Invalid string length`, and so a\n * single huge proxied response cannot spike the heap unbounded. Overridable\n * via `RecordConfig.maxProxyBufferBytes` / `--max-proxy-buffer-bytes`.\n */\nexport const DEFAULT_MAX_PROXY_BUFFER_BYTES = 64 * 1024 * 1024; // 64 MiB\n\n/**\n * Default ceiling for the number of SSE/NDJSON/EventStream frames whose\n * per-frame state (`frameTimestamps`, parse buffers) aimock retains for a\n * single proxied response. Frame state is count-indexed, not byte-sized, so a\n * long-lived / never-ending stream accumulates `frameTimestamps` entries (and,\n * if a frame never completes, parse-buffer bytes) UNBOUNDED even when the byte\n * cap is generous — observed as multi-GB heap growth over many hours from a few\n * long nested-sub-agent streams. Tripping truncation on EITHER bytes OR frame\n * count bounds both. 5M frames is generous for any real response (a normal\n * completion is hundreds-to-thousands of frames) while still bounding a runaway\n * stream to ~tens of MB of frame state. Overridable via\n * `RecordConfig.maxProxyBufferFrames` / `--max-proxy-buffer-frames`.\n */\nexport const DEFAULT_MAX_PROXY_BUFFER_FRAMES = 5_000_000;\n\n/**\n * Absolute hard ceiling (bytes) for any in-memory proxy buffer, independent of\n * the configurable `maxProxyBufferBytes`. V8's maximum STRING length on 64-bit\n * is 2^29 - 1 (~512 MiB of UTF-16 code units), and the proxy buffer is\n * eventually stringified via `rawBuffer.toString()` for collapse/relay — so the\n * BYTE buffer must stay safely under that string limit or the toString throws\n * `RangeError: Invalid string length`. 256 MiB of bytes is well under the\n * ~512 MiB string-length boundary (these are different units — bytes vs UTF-16\n * code units — but 256 MiB of bytes can never decode to more than 256 Mi code\n * units, comfortably below 2^29 - 1). Used to clamp the configurable cap AND to\n * bound the non-progressive relay buffer that must be retained past a cap trip.\n */\nexport const PROXY_BUFFER_HARD_CEILING = 256 * 1024 * 1024; // 256 MiB\n\n/**\n * Test-only override of the effective hard ceiling. Lets the proxy-buffer\n * enforcement suite exercise the >hard-ceiling fail-loud path with a small\n * body instead of streaming 256 MiB. `undefined` (the default) uses the real\n * `PROXY_BUFFER_HARD_CEILING`. NEVER set from production code.\n */\nlet proxyBufferHardCeilingOverride: number | undefined;\n\n/** @internal test-only — see `proxyBufferHardCeilingOverride`. */\nexport function setProxyBufferHardCeilingForTests(value: number | undefined): void {\n  proxyBufferHardCeilingOverride = value;\n}\n\n/** Effective hard ceiling, honoring any active test-only override. */\nfunction effectiveHardCeiling(): number {\n  return proxyBufferHardCeilingOverride ?? PROXY_BUFFER_HARD_CEILING;\n}\n\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  // Mock-internal control headers — meaningless (and potentially confusing\n  // or leaky) on a real provider's wire. x-aimock-chaos-* is stripped by\n  // prefix in buildForwardHeaders below.\n  \"x-test-id\",\n  \"x-aimock-strict\",\n  \"x-aimock-context\",\n]);\n\n/**\n * Build the header set forwarded to an upstream provider from an incoming\n * request: everything except hop-by-hop, client-set, and mock-internal\n * headers (STRIP_HEADERS, plus the x-aimock-chaos-* prefix family). Shared\n * by the generic recorder proxy and the OpenRouter-video live lifecycle\n * proxy.\n */\nexport function buildForwardHeaders(req: http.IncomingMessage): Record<string, string> {\n  const out: Record<string, string> = {};\n  for (const [name, val] of Object.entries(req.headers)) {\n    const lower = name.toLowerCase();\n    if (val === undefined || STRIP_HEADERS.has(lower) || lower.startsWith(\"x-aimock-chaos-\")) {\n      continue;\n    }\n    out[name] = Array.isArray(val) ? val.join(\", \") : val;\n  }\n  return out;\n}\n\n/**\n * Captured upstream response, exposed to the `beforeWriteResponse` hook so\n * callers can decide whether to relay it or mutate it (e.g. chaos injection).\n */\nexport interface ProxyCapturedResponse {\n  status: number;\n  contentType: string;\n  body: Buffer;\n}\n\nexport interface ProxyOptions {\n  /**\n   * Called after the upstream response has been captured and recorded, but\n   * before the relay to the client. Contract when the hook returns `true`:\n   *   1. It wrote its own response body on `res`.\n   *   2. It journaled the outcome (proxyAndRecord will NOT journal it).\n   *   3. proxyAndRecord skips its default relay and returns `\"handled_by_hook\"`.\n   *\n   * Returning `false` (or omitting the hook) lets proxyAndRecord relay the\n   * upstream response normally and leaves journaling to the caller via the\n   * `\"relayed\"` outcome. Rejected promises propagate and leave the response\n   * unwritten.\n   *\n   * NOT invoked when the upstream response was streamed progressively to the\n   * client (SSE, NDJSON, or binary event streams) — the bytes are already on\n   * the wire and can't be mutated.\n   * Callers that need to observe the bypass should pass `onHookBypassed`.\n   */\n  beforeWriteResponse?: (response: ProxyCapturedResponse) => boolean | Promise<boolean>;\n  /**\n   * Called when `beforeWriteResponse` was provided but could not be invoked\n   * because the upstream response was streamed to the client progressively.\n   * The hook was rolled + wired but the bytes left before it could fire.\n   * Intended for observability (log/metric/journal annotation) — proxyAndRecord\n   * still returns `\"relayed\"`.\n   */\n  onHookBypassed?: (reason: \"sse_streamed\" | \"ndjson_streamed\" | \"binary_streamed\") => void;\n}\n\n/**\n * Outcome of a proxyAndRecord call, returned so the caller can decide whether\n * to journal, fall through, or stop — without sharing a mutable flag with the\n * `beforeWriteResponse` hook.\n *\n * - `\"not_configured\"` — no upstream URL for this provider; caller should fall\n *    through to its next branch (typically strict/404).\n * - `\"relayed\"` — the default code path wrote a response (upstream success or\n *    synthesized 502 error). Caller should journal the outcome.\n * - `\"handled_by_hook\"` — the hook wrote + journaled its own response. Caller\n *    should not double-journal.\n */\nexport type ProxyOutcome = \"not_configured\" | \"relayed\" | \"handled_by_hook\";\n\n/**\n * Result of `persistFixture`:\n * - `\"skipped\"` — proxy-only mode; the caller has nothing else to do.\n * - `\"written\"` — fixture saved to `filepath` and (unless the match was empty)\n *    registered into the in-memory cache so the next identical request matches.\n * - `\"failed\"` — filesystem write failed. Caller decides how to surface it\n *    (e.g. setting `X-AIMock-Record-Error` on a relay response).\n */\nexport type PersistFixtureResult =\n  | { kind: \"skipped\" }\n  | { kind: \"written\"; filepath: string }\n  | { kind: \"failed\"; error: string };\n\n/**\n * Make an arbitrary string safe to set as an HTTP header value. Node's\n * `res.setHeader` throws ERR_INVALID_CHAR on anything outside Latin-1 (plus\n * control characters) — and persist errors embed filesystem paths verbatim,\n * so a Unicode fixture path would turn a recoverable record failure into a\n * 500. Out-of-range characters are replaced with `?` rather than stripped so\n * the value's shape stays legible.\n */\nexport function sanitizeHeaderValue(value: string): string {\n  return value.replace(/[^\\t\\x20-\\x7e\\x80-\\xff]/g, \"?\");\n}\n\n/**\n * Resolve a `content-type` header value to a single string. Node's\n * `http.IncomingHttpHeaders` types `content-type` as `string | string[]`, and\n * a constructed/proxied header object CAN carry an array (e.g. duplicated\n * header lines surfaced by some HTTP stacks). `join(\", \")`-ing such an array\n * produces a malformed `Content-Type: application/json, text/html` value — pick\n * the FIRST element instead, which is the value the client should act on.\n * Returns `\"\"` when the header is absent or an empty array.\n */\nexport function pickContentType(contentType: string | string[] | undefined): string {\n  if (Array.isArray(contentType)) {\n    return contentType.length > 0 ? contentType[0] : \"\";\n  }\n  return contentType ?? \"\";\n}\n\n/**\n * Write a built fixture to disk (snapshot vs. timestamp file layout) and, when\n * the match is non-empty, register it in the in-memory cache so subsequent\n * identical requests match. Extracted from `proxyAndRecord` so the fal\n * queue-walk recorder (which makes multiple upstream calls before knowing the\n * final body) can share the same persistence behavior without re-implementing\n * snapshot-mode merging and warnings.\n */\nexport function persistFixture(opts: {\n  record: RecordConfig;\n  providerKey: RecordProviderKey;\n  testId: string;\n  fixture: Fixture;\n  fixtures: Fixture[];\n  warnings?: string[];\n  logger: Logger;\n}): PersistFixtureResult {\n  const { record, providerKey, testId, fixture, fixtures, warnings = [], logger } = opts;\n\n  // Match criteria with no userMessage / inputText / endpoint will not match\n  // any future request — warn, then save to disk for inspection but skip the\n  // in-memory registration so a defective fixture doesn't shadow real ones.\n  // turnIndex/hasToolResult are pure multi-turn disambiguators on their own.\n  const m = fixture.match;\n  const isEmptyMatch =\n    m.userMessage === undefined && m.inputText === undefined && m.endpoint === undefined;\n\n  if (record.proxyOnly) {\n    logger.info(`Proxied ${providerKey} request (proxy-only mode)`);\n    return { kind: \"skipped\" };\n  }\n\n  // Warned only past the proxy-only early-return: under proxy-only nothing is\n  // persisted or registered, so an empty match has no consequence there.\n  if (isEmptyMatch) {\n    logger.warn(\"Recorded fixture has empty match criteria — skipping in-memory registration\");\n  }\n\n  const fixturePath = record.fixturePath ?? \"./fixtures/recorded\";\n  let isSnapshotMode = testId !== DEFAULT_TEST_ID;\n  let filepath: string;\n  let mergeExisting = false;\n\n  if (isSnapshotMode) {\n    const slug = slugifyTestId(testId);\n    if (!slug) {\n      // Slug resolved to empty (e.g. testId was all punctuation) — fall back\n      // to timestamp-based recording so we still capture the fixture.\n      isSnapshotMode = false;\n      const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n      filepath = path.join(\n        fixturePath,\n        `${providerKey}-${timestamp}-${crypto.randomUUID().slice(0, 8)}.json`,\n      );\n    } else {\n      filepath = path.join(fixturePath, slug, `${providerKey}.json`);\n      mergeExisting = true;\n    }\n  } else {\n    const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n    const timestampFile = `${providerKey}-${timestamp}-${crypto.randomUUID().slice(0, 8)}.json`;\n    // The context becomes a directory segment, but it originates from the\n    // attacker-controllable `X-AIMock-Context` header — slugify it (mirroring\n    // testId above) so `../`, separators, and absolute prefixes can't escape\n    // the fixtures base dir. An empty slug → no context segment.\n    const contextSegment = fixture.match.context\n      ? slugifyContext(fixture.match.context)\n      : undefined;\n    filepath = contextSegment\n      ? path.join(fixturePath, contextSegment, timestampFile)\n      : path.join(fixturePath, timestampFile);\n  }\n\n  const fileWarnings = [\n    ...(isEmptyMatch ? [\"Empty match criteria — this fixture will not match any request\"] : []),\n    ...warnings,\n  ];\n\n  try {\n    fs.mkdirSync(path.dirname(filepath), { recursive: true });\n\n    // Auth headers are forwarded to upstream but excluded from saved fixtures.\n    // The persisted fixture is always the real upstream response, even when\n    // chaos later mutates the relay; replay must see what upstream said.\n    // Warnings are persisted as a `_warnings` JSON array (authoritative,\n    // non-fragmenting) AND a legacy \"; \"-joined `_warning` string (kept for\n    // backward-compatible readers). The array is the source of truth on a\n    // snapshot merge: a single warning that itself contains \"; \" (e.g.\n    // \"captured A; then B\") would be fragmented into two bogus entries if the\n    // joined string were split back apart on merge. Carrying the array forward\n    // avoids that round-trip fragmentation entirely.\n    let fileContent: { fixtures: unknown[]; _warning?: string; _warnings?: string[] };\n    if (mergeExisting && fs.existsSync(filepath)) {\n      try {\n        const existing = JSON.parse(fs.readFileSync(filepath, \"utf-8\"));\n        // Guard the spread: a non-array `fixtures` (e.g. a string from a\n        // hand-edited file) would silently spread into single characters and\n        // mangle the merged file. Treat it like the corrupt-JSON case below.\n        const existingFixtures = Array.isArray(existing.fixtures)\n          ? (existing.fixtures as unknown[])\n          : undefined;\n        if (existingFixtures === undefined && existing.fixtures !== undefined) {\n          logger.warn(\n            `Existing fixture file ${filepath} has a non-array \"fixtures\" — discarding it and starting fresh`,\n          );\n        }\n        fileContent = { fixtures: [...(existingFixtures ?? []), fixture] };\n        // Carry existing warnings forward — a later clean capture merging into\n        // the same snapshot file must not erase an earlier capture's warning\n        // (e.g. an over-cap b64 omission). Prefer the new `_warnings` array;\n        // fall back to the legacy \"; \"-joined `_warning` string for files\n        // written before this format change. The legacy split can still\n        // fragment an embedded-\"; \" warning in a pre-existing file, but new\n        // captures no longer create that hazard.\n        if (Array.isArray(existing._warnings)) {\n          fileWarnings.unshift(\n            ...(existing._warnings as unknown[]).filter((w): w is string => typeof w === \"string\"),\n          );\n        } else if (typeof existing._warning === \"string\" && existing._warning) {\n          fileWarnings.unshift(...existing._warning.split(\"; \"));\n        }\n      } catch (mergeErr) {\n        const msg = mergeErr instanceof Error ? mergeErr.message : \"unknown\";\n        logger.warn(`Could not read existing fixture file ${filepath} (${msg}) — overwriting`);\n        fileContent = { fixtures: [fixture] };\n      }\n    } else {\n      fileContent = { fixtures: [fixture] };\n    }\n    if (fileWarnings.length > 0) {\n      // Exact-duplicate warnings (e.g. repeated over-cap captures merging into\n      // the same snapshot file) collapse to one entry. Emit BOTH the\n      // authoritative array and the legacy joined string.\n      const dedupedWarnings = [...new Set(fileWarnings)];\n      fileContent._warnings = dedupedWarnings;\n      fileContent._warning = dedupedWarnings.join(\"; \");\n    }\n    // Atomic write: write to temp file then rename to avoid read-modify-write\n    // races. Keep synchronous — for streamed responses the HTTP reply is\n    // already on the wire, so async writes would race with callers checking\n    // the filesystem before the fixture has landed.\n    const tmpPath = filepath + \".tmp.\" + process.pid;\n    fs.writeFileSync(tmpPath, JSON.stringify(fileContent, null, 2), \"utf-8\");\n    fs.renameSync(tmpPath, filepath);\n\n    if (!isEmptyMatch) {\n      fixtures.push(fixture);\n    }\n    logger.warn(`Response recorded → ${filepath}`);\n    return { kind: \"written\", filepath };\n  } catch (err) {\n    const msg = err instanceof Error ? err.message : \"Unknown filesystem error\";\n    logger.error(`Failed to save fixture to disk: ${msg}`);\n    return { kind: \"failed\", error: msg };\n  }\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 */\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  options?: ProxyOptions,\n): Promise<ProxyOutcome> {\n  const record = defaults.record;\n  if (!record) return \"not_configured\";\n\n  const providers = record.providers;\n  // Gemini Interactions uses the same upstream API as Gemini (identical base URL\n  // and auth), so we remap the provider key to reuse the configured Gemini URL.\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 \"not_configured\";\n  }\n\n  let target: URL;\n  try {\n    target = resolveUpstreamUrl(upstreamUrl, pathname);\n  } catch (err) {\n    const msg = err instanceof Error ? err.message : String(err);\n    defaults.logger.error(\n      `Invalid upstream URL for provider \"${providerKey}\": ${upstreamUrl} (${msg})`,\n    );\n    writeErrorResponse(\n      res,\n      502,\n      JSON.stringify({\n        error: { message: `Invalid upstream URL: ${upstreamUrl}`, type: \"proxy_error\" },\n      }),\n    );\n    return \"relayed\";\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 = buildForwardHeaders(req);\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  let bufferTruncated = false;\n  let truncationCap: \"byte\" | \"frame\" | undefined;\n  let totalBytes = 0;\n  let hardCeilingExceeded = false;\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  let frameTimestamps: number[] = [];\n  let streamStartTime = 0;\n  const maxProxyBufferBytes = clampMaxBufferBytes(record.maxProxyBufferBytes);\n  const maxProxyBufferFrames = clampMaxBufferFrames(record.maxProxyBufferFrames);\n  try {\n    const result = await makeUpstreamRequest(\n      target,\n      forwardHeaders,\n      requestBody,\n      res,\n      req.method,\n      defaults.logger,\n      { upstreamTimeoutMs: record.upstreamTimeoutMs, bodyTimeoutMs: record.bodyTimeoutMs },\n      maxProxyBufferBytes,\n      maxProxyBufferFrames,\n    );\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    frameTimestamps = result.frameTimestamps;\n    streamStartTime = result.streamStartTime;\n    bufferTruncated = result.bufferTruncated;\n    truncationCap = result.truncationCap;\n    totalBytes = result.totalBytes;\n    hardCeilingExceeded = result.hardCeilingExceeded;\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      // Streaming headers (200) are already on the wire, so we cannot change the\n      // status — but we MUST NOT call res.end(), which the client reads as a\n      // clean EOF and treats as a complete (silently truncated) response.\n      // Destroy the connection instead so the client sees an aborted stream and\n      // can surface the upstream failure rather than acting on partial data.\n      res.destroy();\n    }\n    return \"relayed\";\n  }\n\n  // Buffer cap tripped: skip collapse + recording (the in-memory buffer can't\n  // be safely/faithfully journaled), but ALWAYS deliver a faithful response to\n  // the client. The cap means \"don't journal\", not \"don't answer\".\n  //  - PROGRESSIVE stream: the bytes were already teed to the client live, so\n  //    nothing more to write — just end if still open.\n  //  - NON-progressive response, UNDER the hard ceiling: makeUpstreamRequest\n  //    kept the FULL body (bounded by PROXY_BUFFER_HARD_CEILING) precisely so we\n  //    can relay it here. Relay the real body so a large single-shot JSON\n  //    (embeddings / fal / image-b64) reaches the client intact — but NORMALIZE\n  //    the status (success→200 / error→502) exactly like every other relay path\n  //    so upstream provider details don't leak through this branch alone.\n  //  - NON-progressive response, OVER the hard ceiling: the retained buffer is\n  //    PARTIAL (we stopped buffering at the ceiling and there is no live tee),\n  //    so relaying it would present a truncated body as success. FAIL LOUD with\n  //    a 502 instead — never deliver a silently-truncated body as 2xx.\n  if (bufferTruncated) {\n    const capDetail =\n      truncationCap === \"frame\"\n        ? `the ${maxProxyBufferFrames}-frame cap`\n        : `the ${maxProxyBufferBytes}-byte cap`;\n    if (!streamedToClient && hardCeilingExceeded && !res.headersSent) {\n      const ceilingMiB = Math.round(PROXY_BUFFER_HARD_CEILING / (1024 * 1024));\n      defaults.logger.error(\n        `Upstream response exceeded the ${ceilingMiB} MiB proxy hard ceiling (saw ${totalBytes} bytes) on a non-progressive body — cannot relay the full body and refusing to relay a truncated one; returning 502`,\n      );\n      res.writeHead(502, { \"Content-Type\": \"application/json\" });\n      res.end(\n        JSON.stringify({\n          error: {\n            message: `Upstream response exceeds ${ceilingMiB}MiB proxy ceiling`,\n            type: \"proxy_error\",\n          },\n        }),\n      );\n      return \"relayed\";\n    }\n    defaults.logger.warn(\n      `Upstream response exceeded ${capDetail} (saw ${totalBytes} bytes) — relayed to client, recording skipped`,\n    );\n    if (!streamedToClient && !res.headersSent) {\n      // Normalize the relayed status like the under-cap relay paths\n      // (success→200, error→502) so this branch does not leak a raw upstream\n      // 429/503/etc. The full real body (under the hard ceiling) is relayed.\n      const clientStatus = upstreamStatus >= 200 && upstreamStatus < 300 ? 200 : 502;\n      const relayHeaders: Record<string, string> = {};\n      const ct = upstreamHeaders[\"content-type\"];\n      const ctStr = Array.isArray(ct) ? ct.join(\", \") : (ct ?? \"\");\n      relayHeaders[\"Content-Type\"] = ctStr || \"application/json\";\n      res.writeHead(clientStatus, relayHeaders);\n      res.end(rawBuffer);\n    } else if (!res.writableEnded) {\n      res.end();\n    }\n    return \"relayed\";\n  }\n\n  // Detect streaming response and collapse if necessary.\n  // NOTE: collapse buffers the upstream body in memory up to maxProxyBufferBytes\n  // (see makeUpstreamRequest). Over-cap responses short-circuit above, so the\n  // buffer reaching here is bounded and safe to stringify/collapse.\n  const contentType = upstreamHeaders[\"content-type\"];\n  const ctString = pickContentType(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. Classify on the audio content-type\n  // when upstream succeeded (2xx), regardless of body length: a zero-length\n  // audio body is still an audio response (empty audio), NOT an opaque\n  // proxy_error — letting it fall through to the JSON path would mis-record it.\n  // A non-2xx audio response (error JSON in the body) is left to the JSON/error\n  // path below.\n  const isAudioResponse = ctString.toLowerCase().startsWith(\"audio/\");\n  const isAudioSuccess = upstreamStatus >= 200 && upstreamStatus < 300;\n  if (isAudioResponse && isAudioSuccess) {\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    if (rawBuffer.length === 0) {\n      defaults.logger.warn(\n        \"Audio response had a zero-length body — recording an empty audio fixture\",\n      );\n    }\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(\n        `${collapsed.droppedChunks} chunk(s) dropped during stream collapse${collapsed.firstDroppedSample ? ` — first: ${collapsed.firstDroppedSample}` : \"\"}`,\n      );\n    }\n    if (collapsed.harmonyUnparsed) {\n      defaults.logger.warn(\n        `Harmony tokens present but unparseable — content preserved verbatim${collapsed.harmonyNote ? ` (${collapsed.harmonyNote})` : \"\"}`,\n      );\n    }\n    // Audio from streamed inlineData (e.g. Gemini SSE with audio parts).\n    // A single Gemini turn can interleave audio with a functionCall and/or\n    // text/thought parts; preserve those companion modalities so the tool call\n    // / content / reasoning are not silently dropped when audio is present.\n    if (collapsed.audioB64) {\n      const audioToolCallsSpread =\n        collapsed.toolCalls && collapsed.toolCalls.length > 0\n          ? {\n              toolCalls: collapsed.toolCalls.map((tc) => ({\n                ...tc,\n                name: tc.name ?? \"\",\n                arguments: tc.arguments ?? \"{}\",\n              })),\n            }\n          : {};\n      const audioContentSpread = collapsed.content ? { content: collapsed.content } : {};\n      const audioReasoningSpread = collapsed.reasoning ? { reasoning: collapsed.reasoning } : {};\n      fixtureResponse = {\n        audio: {\n          b64Json: collapsed.audioB64,\n          contentType: collapsed.audioMimeType ?? \"audio/mpeg\",\n        },\n        ...audioToolCallsSpread,\n        ...audioContentSpread,\n        ...audioReasoningSpread,\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      // Carry the real Anthropic thinking-block signature only when reasoning is\n      // also present (a bare signature has nothing to attach to on replay), so a\n      // recorded thinking turn replays its actual signature instead of the\n      // round-trip-safe placeholder.\n      const reasoningSignatureSpread =\n        collapsed.reasoning && collapsed.reasoningSignature\n          ? { reasoningSignature: collapsed.reasoningSignature }\n          : {};\n      logDroppedReasoningSignature(\n        defaults.logger,\n        collapsed.reasoning,\n        collapsed.reasoningSignature,\n      );\n      // Redacted-thinking blocks carry their OWN encrypted reasoning, so they are\n      // carried independently of any plaintext `reasoning` (a turn can have only\n      // redacted thinking) so the recorded turn round-trips its redacted blocks.\n      const redactedThinkingSpread = collapsed.redactedThinking?.length\n        ? { redactedThinking: collapsed.redactedThinking }\n        : {};\n      const webSearchesSpread = collapsed.webSearches?.length\n        ? { webSearches: collapsed.webSearches }\n        : {};\n      fixtureResponse = {\n        content: collapsed.content ?? \"\",\n        ...reasoningSpread,\n        ...reasoningSignatureSpread,\n        ...redactedThinkingSpread,\n        ...webSearchesSpread,\n      };\n    } else {\n      const reasoningSpread = collapsed.reasoning ? { reasoning: collapsed.reasoning } : {};\n      // Carry the real Anthropic thinking-block signature only when reasoning is\n      // also present; see the empty-content branch above.\n      const reasoningSignatureSpread =\n        collapsed.reasoning && collapsed.reasoningSignature\n          ? { reasoningSignature: collapsed.reasoningSignature }\n          : {};\n      logDroppedReasoningSignature(\n        defaults.logger,\n        collapsed.reasoning,\n        collapsed.reasoningSignature,\n      );\n      // Redacted-thinking blocks carry their OWN encrypted reasoning, so they are\n      // carried independently of any plaintext `reasoning`; see the empty-content\n      // branch above.\n      const redactedThinkingSpread = collapsed.redactedThinking?.length\n        ? { redactedThinking: collapsed.redactedThinking }\n        : {};\n      const webSearchesSpread = collapsed.webSearches?.length\n        ? { webSearches: collapsed.webSearches }\n        : {};\n      if (collapsed.toolCalls && collapsed.toolCalls.length > 0) {\n        const sanitizedToolCalls = collapsed.toolCalls.map((tc) => ({\n          ...tc,\n          name: tc.name ?? \"\",\n          arguments: tc.arguments ?? \"{}\",\n        }));\n        if (collapsed.content) {\n          // Both content and toolCalls present — save as ContentWithToolCallsResponse\n          fixtureResponse = {\n            content: collapsed.content,\n            toolCalls: sanitizedToolCalls,\n            ...reasoningSpread,\n            ...reasoningSignatureSpread,\n            ...redactedThinkingSpread,\n            ...webSearchesSpread,\n          };\n        } else {\n          fixtureResponse = {\n            toolCalls: sanitizedToolCalls,\n            ...reasoningSpread,\n            ...reasoningSignatureSpread,\n            ...redactedThinkingSpread,\n            ...webSearchesSpread,\n          };\n        }\n      } else {\n        fixtureResponse = {\n          content: collapsed.content ?? \"\",\n          ...reasoningSpread,\n          ...reasoningSignatureSpread,\n          ...redactedThinkingSpread,\n          ...webSearchesSpread,\n        };\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 (parseErr) {\n      const msg = parseErr instanceof Error ? parseErr.message : \"unknown\";\n      defaults.logger.warn(\n        `Upstream response is not valid JSON (${msg}) — saving as error fixture`,\n      );\n    }\n    // fal.ai returns arbitrary, model-specific JSON shapes (images, video URLs,\n    // audio file objects, etc.). Round-trip the payload verbatim instead of\n    // letting buildFixtureResponse mis-classify it as ImageResponse / VideoResponse.\n    if (request._endpointType === \"fal\" && parsedResponse !== null) {\n      const obj = parsedResponse as Record<string, unknown>;\n      const isErrorShape =\n        typeof obj.error === \"object\" &&\n        obj.error !== null &&\n        typeof (obj.error as Record<string, unknown>).message === \"string\";\n      if (isErrorShape) {\n        const err = obj.error as Record<string, unknown>;\n        fixtureResponse = {\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: upstreamStatus,\n        };\n      } else {\n        fixtureResponse = { json: parsedResponse, status: upstreamStatus };\n      }\n    } else {\n      // NOTE: base64 embeddings are decoded unconditionally inside\n      // buildFixtureResponse regardless of the request's `encoding_format`, so\n      // there is no need to re-parse it here — it was a dead param.\n      fixtureResponse = buildFixtureResponse(parsedResponse, upstreamStatus, defaults.logger);\n    }\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 \"relayed\";\n  }\n\n  // Build RecordedTimings from frame timestamps captured during streaming.\n  // Requires at least 2 timestamps (first frame + at least one more) to\n  // produce meaningful timing data.\n  let recordedTimings: RecordedTimings | undefined;\n  if (frameTimestamps.length > 1) {\n    const ts = frameTimestamps;\n    recordedTimings = {\n      ttftMs: ts[0] - streamStartTime,\n      interChunkDelaysMs: ts.slice(1).map((t, i) => t - ts[i]),\n      totalDurationMs: ts[ts.length - 1] - streamStartTime,\n    };\n  }\n\n  const matchRequest = defaults.requestTransform ? defaults.requestTransform(request) : request;\n  const metadata = buildFixtureMetadata(request);\n  const fixture: Fixture = {\n    match: buildFixtureMatch(matchRequest, defaults.record),\n    response: fixtureResponse,\n    ...(recordedTimings && { recordedTimings }),\n    ...(metadata && { metadata }),\n  };\n\n  const persistWarnings: string[] = [];\n  if (collapsed?.truncated) {\n    persistWarnings.push(\"Stream response was truncated — fixture may be incomplete\");\n  }\n  const persistResult = persistFixture({\n    record,\n    providerKey,\n    testId: getTestId(req),\n    fixture,\n    fixtures,\n    warnings: persistWarnings,\n    logger: defaults.logger,\n  });\n  if (persistResult.kind === \"failed\") {\n    if (!res.headersSent) {\n      res.setHeader(\"X-AIMock-Record-Error\", sanitizeHeaderValue(persistResult.error));\n    } else {\n      defaults.logger.warn(`Cannot set X-AIMock-Record-Error header — headers already sent`);\n    }\n    defaults.logger.warn(`Response relayed but NOT saved to disk — see error above`);\n  }\n\n  // Relay upstream response to client (skip when the response was already\n  // streamed progressively by makeUpstreamRequest — headers and body are\n  // already on the wire).\n  if (streamedToClient) {\n    // The hook can't run because the body is already on the wire. Surface\n    // the bypass so the caller (typically the chaos layer) can record it —\n    // otherwise a configured chaos action silently no-ops on streamed traffic.\n    if (options?.beforeWriteResponse && options.onHookBypassed) {\n      const bypassReason: \"sse_streamed\" | \"ndjson_streamed\" | \"binary_streamed\" = isBinaryStream\n        ? \"binary_streamed\"\n        : ctString.toLowerCase().includes(\"application/x-ndjson\")\n          ? \"ndjson_streamed\"\n          : \"sse_streamed\";\n      try {\n        options.onHookBypassed(bypassReason);\n      } catch (err) {\n        defaults.logger.warn(\n          `onHookBypassed callback threw: ${err instanceof Error ? err.message : String(err)}`,\n        );\n      }\n    }\n  } else {\n    // Give the caller a chance to mutate or replace the response before relay.\n    // Used by the chaos layer to turn a successful proxy into a malformed body.\n    // `body` is the raw upstream bytes so binary payloads survive round-tripping.\n    if (options?.beforeWriteResponse) {\n      let handled: boolean | undefined;\n      try {\n        handled = await options.beforeWriteResponse({\n          status: upstreamStatus,\n          contentType: ctString,\n          body: rawBuffer,\n        });\n      } catch (err) {\n        const msg = err instanceof Error ? err.message : String(err);\n        throw new Error(`beforeWriteResponse hook failed for ${providerKey}: ${msg}`);\n      }\n      if (handled) return \"handled_by_hook\";\n    }\n\n    // Normalize status codes for the client: aimock acts as a gateway, so\n    // upstream provider details (429 rate-limits, 503 outages, etc.) should\n    // not leak. Successes → 200, errors → 502 (Bad Gateway).\n    const clientStatus = upstreamStatus >= 200 && upstreamStatus < 300 ? 200 : 502;\n    const isAudioRelay = ctString.toLowerCase().startsWith(\"audio/\");\n    // When an upstream error (non-2xx) is relayed for an audio endpoint, the\n    // body is typically a JSON error object — override the content-type so\n    // clients don't try to decode JSON as audio.\n    const relayHeaders: Record<string, string> = {};\n    const clientCt =\n      (clientStatus >= 200 && clientStatus < 300) || !isAudioRelay\n        ? ctString || \"application/json\"\n        : \"application/json\";\n    if (clientCt) {\n      relayHeaders[\"Content-Type\"] = clientCt;\n    }\n    res.writeHead(clientStatus, relayHeaders);\n    res.end(isBinaryStream || isAudioRelay ? rawBuffer : upstreamBody);\n  }\n\n  return \"relayed\";\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Decodes a sequence of byte chunks to UTF-8 text for SSE/NDJSON frame\n * splitting on the streamed-capture path. Wraps Node's StringDecoder so a\n * multibyte UTF-8 character (CJK, emoji, ...) whose bytes are split across a\n * TCP chunk boundary buffers across chunks instead of decoding to U+FFFD\n * replacement characters — decoding each chunk independently with\n * Buffer#toString() would corrupt the recorded frame text.\n */\nexport class StreamingFrameDecoder {\n  private decoder = new StringDecoder(\"utf8\");\n  /** Decode a chunk, holding back any trailing partial multibyte sequence. */\n  write(chunk: Buffer): string {\n    return this.decoder.write(chunk);\n  }\n  /** Flush any buffered bytes once the stream has ended. */\n  end(): string {\n    return this.decoder.end();\n  }\n}\n\n/**\n * Sanitize a configured timeout: non-finite or non-positive values fall back\n * to the default. Shared with the OpenRouter-video live lifecycle proxy so\n * `record.upstreamTimeoutMs` is clamped identically on every proxy path.\n */\nexport function clampTimeout(value: number | undefined, fallback: number): number {\n  if (value == null || !Number.isFinite(value) || value <= 0) return fallback;\n  return value;\n}\n\n/**\n * Sanitize the configured proxy-buffer cap: non-finite or non-positive values\n * fall back to the default. Also hard-clamps to just under V8's max string\n * length so a misconfigured large value can never permit an over-string-limit\n * buffer to reach `.toString()`.\n */\nexport function clampMaxBufferBytes(value: number | undefined): number {\n  if (value == null || !Number.isFinite(value) || value <= 0) {\n    return DEFAULT_MAX_PROXY_BUFFER_BYTES;\n  }\n  return Math.min(value, PROXY_BUFFER_HARD_CEILING);\n}\n\n/**\n * Sanitize the configured proxy-buffer frame cap: non-finite or non-positive\n * values fall back to the default. Bounds the count-indexed per-frame state\n * (`frameTimestamps` + parse buffers) that the byte cap cannot bound on its own.\n */\nexport function clampMaxBufferFrames(value: number | undefined): number {\n  // INTENTIONAL DIVERGENCE from journal-max's `0 = unbounded` convention: here\n  // 0 (and any non-positive / non-finite value) maps to the DEFAULT cap, never\n  // \"unbounded\". The frame cap exists as leak-safety for never-ending proxy\n  // streams; allowing 0 to disable it would reintroduce the exact unbounded\n  // per-frame-state growth this cap guards against. Do NOT make 0 mean\n  // unbounded.\n  if (value == null || !Number.isFinite(value) || value <= 0) {\n    return DEFAULT_MAX_PROXY_BUFFER_FRAMES;\n  }\n  return Math.floor(value);\n}\n\nfunction makeUpstreamRequest(\n  target: URL,\n  headers: Record<string, string>,\n  body: string,\n  clientRes?: http.ServerResponse,\n  method: string = \"POST\",\n  logger?: Logger,\n  timeouts?: Pick<RecordConfig, \"upstreamTimeoutMs\" | \"bodyTimeoutMs\">,\n  maxBufferBytes: number = DEFAULT_MAX_PROXY_BUFFER_BYTES,\n  maxBufferFrames: number = DEFAULT_MAX_PROXY_BUFFER_FRAMES,\n): Promise<{\n  status: number;\n  headers: http.IncomingHttpHeaders;\n  body: string;\n  rawBuffer: Buffer;\n  streamedToClient: boolean;\n  clientDisconnected: boolean;\n  frameTimestamps: number[];\n  streamStartTime: number;\n  /**\n   * True when the upstream response exceeded `maxBufferBytes`. The client still\n   * received every byte (the relay is independent of this buffer), but the\n   * in-memory buffer was capped, so `body`/`rawBuffer` are partial and the\n   * caller MUST skip collapse/recording.\n   */\n  bufferTruncated: boolean;\n  /**\n   * Which cap tripped truncation (`\"byte\"` or `\"frame\"`), or `undefined` when\n   * not truncated. Lets the caller log accurately which budget was exceeded\n   * rather than conflating the two.\n   */\n  truncationCap?: \"byte\" | \"frame\";\n  /** Total bytes seen from upstream (may exceed the buffered amount when capped). */\n  totalBytes: number;\n  /**\n   * True when a NON-progressive (non-teed) upstream body exceeded the proxy\n   * hard ceiling, so the retained `rawBuffer` is PARTIAL. The caller MUST fail\n   * loud (502) rather than relay this truncated body as a success status.\n   */\n  hardCeilingExceeded: boolean;\n}> {\n  return new Promise((resolve, reject) => {\n    const transport = target.protocol === \"https:\" ? https : http;\n    const UPSTREAM_TIMEOUT_MS = clampTimeout(timeouts?.upstreamTimeoutMs, 30_000);\n    const BODY_TIMEOUT_MS = clampTimeout(timeouts?.bodyTimeoutMs, 30_000);\n    // Capture the moment the request is dispatched (set just before\n    // `req.write` below). ttft/total durations are measured from request SEND,\n    // not from when upstream HEADERS arrive — basing them on headers-received\n    // time would exclude the upstream's time-to-first-byte and understate the\n    // recorded time-to-first-token.\n    let requestSendTime = 0;\n    const req = transport.request(\n      target,\n      {\n        method,\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 streaming content types 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 frame into one client-visible write,\n        // which defeats progressive rendering in downstream consumers and\n        // can trip HTTP idle timeouts on slow calls.\n        const ct = res.headers[\"content-type\"];\n        const ctStr = pickContentType(ct);\n        const ctLower = ctStr.toLowerCase();\n        const isSSE = ctLower.includes(\"text/event-stream\");\n        const isNDJSON = ctLower.includes(\"application/x-ndjson\");\n        const isBinaryEventStream = ctLower.includes(\"application/vnd.amazon.eventstream\");\n        const isProgressiveStream = isSSE || isNDJSON || isBinaryEventStream;\n        // SSE/NDJSON frame timing capture — timestamps each complete frame\n        // so proxyAndRecord can build RecordedTimings for the fixture.\n        const frameTimestamps: number[] = [];\n        // Measure from request send (see requestSendTime above), falling back to\n        // now only if it was somehow not set before the response callback fired.\n        const streamStartTime = requestSendTime || Date.now();\n        let frameBuffer = \"\";\n        // Decode chunks through a streaming-aware decoder so a multibyte UTF-8\n        // character split across a TCP chunk boundary buffers across chunks\n        // instead of decoding to U+FFFD replacement characters.\n        const frameDecoder = new StreamingFrameDecoder();\n        let binaryFrameBuffer = Buffer.alloc(0);\n\n        let streamedToClient = false;\n        let clientDisconnected = false;\n        if (isProgressiveStream && clientRes && !clientRes.headersSent) {\n          const relayHeaders: Record<string, string> = {\n            \"Cache-Control\": \"no-cache, no-transform\",\n            Connection: \"keep-alive\",\n            \"X-Accel-Buffering\": \"no\",\n          };\n          if (ctStr) relayHeaders[\"Content-Type\"] = ctStr;\n          // Normalize status codes for the client: aimock acts as a gateway,\n          // so upstream provider details should not leak.\n          // Successes → 200, errors → 502 (Bad Gateway).\n          const rawStatus = res.statusCode ?? 200;\n          const clientStatus = rawStatus >= 200 && rawStatus < 300 ? 200 : 502;\n          clientRes.writeHead(clientStatus, 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          // Check writableFinished to distinguish normal completion (where\n          // \"close\" also fires) from premature client disconnects.\n          clientRes.on(\"close\", () => {\n            if (!clientRes.writableFinished) {\n              clientDisconnected = true;\n              req.destroy();\n              // Stop in-flight buffering immediately rather than draining the\n              // upstream socket under backpressure: detach the data listener so\n              // no further chunks accumulate, and free what's already buffered.\n              // The promise still settles via the 'end'/'error'/'close' the\n              // destroyed request emits; collapse/recording is skipped because\n              // the client disconnected.\n              res.removeListener(\"data\", onUpstreamData);\n              chunks.length = 0;\n              bufferedBytes = 0;\n              frameTimestamps.length = 0;\n              frameBuffer = \"\";\n              binaryFrameBuffer = Buffer.alloc(0);\n            }\n          });\n        }\n        const chunks: Buffer[] = [];\n        // Bound the in-memory buffer used to collapse/journal the response so a\n        // single huge proxied stream can neither spike the heap nor build a\n        // string past V8's ~512 MiB limit (RangeError: Invalid string length).\n        // The client relay below is independent of this buffer, so capping it\n        // does NOT truncate what the client receives.\n        let bufferedBytes = 0;\n        let totalBytes = 0;\n        let bufferTruncated = false;\n        /** Which cap tripped truncation — surfaced to the caller for an accurate warning. */\n        let truncationCap: \"byte\" | \"frame\" | undefined;\n        // Snapshot the effective hard ceiling once for this request (honors the\n        // test-only override). Bounds the non-progressive relay buffer; when the\n        // body would exceed it we can neither buffer the full copy nor safely\n        // relay the partial one, so the caller fails loud instead of truncating.\n        const hardCeiling = effectiveHardCeiling();\n        /**\n         * True when a NON-progressive upstream body exceeded `hardCeiling`, so\n         * the buffered `rawBuffer` is a PARTIAL copy that must NOT be relayed as\n         * success. Distinct from `bufferTruncated` (which also covers the\n         * under-ceiling over-soft-cap case where the full body IS retained).\n         */\n        let hardCeilingExceeded = false;\n        // Trip truncation: mark the response over-cap so the caller skips\n        // collapse/recording, and eagerly drop the accumulated PARSE state\n        // (frameTimestamps + frame/binary parse buffers) which only ever feeds\n        // recording. Reused by the top-of-callback byte guard AND the per-frame\n        // guards inside the SSE/NDJSON and binary splitter loops, so a single\n        // coalesced chunk carrying many complete frames cannot overshoot the\n        // frame cap before the next data event re-checks.\n        //\n        // The raw `chunks` array is handled differently by stream shape:\n        //  - progressive streams (SSE/NDJSON/binary) are teed to the client\n        //    live, so the bytes are already on the wire — `chunks` is freed\n        //    immediately and the partial buffer is never relayed.\n        //  - non-progressive responses (a single non-stream body) are NOT teed;\n        //    the only copy the client can receive is `chunks`, so we KEEP\n        //    accumulating it (bounded by HARD_CEILING below) and relay it in\n        //    full. We still skip recording — the cap means \"don't journal\", not\n        //    \"don't answer the client\".\n        const tripTruncation = (cap: \"byte\" | \"frame\") => {\n          bufferTruncated = true;\n          truncationCap = cap;\n          frameTimestamps.length = 0;\n          frameBuffer = \"\";\n          // Intentional: the returned flush string is discarded (frameBuffer was\n          // just cleared and recording is skipped), but `.end()` releases any\n          // partial-multibyte bytes the StringDecoder is internally holding — so\n          // this frees decoder state rather than being dead cleanup.\n          frameDecoder.end();\n          binaryFrameBuffer = Buffer.alloc(0);\n          if (isProgressiveStream) {\n            chunks.length = 0;\n            bufferedBytes = 0;\n          }\n          // State which cap tripped accurately (do not conflate the two caps),\n          // and report the relay truthfully — the client received every byte on\n          // the streamed path and, post-fix, on the non-streamed path too.\n          const detail =\n            cap === \"byte\"\n              ? `byte cap (${maxBufferBytes} bytes)`\n              : `frame cap (${maxBufferFrames} frames)`;\n          logger?.warn(\n            `Upstream response exceeded the proxy buffer ${detail} — relaying full body to client, but skipping in-memory collapse/recording to bound memory`,\n          );\n        };\n        const onUpstreamData = (chunk: Buffer) => {\n          totalBytes += chunk.length;\n          // Trip truncation on EITHER bytes OR frame count. The byte cap alone\n          // never bounds `frameTimestamps` (count-indexed, not byte-sized) nor a\n          // never-completing parse buffer, so a long-lived / never-ending stream\n          // would otherwise grow per-frame state forever. `frameTimestamps.length`\n          // is the running complete-frame count. `>=` (not `>`) so we never\n          // retain MORE than `maxBufferFrames` frames — see the \"maximum N\n          // frames retained\" contract on DEFAULT_MAX_PROXY_BUFFER_FRAMES.\n          if (!bufferTruncated && bufferedBytes + chunk.length > maxBufferBytes) {\n            tripTruncation(\"byte\");\n          } else if (!bufferTruncated && frameTimestamps.length >= maxBufferFrames) {\n            tripTruncation(\"frame\");\n          }\n          // Buffer the raw bytes. Under cap: always. Over cap on a progressive\n          // stream: never (bytes are already teed live; chunks was freed). Over\n          // cap on a NON-progressive response: keep buffering for the relay —\n          // it has no live tee, so `chunks` is the only copy the client can get\n          // — but hard-cap it at HARD_CEILING (well under V8's max string\n          // length) so the eventual rawBuffer.toString() relay can never throw\n          // RangeError: Invalid string length.\n          if (!bufferTruncated) {\n            chunks.push(chunk);\n            bufferedBytes += chunk.length;\n          } else if (!isProgressiveStream) {\n            if (bufferedBytes + chunk.length <= hardCeiling) {\n              chunks.push(chunk);\n              bufferedBytes += chunk.length;\n            } else {\n              // The non-progressive relay copy would exceed the hard ceiling.\n              // We CANNOT relay the full body (it can't be buffered safely) and\n              // MUST NOT relay the partial buffer as a success — flag it so the\n              // caller fails loud (502) instead of presenting a truncated 2xx.\n              hardCeilingExceeded = true;\n            }\n          }\n\n          // Capture per-frame timestamps for SSE/NDJSON streams. Gated on\n          // !bufferTruncated so per-frame parse/timing state stops growing once\n          // the cap trips (the byte/frame guard above already freed it).\n          // TCP data events don't align with SSE frames — buffer and\n          // split on the protocol delimiter to timestamp each complete frame.\n          if (!bufferTruncated && (isSSE || isNDJSON)) {\n            frameBuffer += frameDecoder.write(chunk);\n            // Split on the protocol delimiter, tolerating CRLF line endings.\n            // The SSE spec permits CRLF, and some upstreams/proxies emit\n            // \\r\\n\\r\\n (SSE) or \\r\\n (NDJSON) frame boundaries. An LF-only\n            // split would see the whole CRLF stream as a single frame and\n            // lose per-frame timing. The last split element (a partial frame\n            // tail) stays buffered, exactly as with a string delimiter.\n            const delimiter = isNDJSON ? /\\r?\\n/ : /\\r?\\n\\r?\\n/;\n            const parts = frameBuffer.split(delimiter);\n            // All complete frames (everything except the last part which\n            // may be incomplete). Enforce the frame cap PER-FRAME: a single\n            // coalesced chunk can carry many complete frames, so checking only\n            // at the top of the callback would let one event push them all and\n            // overshoot the cap unbounded. Trip + bail mid-loop instead.\n            for (let fi = 0; fi < parts.length - 1; fi++) {\n              if (frameTimestamps.length >= maxBufferFrames) {\n                tripTruncation(\"frame\");\n                break;\n              }\n              if (parts[fi].trim().length > 0) {\n                frameTimestamps.push(Date.now());\n              }\n            }\n            // Last part stays in buffer (may be incomplete). Skip when the\n            // per-frame guard just tripped — tripTruncation already cleared it.\n            if (!bufferTruncated) {\n              frameBuffer = parts[parts.length - 1];\n            }\n          }\n\n          // Binary EventStream frame boundary detection — parse the 4-byte\n          // total-length prefix to detect complete frames without decoding\n          // frame contents (CRC validation happens in stream-collapse).\n          // Also gated on !bufferTruncated so binaryFrameBuffer stops growing\n          // once the cap trips.\n          if (!bufferTruncated && isBinaryEventStream) {\n            // Count the binary parse buffer's growth toward the byte cap.\n            // binaryFrameBuffer is a SECOND, parallel copy of the bytes (the\n            // raw `chunks` array also holds them), so without this a\n            // never-completing / malformed (`totalLen<12`) frame — which pushes\n            // no frameTimestamps — would let a full second copy accumulate up\n            // to the byte cap, peaking at ~2× the configured cap. Trip on the\n            // COMBINED footprint so the byte cap bounds both copies together.\n            // NOTE: `bufferedBytes` ALREADY includes the current `chunk.length`\n            // (added in the raw-buffer accumulation above), so the combined\n            // footprint is `bufferedBytes + binaryFrameBuffer.length`. Adding\n            // `chunk.length` again would double-count it and trip the cap one\n            // chunk early (matching the L<byte-guard> accounting at the top).\n            if (bufferedBytes + binaryFrameBuffer.length > maxBufferBytes) {\n              tripTruncation(\"byte\");\n            } else {\n              binaryFrameBuffer = Buffer.concat([binaryFrameBuffer, chunk]);\n              while (binaryFrameBuffer.length >= 4) {\n                if (frameTimestamps.length >= maxBufferFrames) {\n                  // Per-frame cap: a single chunk can complete many binary\n                  // frames; trip mid-loop rather than overshoot.\n                  tripTruncation(\"frame\");\n                  break;\n                }\n                const totalLen = binaryFrameBuffer.readUInt32BE(0);\n                if (totalLen < 12 || binaryFrameBuffer.length < totalLen) break;\n                frameTimestamps.push(Date.now());\n                binaryFrameBuffer = binaryFrameBuffer.subarray(totalLen);\n              }\n            }\n          }\n\n          if (\n            streamedToClient &&\n            clientRes &&\n            !clientDisconnected &&\n            !clientRes.destroyed &&\n            !clientRes.writableEnded\n          ) {\n            try {\n              clientRes.write(chunk);\n            } catch (writeErr) {\n              logger?.debug(\n                `Failed to relay chunk to client: ${writeErr instanceof Error ? writeErr.message : \"unknown\"}`,\n              );\n              clientDisconnected = true;\n            }\n          }\n        };\n        res.on(\"data\", onUpstreamData);\n        res.on(\"error\", reject);\n        res.on(\"end\", () => {\n          if (res.socket) res.setTimeout(0);\n          // Flush remaining text frame buffer — captures the last frame if\n          // the stream ended without a trailing delimiter. Binary EventStream\n          // frames are length-prefixed so partial frames at end-of-stream are\n          // genuinely incomplete and should not be timestamped. Skipped when\n          // truncated: the decoder/parse buffer were already drained+cleared on\n          // the trip, and recording is skipped, so there is nothing to flush.\n          if (!bufferTruncated && (isSSE || isNDJSON)) {\n            // Drain any bytes the decoder buffered for an incomplete multibyte\n            // sequence so the final frame text is complete before we test it.\n            frameBuffer += frameDecoder.end();\n            if (frameBuffer.trim().length > 0) {\n              frameTimestamps.push(Date.now());\n            }\n          }\n          const rawBuffer = Buffer.concat(chunks);\n          if (\n            streamedToClient &&\n            clientRes &&\n            !clientDisconnected &&\n            !clientRes.destroyed &&\n            !clientRes.writableEnded\n          ) {\n            try {\n              clientRes.end();\n            } catch (endErr) {\n              logger?.debug(\n                `Failed to end client response: ${endErr instanceof Error ? endErr.message : \"unknown\"}`,\n              );\n            }\n          }\n          // Decide the string `body`:\n          //  - not truncated: stringify the full buffer as usual.\n          //  - truncated PROGRESSIVE stream: `chunks` was freed on the trip, so\n          //    rawBuffer is empty; skip toString to keep the path allocation-free\n          //    (the client already got every byte via the live tee).\n          //  - truncated NON-progressive response: we deliberately kept the full\n          //    bytes (bounded by HARD_CEILING) so they can be relayed — stringify\n          //    them so proxyAndRecord can `res.end(body)` the real response.\n          const bodyString =\n            !bufferTruncated || (bufferTruncated && !streamedToClient) ? rawBuffer.toString() : \"\";\n          resolve({\n            status: res.statusCode ?? 500,\n            headers: res.headers,\n            body: bodyString,\n            rawBuffer,\n            streamedToClient,\n            clientDisconnected,\n            frameTimestamps,\n            streamStartTime,\n            bufferTruncated,\n            truncationCap,\n            totalBytes,\n            hardCeilingExceeded,\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    // Stamp the send time immediately before dispatching the body so ttft and\n    // total-duration are measured from request send, not headers-received.\n    requestSendTime = Date.now();\n    req.write(body);\n    req.end();\n  });\n}\n\n/**\n * A captured Anthropic thinking-block signature is only persisted alongside\n * non-empty plaintext `reasoning` (a bare signature has nothing to attach to on\n * replay). When that gate drops a present signature, warn so the loss is\n * observable (matching the recording-side anomaly convention in this file).\n */\nfunction logDroppedReasoningSignature(\n  logger: Logger | undefined,\n  reasoning: string | undefined,\n  reasoningSignature: string | undefined,\n): void {\n  if (reasoningSignature && !reasoning) {\n    logger?.warn(\"Dropping captured reasoningSignature — no plaintext reasoning to attach it to\");\n  }\n}\n\n/**\n * Coerce a tool call's `arguments` field into the string the fixture contract\n * requires (types.ts `ToolCall.arguments: string`). When the upstream omits the\n * arguments field entirely, `JSON.stringify(undefined)` yields the JS value\n * `undefined` (not a string), which then gets dropped on disk-write and breaks\n * cross-provider replay through the OpenAI streaming path. Coalesce to \"{}\" so\n * `arguments` is ALWAYS a string, matching the streaming recorder's behavior.\n */\nfunction toToolCallArguments(raw: unknown): string {\n  if (typeof raw === \"string\") return raw;\n  if (raw === undefined || raw === null) return \"{}\";\n  return JSON.stringify(raw);\n}\n\n/**\n * Detect the response format from the parsed upstream JSON and convert\n * it into an aimock FixtureResponse.\n */\nfunction buildFixtureResponse(parsed: unknown, status: number, logger?: Logger): 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    // A string embedding is a base64-packed Float32 array. Decode it regardless\n    // of whether the request echoed `encoding_format: \"base64\"` — some providers\n    // return base64 without the client having asked for it, and gating on the\n    // request echo silently drops a valid embedding into the error fixture.\n    if (typeof first.embedding === \"string\") {\n      const buf = Buffer.from(first.embedding, \"base64\");\n      if (buf.byteLength === 0 || buf.byteLength % 4 !== 0) {\n        // Malformed base64 (not a whole number of Float32s). Don't silently\n        // return a valid-looking zero-dimension embedding — log it and fall\n        // through to the generic error fixture so the loss is diagnosable.\n        logger?.warn(\n          `Could not decode base64 embedding (byteLength=${buf.byteLength} is not a positive multiple of 4) — saving as error fixture`,\n        );\n      } else {\n        // Uint8Array constructor copies Buffer data to a fresh ArrayBuffer at offset 0,\n        // guaranteeing the alignment Float32Array requires.\n        const copied = new Uint8Array(buf);\n        const floats = new Float32Array(copied.buffer, 0, buf.byteLength / 4);\n        return { embedding: Array.from(floats) };\n      }\n    }\n    // OpenAI image generation: { created, data: [{ url, b64_json, revised_prompt }] }\n    // Enter the branch when ANY item carries media — not just data[0]. A batch\n    // whose first element lacks both url and b64_json (e.g. a partial/placeholder\n    // entry) but whose later element HAS one would otherwise skip the branch and\n    // fall through to the error fixture, silently dropping every captured image.\n    if ((obj.data as Array<Record<string, unknown>>).some((item) => item.url || item.b64_json)) {\n      // Map only items that actually carry media (url or b64_json). A later item\n      // lacking both — including one whose `b64_json` is an empty string — would\n      // otherwise produce an empty {} image entry (all the conditional spreads\n      // are skipped) — silent fidelity loss.\n      const dataItems = obj.data as Array<Record<string, unknown>>;\n      const images = dataItems\n        .filter((item) => item.url || item.b64_json)\n        .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      // Surface any dropped items (e.g. empty-string b64_json placeholders) so\n      // the fidelity loss is diagnosable rather than silent.\n      const droppedCount = dataItems.length - images.length;\n      if (droppedCount > 0) {\n        logger?.warn(\n          `Dropped ${droppedCount} image item(s) from batch lacking a non-empty url or b64_json`,\n        );\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  // Tightened: a bare `text` string alongside a single incidental `language` or\n  // `duration` field is too weak — many non-transcription payloads carry a\n  // `text` plus a `duration`-like number. Require an explicit\n  // `task: \"transcribe\"`, OR BOTH `language` and `duration` (the verbose\n  // transcription shape). Also reject anything carrying clear non-transcription\n  // markers (chat completions, events, etc.) so they route to their own branch.\n  const looksLikeTranscription =\n    typeof obj.text === \"string\" &&\n    (obj.task === \"transcribe\" || (obj.language !== undefined && obj.duration !== undefined)) &&\n    !(\"choices\" in obj) &&\n    !(\"candidates\" in obj) &&\n    !(\"object\" in obj) &&\n    !(\"message\" in obj) &&\n    !(\"outputs\" in obj);\n  if (looksLikeTranscription) {\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 (\n    Array.isArray(obj.outputs) &&\n    obj.outputs.length > 0 &&\n    !(\"choices\" in obj) &&\n    !(\"content\" in obj) &&\n    !(\"candidates\" in obj)\n  ) {\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: toToolCallArguments(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    !(\"model\" in obj) &&\n    !(\"response\" in obj) &&\n    !(\"done\" in obj) &&\n    !(\"usage\" in obj) &&\n    !(\"error\" 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      // Reasoning is exposed under different keys across OpenAI-compatible\n      // providers: OpenAI/vLLM use `reasoning_content`, while DeepSeek and\n      // OpenRouter use `reasoning`. Read both (preferring `reasoning_content`)\n      // so a reasoning turn is not silently dropped on the latter providers.\n      const openaiReasoning =\n        typeof message.reasoning_content === \"string\" && message.reasoning_content.length > 0\n          ? message.reasoning_content\n          : typeof message.reasoning === \"string\" && message.reasoning.length > 0\n            ? message.reasoning\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: toToolCallArguments(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    // Raw `redacted_thinking` block presence drives reasoning-bearing\n    // classification below (mirrors how `thinkingBlocks` keys on PRESENCE, not\n    // surviving payload). A turn whose redacted blocks all carry empty `data`\n    // is constructible upstream and must NOT fall through to the error fallback.\n    const redactedBlocks = blocks.filter((b) => b.type === \"redacted_thinking\");\n    // A `redacted_thinking` block carries its encrypted reasoning in an opaque\n    // `data` string; collect the SURVIVING (non-empty) payloads in content-array\n    // order for the persisted fixture so the recorded turn round-trips its\n    // redacted blocks (mirrors the streaming collapse path; see\n    // capturedRedactedData for the non-empty rule — empty-data blocks are dropped\n    // from the payload, but still count toward classification via redactedBlocks).\n    const redactedThinking = blocks\n      .map((b) => capturedRedactedData(b))\n      .filter((data): data is string => data !== undefined);\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    // The real cryptographic signature is carried only when reasoning is also\n    // present (a bare signature has nothing to attach to on replay), matching the\n    // streaming recorder's gating. Multi-thinking-block parity: collapseAnthropicSSE\n    // overwrites reasoningSignature on every signature_delta (last-signature-wins),\n    // and a thinking block that streams NO signature_delta leaves the prior value\n    // intact. Mirror both: take the LAST block that actually carries a signature, so\n    // a block missing one does not clobber an earlier signature.\n    const anthropicReasoningSignature = (() => {\n      let sig: string | undefined;\n      for (const b of thinkingBlocks) {\n        if (typeof b.signature === \"string\") sig = String(b.signature);\n      }\n      return sig;\n    })();\n    // Carry the real Anthropic thinking-block signature only when reasoning is\n    // also present; redacted blocks carry their OWN encrypted reasoning so they\n    // are carried independently of any plaintext `reasoning`. Both mirror the\n    // streaming spread gating in proxyAndRecord.\n    const reasoningSignatureSpread =\n      anthropicReasoning && anthropicReasoningSignature\n        ? { reasoningSignature: anthropicReasoningSignature }\n        : {};\n    logDroppedReasoningSignature(logger, anthropicReasoning, anthropicReasoningSignature);\n    const redactedThinkingSpread = redactedThinking.length > 0 ? { redactedThinking } : {};\n\n    if (hasToolCalls) {\n      const toolCalls: ToolCall[] = toolUseBlocks.map((b) => ({\n        name: String(b.name),\n        arguments: toToolCallArguments(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          ...reasoningSignatureSpread,\n          ...redactedThinkingSpread,\n        };\n      }\n      return {\n        toolCalls,\n        ...(anthropicReasoning ? { reasoning: anthropicReasoning } : {}),\n        ...reasoningSignatureSpread,\n        ...redactedThinkingSpread,\n      };\n    }\n    if (hasContent) {\n      return {\n        content: joinedText,\n        ...(anthropicReasoning ? { reasoning: anthropicReasoning } : {}),\n        ...reasoningSignatureSpread,\n        ...redactedThinkingSpread,\n      };\n    }\n    // Thinking-only / redacted-only response (no text, no tool calls). A turn can\n    // carry only thinking blocks (even ones whose plaintext is empty but which\n    // bear a real signature) or only redacted_thinking blocks (even ones whose\n    // `data` is empty and thus dropped from the persisted payload), so key on the\n    // PRESENCE of those RAW blocks — not the truthiness of the joined thinking text\n    // nor the post-filter `redactedThinking` array — and produce a normal\n    // empty-content fixture rather than falling through to the error fallback\n    // below. This matches the streaming path, which classifies on content\n    // emptiness for the same logical turn. (Per the persistence contract, a bare\n    // signature with empty reasoning is still dropped via\n    // logDroppedReasoningSignature, and empty-data redacted blocks yield NO\n    // redactedThinking field.)\n    if (thinkingBlocks.length > 0 || redactedBlocks.length > 0) {\n      return {\n        content: \"\",\n        ...(anthropicReasoning ? { reasoning: anthropicReasoning } : {}),\n        ...reasoningSignatureSpread,\n        ...redactedThinkingSpread,\n      };\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. Key on the PRESENCE of\n      // `inlineData.data` rather than solely on the mimeType: an audio part can\n      // arrive with a missing or unexpected mimeType, and filtering strictly on\n      // an `audio/` prefix would then drop a part that genuinely carries audio\n      // bytes, producing an empty b64Json. A NON-audio mimeType (e.g. `image/`)\n      // still routes elsewhere, so image inlineData is not misclassified — only\n      // an explicit `audio/` prefix OR a missing/blank mimeType counts as audio.\n      const audioParts = parts.filter((p: Record<string, unknown>) => {\n        const inline = p.inlineData as Record<string, unknown> | undefined;\n        if (\n          inline === undefined ||\n          inline === null ||\n          typeof inline.data !== \"string\" ||\n          inline.data.length === 0\n        ) {\n          return false;\n        }\n        const mt = inline.mimeType;\n        if (typeof mt === \"string\" && mt.length > 0) {\n          return mt.startsWith(\"audio/\");\n        }\n        // Missing/blank mimeType but data present → treat as audio.\n        return true;\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:\n              typeof inlineData.mimeType === \"string\" && inlineData.mimeType.length > 0\n                ? inlineData.mimeType\n                : \"audio/mpeg\",\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: toToolCallArguments(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: toToolCallArguments(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    // Join ALL text blocks, not just the first — a multi-block Cohere response\n    // would otherwise be truncated to its leading segment.\n    const joinedText = contentBlocks\n      .filter((b) => b.type === \"text\" && typeof b.text === \"string\")\n      .map((b) => String(b.text ?? \"\"))\n      .join(\"\");\n    const hasContent = joinedText.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                : toToolCallArguments((b.function as Record<string, unknown>)?.arguments),\n        ...(b.id ? { id: String(b.id) } : {}),\n      }));\n      if (hasContent) {\n        return { content: joinedText, 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                  : toToolCallArguments(fn?.arguments),\n          ...(tc.id ? { id: String(tc.id) } : {}),\n        };\n      });\n      if (hasContent) {\n        return { content: joinedText, toolCalls };\n      }\n      return { toolCalls };\n    }\n    if (hasContent) {\n      return { content: joinedText };\n    }\n    // Recognized Cohere v2 shape (finish_reason + message.content array) with\n    // no text and no tool calls. Own the empty turn here and return an\n    // empty-content fixture rather than falling through to the Ollama branch\n    // below, which would re-handle `message.content` and mis-route it.\n    return { content: \"\" };\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: toToolCallArguments(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  // Narrowed: require `done` to be a boolean — Ollama always sends a boolean\n  // here, and gating merely on `\"done\" in obj` would capture unrelated payloads\n  // that happen to carry a `response` string and some other `done`-keyed value.\n  if (typeof obj.response === \"string\" && typeof obj.done === \"boolean\") {\n    return { content: obj.response };\n  }\n\n  // Fallback: unknown format — save as error. Log the observed top-level shape\n  // so an unrecognized/new provider response is diagnosable instead of silently\n  // becoming an opaque error fixture.\n  logger?.warn(\n    `Could not detect response format from upstream (status=${status}) — saving as error fixture; top-level keys: [${Object.keys(\n      obj,\n    ).join(\", \")}]`,\n  );\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 = NonNullable<FixtureMatch[\"endpoint\"]>;\n\nexport function buildFixtureMatch(\n  request: ChatCompletionRequest,\n  recordConfig?: RecordConfig,\n): {\n  userMessage?: string;\n  inputText?: string;\n  model?: string;\n  endpoint?: EndpointType;\n  turnIndex?: number;\n  hasToolResult?: boolean;\n  context?: string;\n} {\n  const match: {\n    userMessage?: string;\n    inputText?: string;\n    model?: string;\n    endpoint?: EndpointType;\n    turnIndex?: number;\n    hasToolResult?: boolean;\n    context?: string;\n  } = {};\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  // Record normalized model for all requests so fixtures disambiguate\n  // calls that share the same userMessage but target different models.\n  if (request.model) {\n    match.model =\n      normalizeModelName(request.model, recordConfig?.recordFullModelVersion) ?? request.model;\n  }\n\n  // Multi-turn disambiguation: writing only `userMessage` lets the recorder's\n  // in-memory cache shadow follow-up turns that share it (initial tool call\n  // vs. text reply after the tool result). turnIndex + hasToolResult give\n  // each call a distinct, matcher-aware key. Skip for non-chat (no messages).\n  const messages = request.messages ?? [];\n  if (\n    messages.length > 0 &&\n    (request._endpointType === \"chat\" || request._endpointType === undefined)\n  ) {\n    match.turnIndex = messages.filter((m) => m.role === \"assistant\").length;\n    match.hasToolResult = messages.some((m) => m.role === \"tool\");\n  }\n\n  if (request._context) {\n    match.context = request._context;\n  }\n\n  return match;\n}\n\n/**\n * Build optional metadata for drift detection. Contains 8-char SHA-256\n * hashes of the system prompt and tool definitions present in the request.\n * Returns undefined when neither is present.\n */\nfunction buildFixtureMetadata(\n  request: ChatCompletionRequest,\n): { systemHash?: string; toolsHash?: string } | undefined {\n  const meta: { systemHash?: string; toolsHash?: string } = {};\n\n  const messages = request.messages ?? [];\n  const systemTexts = messages\n    .filter((m) => m.role === \"system\")\n    .map((m) => (typeof m.content === \"string\" ? m.content : JSON.stringify(m.content)))\n    .join(\"\\n\");\n  if (systemTexts) {\n    meta.systemHash = crypto.createHash(\"sha256\").update(systemTexts).digest(\"hex\").slice(0, 8);\n  }\n\n  if (request.tools && request.tools.length > 0) {\n    meta.toolsHash = crypto\n      .createHash(\"sha256\")\n      .update(JSON.stringify(request.tools))\n      .digest(\"hex\")\n      .slice(0, 8);\n  }\n\n  return Object.keys(meta).length > 0 ? meta : undefined;\n}\n"],"mappings":"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"}