@copilotkit/aimock 1.18.0 → 1.19.1
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 +20 -0
- package/dist/aimock-cli.cjs +2 -3
- package/dist/aimock-cli.cjs.map +1 -1
- package/dist/aimock-cli.js +3 -4
- package/dist/aimock-cli.js.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 +3 -3
- 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 +3 -3
- package/dist/bedrock.js.map +1 -1
- package/dist/cohere.cjs +1 -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 +2 -2
- 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 +1 -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 +2 -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.d.cts.map +1 -1
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/embeddings.js +2 -2
- package/dist/embeddings.js.map +1 -1
- package/dist/fal-audio.cjs +2 -2
- package/dist/fal-audio.cjs.map +1 -1
- package/dist/fal-audio.d.cts.map +1 -1
- package/dist/fal-audio.d.ts.map +1 -1
- package/dist/fal-audio.js +3 -3
- package/dist/fal-audio.js.map +1 -1
- package/dist/fal.cjs +1 -1
- 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/fixture-loader.cjs +157 -127
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +157 -127
- package/dist/fixture-loader.js.map +1 -1
- package/dist/gemini-interactions.cjs +1 -1
- package/dist/gemini-interactions.cjs.map +1 -1
- package/dist/gemini-interactions.d.cts.map +1 -1
- package/dist/gemini-interactions.d.ts.map +1 -1
- package/dist/gemini-interactions.js +2 -2
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +1 -1
- package/dist/gemini.cjs.map +1 -1
- package/dist/gemini.d.cts.map +1 -1
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +2 -2
- package/dist/gemini.js.map +1 -1
- package/dist/helpers.cjs +25 -0
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +24 -1
- package/dist/helpers.js.map +1 -1
- package/dist/images.cjs +1 -1
- package/dist/images.cjs.map +1 -1
- package/dist/images.d.cts.map +1 -1
- package/dist/images.d.ts.map +1 -1
- package/dist/images.js +2 -2
- package/dist/images.js.map +1 -1
- package/dist/llmock.cjs +1 -1
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +8 -7
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +8 -7
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +1 -1
- package/dist/llmock.js.map +1 -1
- package/dist/messages.cjs +1 -1
- package/dist/messages.cjs.map +1 -1
- package/dist/messages.d.cts.map +1 -1
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +2 -2
- package/dist/messages.js.map +1 -1
- package/dist/ollama.cjs +2 -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 +3 -3
- package/dist/ollama.js.map +1 -1
- package/dist/recorder.cjs +27 -4
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.d.cts.map +1 -1
- package/dist/recorder.d.ts.map +1 -1
- package/dist/recorder.js +27 -4
- package/dist/recorder.js.map +1 -1
- package/dist/responses.cjs +1 -1
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.d.cts.map +1 -1
- package/dist/responses.d.ts.map +1 -1
- package/dist/responses.js +2 -2
- package/dist/responses.js.map +1 -1
- package/dist/router.cjs +3 -1
- package/dist/router.cjs.map +1 -1
- package/dist/router.js +3 -1
- package/dist/router.js.map +1 -1
- package/dist/server.cjs +2 -2
- 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 +3 -3
- package/dist/server.js.map +1 -1
- package/dist/speech.cjs +1 -1
- package/dist/speech.cjs.map +1 -1
- package/dist/speech.d.cts.map +1 -1
- package/dist/speech.d.ts.map +1 -1
- package/dist/speech.js +2 -2
- package/dist/speech.js.map +1 -1
- package/dist/transcription.cjs +1 -1
- package/dist/transcription.cjs.map +1 -1
- package/dist/transcription.d.cts.map +1 -1
- package/dist/transcription.d.ts.map +1 -1
- package/dist/transcription.js +2 -2
- package/dist/transcription.js.map +1 -1
- package/dist/types.d.cts +3 -2
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +3 -2
- package/dist/types.d.ts.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 +2 -2
- package/dist/video.js.map +1 -1
- package/dist/ws-gemini-live.cjs +1 -1
- package/dist/ws-gemini-live.cjs.map +1 -1
- package/dist/ws-gemini-live.d.cts.map +1 -1
- package/dist/ws-gemini-live.d.ts.map +1 -1
- package/dist/ws-gemini-live.js +2 -2
- package/dist/ws-gemini-live.js.map +1 -1
- package/dist/ws-realtime.cjs +1 -1
- package/dist/ws-realtime.cjs.map +1 -1
- package/dist/ws-realtime.d.cts.map +1 -1
- package/dist/ws-realtime.d.ts.map +1 -1
- package/dist/ws-realtime.js +2 -2
- package/dist/ws-realtime.js.map +1 -1
- package/dist/ws-responses.cjs +1 -1
- package/dist/ws-responses.cjs.map +1 -1
- package/dist/ws-responses.js +2 -2
- package/dist/ws-responses.js.map +1 -1
- package/package.json +1 -1
package/dist/fal.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fal.js","names":[],"sources":["../src/fal.ts"],"sourcesContent":["import type http from \"node:http\";\nimport crypto from \"node:crypto\";\nimport type { ChatCompletionRequest, Fixture, HandlerDefaults, RawJSONResponse } from \"./types.js\";\nimport {\n isAudioResponse,\n isErrorResponse,\n isJSONResponse,\n flattenHeaders,\n getTestId,\n} from \"./helpers.js\";\nimport { matchFixture } from \"./router.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\nimport type { Journal } from \"./journal.js\";\nimport { audioToFalFile } from \"./fal-audio.js\";\n\n// ─── FalQueueState (TTL + bounded) ───────────────────────────────────────\n\nconst FAL_QUEUE_MAX_ENTRIES = 10_000;\nconst FAL_QUEUE_TTL_MS = 3_600_000; // 1 hour\n\ninterface FalQueueJob {\n requestId: string;\n modelId: string;\n status: \"IN_QUEUE\" | \"IN_PROGRESS\" | \"COMPLETED\";\n result: unknown;\n createdAt: number;\n}\n\ninterface FalQueueEntry {\n job: FalQueueJob;\n createdAt: number;\n}\n\n/**\n * Per-testId queue state for the general fal handler. Mirrors FalJobMap from\n * fal-audio.ts but stores arbitrary JSON payloads instead of audio file\n * objects, so it can serve any fal model (image, video, motion, music, etc.).\n */\nexport class FalQueueStateMap {\n private readonly entries = new Map<string, FalQueueEntry>();\n\n get(key: string): FalQueueJob | undefined {\n const entry = this.entries.get(key);\n if (!entry) return undefined;\n if (Date.now() - entry.createdAt > FAL_QUEUE_TTL_MS) {\n this.entries.delete(key);\n return undefined;\n }\n return entry.job;\n }\n\n set(key: string, job: FalQueueJob): void {\n this.entries.set(key, { job, createdAt: Date.now() });\n if (this.entries.size > FAL_QUEUE_MAX_ENTRIES) {\n const excess = this.entries.size - FAL_QUEUE_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 get size(): number {\n return this.entries.size;\n }\n}\n\nexport const falQueueStates = new FalQueueStateMap();\n\n// ─── Hosts and routing ──────────────────────────────────────────────────\n\nconst FAL_HOSTS = {\n queue: \"queue.fal.run\",\n sync: \"fal.run\",\n storage: \"rest.fal.ai\",\n storageAlpha: \"rest.alpha.fal.ai\",\n gateway: \"gateway.fal.ai\",\n} as const;\n\nconst QUEUE_REQUESTS_RE = /^(.+)\\/requests\\/([^/]+)(\\/status|\\/cancel)?$/;\nconst STORAGE_INITIATE_PATH = \"/storage/upload/initiate\";\n\nfunction stripFalPrefix(pathname: string): string {\n const stripped = pathname.replace(/^\\/fal/, \"\");\n return stripped.length > 0 ? stripped : \"/\";\n}\n\nfunction extractPromptFromBody(body: unknown): string {\n if (!body || typeof body !== \"object\") return \"\";\n const obj = body as Record<string, unknown>;\n if (typeof obj.prompt === \"string\") return obj.prompt;\n if (typeof obj.text === \"string\") return obj.text;\n const input = obj.input;\n if (input && typeof input === \"object\") {\n const inputObj = input as Record<string, unknown>;\n if (typeof inputObj.prompt === \"string\") return inputObj.prompt;\n if (typeof inputObj.text === \"string\") return inputObj.text;\n }\n return \"\";\n}\n\ninterface ParsedFalPath {\n modelId: string;\n requestId?: string;\n action?: \"status\" | \"cancel\" | \"result\";\n}\n\nfunction parseFalPath(stripped: string): ParsedFalPath | null {\n if (!stripped.startsWith(\"/\")) return null;\n const trimmed = stripped.replace(/^\\/+/, \"\");\n if (!trimmed) return null;\n\n const m = QUEUE_REQUESTS_RE.exec(`/${trimmed}`);\n if (m) {\n const modelId = m[1].replace(/^\\/+/, \"\");\n const action = m[3] === \"/status\" ? \"status\" : m[3] === \"/cancel\" ? \"cancel\" : \"result\";\n return { modelId, requestId: m[2], action };\n }\n return { modelId: trimmed };\n}\n\nexport type HandleFalOutcome = \"handled\" | \"passthrough\";\n\ninterface FalRouteInfo {\n kind: \"queue-submit\" | \"queue-status\" | \"queue-result\" | \"queue-cancel\" | \"sync-run\" | \"storage\";\n modelId?: string;\n requestId?: string;\n targetHost: string;\n}\n\nfunction classifyRoute(\n req: http.IncomingMessage,\n pathname: string,\n targetHost: string,\n): FalRouteInfo | null {\n const stripped = stripFalPrefix(pathname);\n\n if (targetHost === FAL_HOSTS.storage || targetHost === FAL_HOSTS.storageAlpha) {\n if (req.method === \"POST\" && stripped === STORAGE_INITIATE_PATH) {\n return { kind: \"storage\", targetHost };\n }\n return null;\n }\n\n const parsed = parseFalPath(stripped);\n if (!parsed) return null;\n\n if (targetHost === FAL_HOSTS.queue) {\n if (parsed.requestId) {\n if (parsed.action === \"status\" && req.method === \"GET\") {\n return {\n kind: \"queue-status\",\n modelId: parsed.modelId,\n requestId: parsed.requestId,\n targetHost,\n };\n }\n if (parsed.action === \"cancel\" && req.method === \"PUT\") {\n return {\n kind: \"queue-cancel\",\n modelId: parsed.modelId,\n requestId: parsed.requestId,\n targetHost,\n };\n }\n if (parsed.action === \"result\" && req.method === \"GET\") {\n return {\n kind: \"queue-result\",\n modelId: parsed.modelId,\n requestId: parsed.requestId,\n targetHost,\n };\n }\n return null;\n }\n if (req.method === \"POST\") {\n return { kind: \"queue-submit\", modelId: parsed.modelId, targetHost };\n }\n return null;\n }\n\n if (targetHost === FAL_HOSTS.sync) {\n if (req.method === \"POST\" && parsed.modelId) {\n return { kind: \"sync-run\", modelId: parsed.modelId, targetHost };\n }\n return null;\n }\n\n return null;\n}\n\n/**\n * General fal.ai handler. Routes by `x-fal-target-host` header (the convention\n * used by `@fal-ai/client`'s server-side requestMiddleware workaround for the\n * fact that `proxyUrl` is browser-only).\n *\n * Returns `\"passthrough\"` when the request does not look like a host-mirrored\n * fal call, so the caller can fall back to the legacy `/fal/queue/...` and\n * `/fal/run/...` audio routes.\n */\nexport async function handleFal(\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<HandleFalOutcome> {\n const targetHostHeader = req.headers[\"x-fal-target-host\"];\n const targetHost = Array.isArray(targetHostHeader) ? targetHostHeader[0] : targetHostHeader;\n if (!targetHost) return \"passthrough\";\n\n const route = classifyRoute(req, pathname, targetHost);\n if (!route) return \"passthrough\";\n\n const testId = getTestId(req);\n const stateKey = (id: string) => `${testId}:${id}`;\n\n switch (route.kind) {\n case \"queue-status\": {\n const job = falQueueStates.get(stateKey(route.requestId!));\n if (!job) {\n respondNotFound(req, res, pathname, journal, route.requestId!);\n return \"handled\";\n }\n const responseBody = {\n status: job.status,\n request_id: job.requestId,\n response_url: `https://${FAL_HOSTS.queue}/${job.modelId}/requests/${job.requestId}`,\n };\n writeJson(req, res, 200, responseBody, pathname, journal);\n return \"handled\";\n }\n\n case \"queue-result\": {\n const job = falQueueStates.get(stateKey(route.requestId!));\n if (!job) {\n respondNotFound(req, res, pathname, journal, route.requestId!);\n return \"handled\";\n }\n writeJson(req, res, 200, job.result, pathname, journal);\n return \"handled\";\n }\n\n case \"queue-cancel\": {\n const job = falQueueStates.get(stateKey(route.requestId!));\n if (!job) {\n journal.add({\n method: req.method ?? \"PUT\",\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 \"handled\";\n }\n journal.add({\n method: req.method ?? \"PUT\",\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 return \"handled\";\n }\n\n case \"storage\": {\n let filename = \"upload.bin\";\n try {\n const parsed = body ? (JSON.parse(body) as Record<string, unknown>) : {};\n if (typeof parsed.filename === \"string\") filename = parsed.filename;\n if (typeof parsed.file_name === \"string\") filename = parsed.file_name;\n } catch {\n // ignore — stub doesn't require a structured body\n }\n const fileId = crypto.randomUUID();\n const responseBody = {\n upload_url: `https://${route.targetHost}/storage/upload/${fileId}`,\n file_url: `https://${route.targetHost}/files/${fileId}/${filename}`,\n };\n writeJson(req, res, 200, responseBody, pathname, journal);\n return \"handled\";\n }\n\n case \"queue-submit\":\n case \"sync-run\": {\n const modelId = route.modelId!;\n const parsedBody = parseBody(body);\n const prompt = extractPromptFromBody(parsedBody);\n const syntheticReq: ChatCompletionRequest = {\n model: modelId,\n messages: [{ role: \"user\", content: prompt || JSON.stringify(parsedBody ?? {}) }],\n _endpointType: \"fal\",\n };\n\n const matchCounts = journal.getFixtureMatchCountsForTest(testId);\n const fixture = matchFixture(fixtures, syntheticReq, matchCounts, defaults.requestTransform);\n\n if (!fixture) {\n if (defaults.record) {\n const effectiveDefaults = withFalUpstream(defaults, route.targetHost);\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"fal\",\n stripFalPrefix(pathname),\n fixtures,\n effectiveDefaults,\n body,\n );\n if (outcome === \"handled_by_hook\") return \"handled\";\n if (outcome === \"relayed\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return \"handled\";\n }\n }\n\n const strictStatus = defaults.strict ? 503 : 404;\n const strictMessage = defaults.strict\n ? \"Strict mode: no fixture matched\"\n : \"No fixture matched\";\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: strictStatus, fixture: null },\n });\n res.writeHead(strictStatus, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return \"handled\";\n }\n\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n const response = fixture.response;\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(JSON.stringify(response));\n return \"handled\";\n }\n\n let payload: unknown;\n if (isJSONResponse(response)) {\n payload = (response as RawJSONResponse).json;\n } else if (isAudioResponse(response)) {\n payload = audioToFalFile(response);\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: {\n message: \"Fixture response is not JSON or audio for fal endpoint\",\n type: \"server_error\",\n },\n }),\n );\n return \"handled\";\n }\n\n if (route.kind === \"sync-run\") {\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(payload));\n return \"handled\";\n }\n\n const requestId = crypto.randomUUID();\n falQueueStates.set(stateKey(requestId), {\n requestId,\n modelId,\n status: \"COMPLETED\",\n result: payload,\n createdAt: Date.now(),\n });\n const envelope = {\n request_id: requestId,\n response_url: `https://${FAL_HOSTS.queue}/${modelId}/requests/${requestId}`,\n status_url: `https://${FAL_HOSTS.queue}/${modelId}/requests/${requestId}/status`,\n cancel_url: `https://${FAL_HOSTS.queue}/${modelId}/requests/${requestId}/cancel`,\n queue_position: 0,\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(envelope));\n return \"handled\";\n }\n }\n}\n\nfunction parseBody(raw: string): Record<string, unknown> | null {\n if (!raw.trim()) return null;\n try {\n return JSON.parse(raw) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\nfunction withFalUpstream(defaults: HandlerDefaults, targetHost: string): HandlerDefaults {\n if (!defaults.record) return defaults;\n // Respect an explicit record.providers.fal — tests and dev configs need to\n // point at a stub upstream. Only synthesise from the header when the user\n // didn't configure one (the \"or omit upstream URL — it's in the request\n // hostname\" mode from the issue).\n if (defaults.record.providers.fal) return defaults;\n return {\n ...defaults,\n record: {\n ...defaults.record,\n providers: {\n ...defaults.record.providers,\n fal: `https://${targetHost}`,\n },\n },\n };\n}\n\nfunction writeJson(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n status: number,\n payload: unknown,\n pathname: string,\n journal: Journal,\n): void {\n journal.add({\n method: req.method ?? \"GET\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status, fixture: null },\n });\n res.writeHead(status, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(payload));\n}\n\nfunction respondNotFound(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n pathname: string,\n journal: Journal,\n requestId: string,\n): void {\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}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fal.js","names":[],"sources":["../src/fal.ts"],"sourcesContent":["import type http from \"node:http\";\nimport crypto from \"node:crypto\";\nimport type { ChatCompletionRequest, Fixture, HandlerDefaults, RawJSONResponse } from \"./types.js\";\nimport {\n isAudioResponse,\n isErrorResponse,\n isJSONResponse,\n flattenHeaders,\n getTestId,\n resolveResponse,\n} from \"./helpers.js\";\nimport { matchFixture } from \"./router.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\nimport type { Journal } from \"./journal.js\";\nimport { audioToFalFile } from \"./fal-audio.js\";\n\n// ─── FalQueueState (TTL + bounded) ───────────────────────────────────────\n\nconst FAL_QUEUE_MAX_ENTRIES = 10_000;\nconst FAL_QUEUE_TTL_MS = 3_600_000; // 1 hour\n\ninterface FalQueueJob {\n requestId: string;\n modelId: string;\n status: \"IN_QUEUE\" | \"IN_PROGRESS\" | \"COMPLETED\";\n result: unknown;\n createdAt: number;\n}\n\ninterface FalQueueEntry {\n job: FalQueueJob;\n createdAt: number;\n}\n\n/**\n * Per-testId queue state for the general fal handler. Mirrors FalJobMap from\n * fal-audio.ts but stores arbitrary JSON payloads instead of audio file\n * objects, so it can serve any fal model (image, video, motion, music, etc.).\n */\nexport class FalQueueStateMap {\n private readonly entries = new Map<string, FalQueueEntry>();\n\n get(key: string): FalQueueJob | undefined {\n const entry = this.entries.get(key);\n if (!entry) return undefined;\n if (Date.now() - entry.createdAt > FAL_QUEUE_TTL_MS) {\n this.entries.delete(key);\n return undefined;\n }\n return entry.job;\n }\n\n set(key: string, job: FalQueueJob): void {\n this.entries.set(key, { job, createdAt: Date.now() });\n if (this.entries.size > FAL_QUEUE_MAX_ENTRIES) {\n const excess = this.entries.size - FAL_QUEUE_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 get size(): number {\n return this.entries.size;\n }\n}\n\nexport const falQueueStates = new FalQueueStateMap();\n\n// ─── Hosts and routing ──────────────────────────────────────────────────\n\nconst FAL_HOSTS = {\n queue: \"queue.fal.run\",\n sync: \"fal.run\",\n storage: \"rest.fal.ai\",\n storageAlpha: \"rest.alpha.fal.ai\",\n gateway: \"gateway.fal.ai\",\n} as const;\n\nconst QUEUE_REQUESTS_RE = /^(.+)\\/requests\\/([^/]+)(\\/status|\\/cancel)?$/;\nconst STORAGE_INITIATE_PATH = \"/storage/upload/initiate\";\n\nfunction stripFalPrefix(pathname: string): string {\n const stripped = pathname.replace(/^\\/fal/, \"\");\n return stripped.length > 0 ? stripped : \"/\";\n}\n\nfunction extractPromptFromBody(body: unknown): string {\n if (!body || typeof body !== \"object\") return \"\";\n const obj = body as Record<string, unknown>;\n if (typeof obj.prompt === \"string\") return obj.prompt;\n if (typeof obj.text === \"string\") return obj.text;\n const input = obj.input;\n if (input && typeof input === \"object\") {\n const inputObj = input as Record<string, unknown>;\n if (typeof inputObj.prompt === \"string\") return inputObj.prompt;\n if (typeof inputObj.text === \"string\") return inputObj.text;\n }\n return \"\";\n}\n\ninterface ParsedFalPath {\n modelId: string;\n requestId?: string;\n action?: \"status\" | \"cancel\" | \"result\";\n}\n\nfunction parseFalPath(stripped: string): ParsedFalPath | null {\n if (!stripped.startsWith(\"/\")) return null;\n const trimmed = stripped.replace(/^\\/+/, \"\");\n if (!trimmed) return null;\n\n const m = QUEUE_REQUESTS_RE.exec(`/${trimmed}`);\n if (m) {\n const modelId = m[1].replace(/^\\/+/, \"\");\n const action = m[3] === \"/status\" ? \"status\" : m[3] === \"/cancel\" ? \"cancel\" : \"result\";\n return { modelId, requestId: m[2], action };\n }\n return { modelId: trimmed };\n}\n\nexport type HandleFalOutcome = \"handled\" | \"passthrough\";\n\ninterface FalRouteInfo {\n kind: \"queue-submit\" | \"queue-status\" | \"queue-result\" | \"queue-cancel\" | \"sync-run\" | \"storage\";\n modelId?: string;\n requestId?: string;\n targetHost: string;\n}\n\nfunction classifyRoute(\n req: http.IncomingMessage,\n pathname: string,\n targetHost: string,\n): FalRouteInfo | null {\n const stripped = stripFalPrefix(pathname);\n\n if (targetHost === FAL_HOSTS.storage || targetHost === FAL_HOSTS.storageAlpha) {\n if (req.method === \"POST\" && stripped === STORAGE_INITIATE_PATH) {\n return { kind: \"storage\", targetHost };\n }\n return null;\n }\n\n const parsed = parseFalPath(stripped);\n if (!parsed) return null;\n\n if (targetHost === FAL_HOSTS.queue) {\n if (parsed.requestId) {\n if (parsed.action === \"status\" && req.method === \"GET\") {\n return {\n kind: \"queue-status\",\n modelId: parsed.modelId,\n requestId: parsed.requestId,\n targetHost,\n };\n }\n if (parsed.action === \"cancel\" && req.method === \"PUT\") {\n return {\n kind: \"queue-cancel\",\n modelId: parsed.modelId,\n requestId: parsed.requestId,\n targetHost,\n };\n }\n if (parsed.action === \"result\" && req.method === \"GET\") {\n return {\n kind: \"queue-result\",\n modelId: parsed.modelId,\n requestId: parsed.requestId,\n targetHost,\n };\n }\n return null;\n }\n if (req.method === \"POST\") {\n return { kind: \"queue-submit\", modelId: parsed.modelId, targetHost };\n }\n return null;\n }\n\n if (targetHost === FAL_HOSTS.sync) {\n if (req.method === \"POST\" && parsed.modelId) {\n return { kind: \"sync-run\", modelId: parsed.modelId, targetHost };\n }\n return null;\n }\n\n return null;\n}\n\n/**\n * General fal.ai handler. Routes by `x-fal-target-host` header (the convention\n * used by `@fal-ai/client`'s server-side requestMiddleware workaround for the\n * fact that `proxyUrl` is browser-only).\n *\n * Returns `\"passthrough\"` when the request does not look like a host-mirrored\n * fal call, so the caller can fall back to the legacy `/fal/queue/...` and\n * `/fal/run/...` audio routes.\n */\nexport async function handleFal(\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<HandleFalOutcome> {\n const targetHostHeader = req.headers[\"x-fal-target-host\"];\n const targetHost = Array.isArray(targetHostHeader) ? targetHostHeader[0] : targetHostHeader;\n if (!targetHost) return \"passthrough\";\n\n const route = classifyRoute(req, pathname, targetHost);\n if (!route) return \"passthrough\";\n\n const testId = getTestId(req);\n const stateKey = (id: string) => `${testId}:${id}`;\n\n switch (route.kind) {\n case \"queue-status\": {\n const job = falQueueStates.get(stateKey(route.requestId!));\n if (!job) {\n respondNotFound(req, res, pathname, journal, route.requestId!);\n return \"handled\";\n }\n const responseBody = {\n status: job.status,\n request_id: job.requestId,\n response_url: `https://${FAL_HOSTS.queue}/${job.modelId}/requests/${job.requestId}`,\n };\n writeJson(req, res, 200, responseBody, pathname, journal);\n return \"handled\";\n }\n\n case \"queue-result\": {\n const job = falQueueStates.get(stateKey(route.requestId!));\n if (!job) {\n respondNotFound(req, res, pathname, journal, route.requestId!);\n return \"handled\";\n }\n writeJson(req, res, 200, job.result, pathname, journal);\n return \"handled\";\n }\n\n case \"queue-cancel\": {\n const job = falQueueStates.get(stateKey(route.requestId!));\n if (!job) {\n journal.add({\n method: req.method ?? \"PUT\",\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 \"handled\";\n }\n journal.add({\n method: req.method ?? \"PUT\",\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 return \"handled\";\n }\n\n case \"storage\": {\n let filename = \"upload.bin\";\n try {\n const parsed = body ? (JSON.parse(body) as Record<string, unknown>) : {};\n if (typeof parsed.filename === \"string\") filename = parsed.filename;\n if (typeof parsed.file_name === \"string\") filename = parsed.file_name;\n } catch {\n // ignore — stub doesn't require a structured body\n }\n const fileId = crypto.randomUUID();\n const responseBody = {\n upload_url: `https://${route.targetHost}/storage/upload/${fileId}`,\n file_url: `https://${route.targetHost}/files/${fileId}/${filename}`,\n };\n writeJson(req, res, 200, responseBody, pathname, journal);\n return \"handled\";\n }\n\n case \"queue-submit\":\n case \"sync-run\": {\n const modelId = route.modelId!;\n const parsedBody = parseBody(body);\n const prompt = extractPromptFromBody(parsedBody);\n const syntheticReq: ChatCompletionRequest = {\n model: modelId,\n messages: [{ role: \"user\", content: prompt || JSON.stringify(parsedBody ?? {}) }],\n _endpointType: \"fal\",\n };\n\n const matchCounts = journal.getFixtureMatchCountsForTest(testId);\n const fixture = matchFixture(fixtures, syntheticReq, matchCounts, defaults.requestTransform);\n\n if (!fixture) {\n if (defaults.record) {\n const effectiveDefaults = withFalUpstream(defaults, route.targetHost);\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"fal\",\n stripFalPrefix(pathname),\n fixtures,\n effectiveDefaults,\n body,\n );\n if (outcome === \"handled_by_hook\") return \"handled\";\n if (outcome === \"relayed\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return \"handled\";\n }\n }\n\n const strictStatus = defaults.strict ? 503 : 404;\n const strictMessage = defaults.strict\n ? \"Strict mode: no fixture matched\"\n : \"No fixture matched\";\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: strictStatus, fixture: null },\n });\n res.writeHead(strictStatus, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return \"handled\";\n }\n\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\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(JSON.stringify(response));\n return \"handled\";\n }\n\n let payload: unknown;\n if (isJSONResponse(response)) {\n payload = (response as RawJSONResponse).json;\n } else if (isAudioResponse(response)) {\n payload = audioToFalFile(response);\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: {\n message: \"Fixture response is not JSON or audio for fal endpoint\",\n type: \"server_error\",\n },\n }),\n );\n return \"handled\";\n }\n\n if (route.kind === \"sync-run\") {\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(payload));\n return \"handled\";\n }\n\n const requestId = crypto.randomUUID();\n falQueueStates.set(stateKey(requestId), {\n requestId,\n modelId,\n status: \"COMPLETED\",\n result: payload,\n createdAt: Date.now(),\n });\n const envelope = {\n request_id: requestId,\n response_url: `https://${FAL_HOSTS.queue}/${modelId}/requests/${requestId}`,\n status_url: `https://${FAL_HOSTS.queue}/${modelId}/requests/${requestId}/status`,\n cancel_url: `https://${FAL_HOSTS.queue}/${modelId}/requests/${requestId}/cancel`,\n queue_position: 0,\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(envelope));\n return \"handled\";\n }\n }\n}\n\nfunction parseBody(raw: string): Record<string, unknown> | null {\n if (!raw.trim()) return null;\n try {\n return JSON.parse(raw) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\nfunction withFalUpstream(defaults: HandlerDefaults, targetHost: string): HandlerDefaults {\n if (!defaults.record) return defaults;\n // Respect an explicit record.providers.fal — tests and dev configs need to\n // point at a stub upstream. Only synthesise from the header when the user\n // didn't configure one (the \"or omit upstream URL — it's in the request\n // hostname\" mode from the issue).\n if (defaults.record.providers.fal) return defaults;\n return {\n ...defaults,\n record: {\n ...defaults.record,\n providers: {\n ...defaults.record.providers,\n fal: `https://${targetHost}`,\n },\n },\n };\n}\n\nfunction writeJson(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n status: number,\n payload: unknown,\n pathname: string,\n journal: Journal,\n): void {\n journal.add({\n method: req.method ?? \"GET\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status, fixture: null },\n });\n res.writeHead(status, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(payload));\n}\n\nfunction respondNotFound(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n pathname: string,\n journal: Journal,\n requestId: string,\n): void {\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}\n"],"mappings":"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"}
|
package/dist/fixture-loader.cjs
CHANGED
|
@@ -78,11 +78,12 @@ function loadFixturesFromDir(dirPath, logger) {
|
|
|
78
78
|
return [];
|
|
79
79
|
}
|
|
80
80
|
const jsonFiles = [];
|
|
81
|
+
const subdirs = [];
|
|
81
82
|
for (const name of entries) {
|
|
82
83
|
const fullPath = (0, node_path.join)(dirPath, name);
|
|
83
84
|
try {
|
|
84
85
|
if ((0, node_fs.statSync)(fullPath).isDirectory()) {
|
|
85
|
-
|
|
86
|
+
subdirs.push(name);
|
|
86
87
|
continue;
|
|
87
88
|
}
|
|
88
89
|
} catch (err) {
|
|
@@ -97,6 +98,33 @@ function loadFixturesFromDir(dirPath, logger) {
|
|
|
97
98
|
const filePath = (0, node_path.join)(dirPath, name);
|
|
98
99
|
fixtures.push(...loadFixtureFile(filePath, logger));
|
|
99
100
|
}
|
|
101
|
+
subdirs.sort();
|
|
102
|
+
for (const sub of subdirs) {
|
|
103
|
+
const subPath = (0, node_path.join)(dirPath, sub);
|
|
104
|
+
let subEntries;
|
|
105
|
+
try {
|
|
106
|
+
subEntries = (0, node_fs.readdirSync)(subPath);
|
|
107
|
+
} catch (err) {
|
|
108
|
+
warn(logger, `Could not read subdirectory ${subPath}:`, err);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const subJsonFiles = [];
|
|
112
|
+
for (const subName of subEntries) {
|
|
113
|
+
const subFullPath = (0, node_path.join)(subPath, subName);
|
|
114
|
+
try {
|
|
115
|
+
if ((0, node_fs.statSync)(subFullPath).isDirectory()) continue;
|
|
116
|
+
} catch (err) {
|
|
117
|
+
if (err.code !== "ENOENT") warn(logger, `Could not stat ${subFullPath}:`, err);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (subName.endsWith(".json")) subJsonFiles.push(subName);
|
|
121
|
+
}
|
|
122
|
+
subJsonFiles.sort();
|
|
123
|
+
for (const subName of subJsonFiles) {
|
|
124
|
+
const filePath = (0, node_path.join)(subPath, subName);
|
|
125
|
+
fixtures.push(...loadFixtureFile(filePath, logger));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
100
128
|
return fixtures;
|
|
101
129
|
}
|
|
102
130
|
function validateReasoning(response, fixtureIndex, results) {
|
|
@@ -146,159 +174,161 @@ function validateFixtures(fixtures) {
|
|
|
146
174
|
for (let i = 0; i < fixtures.length; i++) {
|
|
147
175
|
const f = fixtures[i];
|
|
148
176
|
const response = f.response;
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
fixtureIndex: i,
|
|
152
|
-
message: "response is not a recognized type (must have content, toolCalls, error, embedding, image, audio, transcription, video, or json)"
|
|
153
|
-
});
|
|
154
|
-
if (require_helpers.isTextResponse(response)) {
|
|
155
|
-
if (response.content === "") results.push({
|
|
156
|
-
severity: "error",
|
|
157
|
-
fixtureIndex: i,
|
|
158
|
-
message: "content is empty string"
|
|
159
|
-
});
|
|
160
|
-
validateReasoning(response, i, results);
|
|
161
|
-
validateWebSearches(response, i, results);
|
|
162
|
-
}
|
|
163
|
-
if (require_helpers.isContentWithToolCallsResponse(response)) {
|
|
164
|
-
if (response.content === "") results.push({
|
|
177
|
+
if (typeof response === "function") {} else {
|
|
178
|
+
if (!require_helpers.isContentWithToolCallsResponse(response) && !require_helpers.isTextResponse(response) && !require_helpers.isToolCallResponse(response) && !require_helpers.isErrorResponse(response) && !require_helpers.isEmbeddingResponse(response) && !require_helpers.isImageResponse(response) && !require_helpers.isAudioResponse(response) && !require_helpers.isTranscriptionResponse(response) && !require_helpers.isVideoResponse(response) && !require_helpers.isJSONResponse(response)) results.push({
|
|
165
179
|
severity: "error",
|
|
166
180
|
fixtureIndex: i,
|
|
167
|
-
message: "
|
|
181
|
+
message: "response is not a recognized type (must have content, toolCalls, error, embedding, image, audio, transcription, video, or json)"
|
|
168
182
|
});
|
|
169
|
-
if (response
|
|
170
|
-
|
|
171
|
-
fixtureIndex: i,
|
|
172
|
-
message: "toolCalls array is empty — fixture will never produce tool calls"
|
|
173
|
-
});
|
|
174
|
-
for (let j = 0; j < response.toolCalls.length; j++) {
|
|
175
|
-
const tc = response.toolCalls[j];
|
|
176
|
-
if (!tc.name) results.push({
|
|
183
|
+
if (require_helpers.isTextResponse(response)) {
|
|
184
|
+
if (response.content === "") results.push({
|
|
177
185
|
severity: "error",
|
|
178
186
|
fixtureIndex: i,
|
|
179
|
-
message:
|
|
187
|
+
message: "content is empty string"
|
|
180
188
|
});
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
189
|
+
validateReasoning(response, i, results);
|
|
190
|
+
validateWebSearches(response, i, results);
|
|
191
|
+
}
|
|
192
|
+
if (require_helpers.isContentWithToolCallsResponse(response)) {
|
|
193
|
+
if (response.content === "") results.push({
|
|
194
|
+
severity: "error",
|
|
195
|
+
fixtureIndex: i,
|
|
196
|
+
message: "content is empty string"
|
|
197
|
+
});
|
|
198
|
+
if (response.toolCalls.length === 0) results.push({
|
|
199
|
+
severity: "warning",
|
|
200
|
+
fixtureIndex: i,
|
|
201
|
+
message: "toolCalls array is empty — fixture will never produce tool calls"
|
|
202
|
+
});
|
|
203
|
+
for (let j = 0; j < response.toolCalls.length; j++) {
|
|
204
|
+
const tc = response.toolCalls[j];
|
|
205
|
+
if (!tc.name) results.push({
|
|
185
206
|
severity: "error",
|
|
186
207
|
fixtureIndex: i,
|
|
187
|
-
message: `toolCalls[${j}].
|
|
208
|
+
message: `toolCalls[${j}].name is empty`
|
|
188
209
|
});
|
|
210
|
+
try {
|
|
211
|
+
JSON.parse(tc.arguments);
|
|
212
|
+
} catch {
|
|
213
|
+
results.push({
|
|
214
|
+
severity: "error",
|
|
215
|
+
fixtureIndex: i,
|
|
216
|
+
message: `toolCalls[${j}].arguments is not valid JSON: ${tc.arguments}`
|
|
217
|
+
});
|
|
218
|
+
}
|
|
189
219
|
}
|
|
220
|
+
validateReasoning(response, i, results);
|
|
221
|
+
validateWebSearches(response, i, results);
|
|
190
222
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
223
|
+
if (require_helpers.isToolCallResponse(response)) {
|
|
224
|
+
if (response.toolCalls.length === 0) results.push({
|
|
225
|
+
severity: "warning",
|
|
226
|
+
fixtureIndex: i,
|
|
227
|
+
message: "toolCalls array is empty — fixture will never produce tool calls"
|
|
228
|
+
});
|
|
229
|
+
for (let j = 0; j < response.toolCalls.length; j++) {
|
|
230
|
+
const tc = response.toolCalls[j];
|
|
231
|
+
if (!tc.name) results.push({
|
|
232
|
+
severity: "error",
|
|
233
|
+
fixtureIndex: i,
|
|
234
|
+
message: `toolCalls[${j}].name is empty`
|
|
235
|
+
});
|
|
236
|
+
try {
|
|
237
|
+
JSON.parse(tc.arguments);
|
|
238
|
+
} catch {
|
|
239
|
+
results.push({
|
|
240
|
+
severity: "error",
|
|
241
|
+
fixtureIndex: i,
|
|
242
|
+
message: `toolCalls[${j}].arguments is not valid JSON: ${tc.arguments}`
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (require_helpers.isErrorResponse(response)) {
|
|
248
|
+
if (!response.error.message) results.push({
|
|
203
249
|
severity: "error",
|
|
204
250
|
fixtureIndex: i,
|
|
205
|
-
message:
|
|
251
|
+
message: "error.message is empty"
|
|
206
252
|
});
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
253
|
+
if (response.status !== void 0 && (response.status < 100 || response.status > 599)) results.push({
|
|
254
|
+
severity: "error",
|
|
255
|
+
fixtureIndex: i,
|
|
256
|
+
message: `error status ${response.status} is not a valid HTTP status code`
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
if (require_helpers.isEmbeddingResponse(response)) {
|
|
260
|
+
if (response.embedding.length === 0) results.push({
|
|
261
|
+
severity: "error",
|
|
262
|
+
fixtureIndex: i,
|
|
263
|
+
message: "embedding array is empty"
|
|
264
|
+
});
|
|
265
|
+
for (let j = 0; j < response.embedding.length; j++) if (typeof response.embedding[j] !== "number") {
|
|
210
266
|
results.push({
|
|
211
267
|
severity: "error",
|
|
212
268
|
fixtureIndex: i,
|
|
213
|
-
message: `
|
|
269
|
+
message: `embedding[${j}] is not a number`
|
|
214
270
|
});
|
|
271
|
+
break;
|
|
215
272
|
}
|
|
216
273
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
severity: "error",
|
|
221
|
-
fixtureIndex: i,
|
|
222
|
-
message: "error.message is empty"
|
|
223
|
-
});
|
|
224
|
-
if (response.status !== void 0 && (response.status < 100 || response.status > 599)) results.push({
|
|
225
|
-
severity: "error",
|
|
226
|
-
fixtureIndex: i,
|
|
227
|
-
message: `error status ${response.status} is not a valid HTTP status code`
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
if (require_helpers.isEmbeddingResponse(response)) {
|
|
231
|
-
if (response.embedding.length === 0) results.push({
|
|
232
|
-
severity: "error",
|
|
233
|
-
fixtureIndex: i,
|
|
234
|
-
message: "embedding array is empty"
|
|
235
|
-
});
|
|
236
|
-
for (let j = 0; j < response.embedding.length; j++) if (typeof response.embedding[j] !== "number") {
|
|
237
|
-
results.push({
|
|
274
|
+
if (require_helpers.isAudioResponse(response) && typeof response.audio === "object") {
|
|
275
|
+
const audioObj = response.audio;
|
|
276
|
+
if (typeof audioObj.b64Json !== "string" || audioObj.b64Json === "") results.push({
|
|
238
277
|
severity: "error",
|
|
239
278
|
fixtureIndex: i,
|
|
240
|
-
message:
|
|
279
|
+
message: "audio.b64Json must be a non-empty string"
|
|
280
|
+
});
|
|
281
|
+
if (audioObj.contentType !== void 0 && typeof audioObj.contentType !== "string") results.push({
|
|
282
|
+
severity: "error",
|
|
283
|
+
fixtureIndex: i,
|
|
284
|
+
message: `audio.contentType must be a string, got ${typeof audioObj.contentType}`
|
|
241
285
|
});
|
|
242
|
-
break;
|
|
243
286
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
fixtureIndex: i,
|
|
263
|
-
message: `override "id" must be a string, got ${typeof r.id}`
|
|
264
|
-
});
|
|
265
|
-
if (r.created !== void 0 && (typeof r.created !== "number" || r.created < 0)) results.push({
|
|
266
|
-
severity: "error",
|
|
267
|
-
fixtureIndex: i,
|
|
268
|
-
message: `override "created" must be a non-negative number`
|
|
269
|
-
});
|
|
270
|
-
if (r.model !== void 0 && typeof r.model !== "string") results.push({
|
|
271
|
-
severity: "error",
|
|
272
|
-
fixtureIndex: i,
|
|
273
|
-
message: `override "model" must be a string, got ${typeof r.model}`
|
|
274
|
-
});
|
|
275
|
-
if (r.finishReason !== void 0 && typeof r.finishReason !== "string") results.push({
|
|
276
|
-
severity: "error",
|
|
277
|
-
fixtureIndex: i,
|
|
278
|
-
message: `override "finishReason" must be a string, got ${typeof r.finishReason}`
|
|
279
|
-
});
|
|
280
|
-
if (r.role !== void 0 && typeof r.role !== "string") results.push({
|
|
281
|
-
severity: "error",
|
|
282
|
-
fixtureIndex: i,
|
|
283
|
-
message: `override "role" must be a string, got ${typeof r.role}`
|
|
284
|
-
});
|
|
285
|
-
if (r.systemFingerprint !== void 0 && typeof r.systemFingerprint !== "string") results.push({
|
|
286
|
-
severity: "error",
|
|
287
|
-
fixtureIndex: i,
|
|
288
|
-
message: `override "systemFingerprint" must be a string, got ${typeof r.systemFingerprint}`
|
|
289
|
-
});
|
|
290
|
-
if (r.usage !== void 0) if (typeof r.usage !== "object" || r.usage === null || Array.isArray(r.usage)) results.push({
|
|
291
|
-
severity: "error",
|
|
292
|
-
fixtureIndex: i,
|
|
293
|
-
message: `override "usage" must be an object`
|
|
294
|
-
});
|
|
295
|
-
else for (const key of Object.keys(r.usage)) {
|
|
296
|
-
const val = r.usage[key];
|
|
297
|
-
if (val !== void 0 && typeof val !== "number") results.push({
|
|
287
|
+
if (require_helpers.isTextResponse(response) || require_helpers.isToolCallResponse(response) || require_helpers.isContentWithToolCallsResponse(response)) {
|
|
288
|
+
const r = response;
|
|
289
|
+
if (r.id !== void 0 && typeof r.id !== "string") results.push({
|
|
290
|
+
severity: "error",
|
|
291
|
+
fixtureIndex: i,
|
|
292
|
+
message: `override "id" must be a string, got ${typeof r.id}`
|
|
293
|
+
});
|
|
294
|
+
if (r.created !== void 0 && (typeof r.created !== "number" || r.created < 0)) results.push({
|
|
295
|
+
severity: "error",
|
|
296
|
+
fixtureIndex: i,
|
|
297
|
+
message: `override "created" must be a non-negative number`
|
|
298
|
+
});
|
|
299
|
+
if (r.model !== void 0 && typeof r.model !== "string") results.push({
|
|
300
|
+
severity: "error",
|
|
301
|
+
fixtureIndex: i,
|
|
302
|
+
message: `override "model" must be a string, got ${typeof r.model}`
|
|
303
|
+
});
|
|
304
|
+
if (r.finishReason !== void 0 && typeof r.finishReason !== "string") results.push({
|
|
298
305
|
severity: "error",
|
|
299
306
|
fixtureIndex: i,
|
|
300
|
-
message: `override "
|
|
307
|
+
message: `override "finishReason" must be a string, got ${typeof r.finishReason}`
|
|
301
308
|
});
|
|
309
|
+
if (r.role !== void 0 && typeof r.role !== "string") results.push({
|
|
310
|
+
severity: "error",
|
|
311
|
+
fixtureIndex: i,
|
|
312
|
+
message: `override "role" must be a string, got ${typeof r.role}`
|
|
313
|
+
});
|
|
314
|
+
if (r.systemFingerprint !== void 0 && typeof r.systemFingerprint !== "string") results.push({
|
|
315
|
+
severity: "error",
|
|
316
|
+
fixtureIndex: i,
|
|
317
|
+
message: `override "systemFingerprint" must be a string, got ${typeof r.systemFingerprint}`
|
|
318
|
+
});
|
|
319
|
+
if (r.usage !== void 0) if (typeof r.usage !== "object" || r.usage === null || Array.isArray(r.usage)) results.push({
|
|
320
|
+
severity: "error",
|
|
321
|
+
fixtureIndex: i,
|
|
322
|
+
message: `override "usage" must be an object`
|
|
323
|
+
});
|
|
324
|
+
else for (const key of Object.keys(r.usage)) {
|
|
325
|
+
const val = r.usage[key];
|
|
326
|
+
if (val !== void 0 && typeof val !== "number") results.push({
|
|
327
|
+
severity: "error",
|
|
328
|
+
fixtureIndex: i,
|
|
329
|
+
message: `override "usage.${key}" must be a number, got ${typeof val}`
|
|
330
|
+
});
|
|
331
|
+
}
|
|
302
332
|
}
|
|
303
333
|
}
|
|
304
334
|
if (f.latency !== void 0 && f.latency < 0) results.push({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fixture-loader.cjs","names":["isContentWithToolCallsResponse","isTextResponse","isToolCallResponse","isErrorResponse","isEmbeddingResponse","isImageResponse","isAudioResponse","isTranscriptionResponse","isVideoResponse","isJSONResponse"],"sources":["../src/fixture-loader.ts"],"sourcesContent":["import { readFileSync, readdirSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type {\n Fixture,\n FixtureFile,\n FixtureFileEntry,\n FixtureFileResponse,\n FixtureResponse,\n ResponseOverrides,\n} from \"./types.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n isEmbeddingResponse,\n isImageResponse,\n isAudioResponse,\n isTranscriptionResponse,\n isVideoResponse,\n isJSONResponse,\n} from \"./helpers.js\";\nimport type { Logger } from \"./logger.js\";\n\n/**\n * Auto-stringify object-valued `content` and `toolCalls[].arguments` fields.\n * This lets fixture authors write plain JSON objects instead of escaped strings.\n * All other fields (including ResponseOverrides) pass through unmodified.\n */\nexport function normalizeResponse(raw: FixtureFileResponse): FixtureResponse {\n // Shallow-clone so we don't mutate the parsed JSON input.\n const response = { ...raw } as Record<string, unknown>;\n\n // Auto-stringify object content (e.g. structured output)\n if (typeof response.content === \"object\" && response.content !== null) {\n response.content = JSON.stringify(response.content);\n }\n\n // Auto-stringify object arguments in toolCalls\n if (Array.isArray(response.toolCalls)) {\n response.toolCalls = (response.toolCalls as Array<Record<string, unknown>>).map((tc) => {\n if (typeof tc.arguments === \"object\" && tc.arguments !== null) {\n return { ...tc, arguments: JSON.stringify(tc.arguments) };\n }\n return tc;\n });\n }\n\n return response as unknown as FixtureResponse;\n}\n\nexport function entryToFixture(entry: FixtureFileEntry): Fixture {\n return {\n match: {\n userMessage: entry.match.userMessage,\n inputText: entry.match.inputText,\n toolCallId: entry.match.toolCallId,\n toolName: entry.match.toolName,\n model: entry.match.model,\n responseFormat: entry.match.responseFormat,\n endpoint: entry.match.endpoint,\n ...(entry.match.sequenceIndex !== undefined && { sequenceIndex: entry.match.sequenceIndex }),\n ...(entry.match.turnIndex !== undefined && {\n turnIndex: entry.match.turnIndex,\n }),\n ...(entry.match.hasToolResult !== undefined && {\n hasToolResult: entry.match.hasToolResult,\n }),\n },\n response: normalizeResponse(entry.response),\n ...(entry.latency !== undefined && { latency: entry.latency }),\n ...(entry.chunkSize !== undefined && { chunkSize: entry.chunkSize }),\n ...(entry.truncateAfterChunks !== undefined && {\n truncateAfterChunks: entry.truncateAfterChunks,\n }),\n ...(entry.disconnectAfterMs !== undefined && { disconnectAfterMs: entry.disconnectAfterMs }),\n ...(entry.streamingProfile !== undefined && { streamingProfile: entry.streamingProfile }),\n ...(entry.chaos !== undefined && { chaos: entry.chaos }),\n };\n}\n\n// Logging helper — uses logger if provided, falls back to console.warn.\nfunction warn(logger: Logger | undefined, msg: string, ...rest: unknown[]): void {\n if (logger) {\n logger.warn(msg, ...rest);\n } else {\n console.warn(`[fixture-loader] ${msg}`, ...rest);\n }\n}\n\nexport function loadFixtureFile(filePath: string, logger?: Logger): Fixture[] {\n let raw: string;\n try {\n raw = readFileSync(filePath, \"utf-8\");\n } catch (err) {\n warn(logger, `Could not read file ${filePath}:`, err);\n return [];\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n warn(logger, `Invalid JSON in ${filePath}:`, err);\n return [];\n }\n\n if (\n typeof parsed !== \"object\" ||\n parsed === null ||\n !Array.isArray((parsed as FixtureFile).fixtures)\n ) {\n warn(logger, `Missing or invalid \"fixtures\" array in ${filePath}`);\n return [];\n }\n\n return (parsed as FixtureFile).fixtures.map(entryToFixture);\n}\n\nexport function loadFixturesFromDir(dirPath: string, logger?: Logger): Fixture[] {\n let entries: string[];\n try {\n entries = readdirSync(dirPath);\n } catch (err) {\n warn(logger, `Could not read directory ${dirPath}:`, err);\n return [];\n }\n\n const jsonFiles: string[] = [];\n for (const name of entries) {\n const fullPath = join(dirPath, name);\n try {\n if (statSync(fullPath).isDirectory()) {\n warn(logger, `Skipping subdirectory ${fullPath} (fixtures are not loaded recursively)`);\n continue;\n }\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== \"ENOENT\") {\n warn(logger, `Could not stat ${fullPath}:`, err);\n }\n continue;\n }\n if (name.endsWith(\".json\")) {\n jsonFiles.push(name);\n }\n }\n jsonFiles.sort();\n\n const fixtures: Fixture[] = [];\n for (const name of jsonFiles) {\n const filePath = join(dirPath, name);\n fixtures.push(...loadFixtureFile(filePath, logger));\n }\n\n return fixtures;\n}\n\n// ---------------------------------------------------------------------------\n// Fixture validation\n// ---------------------------------------------------------------------------\n\nexport interface ValidationResult {\n severity: \"error\" | \"warning\";\n fixtureIndex: number;\n message: string;\n}\n\nfunction validateReasoning(\n response: { reasoning?: unknown },\n fixtureIndex: number,\n results: ValidationResult[],\n): void {\n if (response.reasoning !== undefined) {\n if (typeof response.reasoning !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex,\n message: \"reasoning must be a string\",\n });\n } else if (response.reasoning === \"\") {\n results.push({\n severity: \"warning\",\n fixtureIndex,\n message: \"reasoning is empty string — no reasoning events will be emitted\",\n });\n }\n }\n}\n\nfunction validateWebSearches(\n response: { webSearches?: unknown },\n fixtureIndex: number,\n results: ValidationResult[],\n): void {\n if (response.webSearches !== undefined) {\n if (!Array.isArray(response.webSearches)) {\n results.push({\n severity: \"error\",\n fixtureIndex,\n message: \"webSearches must be an array of strings\",\n });\n } else if (response.webSearches.length === 0) {\n results.push({\n severity: \"warning\",\n fixtureIndex,\n message: \"webSearches is empty array — no web search events will be emitted\",\n });\n } else {\n for (let j = 0; j < response.webSearches.length; j++) {\n if (typeof response.webSearches[j] !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex,\n message: `webSearches[${j}] is not a string`,\n });\n break;\n }\n if (response.webSearches[j] === \"\") {\n results.push({\n severity: \"warning\",\n fixtureIndex,\n message: `webSearches[${j}] is empty string`,\n });\n }\n }\n }\n }\n}\n\nexport function validateFixtures(fixtures: Fixture[]): ValidationResult[] {\n const results: ValidationResult[] = [];\n\n const seenUserMessages = new Map<string, number>();\n\n for (let i = 0; i < fixtures.length; i++) {\n const f = fixtures[i];\n const response = f.response;\n\n // --- Error checks ---\n\n // Response type recognition\n // Note: isContentWithToolCallsResponse must be checked before isTextResponse\n // and isToolCallResponse since it is a structural superset of both.\n if (\n !isContentWithToolCallsResponse(response) &&\n !isTextResponse(response) &&\n !isToolCallResponse(response) &&\n !isErrorResponse(response) &&\n !isEmbeddingResponse(response) &&\n !isImageResponse(response) &&\n !isAudioResponse(response) &&\n !isTranscriptionResponse(response) &&\n !isVideoResponse(response) &&\n !isJSONResponse(response)\n ) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message:\n \"response is not a recognized type (must have content, toolCalls, error, embedding, image, audio, transcription, video, or json)\",\n });\n }\n\n // Text response checks\n if (isTextResponse(response)) {\n if (response.content === \"\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"content is empty string\",\n });\n }\n validateReasoning(response, i, results);\n validateWebSearches(response, i, results);\n }\n\n // ContentWithToolCalls response checks\n if (isContentWithToolCallsResponse(response)) {\n if (response.content === \"\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"content is empty string\",\n });\n }\n if (response.toolCalls.length === 0) {\n results.push({\n severity: \"warning\",\n fixtureIndex: i,\n message: \"toolCalls array is empty — fixture will never produce tool calls\",\n });\n }\n for (let j = 0; j < response.toolCalls.length; j++) {\n const tc = response.toolCalls[j];\n if (!tc.name) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `toolCalls[${j}].name is empty`,\n });\n }\n try {\n JSON.parse(tc.arguments);\n } catch {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `toolCalls[${j}].arguments is not valid JSON: ${tc.arguments}`,\n });\n }\n }\n validateReasoning(response, i, results);\n validateWebSearches(response, i, results);\n }\n\n // Tool call response checks\n if (isToolCallResponse(response)) {\n if (response.toolCalls.length === 0) {\n results.push({\n severity: \"warning\",\n fixtureIndex: i,\n message: \"toolCalls array is empty — fixture will never produce tool calls\",\n });\n }\n for (let j = 0; j < response.toolCalls.length; j++) {\n const tc = response.toolCalls[j];\n if (!tc.name) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `toolCalls[${j}].name is empty`,\n });\n }\n try {\n JSON.parse(tc.arguments);\n } catch {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `toolCalls[${j}].arguments is not valid JSON: ${tc.arguments}`,\n });\n }\n }\n }\n\n // Error response checks\n if (isErrorResponse(response)) {\n if (!response.error.message) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"error.message is empty\",\n });\n }\n if (response.status !== undefined && (response.status < 100 || response.status > 599)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `error status ${response.status} is not a valid HTTP status code`,\n });\n }\n }\n\n // Embedding response checks\n if (isEmbeddingResponse(response)) {\n if (response.embedding.length === 0) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"embedding array is empty\",\n });\n }\n for (let j = 0; j < response.embedding.length; j++) {\n if (typeof response.embedding[j] !== \"number\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `embedding[${j}] is not a number`,\n });\n break; // one error is enough\n }\n }\n }\n\n // Audio response checks — validate object-form audio\n if (isAudioResponse(response) && typeof response.audio === \"object\") {\n const audioObj = response.audio;\n if (typeof audioObj.b64Json !== \"string\" || audioObj.b64Json === \"\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"audio.b64Json must be a non-empty string\",\n });\n }\n if (audioObj.contentType !== undefined && typeof audioObj.contentType !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `audio.contentType must be a string, got ${typeof audioObj.contentType}`,\n });\n }\n }\n\n // Validate ResponseOverrides fields\n if (\n isTextResponse(response) ||\n isToolCallResponse(response) ||\n isContentWithToolCallsResponse(response)\n ) {\n const r = response as ResponseOverrides;\n if (r.id !== undefined && typeof r.id !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"id\" must be a string, got ${typeof r.id}`,\n });\n }\n if (r.created !== undefined && (typeof r.created !== \"number\" || r.created < 0)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"created\" must be a non-negative number`,\n });\n }\n if (r.model !== undefined && typeof r.model !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"model\" must be a string, got ${typeof r.model}`,\n });\n }\n if (r.finishReason !== undefined && typeof r.finishReason !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"finishReason\" must be a string, got ${typeof r.finishReason}`,\n });\n }\n if (r.role !== undefined && typeof r.role !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"role\" must be a string, got ${typeof r.role}`,\n });\n }\n if (r.systemFingerprint !== undefined && typeof r.systemFingerprint !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"systemFingerprint\" must be a string, got ${typeof r.systemFingerprint}`,\n });\n }\n if (r.usage !== undefined) {\n if (typeof r.usage !== \"object\" || r.usage === null || Array.isArray(r.usage)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"usage\" must be an object`,\n });\n } else {\n // Check all known usage fields are numbers if present\n for (const key of Object.keys(r.usage)) {\n const val = (r.usage as Record<string, unknown>)[key];\n if (val !== undefined && typeof val !== \"number\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"usage.${key}\" must be a number, got ${typeof val}`,\n });\n }\n }\n }\n }\n }\n\n // Numeric sanity checks\n if (f.latency !== undefined && f.latency < 0) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"latency must be >= 0\",\n });\n }\n if (f.chunkSize !== undefined && f.chunkSize < 1) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"chunkSize must be >= 1\",\n });\n }\n if (f.truncateAfterChunks !== undefined && f.truncateAfterChunks < 1) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"truncateAfterChunks must be >= 1\",\n });\n }\n if (f.disconnectAfterMs !== undefined && f.disconnectAfterMs < 0) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"disconnectAfterMs must be >= 0\",\n });\n }\n if (f.streamingProfile !== undefined) {\n const sp = f.streamingProfile;\n if (sp.ttft !== undefined && sp.ttft < 0) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"streamingProfile.ttft must be >= 0\",\n });\n }\n if (sp.tps !== undefined && sp.tps <= 0) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"streamingProfile.tps must be > 0\",\n });\n }\n if (sp.jitter !== undefined && (sp.jitter < 0 || sp.jitter > 1)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"streamingProfile.jitter must be between 0 and 1\",\n });\n }\n }\n if (f.chaos !== undefined) {\n const ch = f.chaos;\n if (ch.dropRate !== undefined && (ch.dropRate < 0 || ch.dropRate > 1)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"chaos.dropRate must be between 0 and 1\",\n });\n }\n if (ch.malformedRate !== undefined && (ch.malformedRate < 0 || ch.malformedRate > 1)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"chaos.malformedRate must be between 0 and 1\",\n });\n }\n if (ch.disconnectRate !== undefined && (ch.disconnectRate < 0 || ch.disconnectRate > 1)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"chaos.disconnectRate must be between 0 and 1\",\n });\n }\n }\n\n // Match field type checks\n if (f.match.turnIndex !== undefined) {\n if (\n typeof f.match.turnIndex !== \"number\" ||\n f.match.turnIndex < 0 ||\n !Number.isInteger(f.match.turnIndex)\n ) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"match.turnIndex must be a non-negative integer\",\n });\n }\n }\n if (f.match.hasToolResult !== undefined && typeof f.match.hasToolResult !== \"boolean\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `match.hasToolResult must be a boolean, got ${typeof f.match.hasToolResult}`,\n });\n }\n\n // --- Warning checks ---\n\n // Duplicate userMessage shadowing — include turnIndex, hasToolResult, and\n // sequenceIndex in the dedup key so that fixtures which share a userMessage\n // but differ on those fields are NOT considered duplicates.\n const um = f.match.userMessage;\n if (typeof um === \"string\" && um) {\n const dedupKey = `${um}|${f.match.turnIndex}|${f.match.hasToolResult}|${f.match.sequenceIndex}`;\n const prev = seenUserMessages.get(dedupKey);\n if (prev !== undefined) {\n results.push({\n severity: \"warning\",\n fixtureIndex: i,\n message: `duplicate userMessage '${um}' — shadows fixture ${prev}`,\n });\n } else {\n seenUserMessages.set(dedupKey, i);\n }\n }\n\n // Catch-all not in last position\n const match = f.match;\n const hasDiscriminator =\n match.endpoint !== undefined ||\n match.userMessage !== undefined ||\n match.inputText !== undefined ||\n match.responseFormat !== undefined ||\n match.toolCallId !== undefined ||\n match.toolName !== undefined ||\n match.model !== undefined ||\n match.predicate !== undefined ||\n match.turnIndex !== undefined ||\n match.hasToolResult !== undefined;\n\n if (!hasDiscriminator && i < fixtures.length - 1) {\n results.push({\n severity: \"warning\",\n fixtureIndex: i,\n message: `empty match acts as catch-all but is not the last fixture — shadows fixtures ${i + 1}+`,\n });\n }\n }\n\n return results;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fixture-loader.cjs","names":["isContentWithToolCallsResponse","isTextResponse","isToolCallResponse","isErrorResponse","isEmbeddingResponse","isImageResponse","isAudioResponse","isTranscriptionResponse","isVideoResponse","isJSONResponse"],"sources":["../src/fixture-loader.ts"],"sourcesContent":["import { readFileSync, readdirSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type {\n Fixture,\n FixtureFile,\n FixtureFileEntry,\n FixtureFileResponse,\n FixtureResponse,\n ResponseOverrides,\n} from \"./types.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n isEmbeddingResponse,\n isImageResponse,\n isAudioResponse,\n isTranscriptionResponse,\n isVideoResponse,\n isJSONResponse,\n} from \"./helpers.js\";\nimport type { Logger } from \"./logger.js\";\n\n/**\n * Auto-stringify object-valued `content` and `toolCalls[].arguments` fields.\n * This lets fixture authors write plain JSON objects instead of escaped strings.\n * All other fields (including ResponseOverrides) pass through unmodified.\n */\nexport function normalizeResponse(raw: FixtureFileResponse): FixtureResponse {\n // Shallow-clone so we don't mutate the parsed JSON input.\n const response = { ...raw } as Record<string, unknown>;\n\n // Auto-stringify object content (e.g. structured output)\n if (typeof response.content === \"object\" && response.content !== null) {\n response.content = JSON.stringify(response.content);\n }\n\n // Auto-stringify object arguments in toolCalls\n if (Array.isArray(response.toolCalls)) {\n response.toolCalls = (response.toolCalls as Array<Record<string, unknown>>).map((tc) => {\n if (typeof tc.arguments === \"object\" && tc.arguments !== null) {\n return { ...tc, arguments: JSON.stringify(tc.arguments) };\n }\n return tc;\n });\n }\n\n return response as unknown as FixtureResponse;\n}\n\nexport function entryToFixture(entry: FixtureFileEntry): Fixture {\n return {\n match: {\n userMessage: entry.match.userMessage,\n inputText: entry.match.inputText,\n toolCallId: entry.match.toolCallId,\n toolName: entry.match.toolName,\n model: entry.match.model,\n responseFormat: entry.match.responseFormat,\n endpoint: entry.match.endpoint,\n ...(entry.match.sequenceIndex !== undefined && { sequenceIndex: entry.match.sequenceIndex }),\n ...(entry.match.turnIndex !== undefined && {\n turnIndex: entry.match.turnIndex,\n }),\n ...(entry.match.hasToolResult !== undefined && {\n hasToolResult: entry.match.hasToolResult,\n }),\n },\n response: normalizeResponse(entry.response),\n ...(entry.latency !== undefined && { latency: entry.latency }),\n ...(entry.chunkSize !== undefined && { chunkSize: entry.chunkSize }),\n ...(entry.truncateAfterChunks !== undefined && {\n truncateAfterChunks: entry.truncateAfterChunks,\n }),\n ...(entry.disconnectAfterMs !== undefined && { disconnectAfterMs: entry.disconnectAfterMs }),\n ...(entry.streamingProfile !== undefined && { streamingProfile: entry.streamingProfile }),\n ...(entry.chaos !== undefined && { chaos: entry.chaos }),\n };\n}\n\n// Logging helper — uses logger if provided, falls back to console.warn.\nfunction warn(logger: Logger | undefined, msg: string, ...rest: unknown[]): void {\n if (logger) {\n logger.warn(msg, ...rest);\n } else {\n console.warn(`[fixture-loader] ${msg}`, ...rest);\n }\n}\n\nexport function loadFixtureFile(filePath: string, logger?: Logger): Fixture[] {\n let raw: string;\n try {\n raw = readFileSync(filePath, \"utf-8\");\n } catch (err) {\n warn(logger, `Could not read file ${filePath}:`, err);\n return [];\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n warn(logger, `Invalid JSON in ${filePath}:`, err);\n return [];\n }\n\n if (\n typeof parsed !== \"object\" ||\n parsed === null ||\n !Array.isArray((parsed as FixtureFile).fixtures)\n ) {\n warn(logger, `Missing or invalid \"fixtures\" array in ${filePath}`);\n return [];\n }\n\n return (parsed as FixtureFile).fixtures.map(entryToFixture);\n}\n\nexport function loadFixturesFromDir(dirPath: string, logger?: Logger): Fixture[] {\n let entries: string[];\n try {\n entries = readdirSync(dirPath);\n } catch (err) {\n warn(logger, `Could not read directory ${dirPath}:`, err);\n return [];\n }\n\n const jsonFiles: string[] = [];\n const subdirs: string[] = [];\n for (const name of entries) {\n const fullPath = join(dirPath, name);\n try {\n if (statSync(fullPath).isDirectory()) {\n subdirs.push(name);\n continue;\n }\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== \"ENOENT\") {\n warn(logger, `Could not stat ${fullPath}:`, err);\n }\n continue;\n }\n if (name.endsWith(\".json\")) {\n jsonFiles.push(name);\n }\n }\n jsonFiles.sort();\n\n const fixtures: Fixture[] = [];\n for (const name of jsonFiles) {\n const filePath = join(dirPath, name);\n fixtures.push(...loadFixtureFile(filePath, logger));\n }\n\n // Recurse one level into subdirectories to support snapshot-style layouts\n // where the recorder writes to <fixturePath>/<testId>/<provider>.json.\n subdirs.sort();\n for (const sub of subdirs) {\n const subPath = join(dirPath, sub);\n let subEntries: string[];\n try {\n subEntries = readdirSync(subPath);\n } catch (err) {\n warn(logger, `Could not read subdirectory ${subPath}:`, err);\n continue;\n }\n const subJsonFiles: string[] = [];\n for (const subName of subEntries) {\n const subFullPath = join(subPath, subName);\n try {\n if (statSync(subFullPath).isDirectory()) {\n // Only one level of recursion — skip deeper nesting\n continue;\n }\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== \"ENOENT\") {\n warn(logger, `Could not stat ${subFullPath}:`, err);\n }\n continue;\n }\n if (subName.endsWith(\".json\")) {\n subJsonFiles.push(subName);\n }\n }\n subJsonFiles.sort();\n for (const subName of subJsonFiles) {\n const filePath = join(subPath, subName);\n fixtures.push(...loadFixtureFile(filePath, logger));\n }\n }\n\n return fixtures;\n}\n\n// ---------------------------------------------------------------------------\n// Fixture validation\n// ---------------------------------------------------------------------------\n\nexport interface ValidationResult {\n severity: \"error\" | \"warning\";\n fixtureIndex: number;\n message: string;\n}\n\nfunction validateReasoning(\n response: { reasoning?: unknown },\n fixtureIndex: number,\n results: ValidationResult[],\n): void {\n if (response.reasoning !== undefined) {\n if (typeof response.reasoning !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex,\n message: \"reasoning must be a string\",\n });\n } else if (response.reasoning === \"\") {\n results.push({\n severity: \"warning\",\n fixtureIndex,\n message: \"reasoning is empty string — no reasoning events will be emitted\",\n });\n }\n }\n}\n\nfunction validateWebSearches(\n response: { webSearches?: unknown },\n fixtureIndex: number,\n results: ValidationResult[],\n): void {\n if (response.webSearches !== undefined) {\n if (!Array.isArray(response.webSearches)) {\n results.push({\n severity: \"error\",\n fixtureIndex,\n message: \"webSearches must be an array of strings\",\n });\n } else if (response.webSearches.length === 0) {\n results.push({\n severity: \"warning\",\n fixtureIndex,\n message: \"webSearches is empty array — no web search events will be emitted\",\n });\n } else {\n for (let j = 0; j < response.webSearches.length; j++) {\n if (typeof response.webSearches[j] !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex,\n message: `webSearches[${j}] is not a string`,\n });\n break;\n }\n if (response.webSearches[j] === \"\") {\n results.push({\n severity: \"warning\",\n fixtureIndex,\n message: `webSearches[${j}] is empty string`,\n });\n }\n }\n }\n }\n}\n\nexport function validateFixtures(fixtures: Fixture[]): ValidationResult[] {\n const results: ValidationResult[] = [];\n\n const seenUserMessages = new Map<string, number>();\n\n for (let i = 0; i < fixtures.length; i++) {\n const f = fixtures[i];\n const response = f.response;\n\n // Skip response-shape validation for function responses — they are\n // evaluated at runtime so we cannot statically inspect them.\n if (typeof response === \"function\") {\n // Still validate match fields and numeric options below.\n } else {\n // --- Error checks ---\n\n // Response type recognition\n // Note: isContentWithToolCallsResponse must be checked before isTextResponse\n // and isToolCallResponse since it is a structural superset of both.\n if (\n !isContentWithToolCallsResponse(response) &&\n !isTextResponse(response) &&\n !isToolCallResponse(response) &&\n !isErrorResponse(response) &&\n !isEmbeddingResponse(response) &&\n !isImageResponse(response) &&\n !isAudioResponse(response) &&\n !isTranscriptionResponse(response) &&\n !isVideoResponse(response) &&\n !isJSONResponse(response)\n ) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message:\n \"response is not a recognized type (must have content, toolCalls, error, embedding, image, audio, transcription, video, or json)\",\n });\n }\n\n // Text response checks\n if (isTextResponse(response)) {\n if (response.content === \"\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"content is empty string\",\n });\n }\n validateReasoning(response, i, results);\n validateWebSearches(response, i, results);\n }\n\n // ContentWithToolCalls response checks\n if (isContentWithToolCallsResponse(response)) {\n if (response.content === \"\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"content is empty string\",\n });\n }\n if (response.toolCalls.length === 0) {\n results.push({\n severity: \"warning\",\n fixtureIndex: i,\n message: \"toolCalls array is empty — fixture will never produce tool calls\",\n });\n }\n for (let j = 0; j < response.toolCalls.length; j++) {\n const tc = response.toolCalls[j];\n if (!tc.name) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `toolCalls[${j}].name is empty`,\n });\n }\n try {\n JSON.parse(tc.arguments);\n } catch {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `toolCalls[${j}].arguments is not valid JSON: ${tc.arguments}`,\n });\n }\n }\n validateReasoning(response, i, results);\n validateWebSearches(response, i, results);\n }\n\n // Tool call response checks\n if (isToolCallResponse(response)) {\n if (response.toolCalls.length === 0) {\n results.push({\n severity: \"warning\",\n fixtureIndex: i,\n message: \"toolCalls array is empty — fixture will never produce tool calls\",\n });\n }\n for (let j = 0; j < response.toolCalls.length; j++) {\n const tc = response.toolCalls[j];\n if (!tc.name) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `toolCalls[${j}].name is empty`,\n });\n }\n try {\n JSON.parse(tc.arguments);\n } catch {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `toolCalls[${j}].arguments is not valid JSON: ${tc.arguments}`,\n });\n }\n }\n }\n\n // Error response checks\n if (isErrorResponse(response)) {\n if (!response.error.message) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"error.message is empty\",\n });\n }\n if (response.status !== undefined && (response.status < 100 || response.status > 599)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `error status ${response.status} is not a valid HTTP status code`,\n });\n }\n }\n\n // Embedding response checks\n if (isEmbeddingResponse(response)) {\n if (response.embedding.length === 0) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"embedding array is empty\",\n });\n }\n for (let j = 0; j < response.embedding.length; j++) {\n if (typeof response.embedding[j] !== \"number\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `embedding[${j}] is not a number`,\n });\n break; // one error is enough\n }\n }\n }\n\n // Audio response checks — validate object-form audio\n if (isAudioResponse(response) && typeof response.audio === \"object\") {\n const audioObj = response.audio;\n if (typeof audioObj.b64Json !== \"string\" || audioObj.b64Json === \"\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"audio.b64Json must be a non-empty string\",\n });\n }\n if (audioObj.contentType !== undefined && typeof audioObj.contentType !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `audio.contentType must be a string, got ${typeof audioObj.contentType}`,\n });\n }\n }\n\n // Validate ResponseOverrides fields\n if (\n isTextResponse(response) ||\n isToolCallResponse(response) ||\n isContentWithToolCallsResponse(response)\n ) {\n const r = response as ResponseOverrides;\n if (r.id !== undefined && typeof r.id !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"id\" must be a string, got ${typeof r.id}`,\n });\n }\n if (r.created !== undefined && (typeof r.created !== \"number\" || r.created < 0)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"created\" must be a non-negative number`,\n });\n }\n if (r.model !== undefined && typeof r.model !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"model\" must be a string, got ${typeof r.model}`,\n });\n }\n if (r.finishReason !== undefined && typeof r.finishReason !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"finishReason\" must be a string, got ${typeof r.finishReason}`,\n });\n }\n if (r.role !== undefined && typeof r.role !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"role\" must be a string, got ${typeof r.role}`,\n });\n }\n if (r.systemFingerprint !== undefined && typeof r.systemFingerprint !== \"string\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"systemFingerprint\" must be a string, got ${typeof r.systemFingerprint}`,\n });\n }\n if (r.usage !== undefined) {\n if (typeof r.usage !== \"object\" || r.usage === null || Array.isArray(r.usage)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"usage\" must be an object`,\n });\n } else {\n // Check all known usage fields are numbers if present\n for (const key of Object.keys(r.usage)) {\n const val = (r.usage as Record<string, unknown>)[key];\n if (val !== undefined && typeof val !== \"number\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `override \"usage.${key}\" must be a number, got ${typeof val}`,\n });\n }\n }\n }\n }\n }\n } // end: skip response-shape validation for function responses\n\n // Numeric sanity checks\n if (f.latency !== undefined && f.latency < 0) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"latency must be >= 0\",\n });\n }\n if (f.chunkSize !== undefined && f.chunkSize < 1) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"chunkSize must be >= 1\",\n });\n }\n if (f.truncateAfterChunks !== undefined && f.truncateAfterChunks < 1) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"truncateAfterChunks must be >= 1\",\n });\n }\n if (f.disconnectAfterMs !== undefined && f.disconnectAfterMs < 0) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"disconnectAfterMs must be >= 0\",\n });\n }\n if (f.streamingProfile !== undefined) {\n const sp = f.streamingProfile;\n if (sp.ttft !== undefined && sp.ttft < 0) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"streamingProfile.ttft must be >= 0\",\n });\n }\n if (sp.tps !== undefined && sp.tps <= 0) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"streamingProfile.tps must be > 0\",\n });\n }\n if (sp.jitter !== undefined && (sp.jitter < 0 || sp.jitter > 1)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"streamingProfile.jitter must be between 0 and 1\",\n });\n }\n }\n if (f.chaos !== undefined) {\n const ch = f.chaos;\n if (ch.dropRate !== undefined && (ch.dropRate < 0 || ch.dropRate > 1)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"chaos.dropRate must be between 0 and 1\",\n });\n }\n if (ch.malformedRate !== undefined && (ch.malformedRate < 0 || ch.malformedRate > 1)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"chaos.malformedRate must be between 0 and 1\",\n });\n }\n if (ch.disconnectRate !== undefined && (ch.disconnectRate < 0 || ch.disconnectRate > 1)) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"chaos.disconnectRate must be between 0 and 1\",\n });\n }\n }\n\n // Match field type checks\n if (f.match.turnIndex !== undefined) {\n if (\n typeof f.match.turnIndex !== \"number\" ||\n f.match.turnIndex < 0 ||\n !Number.isInteger(f.match.turnIndex)\n ) {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: \"match.turnIndex must be a non-negative integer\",\n });\n }\n }\n if (f.match.hasToolResult !== undefined && typeof f.match.hasToolResult !== \"boolean\") {\n results.push({\n severity: \"error\",\n fixtureIndex: i,\n message: `match.hasToolResult must be a boolean, got ${typeof f.match.hasToolResult}`,\n });\n }\n\n // --- Warning checks ---\n\n // Duplicate userMessage shadowing — include turnIndex, hasToolResult, and\n // sequenceIndex in the dedup key so that fixtures which share a userMessage\n // but differ on those fields are NOT considered duplicates.\n const um = f.match.userMessage;\n if (typeof um === \"string\" && um) {\n const dedupKey = `${um}|${f.match.turnIndex}|${f.match.hasToolResult}|${f.match.sequenceIndex}`;\n const prev = seenUserMessages.get(dedupKey);\n if (prev !== undefined) {\n results.push({\n severity: \"warning\",\n fixtureIndex: i,\n message: `duplicate userMessage '${um}' — shadows fixture ${prev}`,\n });\n } else {\n seenUserMessages.set(dedupKey, i);\n }\n }\n\n // Catch-all not in last position\n const match = f.match;\n const hasDiscriminator =\n match.endpoint !== undefined ||\n match.userMessage !== undefined ||\n match.inputText !== undefined ||\n match.responseFormat !== undefined ||\n match.toolCallId !== undefined ||\n match.toolName !== undefined ||\n match.model !== undefined ||\n match.predicate !== undefined ||\n match.turnIndex !== undefined ||\n match.hasToolResult !== undefined;\n\n if (!hasDiscriminator && i < fixtures.length - 1) {\n results.push({\n severity: \"warning\",\n fixtureIndex: i,\n message: `empty match acts as catch-all but is not the last fixture — shadows fixtures ${i + 1}+`,\n });\n }\n }\n\n return results;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fixture-loader.d.cts","names":[],"sources":["../src/fixture-loader.ts"],"sourcesContent":[],"mappings":";;;;;;;AA6BA;;;AAA6D,iBAA7C,iBAAA,CAA6C,GAAA,EAAtB,mBAAsB,CAAA,EAAA,eAAA;AA6D7C,iBAAA,eAAA,CAAe,QAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAA4B,MAA5B,CAAA,EAAqC,OAArC,EAAA;AAAA,iBA6Bf,mBAAA,CA7Be,OAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EA6B+B,MA7B/B,CAAA,EA6BwC,OA7BxC,EAAA;AAA4B,
|
|
1
|
+
{"version":3,"file":"fixture-loader.d.cts","names":[],"sources":["../src/fixture-loader.ts"],"sourcesContent":[],"mappings":";;;;;;;AA6BA;;;AAA6D,iBAA7C,iBAAA,CAA6C,GAAA,EAAtB,mBAAsB,CAAA,EAAA,eAAA;AA6D7C,iBAAA,eAAA,CAAe,QAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAA4B,MAA5B,CAAA,EAAqC,OAArC,EAAA;AAAA,iBA6Bf,mBAAA,CA7Be,OAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EA6B+B,MA7B/B,CAAA,EA6BwC,OA7BxC,EAAA;AAA4B,UA+G1C,gBAAA,CA/G0C;UAAS,EAAA,OAAA,GAAA,SAAA;EAAO,YAAA,EAAA,MAAA;EA6B3D,OAAA,EAAA,MAAA;;AAA8C,iBAsJ9C,gBAAA,CAtJ8C,QAAA,EAsJnB,OAtJmB,EAAA,CAAA,EAsJP,gBAtJO,EAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fixture-loader.d.ts","names":[],"sources":["../src/fixture-loader.ts"],"sourcesContent":[],"mappings":";;;;;;;AA6BA;;;AAA6D,iBAA7C,iBAAA,CAA6C,GAAA,EAAtB,mBAAsB,CAAA,EAAA,eAAA;AA6D7C,iBAAA,eAAA,CAAe,QAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAA4B,MAA5B,CAAA,EAAqC,OAArC,EAAA;AAAA,iBA6Bf,mBAAA,CA7Be,OAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EA6B+B,MA7B/B,CAAA,EA6BwC,OA7BxC,EAAA;AAA4B,
|
|
1
|
+
{"version":3,"file":"fixture-loader.d.ts","names":[],"sources":["../src/fixture-loader.ts"],"sourcesContent":[],"mappings":";;;;;;;AA6BA;;;AAA6D,iBAA7C,iBAAA,CAA6C,GAAA,EAAtB,mBAAsB,CAAA,EAAA,eAAA;AA6D7C,iBAAA,eAAA,CAAe,QAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAA4B,MAA5B,CAAA,EAAqC,OAArC,EAAA;AAAA,iBA6Bf,mBAAA,CA7Be,OAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EA6B+B,MA7B/B,CAAA,EA6BwC,OA7BxC,EAAA;AAA4B,UA+G1C,gBAAA,CA/G0C;UAAS,EAAA,OAAA,GAAA,SAAA;EAAO,YAAA,EAAA,MAAA;EA6B3D,OAAA,EAAA,MAAA;;AAA8C,iBAsJ9C,gBAAA,CAtJ8C,QAAA,EAsJnB,OAtJmB,EAAA,CAAA,EAsJP,gBAtJO,EAAA"}
|