@copilotkit/aimock 1.24.1 → 1.25.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +29 -0
- package/README.md +17 -11
- package/dist/agui-types.d.cts.map +1 -1
- package/dist/agui-types.d.ts.map +1 -1
- package/dist/bedrock-converse.cjs +2 -2
- package/dist/bedrock-converse.cjs.map +1 -1
- package/dist/bedrock-converse.d.cts.map +1 -1
- package/dist/bedrock-converse.d.ts.map +1 -1
- package/dist/bedrock-converse.js +2 -2
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/bedrock.cjs +2 -2
- package/dist/bedrock.cjs.map +1 -1
- package/dist/bedrock.d.cts.map +1 -1
- package/dist/bedrock.d.ts.map +1 -1
- package/dist/bedrock.js +2 -2
- package/dist/bedrock.js.map +1 -1
- package/dist/cli.cjs +25 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +25 -1
- package/dist/cli.js.map +1 -1
- package/dist/cohere.cjs +198 -1
- package/dist/cohere.cjs.map +1 -1
- package/dist/cohere.d.cts.map +1 -1
- package/dist/cohere.d.ts.map +1 -1
- package/dist/cohere.js +199 -3
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.d.cts.map +1 -1
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/elevenlabs-audio.cjs +173 -1
- package/dist/elevenlabs-audio.cjs.map +1 -1
- package/dist/elevenlabs-audio.d.cts.map +1 -1
- package/dist/elevenlabs-audio.d.ts.map +1 -1
- package/dist/elevenlabs-audio.js +173 -2
- package/dist/elevenlabs-audio.js.map +1 -1
- package/dist/embeddings.cjs +1 -1
- package/dist/embeddings.cjs.map +1 -1
- package/dist/embeddings.js +1 -1
- package/dist/embeddings.js.map +1 -1
- package/dist/fal-audio.cjs +2 -4
- package/dist/fal-audio.cjs.map +1 -1
- package/dist/fal-audio.js +2 -4
- package/dist/fal-audio.js.map +1 -1
- package/dist/fal.cjs +2 -2
- package/dist/fal.cjs.map +1 -1
- package/dist/fal.d.cts.map +1 -1
- package/dist/fal.d.ts.map +1 -1
- package/dist/fal.js +2 -2
- package/dist/fal.js.map +1 -1
- package/dist/gemini-embeddings.cjs +166 -0
- package/dist/gemini-embeddings.cjs.map +1 -0
- package/dist/gemini-embeddings.js +166 -0
- package/dist/gemini-embeddings.js.map +1 -0
- package/dist/gemini-interactions.cjs +1 -1
- package/dist/gemini-interactions.cjs.map +1 -1
- package/dist/gemini-interactions.js +1 -1
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +1 -1
- package/dist/gemini.cjs.map +1 -1
- package/dist/gemini.js +1 -1
- package/dist/gemini.js.map +1 -1
- package/dist/helpers.cjs +70 -33
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +9 -5
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts +9 -5
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +68 -34
- package/dist/helpers.js.map +1 -1
- package/dist/images.cjs +295 -13
- package/dist/images.cjs.map +1 -1
- package/dist/images.d.cts +9 -1
- package/dist/images.d.cts.map +1 -1
- package/dist/images.d.ts +9 -1
- package/dist/images.d.ts.map +1 -1
- package/dist/images.js +294 -14
- package/dist/images.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/llmock.cjs +15 -0
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +2 -0
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +2 -0
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +15 -0
- package/dist/llmock.js.map +1 -1
- package/dist/messages.cjs +1 -1
- package/dist/messages.cjs.map +1 -1
- package/dist/messages.js +1 -1
- package/dist/messages.js.map +1 -1
- package/dist/metrics.cjs +2 -0
- package/dist/metrics.cjs.map +1 -1
- package/dist/metrics.d.cts.map +1 -1
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +2 -0
- package/dist/metrics.js.map +1 -1
- package/dist/ollama.cjs +189 -2
- package/dist/ollama.cjs.map +1 -1
- package/dist/ollama.d.cts.map +1 -1
- package/dist/ollama.d.ts.map +1 -1
- package/dist/ollama.js +190 -4
- package/dist/ollama.js.map +1 -1
- package/dist/recorder.cjs +11 -4
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.js +11 -4
- package/dist/recorder.js.map +1 -1
- package/dist/responses.cjs +1 -1
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.js +1 -1
- package/dist/responses.js.map +1 -1
- package/dist/server.cjs +188 -48
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +193 -53
- package/dist/server.js.map +1 -1
- package/dist/speech.cjs +1 -1
- package/dist/speech.cjs.map +1 -1
- package/dist/speech.js +1 -1
- package/dist/speech.js.map +1 -1
- package/dist/sse-writer.cjs +20 -2
- package/dist/sse-writer.cjs.map +1 -1
- package/dist/sse-writer.d.cts +8 -2
- package/dist/sse-writer.d.cts.map +1 -1
- package/dist/sse-writer.d.ts +8 -2
- package/dist/sse-writer.d.ts.map +1 -1
- package/dist/sse-writer.js +20 -2
- package/dist/sse-writer.js.map +1 -1
- package/dist/transcription.cjs +9 -6
- package/dist/transcription.cjs.map +1 -1
- package/dist/transcription.d.cts +2 -2
- package/dist/transcription.d.cts.map +1 -1
- package/dist/transcription.d.ts +2 -2
- package/dist/transcription.d.ts.map +1 -1
- package/dist/transcription.js +8 -7
- package/dist/transcription.js.map +1 -1
- package/dist/types.d.cts +28 -2
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +28 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/vector-types.d.cts.map +1 -1
- package/dist/vector-types.d.ts.map +1 -1
- package/dist/video.cjs +1 -1
- package/dist/video.cjs.map +1 -1
- package/dist/video.d.cts.map +1 -1
- package/dist/video.d.ts.map +1 -1
- package/dist/video.js +1 -1
- package/dist/video.js.map +1 -1
- package/package.json +2 -2
package/dist/embeddings.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embeddings.js","names":[],"sources":["../src/embeddings.ts"],"sourcesContent":["/**\n * OpenAI Embeddings API support for aimock.\n *\n * Handles POST /v1/embeddings requests. Matches fixtures using the `inputText`\n * field, and falls back to generating a deterministic embedding from the input\n * text hash when no fixture matches.\n */\n\nimport type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n Fixture,\n HandlerDefaults,\n RecordProviderKey,\n} from \"./types.js\";\nimport {\n isEmbeddingResponse,\n isErrorResponse,\n serializeErrorResponse,\n generateDeterministicEmbedding,\n buildEmbeddingResponse,\n flattenHeaders,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n} from \"./helpers.js\";\nimport { matchFixture } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\n\n// ─── Embeddings API request types ──────────────────────────────────────────\n\ninterface EmbeddingRequest {\n input: string | string[];\n model: string;\n encoding_format?: \"float\" | \"base64\";\n dimensions?: number;\n [key: string]: unknown;\n}\n\n// ─── Request handler ───────────────────────────────────────────────────────\n\nexport async function handleEmbeddings(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n providerKey: RecordProviderKey = \"openai\",\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n let embeddingReq: EmbeddingRequest;\n try {\n embeddingReq = JSON.parse(raw) as EmbeddingRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: `Malformed JSON body: ${detail}`,\n type: \"invalid_request_error\",\n code: \"invalid_json\",\n },\n }),\n );\n return;\n }\n\n // Validate required input parameter\n if (embeddingReq.input === undefined || embeddingReq.input === null) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Missing required parameter: 'input'\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Normalize input to array of strings\n const inputs: string[] = Array.isArray(embeddingReq.input)\n ? embeddingReq.input\n : [embeddingReq.input];\n\n // Concatenate all inputs for matching purposes\n const combinedInput = inputs.join(\" \");\n\n // Build a synthetic ChatCompletionRequest for the fixture router.\n // We attach `embeddingInput` so the router's inputText matching can use it.\n const syntheticReq: ChatCompletionRequest = {\n model: embeddingReq.model,\n messages: [],\n embeddingInput: combinedInput,\n _endpointType: \"embedding\",\n };\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n logger.debug(`No fixture matched for request`);\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: req.url ?? \"/v1/embeddings\",\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 response = await resolveResponse(fixture, syntheticReq);\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, serializeErrorResponse(response));\n return;\n }\n\n // Embedding response — use the fixture's embedding for each input\n if (isEmbeddingResponse(response)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n const embeddings = inputs.map(() => [...response.embedding]);\n const body = buildEmbeddingResponse(embeddings, embeddingReq.model);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n return;\n }\n\n // Fixture matched but response type is not compatible with embeddings\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message:\n \"Fixture response did not match any known embedding type (must have embedding or error)\",\n type: \"server_error\",\n },\n }),\n );\n return;\n }\n\n // No fixture match — check strict mode first, then try proxy\n if (resolveStrictMode(defaults.strict, req.headers)) {\n logger.error(\n `STRICT: No fixture matched for ${req.method ?? \"POST\"} ${req.url ?? \"/v1/embeddings\"}`,\n );\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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 writeErrorResponse(\n res,\n 503,\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\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n providerKey,\n req.url ?? \"/v1/embeddings\",\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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 // No fixture match — generate deterministic embeddings from input text\n logger.warn(\n `No embedding fixture matched for \"${combinedInput.slice(0, 80)}\" — returning deterministic fallback`,\n );\n const dimensions = embeddingReq.dimensions ?? 1536;\n const embeddings = inputs.map((input) => generateDeterministicEmbedding(input, dimensions));\n\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture: null },\n });\n\n const body = buildEmbeddingResponse(embeddings, embeddingReq.model);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"embeddings.js","names":[],"sources":["../src/embeddings.ts"],"sourcesContent":["/**\n * OpenAI Embeddings API support for aimock.\n *\n * Handles POST /v1/embeddings requests. Matches fixtures using the `inputText`\n * field, and falls back to generating a deterministic embedding from the input\n * text hash when no fixture matches.\n */\n\nimport type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n Fixture,\n HandlerDefaults,\n RecordProviderKey,\n} from \"./types.js\";\nimport {\n isEmbeddingResponse,\n isErrorResponse,\n serializeErrorResponse,\n generateDeterministicEmbedding,\n buildEmbeddingResponse,\n flattenHeaders,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n} from \"./helpers.js\";\nimport { matchFixture } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\n\n// ─── Embeddings API request types ──────────────────────────────────────────\n\ninterface EmbeddingRequest {\n input: string | string[];\n model: string;\n encoding_format?: \"float\" | \"base64\";\n dimensions?: number;\n [key: string]: unknown;\n}\n\n// ─── Request handler ───────────────────────────────────────────────────────\n\nexport async function handleEmbeddings(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n providerKey: RecordProviderKey = \"openai\",\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n let embeddingReq: EmbeddingRequest;\n try {\n embeddingReq = JSON.parse(raw) as EmbeddingRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: `Malformed JSON body: ${detail}`,\n type: \"invalid_request_error\",\n code: \"invalid_json\",\n },\n }),\n );\n return;\n }\n\n // Validate required input parameter\n if (embeddingReq.input === undefined || embeddingReq.input === null) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Missing required parameter: 'input'\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Normalize input to array of strings\n const inputs: string[] = Array.isArray(embeddingReq.input)\n ? embeddingReq.input\n : [embeddingReq.input];\n\n // Concatenate all inputs for matching purposes\n const combinedInput = inputs.join(\" \");\n\n // Build a synthetic ChatCompletionRequest for the fixture router.\n // We attach `embeddingInput` so the router's inputText matching can use it.\n const syntheticReq: ChatCompletionRequest = {\n model: embeddingReq.model,\n messages: [],\n embeddingInput: combinedInput,\n _endpointType: \"embedding\",\n };\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n logger.debug(`No fixture matched for request`);\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: req.url ?? \"/v1/embeddings\",\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 response = await resolveResponse(fixture, syntheticReq);\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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 // Embedding response — use the fixture's embedding for each input\n if (isEmbeddingResponse(response)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n const embeddings = inputs.map(() => [...response.embedding]);\n const body = buildEmbeddingResponse(embeddings, embeddingReq.model);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n return;\n }\n\n // Fixture matched but response type is not compatible with embeddings\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message:\n \"Fixture response did not match any known embedding type (must have embedding or error)\",\n type: \"server_error\",\n },\n }),\n );\n return;\n }\n\n // No fixture match — check strict mode first, then try proxy\n if (resolveStrictMode(defaults.strict, req.headers)) {\n logger.error(\n `STRICT: No fixture matched for ${req.method ?? \"POST\"} ${req.url ?? \"/v1/embeddings\"}`,\n );\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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 writeErrorResponse(\n res,\n 503,\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\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n providerKey,\n req.url ?? \"/v1/embeddings\",\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\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 // No fixture match — generate deterministic embeddings from input text\n logger.warn(\n `No embedding fixture matched for \"${combinedInput.slice(0, 80)}\" — returning deterministic fallback`,\n );\n const dimensions = embeddingReq.dimensions ?? 1536;\n const embeddings = inputs.map((input) => generateDeterministicEmbedding(input, dimensions));\n\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture: null },\n });\n\n const body = buildEmbeddingResponse(embeddings, embeddingReq.model);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n}\n"],"mappings":";;;;;;;AA6CA,eAAsB,iBACpB,KACA,KACA,KACA,UACA,SACA,UACA,gBACA,cAAiC,UAClB;CACf,MAAM,EAAE,WAAW;AACnB,gBAAe,IAAI;CAEnB,IAAI;AACJ,KAAI;AACF,iBAAe,KAAK,MAAM,IAAI;UACvB,UAAU;EACjB,MAAM,SAAS,oBAAoB,QAAQ,SAAS,UAAU;AAC9D,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,qBACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS,wBAAwB;GACjC,MAAM;GACN,MAAM;GACP,EACF,CAAC,CACH;AACD;;AAIF,KAAI,aAAa,UAAU,UAAa,aAAa,UAAU,MAAM;AACnE,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,qBACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAIF,MAAM,SAAmB,MAAM,QAAQ,aAAa,MAAM,GACtD,aAAa,QACb,CAAC,aAAa,MAAM;CAGxB,MAAM,gBAAgB,OAAO,KAAK,IAAI;CAItC,MAAM,eAAsC;EAC1C,OAAO,aAAa;EACpB,UAAU,EAAE;EACZ,gBAAgB;EAChB,eAAe;EAChB;CAED,MAAM,SAAS,UAAU,IAAI;CAC7B,MAAM,UAAU,aACd,UACA,cACA,QAAQ,6BAA6B,OAAO,EAC5C,SAAS,iBACV;AAED,KAAI,SAAS;AACX,UAAQ,2BAA2B,SAAS,UAAU,OAAO;AAC7D,SAAO,MAAM,oBAAoB,KAAK,UAAU,QAAQ,MAAM,CAAC,MAAM,GAAG,IAAI,GAAG;OAE/E,QAAO,MAAM,iCAAiC;AAGhD,KACE,WACE,KACA,SACA,SAAS,OACT,IAAI,SACJ,SACA;EACE,QAAQ,IAAI,UAAU;EACtB,MAAM,IAAI,OAAO;EACjB,SAAS,eAAe,IAAI,QAAQ;EACpC,MAAM;EACP,EACD,UAAU,YAAY,SACtB,SAAS,UACT,SAAS,OACV,CAED;AAEF,KAAI,SAAS;EACX,MAAM,WAAW,MAAM,gBAAgB,SAAS,aAAa;AAG7D,MAAI,gBAAgB,SAAS,EAAE;GAC7B,MAAM,SAAS,SAAS,UAAU;AAClC,WAAQ,IAAI;IACV,QAAQ,IAAI,UAAU;IACtB,MAAM,IAAI,OAAO;IACjB,SAAS,eAAe,IAAI,QAAQ;IACpC,MAAM;IACN,UAAU;KAAE;KAAQ;KAAS;IAC9B,CAAC;AACF,sBAAmB,KAAK,QAAQ,uBAAuB,SAAS,EAAE,EAChE,YAAY,SAAS,YACtB,CAAC;AACF;;AAIF,MAAI,oBAAoB,SAAS,EAAE;AACjC,WAAQ,IAAI;IACV,QAAQ,IAAI,UAAU;IACtB,MAAM,IAAI,OAAO;IACjB,SAAS,eAAe,IAAI,QAAQ;IACpC,MAAM;IACN,UAAU;KAAE,QAAQ;KAAK;KAAS;IACnC,CAAC;GAEF,MAAM,OAAO,uBADM,OAAO,UAAU,CAAC,GAAG,SAAS,UAAU,CAAC,EACZ,aAAa,MAAM;AACnE,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU,KAAK,CAAC;AAC7B;;AAIF,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;AACF,qBACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SACE;GACF,MAAM;GACP,EACF,CAAC,CACH;AACD;;AAIF,KAAI,kBAAkB,SAAS,QAAQ,IAAI,QAAQ,EAAE;AACnD,SAAO,MACL,kCAAkC,IAAI,UAAU,OAAO,GAAG,IAAI,OAAO,mBACtE;AACD,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IACR,QAAQ;IACR,SAAS;IACT,GAAG,oBAAoB,SAAS,QAAQ,IAAI,QAAQ;IACrD;GACF,CAAC;AACF,qBACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACN,MAAM;GACP,EACF,CAAC,CACH;AACD;;AAGF,KAAI,SAAS,QAAQ;EACnB,MAAM,UAAU,MAAM,eACpB,KACA,KACA,cACA,aACA,IAAI,OAAO,kBACX,UACA,UACA,IACD;AACD,MAAI,YAAY,kBAAmB;AACnC,MAAI,YAAY,kBAAkB;AAChC,WAAQ,IAAI;IACV,QAAQ,IAAI,UAAU;IACtB,MAAM,IAAI,OAAO;IACjB,SAAS,eAAe,IAAI,QAAQ;IACpC,MAAM;IACN,UAAU;KAAE,QAAQ,IAAI,cAAc;KAAK,SAAS;KAAM,QAAQ;KAAS;IAC5E,CAAC;AACF;;;AAKJ,QAAO,KACL,qCAAqC,cAAc,MAAM,GAAG,GAAG,CAAC,sCACjE;CACD,MAAM,aAAa,aAAa,cAAc;CAC9C,MAAM,aAAa,OAAO,KAAK,UAAU,+BAA+B,OAAO,WAAW,CAAC;AAE3F,SAAQ,IAAI;EACV,QAAQ,IAAI,UAAU;EACtB,MAAM,IAAI,OAAO;EACjB,SAAS,eAAe,IAAI,QAAQ;EACpC,MAAM;EACN,UAAU;GAAE,QAAQ;GAAK,SAAS;GAAM;EACzC,CAAC;CAEF,MAAM,OAAO,uBAAuB,YAAY,aAAa,MAAM;AACnE,KAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,KAAI,IAAI,KAAK,UAAU,KAAK,CAAC"}
|
package/dist/fal-audio.cjs
CHANGED
|
@@ -253,8 +253,7 @@ async function handleQueueSubmit(req, res, body, pathname, modelId, testId, fixt
|
|
|
253
253
|
fixture
|
|
254
254
|
}
|
|
255
255
|
});
|
|
256
|
-
|
|
257
|
-
res.end(require_helpers.serializeErrorResponse(response));
|
|
256
|
+
require_sse_writer.writeErrorResponse(res, status, require_helpers.serializeErrorResponse(response), { retryAfter: response.retryAfter });
|
|
258
257
|
return;
|
|
259
258
|
}
|
|
260
259
|
let result;
|
|
@@ -688,8 +687,7 @@ async function handleSyncRun(req, res, body, pathname, modelId, fixtures, defaul
|
|
|
688
687
|
fixture
|
|
689
688
|
}
|
|
690
689
|
});
|
|
691
|
-
|
|
692
|
-
res.end(require_helpers.serializeErrorResponse(response));
|
|
690
|
+
require_sse_writer.writeErrorResponse(res, status, require_helpers.serializeErrorResponse(response), { retryAfter: response.retryAfter });
|
|
693
691
|
return;
|
|
694
692
|
}
|
|
695
693
|
if (!require_helpers.isAudioResponse(response)) {
|
package/dist/fal-audio.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fal-audio.cjs","names":["FORMAT_TO_CONTENT_TYPE","getTestId","flattenHeaders","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 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 };\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: {},\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 res.writeHead(status, { \"Content-Type\": \"application/json\" });\n res.end(serializeErrorResponse(response));\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: {},\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: {},\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 const syntheticReq: ChatCompletionRequest = {\n model: modelId,\n messages: [{ role: \"user\", content: prompt }],\n _endpointType: \"fal-audio\",\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: {},\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 res.writeHead(status, { \"Content-Type\": \"application/json\" });\n res.end(serializeErrorResponse(response));\n return;\n }\n\n if (!isAudioResponse(response)) {\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 result = audioToFalFile(response);\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(JSON.stringify(result));\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fal-audio.cjs","names":["FORMAT_TO_CONTENT_TYPE","getTestId","flattenHeaders","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 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 };\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: {},\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: {},\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: {},\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 const syntheticReq: ChatCompletionRequest = {\n model: modelId,\n messages: [{ role: \"user\", content: prompt }],\n _endpointType: \"fal-audio\",\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: {},\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 if (!isAudioResponse(response)) {\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 result = audioToFalFile(response);\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(JSON.stringify(result));\n}\n"],"mappings":"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"}
|
package/dist/fal-audio.js
CHANGED
|
@@ -251,8 +251,7 @@ async function handleQueueSubmit(req, res, body, pathname, modelId, testId, fixt
|
|
|
251
251
|
fixture
|
|
252
252
|
}
|
|
253
253
|
});
|
|
254
|
-
res
|
|
255
|
-
res.end(serializeErrorResponse(response));
|
|
254
|
+
writeErrorResponse(res, status, serializeErrorResponse(response), { retryAfter: response.retryAfter });
|
|
256
255
|
return;
|
|
257
256
|
}
|
|
258
257
|
let result;
|
|
@@ -686,8 +685,7 @@ async function handleSyncRun(req, res, body, pathname, modelId, fixtures, defaul
|
|
|
686
685
|
fixture
|
|
687
686
|
}
|
|
688
687
|
});
|
|
689
|
-
res
|
|
690
|
-
res.end(serializeErrorResponse(response));
|
|
688
|
+
writeErrorResponse(res, status, serializeErrorResponse(response), { retryAfter: response.retryAfter });
|
|
691
689
|
return;
|
|
692
690
|
}
|
|
693
691
|
if (!isAudioResponse(response)) {
|
package/dist/fal-audio.js.map
CHANGED
|
@@ -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 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 };\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: {},\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 res.writeHead(status, { \"Content-Type\": \"application/json\" });\n res.end(serializeErrorResponse(response));\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: {},\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: {},\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 const syntheticReq: ChatCompletionRequest = {\n model: modelId,\n messages: [{ role: \"user\", content: prompt }],\n _endpointType: \"fal-audio\",\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: {},\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 res.writeHead(status, { \"Content-Type\": \"application/json\" });\n res.end(serializeErrorResponse(response));\n return;\n }\n\n if (!isAudioResponse(response)) {\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 result = audioToFalFile(response);\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(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 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 };\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: {},\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: {},\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: {},\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 const syntheticReq: ChatCompletionRequest = {\n model: modelId,\n messages: [{ role: \"user\", content: prompt }],\n _endpointType: \"fal-audio\",\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: {},\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 if (!isAudioResponse(response)) {\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 result = audioToFalFile(response);\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(JSON.stringify(result));\n}\n"],"mappings":"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"}
|
package/dist/fal.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
2
2
|
const require_helpers = require('./helpers.cjs');
|
|
3
3
|
const require_router = require('./router.cjs');
|
|
4
|
+
const require_sse_writer = require('./sse-writer.cjs');
|
|
4
5
|
const require_url = require('./url.cjs');
|
|
5
6
|
const require_recorder = require('./recorder.cjs');
|
|
6
7
|
const require_fal_audio = require('./fal-audio.cjs');
|
|
@@ -493,8 +494,7 @@ async function handleFal(req, res, body, pathname, fixtures, defaults, journal)
|
|
|
493
494
|
fixture
|
|
494
495
|
}
|
|
495
496
|
});
|
|
496
|
-
|
|
497
|
-
res.end(require_helpers.serializeErrorResponse(response));
|
|
497
|
+
require_sse_writer.writeErrorResponse(res, status, require_helpers.serializeErrorResponse(response), { retryAfter: response.retryAfter });
|
|
498
498
|
return "handled";
|
|
499
499
|
}
|
|
500
500
|
let payload;
|