@copilotkit/aimock 1.28.0 → 1.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/bedrock-converse.cjs +63 -31
  3. package/dist/bedrock-converse.cjs.map +1 -1
  4. package/dist/bedrock-converse.d.cts.map +1 -1
  5. package/dist/bedrock-converse.d.ts.map +1 -1
  6. package/dist/bedrock-converse.js +65 -33
  7. package/dist/bedrock-converse.js.map +1 -1
  8. package/dist/bedrock.cjs +95 -33
  9. package/dist/bedrock.cjs.map +1 -1
  10. package/dist/bedrock.d.cts.map +1 -1
  11. package/dist/bedrock.d.ts.map +1 -1
  12. package/dist/bedrock.js +97 -35
  13. package/dist/bedrock.js.map +1 -1
  14. package/dist/cohere.cjs +49 -15
  15. package/dist/cohere.cjs.map +1 -1
  16. package/dist/cohere.d.cts.map +1 -1
  17. package/dist/cohere.d.ts.map +1 -1
  18. package/dist/cohere.js +51 -17
  19. package/dist/cohere.js.map +1 -1
  20. package/dist/config-loader.d.ts.map +1 -1
  21. package/dist/elevenlabs-audio.cjs +8 -4
  22. package/dist/elevenlabs-audio.cjs.map +1 -1
  23. package/dist/elevenlabs-audio.d.cts.map +1 -1
  24. package/dist/elevenlabs-audio.d.ts.map +1 -1
  25. package/dist/elevenlabs-audio.js +10 -6
  26. package/dist/elevenlabs-audio.js.map +1 -1
  27. package/dist/embeddings.cjs +4 -3
  28. package/dist/embeddings.cjs.map +1 -1
  29. package/dist/embeddings.d.cts.map +1 -1
  30. package/dist/embeddings.d.ts.map +1 -1
  31. package/dist/embeddings.js +6 -5
  32. package/dist/embeddings.js.map +1 -1
  33. package/dist/fal-audio.cjs +8 -4
  34. package/dist/fal-audio.cjs.map +1 -1
  35. package/dist/fal-audio.d.cts.map +1 -1
  36. package/dist/fal-audio.d.ts.map +1 -1
  37. package/dist/fal-audio.js +10 -6
  38. package/dist/fal-audio.js.map +1 -1
  39. package/dist/fal.cjs +4 -2
  40. package/dist/fal.cjs.map +1 -1
  41. package/dist/fal.d.cts.map +1 -1
  42. package/dist/fal.d.ts.map +1 -1
  43. package/dist/fal.js +6 -4
  44. package/dist/fal.js.map +1 -1
  45. package/dist/gemini-embeddings.cjs +4 -3
  46. package/dist/gemini-embeddings.cjs.map +1 -1
  47. package/dist/gemini-embeddings.js +6 -5
  48. package/dist/gemini-embeddings.js.map +1 -1
  49. package/dist/gemini-interactions.cjs +3 -3
  50. package/dist/gemini-interactions.cjs.map +1 -1
  51. package/dist/gemini-interactions.d.cts.map +1 -1
  52. package/dist/gemini-interactions.d.ts.map +1 -1
  53. package/dist/gemini-interactions.js +5 -5
  54. package/dist/gemini-interactions.js.map +1 -1
  55. package/dist/gemini.cjs +55 -24
  56. package/dist/gemini.cjs.map +1 -1
  57. package/dist/gemini.d.cts.map +1 -1
  58. package/dist/gemini.d.ts.map +1 -1
  59. package/dist/gemini.js +57 -26
  60. package/dist/gemini.js.map +1 -1
  61. package/dist/helpers.cjs +120 -2
  62. package/dist/helpers.cjs.map +1 -1
  63. package/dist/helpers.d.cts +43 -3
  64. package/dist/helpers.d.cts.map +1 -1
  65. package/dist/helpers.d.ts +43 -3
  66. package/dist/helpers.d.ts.map +1 -1
  67. package/dist/helpers.js +117 -3
  68. package/dist/helpers.js.map +1 -1
  69. package/dist/images.cjs +12 -6
  70. package/dist/images.cjs.map +1 -1
  71. package/dist/images.d.cts.map +1 -1
  72. package/dist/images.d.ts.map +1 -1
  73. package/dist/images.js +14 -8
  74. package/dist/images.js.map +1 -1
  75. package/dist/index.cjs +3 -0
  76. package/dist/index.d.cts +3 -3
  77. package/dist/index.d.ts +3 -3
  78. package/dist/index.js +3 -3
  79. package/dist/journal.cjs +10 -0
  80. package/dist/journal.cjs.map +1 -1
  81. package/dist/journal.d.cts +8 -0
  82. package/dist/journal.d.ts +8 -0
  83. package/dist/journal.js +10 -0
  84. package/dist/journal.js.map +1 -1
  85. package/dist/messages.cjs +325 -85
  86. package/dist/messages.cjs.map +1 -1
  87. package/dist/messages.d.cts.map +1 -1
  88. package/dist/messages.d.ts.map +1 -1
  89. package/dist/messages.js +327 -87
  90. package/dist/messages.js.map +1 -1
  91. package/dist/model-utils.cjs +68 -0
  92. package/dist/model-utils.cjs.map +1 -1
  93. package/dist/model-utils.js +68 -1
  94. package/dist/model-utils.js.map +1 -1
  95. package/dist/ollama.cjs +58 -21
  96. package/dist/ollama.cjs.map +1 -1
  97. package/dist/ollama.d.cts.map +1 -1
  98. package/dist/ollama.d.ts.map +1 -1
  99. package/dist/ollama.js +60 -23
  100. package/dist/ollama.js.map +1 -1
  101. package/dist/recorder.cjs +49 -8
  102. package/dist/recorder.cjs.map +1 -1
  103. package/dist/recorder.js +50 -9
  104. package/dist/recorder.js.map +1 -1
  105. package/dist/responses.cjs +26 -12
  106. package/dist/responses.cjs.map +1 -1
  107. package/dist/responses.d.cts +1 -1
  108. package/dist/responses.d.cts.map +1 -1
  109. package/dist/responses.d.ts +1 -1
  110. package/dist/responses.d.ts.map +1 -1
  111. package/dist/responses.js +28 -14
  112. package/dist/responses.js.map +1 -1
  113. package/dist/router.cjs +37 -8
  114. package/dist/router.cjs.map +1 -1
  115. package/dist/router.d.cts +30 -1
  116. package/dist/router.d.cts.map +1 -1
  117. package/dist/router.d.ts +30 -1
  118. package/dist/router.d.ts.map +1 -1
  119. package/dist/router.js +37 -9
  120. package/dist/router.js.map +1 -1
  121. package/dist/server.cjs +55 -19
  122. package/dist/server.cjs.map +1 -1
  123. package/dist/server.d.cts.map +1 -1
  124. package/dist/server.d.ts.map +1 -1
  125. package/dist/server.js +57 -21
  126. package/dist/server.js.map +1 -1
  127. package/dist/speech.cjs +4 -2
  128. package/dist/speech.cjs.map +1 -1
  129. package/dist/speech.d.cts.map +1 -1
  130. package/dist/speech.d.ts.map +1 -1
  131. package/dist/speech.js +6 -4
  132. package/dist/speech.js.map +1 -1
  133. package/dist/stream-collapse.cjs +44 -1
  134. package/dist/stream-collapse.cjs.map +1 -1
  135. package/dist/stream-collapse.d.cts +28 -0
  136. package/dist/stream-collapse.d.cts.map +1 -1
  137. package/dist/stream-collapse.d.ts +28 -0
  138. package/dist/stream-collapse.d.ts.map +1 -1
  139. package/dist/stream-collapse.js +44 -2
  140. package/dist/stream-collapse.js.map +1 -1
  141. package/dist/transcription.cjs +4 -2
  142. package/dist/transcription.cjs.map +1 -1
  143. package/dist/transcription.d.cts.map +1 -1
  144. package/dist/transcription.d.ts.map +1 -1
  145. package/dist/transcription.js +6 -4
  146. package/dist/transcription.js.map +1 -1
  147. package/dist/types.d.cts +42 -0
  148. package/dist/types.d.cts.map +1 -1
  149. package/dist/types.d.ts +42 -0
  150. package/dist/types.d.ts.map +1 -1
  151. package/dist/vector-types.d.cts.map +1 -1
  152. package/dist/vector-types.d.ts.map +1 -1
  153. package/dist/video.cjs +21 -3
  154. package/dist/video.cjs.map +1 -1
  155. package/dist/video.d.cts.map +1 -1
  156. package/dist/video.d.ts.map +1 -1
  157. package/dist/video.js +23 -5
  158. package/dist/video.js.map +1 -1
  159. package/dist/ws-gemini-live.cjs +4 -3
  160. package/dist/ws-gemini-live.cjs.map +1 -1
  161. package/dist/ws-gemini-live.d.cts.map +1 -1
  162. package/dist/ws-gemini-live.d.ts.map +1 -1
  163. package/dist/ws-gemini-live.js +6 -5
  164. package/dist/ws-gemini-live.js.map +1 -1
  165. package/dist/ws-realtime.cjs +4 -3
  166. package/dist/ws-realtime.cjs.map +1 -1
  167. package/dist/ws-realtime.d.cts.map +1 -1
  168. package/dist/ws-realtime.d.ts.map +1 -1
  169. package/dist/ws-realtime.js +6 -5
  170. package/dist/ws-realtime.js.map +1 -1
  171. package/dist/ws-responses.cjs +8 -6
  172. package/dist/ws-responses.cjs.map +1 -1
  173. package/dist/ws-responses.d.cts.map +1 -1
  174. package/dist/ws-responses.d.ts.map +1 -1
  175. package/dist/ws-responses.js +10 -8
  176. package/dist/ws-responses.js.map +1 -1
  177. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"fal-audio.cjs","names":["FORMAT_TO_CONTENT_TYPE","getTestId","flattenHeaders","getContext","matchFixture","applyChaos","resolveStrictMode","strictOverrideField","resolveResponse","isErrorResponse","serializeErrorResponse","isAudioResponse","isJSONResponse","crypto","proxyAndRecord","walkFalQueue","buildFalForwardHeaders","buildFixtureMatch"],"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} from \"./helpers.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport { matchFixture } from \"./router.js\";\nimport { buildFixtureMatch, persistFixture, proxyAndRecord } from \"./recorder.js\";\nimport { buildFalForwardHeaders, 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 = matchFixture(fixtures, syntheticReq, matchCounts, defaults.requestTransform);\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 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: \"Strict mode: no fixture matched\",\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 strict/404.\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 finalBody = await walkFalQueue({\n upstreamBase,\n submitPath: pathname,\n body,\n headers: buildFalForwardHeaders(req),\n pollIntervalMs: record.fal?.pollIntervalMs,\n timeoutMs: record.fal?.timeoutMs,\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 });\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 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 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 persistFixture({\n record,\n providerKey: \"fal\",\n testId,\n fixture,\n fixtures,\n logger: defaults.logger,\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 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 = matchFixture(fixtures, syntheticReq, matchCounts, defaults.requestTransform);\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 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: \"Strict mode: no fixture matched\",\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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"}
