{"version":3,"file":"fal-audio.cjs","names":["FORMAT_TO_CONTENT_TYPE","getTestId","flattenHeaders","getContext","matchFixtureDiagnostic","applyChaos","resolveStrictMode","strictNoMatchMessage","strictNoMatchLogLine","strictOverrideField","resolveResponse","isErrorResponse","serializeErrorResponse","isAudioResponse","isJSONResponse","crypto","proxyAndRecord","walkFalQueue","buildForwardHeaders","persistFixture","buildFixtureMatch","sanitizeHeaderValue"],"sources":["../src/fal-audio.ts"],"sourcesContent":["import type http from \"node:http\";\nimport crypto from \"node:crypto\";\nimport type {\n  AudioResponse,\n  ChatCompletionRequest,\n  Fixture,\n  HandlerDefaults,\n  RawJSONResponse,\n} from \"./types.js\";\nimport {\n  isAudioResponse,\n  isErrorResponse,\n  isJSONResponse,\n  serializeErrorResponse,\n  flattenHeaders,\n  FORMAT_TO_CONTENT_TYPE,\n  getContext,\n  getTestId,\n  resolveResponse,\n  resolveStrictMode,\n  strictOverrideField,\n  strictNoMatchMessage,\n  strictNoMatchLogLine,\n} from \"./helpers.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport { matchFixtureDiagnostic } from \"./router.js\";\nimport {\n  buildFixtureMatch,\n  buildForwardHeaders,\n  persistFixture,\n  proxyAndRecord,\n  sanitizeHeaderValue,\n} from \"./recorder.js\";\nimport { walkFalQueue } from \"./fal.js\";\nimport type { Journal } from \"./journal.js\";\nimport { applyChaos } from \"./chaos.js\";\n\n// ─── FalJobMap with TTL and size bound ───────────────────────────────────\n\nconst FAL_JOB_MAX_ENTRIES = 10_000;\nconst FAL_JOB_TTL_MS = 3_600_000; // 1 hour\n\ninterface FalJob {\n  requestId: string;\n  modelId: string;\n  status: \"IN_QUEUE\" | \"IN_PROGRESS\" | \"COMPLETED\";\n  result: Record<string, unknown> | null;\n}\n\ninterface FalJobEntry {\n  job: FalJob;\n  createdAt: number;\n}\n\n/**\n * A Map wrapper for fal.ai queue jobs that enforces a maximum size and per-entry TTL.\n * Entries older than FAL_JOB_TTL_MS are lazily evicted on `get`.\n * When the map exceeds FAL_JOB_MAX_ENTRIES on `set`, the oldest entries\n * are removed to stay within bounds.\n * A background sweep runs every 60 s to proactively evict expired entries.\n */\nexport class FalJobMap {\n  private readonly entries = new Map<string, FalJobEntry>();\n  private sweepTimer: ReturnType<typeof setInterval> | null = null;\n\n  constructor() {\n    this.startSweep();\n  }\n\n  /** Start the proactive TTL sweep (every 60 s). */\n  startSweep(): void {\n    if (this.sweepTimer) return;\n    this.sweepTimer = setInterval(() => {\n      const now = Date.now();\n      for (const [key, entry] of this.entries) {\n        if (now - entry.createdAt > FAL_JOB_TTL_MS) {\n          this.entries.delete(key);\n        }\n      }\n    }, 60_000);\n    // Allow the process to exit even if the timer is still active\n    if (this.sweepTimer && typeof this.sweepTimer === \"object\" && \"unref\" in this.sweepTimer) {\n      this.sweepTimer.unref();\n    }\n  }\n\n  /** Stop the proactive TTL sweep. */\n  stopSweep(): void {\n    if (this.sweepTimer) {\n      clearInterval(this.sweepTimer);\n      this.sweepTimer = null;\n    }\n  }\n\n  get(key: string): FalJob | undefined {\n    const entry = this.entries.get(key);\n    if (!entry) return undefined;\n    if (Date.now() - entry.createdAt > FAL_JOB_TTL_MS) {\n      this.entries.delete(key);\n      return undefined;\n    }\n    return entry.job;\n  }\n\n  set(key: string, job: FalJob): void {\n    this.entries.set(key, { job, createdAt: Date.now() });\n    // Evict oldest entries if over capacity\n    if (this.entries.size > FAL_JOB_MAX_ENTRIES) {\n      const excess = this.entries.size - FAL_JOB_MAX_ENTRIES;\n      const iter = this.entries.keys();\n      for (let i = 0; i < excess; i++) {\n        const next = iter.next();\n        if (!next.done) this.entries.delete(next.value);\n      }\n    }\n  }\n\n  delete(key: string): boolean {\n    return this.entries.delete(key);\n  }\n\n  clear(): void {\n    this.entries.clear();\n  }\n\n  destroy(): void {\n    this.stopSweep();\n    this.entries.clear();\n  }\n\n  get size(): number {\n    return this.entries.size;\n  }\n}\n\n// Module-level singleton — exported so server.ts can clear it during reset\nexport const falJobs = new FalJobMap();\n\n// ─── Audio response translation ──────────────────────────────────────────\n\nexport function audioToFalFile(response: AudioResponse): Record<string, unknown> {\n  let contentType: string;\n  let data: string;\n\n  if (typeof response.audio === \"string\") {\n    data = response.audio;\n    contentType = FORMAT_TO_CONTENT_TYPE[response.format ?? \"mp3\"] ?? \"audio/mpeg\";\n  } else {\n    data = response.audio.b64Json;\n    contentType = response.audio.contentType ?? \"audio/mpeg\";\n  }\n\n  const ext =\n    response.format ??\n    (contentType !== \"audio/mpeg\"\n      ? (Object.entries(FORMAT_TO_CONTENT_TYPE).find(([, v]) => v === contentType)?.[0] ?? \"mp3\")\n      : \"mp3\");\n\n  const fileSize =\n    Math.ceil((data.length * 3) / 4) - (data.endsWith(\"==\") ? 2 : data.endsWith(\"=\") ? 1 : 0);\n\n  return {\n    audio: {\n      url: `https://mock.fal.media/files/generated_audio.${ext}`,\n      content_type: contentType,\n      file_name: `generated_audio.${ext}`,\n      file_size: fileSize,\n    },\n  };\n}\n\n// ─── Route patterns ──────────────────────────────────────────────────────\n\nconst QUEUE_SUBMIT_RE = /^\\/fal\\/queue\\/submit\\/(.+)$/;\nconst QUEUE_STATUS_RE = /^\\/fal\\/queue\\/requests\\/([^/]+)\\/status$/;\nconst QUEUE_RESULT_RE = /^\\/fal\\/queue\\/requests\\/([^/]+)$/;\nconst QUEUE_CANCEL_RE = /^\\/fal\\/queue\\/requests\\/([^/]+)\\/cancel$/;\nconst SYNC_RUN_RE = /^\\/fal\\/run\\/(.+)$/;\n\n// ─── Handler ─────────────────────────────────────────────────────────────\n\nexport async function handleFalQueue(\n  req: http.IncomingMessage,\n  res: http.ServerResponse,\n  body: string,\n  pathname: string,\n  fixtures: Fixture[],\n  defaults: HandlerDefaults,\n  journal: Journal,\n): Promise<void> {\n  const testId = getTestId(req);\n  const matchCounts = journal.getFixtureMatchCountsForTest(testId);\n\n  // ── Queue Submit ───────────────────────────────────────────────────\n  const submitMatch = QUEUE_SUBMIT_RE.exec(pathname);\n  if (submitMatch && req.method === \"POST\") {\n    const modelId = submitMatch[1];\n    return handleQueueSubmit(\n      req,\n      res,\n      body,\n      pathname,\n      modelId,\n      testId,\n      fixtures,\n      defaults,\n      matchCounts,\n      journal,\n    );\n  }\n\n  // ── Queue Status ───────────────────────────────────────────────────\n  const statusMatch = QUEUE_STATUS_RE.exec(pathname);\n  if (statusMatch) {\n    const requestId = statusMatch[1];\n    return handleQueueStatus(req, res, pathname, requestId, testId, journal);\n  }\n\n  // ── Queue Cancel ───────────────────────────────────────────────────\n  const cancelMatch = QUEUE_CANCEL_RE.exec(pathname);\n  if (cancelMatch) {\n    const requestId = cancelMatch[1];\n    return handleQueueCancel(req, res, pathname, requestId, testId, journal);\n  }\n\n  // ── Queue Result ───────────────────────────────────────────────────\n  const resultMatch = QUEUE_RESULT_RE.exec(pathname);\n  if (resultMatch) {\n    const requestId = resultMatch[1];\n    return handleQueueResult(req, res, pathname, requestId, testId, journal);\n  }\n\n  // ── Synchronous Run ────────────────────────────────────────────────\n  const runMatch = SYNC_RUN_RE.exec(pathname);\n  if (runMatch && req.method === \"POST\") {\n    const modelId = runMatch[1];\n    return handleSyncRun(\n      req,\n      res,\n      body,\n      pathname,\n      modelId,\n      fixtures,\n      defaults,\n      matchCounts,\n      journal,\n    );\n  }\n\n  // Unknown fal path\n  const errorBody = { error: { message: \"Unknown fal.ai endpoint\", type: \"not_found\" } };\n  journal.add({\n    method: req.method ?? \"GET\",\n    path: pathname,\n    headers: flattenHeaders(req.headers),\n    body: null,\n    response: { status: 404, fixture: null },\n  });\n  res.writeHead(404, { \"Content-Type\": \"application/json\" });\n  res.end(JSON.stringify(errorBody));\n}\n\n// ─── Sub-handlers ────────────────────────────────────────────────────────\n\nasync function handleQueueSubmit(\n  req: http.IncomingMessage,\n  res: http.ServerResponse,\n  body: string,\n  pathname: string,\n  modelId: string,\n  testId: string,\n  fixtures: Fixture[],\n  defaults: HandlerDefaults,\n  matchCounts: Map<Fixture, number>,\n  journal: Journal,\n): Promise<void> {\n  let parsed: Record<string, unknown> = {};\n  if (body.trim()) {\n    try {\n      parsed = JSON.parse(body) as Record<string, unknown>;\n    } catch (parseErr) {\n      const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n      journal.add({\n        method: req.method ?? \"POST\",\n        path: pathname,\n        headers: flattenHeaders(req.headers),\n        body: null,\n        response: { status: 400, fixture: null },\n      });\n      res.writeHead(400, { \"Content-Type\": \"application/json\" });\n      res.end(\n        JSON.stringify({\n          error: { message: `Malformed JSON: ${detail}`, type: \"invalid_request_error\" },\n        }),\n      );\n      return;\n    }\n  }\n\n  const prompt =\n    (typeof parsed.prompt === \"string\" ? parsed.prompt : null) ??\n    (typeof parsed.text === \"string\" ? parsed.text : null) ??\n    \"\";\n\n  const syntheticReq: ChatCompletionRequest = {\n    model: modelId,\n    messages: [{ role: \"user\", content: prompt }],\n    _endpointType: \"fal-audio\",\n    _context: getContext(req),\n  };\n\n  const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n    fixtures,\n    syntheticReq,\n    matchCounts,\n    defaults.requestTransform,\n  );\n\n  if (fixture) {\n    journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n  }\n\n  if (\n    applyChaos(\n      res,\n      fixture,\n      defaults.chaos,\n      req.headers,\n      journal,\n      {\n        method: req.method ?? \"POST\",\n        path: pathname,\n        headers: flattenHeaders(req.headers),\n        body: syntheticReq,\n      },\n      fixture ? \"fixture\" : \"proxy\",\n      defaults.registry,\n      defaults.logger,\n    )\n  )\n    return;\n\n  if (!fixture) {\n    const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n    if (effectiveStrict) {\n      const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n      defaults.logger.error(\n        strictNoMatchLogLine(req.method ?? \"POST\", pathname, skippedBySequenceOrTurn),\n      );\n      journal.add({\n        method: req.method ?? \"POST\",\n        path: pathname,\n        headers: flattenHeaders(req.headers),\n        body: syntheticReq,\n        response: {\n          status: 503,\n          fixture: null,\n          ...strictOverrideField(defaults.strict, req.headers),\n        },\n      });\n      res.writeHead(503, { \"Content-Type\": \"application/json\" });\n      res.end(\n        JSON.stringify({\n          error: {\n            message: strictMessage,\n            type: \"invalid_request_error\",\n            code: \"no_fixture_match\",\n          },\n        }),\n      );\n      return;\n    }\n    if (defaults.record) {\n      const handled = await tryRecordAudioQueueWalk({\n        req,\n        res,\n        syntheticReq,\n        modelId,\n        pathname,\n        body,\n        fixtures,\n        defaults,\n        testId,\n        journal,\n      });\n      if (handled) return;\n    }\n\n    journal.add({\n      method: req.method ?? \"POST\",\n      path: pathname,\n      headers: flattenHeaders(req.headers),\n      body: syntheticReq,\n      response: {\n        status: 404,\n        fixture: null,\n        ...strictOverrideField(defaults.strict, req.headers),\n      },\n    });\n    res.writeHead(404, { \"Content-Type\": \"application/json\" });\n    res.end(\n      JSON.stringify({\n        error: {\n          message: \"No fixture matched\",\n          type: \"invalid_request_error\",\n          code: \"no_fixture_match\",\n        },\n      }),\n    );\n    return;\n  }\n\n  const response = await resolveResponse(fixture, syntheticReq);\n\n  if (isErrorResponse(response)) {\n    const status = response.status ?? 500;\n    journal.add({\n      method: req.method ?? \"POST\",\n      path: pathname,\n      headers: flattenHeaders(req.headers),\n      body: syntheticReq,\n      response: { status, fixture },\n    });\n    writeErrorResponse(res, status, serializeErrorResponse(response), {\n      retryAfter: response.retryAfter,\n    });\n    return;\n  }\n\n  // Two valid recorded shapes for fal audio queues:\n  //  - AudioResponse: legacy authored fixtures with raw base64 audio that we\n  //    wrap into the fal `{ audio: { url, ... } }` envelope on demand.\n  //  - RawJSONResponse: queue-walk recordings that stored the final envelope\n  //    upstream returned (already in fal's `{ audio: { url, ... } }` shape).\n  let result: Record<string, unknown>;\n  if (isAudioResponse(response)) {\n    result = audioToFalFile(response);\n  } else if (isJSONResponse(response)) {\n    const json = (response as RawJSONResponse).json;\n    if (!json || typeof json !== \"object\") {\n      journal.add({\n        method: req.method ?? \"POST\",\n        path: pathname,\n        headers: flattenHeaders(req.headers),\n        body: syntheticReq,\n        response: { status: 500, fixture },\n      });\n      res.writeHead(500, { \"Content-Type\": \"application/json\" });\n      res.end(\n        JSON.stringify({\n          error: {\n            message: \"Recorded fal audio fixture has non-object json\",\n            type: \"server_error\",\n          },\n        }),\n      );\n      return;\n    }\n    result = json as Record<string, unknown>;\n  } else {\n    journal.add({\n      method: req.method ?? \"POST\",\n      path: pathname,\n      headers: flattenHeaders(req.headers),\n      body: syntheticReq,\n      response: { status: 500, fixture },\n    });\n    res.writeHead(500, { \"Content-Type\": \"application/json\" });\n    res.end(\n      JSON.stringify({\n        error: { message: \"Fixture response is not an audio type\", type: \"server_error\" },\n      }),\n    );\n    return;\n  }\n\n  const requestId = crypto.randomUUID();\n\n  const job: FalJob = {\n    requestId,\n    modelId,\n    status: \"COMPLETED\",\n    result,\n  };\n\n  const stateKey = `${testId}:${requestId}`;\n  falJobs.set(stateKey, job);\n\n  journal.add({\n    method: req.method ?? \"POST\",\n    path: pathname,\n    headers: flattenHeaders(req.headers),\n    body: syntheticReq,\n    response: { status: 200, fixture },\n  });\n  res.writeHead(200, { \"Content-Type\": \"application/json\" });\n  res.end(\n    JSON.stringify({\n      request_id: requestId,\n      response_url: `https://queue.fal.run/${modelId}/requests/${requestId}/response`,\n      status_url: `https://queue.fal.run/${modelId}/requests/${requestId}/status`,\n      cancel_url: `https://queue.fal.run/${modelId}/requests/${requestId}/cancel`,\n      queue_position: 0,\n    }),\n  );\n}\n\n/**\n * Walk the upstream queue (submit → poll status → get result), persist the\n * FINAL result body as a fal-audio fixture, then synthesise the local envelope\n * and seed `falJobs` so the same recording run's status/result polls work. The\n * legacy `proxyAndRecord` shortcut wrote the IN_QUEUE envelope as the fixture,\n * which broke replay (the SDK polls until COMPLETED then expects the result\n * body, not the envelope).\n *\n * Returns `true` if the request has been handled (response written and\n * journaled); `false` if recording wasn't configured for this provider and the\n * caller should fall through to 404 (strict is gated before recording).\n */\nasync function tryRecordAudioQueueWalk(args: {\n  req: http.IncomingMessage;\n  res: http.ServerResponse;\n  syntheticReq: ChatCompletionRequest;\n  modelId: string;\n  pathname: string;\n  body: string;\n  fixtures: Fixture[];\n  defaults: HandlerDefaults;\n  testId: string;\n  journal: Journal;\n}): Promise<boolean> {\n  const { req, res, syntheticReq, modelId, pathname, body, fixtures, defaults, testId, journal } =\n    args;\n\n  const record = defaults.record;\n  if (!record) return false;\n  const upstreamBase = record.providers.fal;\n  if (!upstreamBase) {\n    // Fall back to the generic proxy so non-queue-shaped audio endpoints (e.g.\n    // direct audio bytes, when someone misconfigures) still get a chance to\n    // record, mirroring prior behavior.\n    const outcome = await proxyAndRecord(\n      req,\n      res,\n      syntheticReq,\n      \"fal\",\n      pathname,\n      fixtures,\n      defaults,\n      body,\n    );\n    if (outcome === \"handled_by_hook\") return true;\n    if (outcome === \"relayed\") {\n      journal.add({\n        method: req.method ?? \"POST\",\n        path: pathname,\n        headers: flattenHeaders(req.headers),\n        body: syntheticReq,\n        response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n      });\n      return true;\n    }\n    return false;\n  }\n\n  defaults.logger.warn(\n    `NO FIXTURE MATCH — walking legacy fal audio queue at ${upstreamBase}${pathname}`,\n  );\n\n  let finalBody: unknown;\n  try {\n    // The legacy audio queue stores an AudioResponse and serves its own\n    // headers, so the walk's captured billableUnits doesn't apply here — take\n    // the body only.\n    ({ body: finalBody } = await walkFalQueue({\n      upstreamBase,\n      submitPath: pathname,\n      body,\n      headers: buildForwardHeaders(req),\n      pollIntervalMs: record.fal?.pollIntervalMs,\n      timeoutMs: record.fal?.timeoutMs,\n      upstreamTimeoutMs: record.upstreamTimeoutMs,\n      // Legacy aimock-style paths, not the model-prefixed fal.ai layout.\n      fallbackStatusPath: (id) => `/fal/queue/requests/${id}/status`,\n      fallbackResultPath: (id) => `/fal/queue/requests/${id}`,\n      logger: defaults.logger,\n    }));\n  } catch (err) {\n    const msg = err instanceof Error ? err.message : \"Unknown queue-walk error\";\n    defaults.logger.error(`fal-audio queue-walk proxy failed: ${msg}`);\n    // Guard BEFORE journaling (openrouter-video convention): a client that\n    // disconnected during the multi-second walk gets neither a write nor a\n    // journal entry.\n    if (res.destroyed || res.writableEnded) return true;\n    journal.add({\n      method: req.method ?? \"POST\",\n      path: pathname,\n      headers: flattenHeaders(req.headers),\n      body: syntheticReq,\n      response: { status: 502, fixture: null, source: \"proxy\" },\n    });\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    return true;\n  }\n\n  if (!finalBody || typeof finalBody !== \"object\") {\n    defaults.logger.error(\"fal-audio queue-walk produced non-object result\");\n    // Same disconnect guard as the catch above.\n    if (res.destroyed || res.writableEnded) return true;\n    journal.add({\n      method: req.method ?? \"POST\",\n      path: pathname,\n      headers: flattenHeaders(req.headers),\n      body: syntheticReq,\n      response: { status: 502, fixture: null, source: \"proxy\" },\n    });\n    res.writeHead(502, { \"Content-Type\": \"application/json\" });\n    res.end(\n      JSON.stringify({\n        error: { message: \"Upstream result body was not a JSON object\", type: \"proxy_error\" },\n      }),\n    );\n    return true;\n  }\n\n  const matchRequest = defaults.requestTransform\n    ? defaults.requestTransform(syntheticReq)\n    : syntheticReq;\n  const fixture: Fixture = {\n    match: buildFixtureMatch(matchRequest, record),\n    response: { json: finalBody, status: 200 },\n  };\n  const persistResult = persistFixture({\n    record,\n    providerKey: \"fal\",\n    testId,\n    fixture,\n    fixtures,\n    logger: defaults.logger,\n  });\n  // Surface a persist failure on the envelope (parity with fal.ts's\n  // queue-walk record path and the generic recorder relay) — the synthesized\n  // envelope below has not been written yet, so the header can still ride it.\n  if (persistResult.kind === \"failed\" && !res.headersSent) {\n    res.setHeader(\"X-AIMock-Record-Error\", sanitizeHeaderValue(persistResult.error));\n  }\n\n  const requestId = crypto.randomUUID();\n  const job: FalJob = {\n    requestId,\n    modelId,\n    status: \"COMPLETED\",\n    result: finalBody as Record<string, unknown>,\n  };\n  falJobs.set(`${testId}:${requestId}`, job);\n\n  // Guard BEFORE journaling (openrouter-video convention): a client that\n  // disconnected during the multi-second walk gets neither a write nor a\n  // journal entry. The persisted fixture and seeded job above stay — the\n  // captured upstream response is valuable regardless.\n  if (res.destroyed || res.writableEnded) return true;\n  journal.add({\n    method: req.method ?? \"POST\",\n    path: pathname,\n    headers: flattenHeaders(req.headers),\n    body: syntheticReq,\n    response: { status: 200, fixture: null, source: \"proxy\" },\n  });\n  res.writeHead(200, { \"Content-Type\": \"application/json\" });\n  res.end(\n    JSON.stringify({\n      request_id: requestId,\n      response_url: `https://queue.fal.run/${modelId}/requests/${requestId}/response`,\n      status_url: `https://queue.fal.run/${modelId}/requests/${requestId}/status`,\n      cancel_url: `https://queue.fal.run/${modelId}/requests/${requestId}/cancel`,\n      queue_position: 0,\n    }),\n  );\n  return true;\n}\n\nfunction handleQueueStatus(\n  req: http.IncomingMessage,\n  res: http.ServerResponse,\n  pathname: string,\n  requestId: string,\n  testId: string,\n  journal: Journal,\n): void {\n  const stateKey = `${testId}:${requestId}`;\n  const job = falJobs.get(stateKey);\n\n  if (!job) {\n    journal.add({\n      method: req.method ?? \"GET\",\n      path: pathname,\n      headers: flattenHeaders(req.headers),\n      body: null,\n      response: { status: 404, fixture: null },\n    });\n    res.writeHead(404, { \"Content-Type\": \"application/json\" });\n    res.end(\n      JSON.stringify({\n        error: { message: `Request ${requestId} not found`, type: \"not_found\" },\n      }),\n    );\n    return;\n  }\n\n  journal.add({\n    method: req.method ?? \"GET\",\n    path: pathname,\n    headers: flattenHeaders(req.headers),\n    body: null,\n    response: { status: 200, fixture: null },\n  });\n  res.writeHead(200, { \"Content-Type\": \"application/json\" });\n  res.end(\n    JSON.stringify({\n      status: job.status,\n      request_id: job.requestId,\n      response_url: `https://queue.fal.run/${job.modelId}/requests/${requestId}/response`,\n    }),\n  );\n}\n\nfunction handleQueueResult(\n  req: http.IncomingMessage,\n  res: http.ServerResponse,\n  pathname: string,\n  requestId: string,\n  testId: string,\n  journal: Journal,\n): void {\n  const stateKey = `${testId}:${requestId}`;\n  const job = falJobs.get(stateKey);\n\n  if (!job) {\n    journal.add({\n      method: req.method ?? \"GET\",\n      path: pathname,\n      headers: flattenHeaders(req.headers),\n      body: null,\n      response: { status: 404, fixture: null },\n    });\n    res.writeHead(404, { \"Content-Type\": \"application/json\" });\n    res.end(\n      JSON.stringify({\n        error: { message: `Request ${requestId} not found`, type: \"not_found\" },\n      }),\n    );\n    return;\n  }\n\n  if (job.result === null) {\n    journal.add({\n      method: req.method ?? \"GET\",\n      path: pathname,\n      headers: flattenHeaders(req.headers),\n      body: null,\n      response: { status: 409, fixture: null },\n    });\n    writeErrorResponse(\n      res,\n      409,\n      JSON.stringify({\n        error: { message: \"Job result not yet available\", type: \"not_ready\" },\n      }),\n    );\n    return;\n  }\n\n  journal.add({\n    method: req.method ?? \"GET\",\n    path: pathname,\n    headers: flattenHeaders(req.headers),\n    body: null,\n    response: { status: 200, fixture: null },\n  });\n  res.writeHead(200, { \"Content-Type\": \"application/json\" });\n  res.end(JSON.stringify(job.result));\n}\n\nfunction handleQueueCancel(\n  req: http.IncomingMessage,\n  res: http.ServerResponse,\n  pathname: string,\n  requestId: string,\n  testId: string,\n  journal: Journal,\n): void {\n  const stateKey = `${testId}:${requestId}`;\n  const job = falJobs.get(stateKey);\n\n  if (!job) {\n    journal.add({\n      method: req.method ?? \"DELETE\",\n      path: pathname,\n      headers: flattenHeaders(req.headers),\n      body: null,\n      response: { status: 404, fixture: null },\n    });\n    res.writeHead(404, { \"Content-Type\": \"application/json\" });\n    res.end(JSON.stringify({ status: \"NOT_FOUND\" }));\n    return;\n  }\n\n  // Since we complete immediately, cancellation always returns ALREADY_COMPLETED\n  journal.add({\n    method: req.method ?? \"DELETE\",\n    path: pathname,\n    headers: flattenHeaders(req.headers),\n    body: null,\n    response: { status: 400, fixture: null },\n  });\n  res.writeHead(400, { \"Content-Type\": \"application/json\" });\n  res.end(JSON.stringify({ status: \"ALREADY_COMPLETED\" }));\n}\n\nasync function handleSyncRun(\n  req: http.IncomingMessage,\n  res: http.ServerResponse,\n  body: string,\n  pathname: string,\n  modelId: string,\n  fixtures: Fixture[],\n  defaults: HandlerDefaults,\n  matchCounts: Map<Fixture, number>,\n  journal: Journal,\n): Promise<void> {\n  let parsed: Record<string, unknown> = {};\n  if (body.trim()) {\n    try {\n      parsed = JSON.parse(body) as Record<string, unknown>;\n    } catch (parseErr) {\n      const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n      journal.add({\n        method: req.method ?? \"POST\",\n        path: pathname,\n        headers: flattenHeaders(req.headers),\n        body: null,\n        response: { status: 400, fixture: null },\n      });\n      res.writeHead(400, { \"Content-Type\": \"application/json\" });\n      res.end(\n        JSON.stringify({\n          error: { message: `Malformed JSON: ${detail}`, type: \"invalid_request_error\" },\n        }),\n      );\n      return;\n    }\n  }\n\n  const prompt =\n    (typeof parsed.prompt === \"string\" ? parsed.prompt : null) ??\n    (typeof parsed.text === \"string\" ? parsed.text : null) ??\n    \"\";\n\n  // _endpointType is intentionally \"fal-audio\" — the same value used by\n  // handleQueueSubmit. Both the synchronous /fal/run/ and asynchronous\n  // /fal/queue/submit/ paths serve the same fal audio fixtures, so they\n  // share a single endpoint type for fixture matching purposes.\n  const syntheticReq: ChatCompletionRequest = {\n    model: modelId,\n    messages: [{ role: \"user\", content: prompt }],\n    _endpointType: \"fal-audio\",\n    _context: getContext(req),\n  };\n\n  const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n    fixtures,\n    syntheticReq,\n    matchCounts,\n    defaults.requestTransform,\n  );\n\n  if (fixture) {\n    journal.incrementFixtureMatchCount(fixture, fixtures, getTestId(req));\n  }\n\n  if (\n    applyChaos(\n      res,\n      fixture,\n      defaults.chaos,\n      req.headers,\n      journal,\n      {\n        method: req.method ?? \"POST\",\n        path: pathname,\n        headers: flattenHeaders(req.headers),\n        body: syntheticReq,\n      },\n      fixture ? \"fixture\" : \"proxy\",\n      defaults.registry,\n      defaults.logger,\n    )\n  )\n    return;\n\n  if (!fixture) {\n    const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n    if (effectiveStrict) {\n      const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n      defaults.logger.error(\n        strictNoMatchLogLine(req.method ?? \"POST\", pathname, skippedBySequenceOrTurn),\n      );\n      journal.add({\n        method: req.method ?? \"POST\",\n        path: pathname,\n        headers: flattenHeaders(req.headers),\n        body: syntheticReq,\n        response: {\n          status: 503,\n          fixture: null,\n          ...strictOverrideField(defaults.strict, req.headers),\n        },\n      });\n      res.writeHead(503, { \"Content-Type\": \"application/json\" });\n      res.end(\n        JSON.stringify({\n          error: {\n            message: strictMessage,\n            type: \"invalid_request_error\",\n            code: \"no_fixture_match\",\n          },\n        }),\n      );\n      return;\n    }\n    if (defaults.record) {\n      const outcome = await proxyAndRecord(\n        req,\n        res,\n        syntheticReq,\n        \"fal\",\n        pathname,\n        fixtures,\n        defaults,\n        body,\n      );\n      if (outcome === \"handled_by_hook\") return;\n      if (outcome !== \"not_configured\") {\n        journal.add({\n          method: req.method ?? \"POST\",\n          path: pathname,\n          headers: flattenHeaders(req.headers),\n          body: syntheticReq,\n          response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n        });\n        return;\n      }\n    }\n\n    journal.add({\n      method: req.method ?? \"POST\",\n      path: pathname,\n      headers: flattenHeaders(req.headers),\n      body: syntheticReq,\n      response: {\n        status: 404,\n        fixture: null,\n        ...strictOverrideField(defaults.strict, req.headers),\n      },\n    });\n    res.writeHead(404, { \"Content-Type\": \"application/json\" });\n    res.end(\n      JSON.stringify({\n        error: {\n          message: \"No fixture matched\",\n          type: \"invalid_request_error\",\n          code: \"no_fixture_match\",\n        },\n      }),\n    );\n    return;\n  }\n\n  const response = await resolveResponse(fixture, syntheticReq);\n\n  if (isErrorResponse(response)) {\n    const status = response.status ?? 500;\n    journal.add({\n      method: req.method ?? \"POST\",\n      path: pathname,\n      headers: flattenHeaders(req.headers),\n      body: syntheticReq,\n      response: { status, fixture },\n    });\n    writeErrorResponse(res, status, serializeErrorResponse(response), {\n      retryAfter: response.retryAfter,\n    });\n    return;\n  }\n\n  // Two valid recorded shapes for fal audio sync runs:\n  //  - AudioResponse: authored fixtures with raw base64 audio that we wrap into\n  //    the fal `{ audio: { url, ... } }` envelope on demand.\n  //  - RawJSONResponse: queue-walk recordings that stored the final fal envelope\n  //    upstream returned (already in fal's `{ audio: { url, ... } }` shape).\n  let result: Record<string, unknown>;\n  let resultStatus = 200;\n  if (isAudioResponse(response)) {\n    result = audioToFalFile(response);\n  } else if (isJSONResponse(response)) {\n    resultStatus = (response as RawJSONResponse).status ?? 200;\n    const json = (response as RawJSONResponse).json;\n    if (!json || typeof json !== \"object\") {\n      journal.add({\n        method: req.method ?? \"POST\",\n        path: pathname,\n        headers: flattenHeaders(req.headers),\n        body: syntheticReq,\n        response: { status: 500, fixture },\n      });\n      res.writeHead(500, { \"Content-Type\": \"application/json\" });\n      res.end(\n        JSON.stringify({\n          error: {\n            message: \"Recorded fal audio fixture has non-object json\",\n            type: \"server_error\",\n          },\n        }),\n      );\n      return;\n    }\n    result = json as Record<string, unknown>;\n  } else {\n    journal.add({\n      method: req.method ?? \"POST\",\n      path: pathname,\n      headers: flattenHeaders(req.headers),\n      body: syntheticReq,\n      response: { status: 500, fixture },\n    });\n    res.writeHead(500, { \"Content-Type\": \"application/json\" });\n    res.end(\n      JSON.stringify({\n        error: { message: \"Fixture response is not an audio type\", type: \"server_error\" },\n      }),\n    );\n    return;\n  }\n\n  journal.add({\n    method: req.method ?? \"POST\",\n    path: pathname,\n    headers: flattenHeaders(req.headers),\n    body: syntheticReq,\n    response: { status: resultStatus, fixture },\n  });\n  res.writeHead(resultStatus, { \"Content-Type\": \"application/json\" });\n  res.end(JSON.stringify(result));\n}\n"],"mappings":"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"}