1
+ {"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","buildFalForwardHeaders","buildFixtureMatch"],"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 { buildFixtureMatch, persistFixture, proxyAndRecord } from \"./recorder.js\";\nimport { buildFalForwardHeaders, 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 strict/404.\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 finalBody = await walkFalQueue({\n upstreamBase,\n submitPath: pathname,\n body,\n headers: buildFalForwardHeaders(req),\n pollIntervalMs: record.fal?.pollIntervalMs,\n timeoutMs: record.fal?.timeoutMs,\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 });\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 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 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 persistFixture({\n record,\n providerKey: \"fal\",\n testId,\n fixture,\n fixtures,\n logger: defaults.logger,\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 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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"fal-audio.d.cts","names":[],"sources":["../src/fal-audio.ts"],"sourcesContent":[],"mappings":";;;;;;iBA6KsB,cAAA,MACf,IAAA,CAAK,sBACL,IAAA,CAAK,0DAGA,qBACA,0BACD,UACR"}
1
+ {"version":3,"file":"fal-audio.d.cts","names":[],"sources":["../src/fal-audio.ts"],"sourcesContent":[],"mappings":";;;;;;iBA+KsB,cAAA,MACf,IAAA,CAAK,sBACL,IAAA,CAAK,0DAGA,qBACA,0BACD,UACR"}
@@ -1 +1 @@
1
- {"version":3,"file":"fal-audio.d.ts","names":[],"sources":["../src/fal-audio.ts"],"sourcesContent":[],"mappings":";;;;;;iBA6KsB,cAAA,MACf,IAAA,CAAK,sBACL,IAAA,CAAK,0DAGA,qBACA,0BACD,UACR"}
1
+ {"version":3,"file":"fal-audio.d.ts","names":[],"sources":["../src/fal-audio.ts"],"sourcesContent":[],"mappings":";;;;;;iBA+KsB,cAAA,MACf,IAAA,CAAK,sBACL,IAAA,CAAK,0DAGA,qBACA,0BACD,UACR"}
package/dist/fal-audio.js CHANGED
@@ -1,5 +1,5 @@
1
- import { FORMAT_TO_CONTENT_TYPE, flattenHeaders, getContext, getTestId, isAudioResponse, isErrorResponse, isJSONResponse, resolveResponse, resolveStrictMode, serializeErrorResponse, strictOverrideField } from "./helpers.js";
2
- import { matchFixture } from "./router.js";
1
+ import { FORMAT_TO_CONTENT_TYPE, flattenHeaders, getContext, getTestId, isAudioResponse, isErrorResponse, isJSONResponse, resolveResponse, resolveStrictMode, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField } from "./helpers.js";
2
+ import { matchFixtureDiagnostic } from "./router.js";
3
3
  import { writeErrorResponse } from "./sse-writer.js";
4
4
  import { applyChaos } from "./chaos.js";
5
5
  import { buildFixtureMatch, persistFixture, proxyAndRecord } from "./recorder.js";
@@ -177,7 +177,7 @@ async function handleQueueSubmit(req, res, body, pathname, modelId, testId, fixt
177
177
  _endpointType: "fal-audio",
178
178
  _context: getContext(req)
179
179
  };
180
- const fixture = matchFixture(fixtures, syntheticReq, matchCounts, defaults.requestTransform);
180
+ const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(fixtures, syntheticReq, matchCounts, defaults.requestTransform);
181
181
  if (fixture) journal.incrementFixtureMatchCount(fixture, fixtures, testId);
182
182
  if (applyChaos(res, fixture, defaults.chaos, req.headers, journal, {
183
183
  method: req.method ?? "POST",
@@ -187,6 +187,8 @@ async function handleQueueSubmit(req, res, body, pathname, modelId, testId, fixt
187
187
  }, fixture ? "fixture" : "proxy", defaults.registry, defaults.logger)) return;
188
188
  if (!fixture) {
189
189
  if (resolveStrictMode(defaults.strict, req.headers)) {
190
+ const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);
191
+ defaults.logger.error(strictNoMatchLogLine(req.method ?? "POST", pathname, skippedBySequenceOrTurn));
190
192
  journal.add({
191
193
  method: req.method ?? "POST",
192
194
  path: pathname,
@@ -200,7 +202,7 @@ async function handleQueueSubmit(req, res, body, pathname, modelId, testId, fixt
200
202
  });
201
203
  res.writeHead(503, { "Content-Type": "application/json" });
202
204
  res.end(JSON.stringify({ error: {
203
- message: "Strict mode: no fixture matched",
205
+ message: strictMessage,
204
206
  type: "invalid_request_error",
205
207
  code: "no_fixture_match"
206
208
  } }));
@@ -608,7 +610,7 @@ async function handleSyncRun(req, res, body, pathname, modelId, fixtures, defaul
608
610
  _endpointType: "fal-audio",
609
611
  _context: getContext(req)
610
612
  };
611
- const fixture = matchFixture(fixtures, syntheticReq, matchCounts, defaults.requestTransform);
613
+ const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(fixtures, syntheticReq, matchCounts, defaults.requestTransform);
612
614
  if (fixture) journal.incrementFixtureMatchCount(fixture, fixtures, getTestId(req));
613
615
  if (applyChaos(res, fixture, defaults.chaos, req.headers, journal, {
614
616
  method: req.method ?? "POST",
@@ -618,6 +620,8 @@ async function handleSyncRun(req, res, body, pathname, modelId, fixtures, defaul
618
620
  }, fixture ? "fixture" : "proxy", defaults.registry, defaults.logger)) return;
619
621
  if (!fixture) {
620
622
  if (resolveStrictMode(defaults.strict, req.headers)) {
623
+ const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);
624
+ defaults.logger.error(strictNoMatchLogLine(req.method ?? "POST", pathname, skippedBySequenceOrTurn));
621
625
  journal.add({
622
626
  method: req.method ?? "POST",
623
627
  path: pathname,
@@ -631,7 +635,7 @@ async function handleSyncRun(req, res, body, pathname, modelId, fixtures, defaul
631
635
  });
632
636
  res.writeHead(503, { "Content-Type": "application/json" });
633
637
  res.end(JSON.stringify({ error: {
634
- message: "Strict mode: no fixture matched",
638
+ message: strictMessage,
635
639
  type: "invalid_request_error",
636
640
  code: "no_fixture_match"
637
641
  } }));
@@ -1 +1 @@
1
- {"version":3,"file":"fal-audio.js","names":[],"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} from \"./helpers.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport { matchFixture } from \"./router.js\";\nimport { buildFixtureMatch, persistFixture, proxyAndRecord } from \"./recorder.js\";\nimport { buildFalForwardHeaders, 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 = matchFixture(fixtures, syntheticReq, matchCounts, defaults.requestTransform);\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 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: \"Strict mode: no fixture matched\",\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 strict/404.\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 finalBody = await walkFalQueue({\n upstreamBase,\n submitPath: pathname,\n body,\n headers: buildFalForwardHeaders(req),\n pollIntervalMs: record.fal?.pollIntervalMs,\n timeoutMs: record.fal?.timeoutMs,\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 });\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 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 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 persistFixture({\n record,\n providerKey: \"fal\",\n testId,\n fixture,\n fixtures,\n logger: defaults.logger,\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 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 = matchFixture(fixtures, syntheticReq, matchCounts, defaults.requestTransform);\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 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: \"Strict mode: no fixture matched\",\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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"}
1
+ {"version":3,"file":"fal-audio.js","names":[],"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 { buildFixtureMatch, persistFixture, proxyAndRecord } from \"./recorder.js\";\nimport { buildFalForwardHeaders, 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 strict/404.\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 finalBody = await walkFalQueue({\n upstreamBase,\n submitPath: pathname,\n body,\n headers: buildFalForwardHeaders(req),\n pollIntervalMs: record.fal?.pollIntervalMs,\n timeoutMs: record.fal?.timeoutMs,\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 });\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 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 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 persistFixture({\n record,\n providerKey: \"fal\",\n testId,\n fixture,\n fixtures,\n logger: defaults.logger,\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 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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"}
package/dist/fal.cjs CHANGED
@@ -405,9 +405,11 @@ async function handleFal(req, res, body, pathname, fixtures, defaults, journal)
405
405
  _endpointType: "fal",
406
406
  _context: require_helpers.getContext(req)
407
407
  };
408
- const fixture = require_router.matchFixture(fixtures, syntheticReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform);
408
+ const { fixture, skippedBySequenceOrTurn } = require_router.matchFixtureDiagnostic(fixtures, syntheticReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform);
409
409
  if (!fixture) {
410
410
  if (require_helpers.resolveStrictMode(defaults.strict, req.headers)) {
411
+ const strictMessage = require_helpers.strictNoMatchMessage(skippedBySequenceOrTurn);
412
+ defaults.logger.error(require_helpers.strictNoMatchLogLine(req.method ?? "POST", pathname, skippedBySequenceOrTurn));
411
413
  journal.add({
412
414
  method: req.method ?? "POST",
413
415
  path: pathname,
@@ -421,7 +423,7 @@ async function handleFal(req, res, body, pathname, fixtures, defaults, journal)
421
423
  });
422
424
  res.writeHead(503, { "Content-Type": "application/json" });
423
425
  res.end(JSON.stringify({ error: {
424
- message: "Strict mode: no fixture matched",
426
+ message: strictMessage,
425
427
  type: "invalid_request_error",
426
428
  code: "no_fixture_match"
427
429
  } }));