@copilotkit/aimock 1.16.4 → 1.18.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 +35 -0
- package/README.md +10 -10
- package/dist/a2a-mock.d.cts +2 -2
- package/dist/a2a-mock.d.cts.map +1 -1
- package/dist/a2a-mock.d.ts +2 -2
- package/dist/a2a-mock.d.ts.map +1 -1
- package/dist/a2a-mock.js +2 -2
- package/dist/a2a-mock.js.map +1 -1
- package/dist/agui-handler.cjs +120 -5
- package/dist/agui-handler.cjs.map +1 -1
- package/dist/agui-handler.d.cts +41 -5
- package/dist/agui-handler.d.cts.map +1 -1
- package/dist/agui-handler.d.ts +41 -5
- package/dist/agui-handler.d.ts.map +1 -1
- package/dist/agui-handler.js +114 -6
- package/dist/agui-handler.js.map +1 -1
- package/dist/agui-mock.cjs +18 -7
- package/dist/agui-mock.cjs.map +1 -1
- package/dist/agui-mock.d.cts +2 -2
- package/dist/agui-mock.d.cts.map +1 -1
- package/dist/agui-mock.d.ts +2 -2
- package/dist/agui-mock.d.ts.map +1 -1
- package/dist/agui-mock.js +20 -9
- package/dist/agui-mock.js.map +1 -1
- package/dist/agui-recorder.cjs +43 -22
- package/dist/agui-recorder.cjs.map +1 -1
- package/dist/agui-recorder.d.cts +4 -3
- package/dist/agui-recorder.d.cts.map +1 -1
- package/dist/agui-recorder.d.ts +4 -3
- package/dist/agui-recorder.d.ts.map +1 -1
- package/dist/agui-recorder.js +45 -24
- package/dist/agui-recorder.js.map +1 -1
- package/dist/agui-stub.cjs +28 -0
- package/dist/agui-stub.d.cts +5 -0
- package/dist/agui-stub.d.ts +5 -0
- package/dist/agui-stub.js +5 -0
- package/dist/agui-types.d.cts +33 -6
- package/dist/agui-types.d.cts.map +1 -1
- package/dist/agui-types.d.ts +33 -6
- package/dist/agui-types.d.ts.map +1 -1
- package/dist/aimock-cli.cjs +1 -1
- package/dist/aimock-cli.js +1 -1
- package/dist/aws-event-stream.d.cts +2 -2
- package/dist/aws-event-stream.d.cts.map +1 -1
- package/dist/aws-event-stream.d.ts +2 -2
- package/dist/aws-event-stream.d.ts.map +1 -1
- package/dist/bedrock-converse.cjs +4 -4
- package/dist/bedrock-converse.cjs.map +1 -1
- package/dist/bedrock-converse.d.cts +3 -3
- package/dist/bedrock-converse.d.cts.map +1 -1
- package/dist/bedrock-converse.d.ts +3 -3
- package/dist/bedrock-converse.d.ts.map +1 -1
- package/dist/bedrock-converse.js +4 -4
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/bedrock.cjs +4 -4
- package/dist/bedrock.cjs.map +1 -1
- package/dist/bedrock.d.cts +3 -3
- package/dist/bedrock.d.cts.map +1 -1
- package/dist/bedrock.d.ts +3 -3
- package/dist/bedrock.d.ts.map +1 -1
- package/dist/bedrock.js +4 -4
- package/dist/bedrock.js.map +1 -1
- package/dist/chaos.cjs +35 -9
- package/dist/chaos.cjs.map +1 -1
- package/dist/chaos.d.cts +19 -4
- package/dist/chaos.d.cts.map +1 -1
- package/dist/chaos.d.ts +19 -4
- package/dist/chaos.d.ts.map +1 -1
- package/dist/chaos.js +35 -10
- package/dist/chaos.js.map +1 -1
- package/dist/cli.cjs +6 -5
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +6 -5
- package/dist/cli.js.map +1 -1
- package/dist/cohere.cjs +2 -2
- package/dist/cohere.cjs.map +1 -1
- package/dist/cohere.d.cts +2 -2
- package/dist/cohere.d.cts.map +1 -1
- package/dist/cohere.d.ts +2 -2
- 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.cjs +3 -3
- package/dist/config-loader.d.cts +1 -1
- package/dist/config-loader.d.cts.map +1 -1
- package/dist/config-loader.d.ts +1 -1
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/config-loader.js +2 -2
- package/dist/convert-vidaimock.cjs +1 -1
- package/dist/convert-vidaimock.js +1 -1
- package/dist/convert.cjs +1 -1
- package/dist/convert.js +1 -1
- package/dist/elevenlabs-audio.cjs +212 -0
- package/dist/elevenlabs-audio.cjs.map +1 -0
- package/dist/elevenlabs-audio.d.cts +11 -0
- package/dist/elevenlabs-audio.d.cts.map +1 -0
- package/dist/elevenlabs-audio.d.ts +11 -0
- package/dist/elevenlabs-audio.d.ts.map +1 -0
- package/dist/elevenlabs-audio.js +212 -0
- package/dist/elevenlabs-audio.js.map +1 -0
- package/dist/embeddings.cjs +2 -2
- package/dist/embeddings.cjs.map +1 -1
- package/dist/embeddings.d.cts +2 -2
- package/dist/embeddings.d.cts.map +1 -1
- package/dist/embeddings.d.ts +2 -2
- 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 +484 -0
- package/dist/fal-audio.cjs.map +1 -0
- package/dist/fal-audio.d.cts +10 -0
- package/dist/fal-audio.d.cts.map +1 -0
- package/dist/fal-audio.d.ts +10 -0
- package/dist/fal-audio.d.ts.map +1 -0
- package/dist/fal-audio.js +480 -0
- package/dist/fal-audio.js.map +1 -0
- package/dist/fal.cjs +424 -0
- package/dist/fal.cjs.map +1 -0
- package/dist/fal.d.cts +39 -0
- package/dist/fal.d.cts.map +1 -0
- package/dist/fal.d.ts +39 -0
- package/dist/fal.d.ts.map +1 -0
- package/dist/fal.js +420 -0
- package/dist/fal.js.map +1 -0
- package/dist/fixture-loader.cjs +16 -3
- 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 +17 -4
- package/dist/fixture-loader.js.map +1 -1
- package/dist/fixtures-remote.cjs +1 -1
- package/dist/fixtures-remote.js +1 -1
- package/dist/gemini-interactions.cjs +619 -0
- package/dist/gemini-interactions.cjs.map +1 -0
- package/dist/gemini-interactions.d.cts +46 -0
- package/dist/gemini-interactions.d.cts.map +1 -0
- package/dist/gemini-interactions.d.ts +46 -0
- package/dist/gemini-interactions.d.ts.map +1 -0
- package/dist/gemini-interactions.js +618 -0
- package/dist/gemini-interactions.js.map +1 -0
- package/dist/gemini.cjs +78 -2
- package/dist/gemini.cjs.map +1 -1
- package/dist/gemini.d.cts +2 -2
- package/dist/gemini.d.cts.map +1 -1
- package/dist/gemini.d.ts +2 -2
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +79 -3
- package/dist/gemini.js.map +1 -1
- package/dist/helpers.cjs +28 -1
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +13 -3
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts +13 -3
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +26 -2
- package/dist/helpers.js.map +1 -1
- package/dist/images.cjs +2 -2
- package/dist/images.cjs.map +1 -1
- package/dist/images.d.cts +2 -2
- package/dist/images.d.cts.map +1 -1
- package/dist/images.d.ts +2 -2
- package/dist/images.d.ts.map +1 -1
- package/dist/images.js +2 -2
- package/dist/images.js.map +1 -1
- package/dist/index.cjs +24 -4
- package/dist/index.d.cts +12 -8
- package/dist/index.d.ts +12 -8
- package/dist/index.js +11 -7
- package/dist/jest.cjs +1 -1
- package/dist/jest.js +1 -1
- package/dist/jsonrpc.d.cts +3 -3
- package/dist/jsonrpc.d.cts.map +1 -1
- package/dist/jsonrpc.d.ts +3 -3
- package/dist/jsonrpc.d.ts.map +1 -1
- package/dist/llmock.cjs +53 -2
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +6 -0
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +6 -0
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +53 -2
- package/dist/llmock.js.map +1 -1
- package/dist/logger.cjs +5 -4
- package/dist/logger.cjs.map +1 -1
- package/dist/logger.d.cts +1 -1
- package/dist/logger.d.cts.map +1 -1
- package/dist/logger.d.ts +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +5 -4
- package/dist/logger.js.map +1 -1
- package/dist/mcp-mock.d.cts +2 -2
- package/dist/mcp-mock.d.cts.map +1 -1
- package/dist/mcp-mock.d.ts +2 -2
- package/dist/mcp-mock.d.ts.map +1 -1
- package/dist/mcp-mock.js +2 -2
- package/dist/mcp-mock.js.map +1 -1
- package/dist/messages.cjs +2 -2
- package/dist/messages.cjs.map +1 -1
- package/dist/messages.d.cts +2 -2
- package/dist/messages.d.cts.map +1 -1
- package/dist/messages.d.ts +2 -2
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +2 -2
- package/dist/messages.js.map +1 -1
- package/dist/moderation.d.cts +3 -3
- package/dist/moderation.d.cts.map +1 -1
- package/dist/moderation.d.ts +3 -3
- package/dist/moderation.d.ts.map +1 -1
- package/dist/ndjson-writer.d.cts +2 -2
- package/dist/ndjson-writer.d.cts.map +1 -1
- package/dist/ndjson-writer.d.ts +2 -2
- package/dist/ndjson-writer.d.ts.map +1 -1
- package/dist/ollama.cjs +4 -4
- package/dist/ollama.cjs.map +1 -1
- package/dist/ollama.d.cts +3 -3
- package/dist/ollama.d.cts.map +1 -1
- package/dist/ollama.d.ts +3 -3
- package/dist/ollama.d.ts.map +1 -1
- package/dist/ollama.js +4 -4
- package/dist/ollama.js.map +1 -1
- package/dist/recorder.cjs +106 -38
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.d.cts +52 -7
- package/dist/recorder.d.cts.map +1 -1
- package/dist/recorder.d.ts +52 -7
- package/dist/recorder.d.ts.map +1 -1
- package/dist/recorder.js +108 -40
- package/dist/recorder.js.map +1 -1
- package/dist/rerank.d.cts +3 -3
- package/dist/rerank.d.cts.map +1 -1
- package/dist/rerank.d.ts +3 -3
- package/dist/rerank.d.ts.map +1 -1
- package/dist/responses.cjs +2 -2
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.d.cts +2 -2
- package/dist/responses.d.cts.map +1 -1
- package/dist/responses.d.ts +2 -2
- 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 +1 -1
- package/dist/router.cjs.map +1 -1
- package/dist/router.d.cts.map +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +2 -2
- package/dist/router.js.map +1 -1
- package/dist/search.d.cts +3 -3
- package/dist/search.d.cts.map +1 -1
- package/dist/search.d.ts +3 -3
- package/dist/search.d.ts.map +1 -1
- package/dist/server.cjs +253 -8
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +2 -2
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +257 -12
- package/dist/server.js.map +1 -1
- package/dist/speech.cjs +20 -11
- package/dist/speech.cjs.map +1 -1
- package/dist/speech.d.cts +2 -2
- package/dist/speech.d.cts.map +1 -1
- package/dist/speech.d.ts +2 -2
- package/dist/speech.d.ts.map +1 -1
- package/dist/speech.js +20 -11
- package/dist/speech.js.map +1 -1
- package/dist/sse-writer.d.cts +3 -3
- package/dist/sse-writer.d.cts.map +1 -1
- package/dist/sse-writer.d.ts +3 -3
- package/dist/sse-writer.d.ts.map +1 -1
- package/dist/stream-collapse.cjs +80 -9
- package/dist/stream-collapse.cjs.map +1 -1
- package/dist/stream-collapse.d.cts +11 -1
- package/dist/stream-collapse.d.cts.map +1 -1
- package/dist/stream-collapse.d.ts +11 -1
- package/dist/stream-collapse.d.ts.map +1 -1
- package/dist/stream-collapse.js +80 -10
- package/dist/stream-collapse.js.map +1 -1
- package/dist/suite.cjs +1 -1
- package/dist/suite.d.cts +2 -2
- package/dist/suite.d.ts +2 -2
- package/dist/suite.js +1 -1
- package/dist/transcription.cjs +2 -2
- 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 +2 -2
- package/dist/transcription.js.map +1 -1
- package/dist/types.d.cts +38 -11
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +38 -11
- package/dist/types.d.ts.map +1 -1
- package/dist/vector-mock.d.cts +2 -2
- package/dist/vector-mock.d.cts.map +1 -1
- package/dist/vector-mock.d.ts +2 -2
- package/dist/vector-mock.d.ts.map +1 -1
- package/dist/vector-mock.js +2 -2
- package/dist/vector-mock.js.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 +9 -3
- package/dist/video.cjs.map +1 -1
- package/dist/video.d.cts +3 -3
- package/dist/video.d.cts.map +1 -1
- package/dist/video.d.ts +3 -3
- package/dist/video.d.ts.map +1 -1
- package/dist/video.js +9 -3
- package/dist/video.js.map +1 -1
- package/dist/vitest.cjs +1 -1
- package/dist/vitest.js +1 -1
- package/dist/ws-framing.d.cts +2 -2
- package/dist/ws-framing.d.cts.map +1 -1
- package/dist/ws-framing.d.ts +2 -2
- package/dist/ws-framing.d.ts.map +1 -1
- package/dist/ws-gemini-live.cjs +145 -2
- 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 +146 -3
- package/dist/ws-gemini-live.js.map +1 -1
- package/package.json +16 -2
- package/skills/write-fixtures/SKILL.md +10 -10
package/dist/router.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.cjs","names":["isImageResponse","isAudioResponse","isTranscriptionResponse","isVideoResponse"],"sources":["../src/router.ts"],"sourcesContent":["import type { ChatCompletionRequest, ChatMessage, ContentPart, Fixture } from \"./types.js\";\nimport {\n isImageResponse,\n isAudioResponse,\n isTranscriptionResponse,\n isVideoResponse,\n} from \"./helpers.js\";\n\nexport function getLastMessageByRole(messages: ChatMessage[], role: string): ChatMessage | null {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === role) return messages[i];\n }\n return null;\n}\n\n/**\n * Extract the text content from a message's content field.\n * Handles both plain string content and array-of-parts content\n * (e.g. `[{type: \"text\", text: \"...\"}]` as sent by some SDKs).\n */\nexport function getTextContent(content: string | ContentPart[] | null): string | null {\n if (typeof content === \"string\") return content;\n if (Array.isArray(content)) {\n const texts = content\n .filter((p) => p.type === \"text\" && typeof p.text === \"string\" && p.text !== \"\")\n .map((p) => p.text as string);\n return texts.length > 0 ? texts.join(\"\") : null;\n }\n return null;\n}\n\nexport function matchFixture(\n fixtures: Fixture[],\n req: ChatCompletionRequest,\n matchCounts?: Map<Fixture, number>,\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest,\n): Fixture | null {\n // Apply transform once before matching — used for stripping dynamic data\n const effective = requestTransform ? requestTransform(req) : req;\n const useExactMatch = !!requestTransform;\n\n for (const fixture of fixtures) {\n const { match } = fixture;\n\n // predicate — if present, must return true (receives original request)\n if (match.predicate !== undefined) {\n if (!match.predicate(req)) continue;\n }\n\n // endpoint — bidirectional filtering:\n // 1. If fixture has endpoint set, only match requests of that type\n // 2. If request has _endpointType but fixture doesn't, skip fixtures\n // whose response type is incompatible (prevents generic chat fixtures\n // from matching image/speech/video requests and causing 500s)\n const reqEndpoint = effective._endpointType as string | undefined;\n if (match.endpoint !== undefined) {\n if (match.endpoint !== reqEndpoint) continue;\n } else if (reqEndpoint && reqEndpoint !== \"chat\" && reqEndpoint !== \"embedding\") {\n // Fixture has no endpoint restriction but request is multimedia —\n // only match if the response type is compatible\n const r = fixture.response;\n const compatible =\n (reqEndpoint === \"image\" && isImageResponse(r)) ||\n (reqEndpoint === \"speech\" && isAudioResponse(r)) ||\n (reqEndpoint === \"transcription\" && isTranscriptionResponse(r)) ||\n (reqEndpoint === \"video\" && isVideoResponse(r));\n if (!compatible) continue;\n }\n\n // userMessage — case-sensitive match against the last user message content.\n // String matching is intentionally case-sensitive so fixture authors can\n // rely on exact string values. This differs from the case-insensitive\n // matchesPattern() in helpers.ts, which is used for search/rerank/moderation\n // where exact casing rarely matters.\n if (match.userMessage !== undefined) {\n const msg = getLastMessageByRole(effective.messages, \"user\");\n const text = msg ? getTextContent(msg.content) : null;\n if (!text) continue;\n if (typeof match.userMessage === \"string\") {\n if (useExactMatch) {\n if (text !== match.userMessage) continue;\n } else {\n if (!text.includes(match.userMessage)) continue;\n }\n } else {\n match.userMessage.lastIndex = 0;\n if (!match.userMessage.test(text)) continue;\n }\n }\n\n // toolCallId — a toolCallId fixture answers the model's response to a tool\n // result, which by API contract only happens when the conversation's LAST\n // message is a tool result. If a newer user (or other) turn follows the\n // tool message, the stale tool_call_id must not shadow userMessage matchers.\n if (match.toolCallId !== undefined) {\n const last = effective.messages[effective.messages.length - 1];\n if (!last || last.role !== \"tool\" || last.tool_call_id !== match.toolCallId) continue;\n }\n\n // toolName — match against any tool definition by function.name\n if (match.toolName !== undefined) {\n const tools = effective.tools ?? [];\n const found = tools.some((t) => t.function.name === match.toolName);\n if (!found) continue;\n }\n\n // inputText — case-sensitive match against the embedding input text.\n // Same rationale as userMessage above: fixture authors specify exact strings.\n if (match.inputText !== undefined) {\n const embeddingInput = effective.embeddingInput;\n if (!embeddingInput) continue;\n if (typeof match.inputText === \"string\") {\n if (useExactMatch) {\n if (embeddingInput !== match.inputText) continue;\n } else {\n if (!embeddingInput.includes(match.inputText)) continue;\n }\n } else {\n match.inputText.lastIndex = 0;\n if (!match.inputText.test(embeddingInput)) continue;\n }\n }\n\n // responseFormat — exact string match against request response_format.type\n if (match.responseFormat !== undefined) {\n const reqType = effective.response_format?.type;\n if (reqType !== match.responseFormat) continue;\n }\n\n // model — exact string or regexp\n if (match.model !== undefined) {\n if (typeof match.model === \"string\") {\n if (effective.model !== match.model) continue;\n } else {\n match.model.lastIndex = 0;\n if (!match.model.test(effective.model)) continue;\n }\n }\n\n // sequenceIndex — check against the fixture's match count\n if (match.sequenceIndex !== undefined && matchCounts !== undefined) {\n const count = matchCounts.get(fixture) ?? 0;\n if (count !== match.sequenceIndex) continue;\n }\n\n if (match.turnIndex !== undefined) {\n const assistantCount = effective.messages.filter((m) => m.role === \"assistant\").length;\n if (assistantCount !== match.turnIndex) continue;\n }\n\n if (match.hasToolResult !== undefined) {\n const hasTool = effective.messages.some((m) => m.role === \"tool\");\n if (hasTool !== match.hasToolResult) continue;\n }\n\n return fixture;\n }\n\n return null;\n}\n"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"router.cjs","names":["isImageResponse","isAudioResponse","isJSONResponse","isErrorResponse","isTranscriptionResponse","isVideoResponse"],"sources":["../src/router.ts"],"sourcesContent":["import type { ChatCompletionRequest, ChatMessage, ContentPart, Fixture } from \"./types.js\";\nimport {\n isImageResponse,\n isAudioResponse,\n isTranscriptionResponse,\n isVideoResponse,\n isJSONResponse,\n isErrorResponse,\n} from \"./helpers.js\";\n\nexport function getLastMessageByRole(messages: ChatMessage[], role: string): ChatMessage | null {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === role) return messages[i];\n }\n return null;\n}\n\n/**\n * Extract the text content from a message's content field.\n * Handles both plain string content and array-of-parts content\n * (e.g. `[{type: \"text\", text: \"...\"}]` as sent by some SDKs).\n */\nexport function getTextContent(content: string | ContentPart[] | null): string | null {\n if (typeof content === \"string\") return content;\n if (Array.isArray(content)) {\n const texts = content\n .filter((p) => p.type === \"text\" && typeof p.text === \"string\" && p.text !== \"\")\n .map((p) => p.text as string);\n return texts.length > 0 ? texts.join(\"\") : null;\n }\n return null;\n}\n\nexport function matchFixture(\n fixtures: Fixture[],\n req: ChatCompletionRequest,\n matchCounts?: Map<Fixture, number>,\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest,\n): Fixture | null {\n // Apply transform once before matching — used for stripping dynamic data\n const effective = requestTransform ? requestTransform(req) : req;\n const useExactMatch = !!requestTransform;\n\n for (const fixture of fixtures) {\n const { match } = fixture;\n\n // predicate — if present, must return true (receives original request)\n if (match.predicate !== undefined) {\n if (!match.predicate(req)) continue;\n }\n\n // endpoint — bidirectional filtering:\n // 1. If fixture has endpoint set, only match requests of that type\n // 2. If request has _endpointType but fixture doesn't, skip fixtures\n // whose response type is incompatible (prevents generic chat fixtures\n // from matching image/speech/video requests and causing 500s)\n const reqEndpoint = effective._endpointType as string | undefined;\n if (match.endpoint !== undefined) {\n if (match.endpoint !== reqEndpoint) continue;\n } else if (reqEndpoint && reqEndpoint !== \"chat\" && reqEndpoint !== \"embedding\") {\n // Fixture has no endpoint restriction but request is multimedia —\n // only match if the response type is compatible\n const r = fixture.response;\n const compatible =\n (reqEndpoint === \"image\" && isImageResponse(r)) ||\n (reqEndpoint === \"speech\" && isAudioResponse(r)) ||\n (reqEndpoint === \"audio-gen\" && isAudioResponse(r)) ||\n (reqEndpoint === \"fal-audio\" && isAudioResponse(r)) ||\n (reqEndpoint === \"fal\" && (isJSONResponse(r) || isErrorResponse(r))) ||\n (reqEndpoint === \"transcription\" && isTranscriptionResponse(r)) ||\n (reqEndpoint === \"video\" && isVideoResponse(r));\n if (!compatible) continue;\n }\n\n // userMessage — case-sensitive match against the last user message content.\n // String matching is intentionally case-sensitive so fixture authors can\n // rely on exact string values. This differs from the case-insensitive\n // matchesPattern() in helpers.ts, which is used for search/rerank/moderation\n // where exact casing rarely matters.\n if (match.userMessage !== undefined) {\n const msg = getLastMessageByRole(effective.messages, \"user\");\n const text = msg ? getTextContent(msg.content) : null;\n if (!text) continue;\n if (typeof match.userMessage === \"string\") {\n if (useExactMatch) {\n if (text !== match.userMessage) continue;\n } else {\n if (!text.includes(match.userMessage)) continue;\n }\n } else {\n match.userMessage.lastIndex = 0;\n if (!match.userMessage.test(text)) continue;\n }\n }\n\n // toolCallId — a toolCallId fixture answers the model's response to a tool\n // result, which by API contract only happens when the conversation's LAST\n // message is a tool result. If a newer user (or other) turn follows the\n // tool message, the stale tool_call_id must not shadow userMessage matchers.\n if (match.toolCallId !== undefined) {\n const last = effective.messages[effective.messages.length - 1];\n if (!last || last.role !== \"tool\" || last.tool_call_id !== match.toolCallId) continue;\n }\n\n // toolName — match against any tool definition by function.name\n if (match.toolName !== undefined) {\n const tools = effective.tools ?? [];\n const found = tools.some((t) => t.function.name === match.toolName);\n if (!found) continue;\n }\n\n // inputText — case-sensitive match against the embedding input text.\n // Same rationale as userMessage above: fixture authors specify exact strings.\n if (match.inputText !== undefined) {\n const embeddingInput = effective.embeddingInput;\n if (!embeddingInput) continue;\n if (typeof match.inputText === \"string\") {\n if (useExactMatch) {\n if (embeddingInput !== match.inputText) continue;\n } else {\n if (!embeddingInput.includes(match.inputText)) continue;\n }\n } else {\n match.inputText.lastIndex = 0;\n if (!match.inputText.test(embeddingInput)) continue;\n }\n }\n\n // responseFormat — exact string match against request response_format.type\n if (match.responseFormat !== undefined) {\n const reqType = effective.response_format?.type;\n if (reqType !== match.responseFormat) continue;\n }\n\n // model — exact string or regexp\n if (match.model !== undefined) {\n if (typeof match.model === \"string\") {\n if (effective.model !== match.model) continue;\n } else {\n match.model.lastIndex = 0;\n if (!match.model.test(effective.model)) continue;\n }\n }\n\n // sequenceIndex — check against the fixture's match count\n if (match.sequenceIndex !== undefined && matchCounts !== undefined) {\n const count = matchCounts.get(fixture) ?? 0;\n if (count !== match.sequenceIndex) continue;\n }\n\n if (match.turnIndex !== undefined) {\n const assistantCount = effective.messages.filter((m) => m.role === \"assistant\").length;\n if (assistantCount !== match.turnIndex) continue;\n }\n\n if (match.hasToolResult !== undefined) {\n const hasTool = effective.messages.some((m) => m.role === \"tool\");\n if (hasTool !== match.hasToolResult) continue;\n }\n\n return fixture;\n }\n\n return null;\n}\n"],"mappings":"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"}
|
package/dist/router.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.cts","names":[],"sources":["../src/router.ts"],"sourcesContent":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"router.d.cts","names":[],"sources":["../src/router.ts"],"sourcesContent":[],"mappings":";;;;;AAsBA;AAWA;;;AAEO,iBAbS,cAAA,CAaT,OAAA,EAAA,MAAA,GAb0C,WAa1C,EAAA,GAAA,IAAA,CAAA,EAAA,MAAA,GAAA,IAAA;AACa,iBAHJ,YAAA,CAGI,QAAA,EAFR,OAEQ,EAAA,EAAA,GAAA,EADb,qBACa,EAAA,WAAA,CAAA,EAAJ,GAAI,CAAA,OAAA,EAAA,MAAA,CAAA,EAAA,gBAAA,CAAA,EAAA,CAAA,GAAA,EACO,qBADP,EAAA,GACiC,qBADjC,CAAA,EAEjB,OAFiB,GAAA,IAAA"}
|
package/dist/router.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","names":[],"sources":["../src/router.ts"],"sourcesContent":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"router.d.ts","names":[],"sources":["../src/router.ts"],"sourcesContent":[],"mappings":";;;;;AAsBA;AAWA;;;AAEO,iBAbS,cAAA,CAaT,OAAA,EAAA,MAAA,GAb0C,WAa1C,EAAA,GAAA,IAAA,CAAA,EAAA,MAAA,GAAA,IAAA;AACa,iBAHJ,YAAA,CAGI,QAAA,EAFR,OAEQ,EAAA,EAAA,GAAA,EADb,qBACa,EAAA,WAAA,CAAA,EAAJ,GAAI,CAAA,OAAA,EAAA,MAAA,CAAA,EAAA,gBAAA,CAAA,EAAA,CAAA,GAAA,EACO,qBADP,EAAA,GACiC,qBADjC,CAAA,EAEjB,OAFiB,GAAA,IAAA"}
|
package/dist/router.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isAudioResponse, isImageResponse, isTranscriptionResponse, isVideoResponse } from "./helpers.js";
|
|
1
|
+
import { isAudioResponse, isErrorResponse, isImageResponse, isJSONResponse, isTranscriptionResponse, isVideoResponse } from "./helpers.js";
|
|
2
2
|
|
|
3
3
|
//#region src/router.ts
|
|
4
4
|
function getLastMessageByRole(messages, role) {
|
|
@@ -31,7 +31,7 @@ function matchFixture(fixtures, req, matchCounts, requestTransform) {
|
|
|
31
31
|
if (match.endpoint !== reqEndpoint) continue;
|
|
32
32
|
} else if (reqEndpoint && reqEndpoint !== "chat" && reqEndpoint !== "embedding") {
|
|
33
33
|
const r = fixture.response;
|
|
34
|
-
if (!(reqEndpoint === "image" && isImageResponse(r) || reqEndpoint === "speech" && isAudioResponse(r) || reqEndpoint === "transcription" && isTranscriptionResponse(r) || reqEndpoint === "video" && isVideoResponse(r))) continue;
|
|
34
|
+
if (!(reqEndpoint === "image" && isImageResponse(r) || reqEndpoint === "speech" && isAudioResponse(r) || reqEndpoint === "audio-gen" && isAudioResponse(r) || reqEndpoint === "fal-audio" && isAudioResponse(r) || reqEndpoint === "fal" && (isJSONResponse(r) || isErrorResponse(r)) || reqEndpoint === "transcription" && isTranscriptionResponse(r) || reqEndpoint === "video" && isVideoResponse(r))) continue;
|
|
35
35
|
}
|
|
36
36
|
if (match.userMessage !== void 0) {
|
|
37
37
|
const msg = getLastMessageByRole(effective.messages, "user");
|
package/dist/router.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.js","names":[],"sources":["../src/router.ts"],"sourcesContent":["import type { ChatCompletionRequest, ChatMessage, ContentPart, Fixture } from \"./types.js\";\nimport {\n isImageResponse,\n isAudioResponse,\n isTranscriptionResponse,\n isVideoResponse,\n} from \"./helpers.js\";\n\nexport function getLastMessageByRole(messages: ChatMessage[], role: string): ChatMessage | null {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === role) return messages[i];\n }\n return null;\n}\n\n/**\n * Extract the text content from a message's content field.\n * Handles both plain string content and array-of-parts content\n * (e.g. `[{type: \"text\", text: \"...\"}]` as sent by some SDKs).\n */\nexport function getTextContent(content: string | ContentPart[] | null): string | null {\n if (typeof content === \"string\") return content;\n if (Array.isArray(content)) {\n const texts = content\n .filter((p) => p.type === \"text\" && typeof p.text === \"string\" && p.text !== \"\")\n .map((p) => p.text as string);\n return texts.length > 0 ? texts.join(\"\") : null;\n }\n return null;\n}\n\nexport function matchFixture(\n fixtures: Fixture[],\n req: ChatCompletionRequest,\n matchCounts?: Map<Fixture, number>,\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest,\n): Fixture | null {\n // Apply transform once before matching — used for stripping dynamic data\n const effective = requestTransform ? requestTransform(req) : req;\n const useExactMatch = !!requestTransform;\n\n for (const fixture of fixtures) {\n const { match } = fixture;\n\n // predicate — if present, must return true (receives original request)\n if (match.predicate !== undefined) {\n if (!match.predicate(req)) continue;\n }\n\n // endpoint — bidirectional filtering:\n // 1. If fixture has endpoint set, only match requests of that type\n // 2. If request has _endpointType but fixture doesn't, skip fixtures\n // whose response type is incompatible (prevents generic chat fixtures\n // from matching image/speech/video requests and causing 500s)\n const reqEndpoint = effective._endpointType as string | undefined;\n if (match.endpoint !== undefined) {\n if (match.endpoint !== reqEndpoint) continue;\n } else if (reqEndpoint && reqEndpoint !== \"chat\" && reqEndpoint !== \"embedding\") {\n // Fixture has no endpoint restriction but request is multimedia —\n // only match if the response type is compatible\n const r = fixture.response;\n const compatible =\n (reqEndpoint === \"image\" && isImageResponse(r)) ||\n (reqEndpoint === \"speech\" && isAudioResponse(r)) ||\n (reqEndpoint === \"transcription\" && isTranscriptionResponse(r)) ||\n (reqEndpoint === \"video\" && isVideoResponse(r));\n if (!compatible) continue;\n }\n\n // userMessage — case-sensitive match against the last user message content.\n // String matching is intentionally case-sensitive so fixture authors can\n // rely on exact string values. This differs from the case-insensitive\n // matchesPattern() in helpers.ts, which is used for search/rerank/moderation\n // where exact casing rarely matters.\n if (match.userMessage !== undefined) {\n const msg = getLastMessageByRole(effective.messages, \"user\");\n const text = msg ? getTextContent(msg.content) : null;\n if (!text) continue;\n if (typeof match.userMessage === \"string\") {\n if (useExactMatch) {\n if (text !== match.userMessage) continue;\n } else {\n if (!text.includes(match.userMessage)) continue;\n }\n } else {\n match.userMessage.lastIndex = 0;\n if (!match.userMessage.test(text)) continue;\n }\n }\n\n // toolCallId — a toolCallId fixture answers the model's response to a tool\n // result, which by API contract only happens when the conversation's LAST\n // message is a tool result. If a newer user (or other) turn follows the\n // tool message, the stale tool_call_id must not shadow userMessage matchers.\n if (match.toolCallId !== undefined) {\n const last = effective.messages[effective.messages.length - 1];\n if (!last || last.role !== \"tool\" || last.tool_call_id !== match.toolCallId) continue;\n }\n\n // toolName — match against any tool definition by function.name\n if (match.toolName !== undefined) {\n const tools = effective.tools ?? [];\n const found = tools.some((t) => t.function.name === match.toolName);\n if (!found) continue;\n }\n\n // inputText — case-sensitive match against the embedding input text.\n // Same rationale as userMessage above: fixture authors specify exact strings.\n if (match.inputText !== undefined) {\n const embeddingInput = effective.embeddingInput;\n if (!embeddingInput) continue;\n if (typeof match.inputText === \"string\") {\n if (useExactMatch) {\n if (embeddingInput !== match.inputText) continue;\n } else {\n if (!embeddingInput.includes(match.inputText)) continue;\n }\n } else {\n match.inputText.lastIndex = 0;\n if (!match.inputText.test(embeddingInput)) continue;\n }\n }\n\n // responseFormat — exact string match against request response_format.type\n if (match.responseFormat !== undefined) {\n const reqType = effective.response_format?.type;\n if (reqType !== match.responseFormat) continue;\n }\n\n // model — exact string or regexp\n if (match.model !== undefined) {\n if (typeof match.model === \"string\") {\n if (effective.model !== match.model) continue;\n } else {\n match.model.lastIndex = 0;\n if (!match.model.test(effective.model)) continue;\n }\n }\n\n // sequenceIndex — check against the fixture's match count\n if (match.sequenceIndex !== undefined && matchCounts !== undefined) {\n const count = matchCounts.get(fixture) ?? 0;\n if (count !== match.sequenceIndex) continue;\n }\n\n if (match.turnIndex !== undefined) {\n const assistantCount = effective.messages.filter((m) => m.role === \"assistant\").length;\n if (assistantCount !== match.turnIndex) continue;\n }\n\n if (match.hasToolResult !== undefined) {\n const hasTool = effective.messages.some((m) => m.role === \"tool\");\n if (hasTool !== match.hasToolResult) continue;\n }\n\n return fixture;\n }\n\n return null;\n}\n"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"router.js","names":[],"sources":["../src/router.ts"],"sourcesContent":["import type { ChatCompletionRequest, ChatMessage, ContentPart, Fixture } from \"./types.js\";\nimport {\n isImageResponse,\n isAudioResponse,\n isTranscriptionResponse,\n isVideoResponse,\n isJSONResponse,\n isErrorResponse,\n} from \"./helpers.js\";\n\nexport function getLastMessageByRole(messages: ChatMessage[], role: string): ChatMessage | null {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === role) return messages[i];\n }\n return null;\n}\n\n/**\n * Extract the text content from a message's content field.\n * Handles both plain string content and array-of-parts content\n * (e.g. `[{type: \"text\", text: \"...\"}]` as sent by some SDKs).\n */\nexport function getTextContent(content: string | ContentPart[] | null): string | null {\n if (typeof content === \"string\") return content;\n if (Array.isArray(content)) {\n const texts = content\n .filter((p) => p.type === \"text\" && typeof p.text === \"string\" && p.text !== \"\")\n .map((p) => p.text as string);\n return texts.length > 0 ? texts.join(\"\") : null;\n }\n return null;\n}\n\nexport function matchFixture(\n fixtures: Fixture[],\n req: ChatCompletionRequest,\n matchCounts?: Map<Fixture, number>,\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest,\n): Fixture | null {\n // Apply transform once before matching — used for stripping dynamic data\n const effective = requestTransform ? requestTransform(req) : req;\n const useExactMatch = !!requestTransform;\n\n for (const fixture of fixtures) {\n const { match } = fixture;\n\n // predicate — if present, must return true (receives original request)\n if (match.predicate !== undefined) {\n if (!match.predicate(req)) continue;\n }\n\n // endpoint — bidirectional filtering:\n // 1. If fixture has endpoint set, only match requests of that type\n // 2. If request has _endpointType but fixture doesn't, skip fixtures\n // whose response type is incompatible (prevents generic chat fixtures\n // from matching image/speech/video requests and causing 500s)\n const reqEndpoint = effective._endpointType as string | undefined;\n if (match.endpoint !== undefined) {\n if (match.endpoint !== reqEndpoint) continue;\n } else if (reqEndpoint && reqEndpoint !== \"chat\" && reqEndpoint !== \"embedding\") {\n // Fixture has no endpoint restriction but request is multimedia —\n // only match if the response type is compatible\n const r = fixture.response;\n const compatible =\n (reqEndpoint === \"image\" && isImageResponse(r)) ||\n (reqEndpoint === \"speech\" && isAudioResponse(r)) ||\n (reqEndpoint === \"audio-gen\" && isAudioResponse(r)) ||\n (reqEndpoint === \"fal-audio\" && isAudioResponse(r)) ||\n (reqEndpoint === \"fal\" && (isJSONResponse(r) || isErrorResponse(r))) ||\n (reqEndpoint === \"transcription\" && isTranscriptionResponse(r)) ||\n (reqEndpoint === \"video\" && isVideoResponse(r));\n if (!compatible) continue;\n }\n\n // userMessage — case-sensitive match against the last user message content.\n // String matching is intentionally case-sensitive so fixture authors can\n // rely on exact string values. This differs from the case-insensitive\n // matchesPattern() in helpers.ts, which is used for search/rerank/moderation\n // where exact casing rarely matters.\n if (match.userMessage !== undefined) {\n const msg = getLastMessageByRole(effective.messages, \"user\");\n const text = msg ? getTextContent(msg.content) : null;\n if (!text) continue;\n if (typeof match.userMessage === \"string\") {\n if (useExactMatch) {\n if (text !== match.userMessage) continue;\n } else {\n if (!text.includes(match.userMessage)) continue;\n }\n } else {\n match.userMessage.lastIndex = 0;\n if (!match.userMessage.test(text)) continue;\n }\n }\n\n // toolCallId — a toolCallId fixture answers the model's response to a tool\n // result, which by API contract only happens when the conversation's LAST\n // message is a tool result. If a newer user (or other) turn follows the\n // tool message, the stale tool_call_id must not shadow userMessage matchers.\n if (match.toolCallId !== undefined) {\n const last = effective.messages[effective.messages.length - 1];\n if (!last || last.role !== \"tool\" || last.tool_call_id !== match.toolCallId) continue;\n }\n\n // toolName — match against any tool definition by function.name\n if (match.toolName !== undefined) {\n const tools = effective.tools ?? [];\n const found = tools.some((t) => t.function.name === match.toolName);\n if (!found) continue;\n }\n\n // inputText — case-sensitive match against the embedding input text.\n // Same rationale as userMessage above: fixture authors specify exact strings.\n if (match.inputText !== undefined) {\n const embeddingInput = effective.embeddingInput;\n if (!embeddingInput) continue;\n if (typeof match.inputText === \"string\") {\n if (useExactMatch) {\n if (embeddingInput !== match.inputText) continue;\n } else {\n if (!embeddingInput.includes(match.inputText)) continue;\n }\n } else {\n match.inputText.lastIndex = 0;\n if (!match.inputText.test(embeddingInput)) continue;\n }\n }\n\n // responseFormat — exact string match against request response_format.type\n if (match.responseFormat !== undefined) {\n const reqType = effective.response_format?.type;\n if (reqType !== match.responseFormat) continue;\n }\n\n // model — exact string or regexp\n if (match.model !== undefined) {\n if (typeof match.model === \"string\") {\n if (effective.model !== match.model) continue;\n } else {\n match.model.lastIndex = 0;\n if (!match.model.test(effective.model)) continue;\n }\n }\n\n // sequenceIndex — check against the fixture's match count\n if (match.sequenceIndex !== undefined && matchCounts !== undefined) {\n const count = matchCounts.get(fixture) ?? 0;\n if (count !== match.sequenceIndex) continue;\n }\n\n if (match.turnIndex !== undefined) {\n const assistantCount = effective.messages.filter((m) => m.role === \"assistant\").length;\n if (assistantCount !== match.turnIndex) continue;\n }\n\n if (match.hasToolResult !== undefined) {\n const hasTool = effective.messages.some((m) => m.role === \"tool\");\n if (hasTool !== match.hasToolResult) continue;\n }\n\n return fixture;\n }\n\n return null;\n}\n"],"mappings":"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"}
|
package/dist/search.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Journal } from "./journal.cjs";
|
|
2
2
|
import { Logger } from "./logger.cjs";
|
|
3
|
-
import * as http from "node:http";
|
|
3
|
+
import * as http$1 from "node:http";
|
|
4
4
|
|
|
5
5
|
//#region src/search.d.ts
|
|
6
6
|
|
|
@@ -14,9 +14,9 @@ interface SearchFixture {
|
|
|
14
14
|
match: string | RegExp;
|
|
15
15
|
results: SearchResult[];
|
|
16
16
|
}
|
|
17
|
-
declare function handleSearch(req: http.IncomingMessage, res: http.ServerResponse, raw: string, fixtures: SearchFixture[], journal: Journal, defaults: {
|
|
17
|
+
declare function handleSearch(req: http$1.IncomingMessage, res: http$1.ServerResponse, raw: string, fixtures: SearchFixture[], journal: Journal, defaults: {
|
|
18
18
|
logger: Logger;
|
|
19
|
-
}, setCorsHeaders: (res: http.ServerResponse) => void): Promise<void>;
|
|
19
|
+
}, setCorsHeaders: (res: http$1.ServerResponse) => void): Promise<void>;
|
|
20
20
|
//# sourceMappingURL=search.d.ts.map
|
|
21
21
|
//#endregion
|
|
22
22
|
export { SearchFixture, SearchResult, handleSearch };
|
package/dist/search.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.cts","names":[],"sources":["../src/search.ts"],"sourcesContent":[],"mappings":";;;;;;AAwBuB,UATN,YAAA,CASM;EAKD,KAAA,EAAA,MAAA;EAAY,GAAA,EAAA,MAAA;SAC3B,
|
|
1
|
+
{"version":3,"file":"search.d.cts","names":[],"sources":["../src/search.ts"],"sourcesContent":[],"mappings":";;;;;;AAwBuB,UATN,YAAA,CASM;EAKD,KAAA,EAAA,MAAA;EAAY,GAAA,EAAA,MAAA;SAC3B,EAAA,MAAK;OACL,CAAA,EAAA,MAAK;;AAGD,UAZM,aAAA,CAYN;OACW,EAAA,MAAA,GAZJ,MAYI;SACE,EAZb,YAYkB,EAAA;;AACnB,iBARY,YAAA,CAQZ,GAAA,EAPH,MAAA,CAAK,eAOF,EAAA,GAAA,EANH,MAAA,CAAK,cAMF,EAAA,GAAA,EAAA,MAAA,EAAA,QAAA,EAJE,aAIF,EAAA,EAAA,OAAA,EAHC,OAGD,EAAA,QAAA,EAAA;UAFY;yBACE,MAAA,CAAK,0BAC1B"}
|
package/dist/search.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Journal } from "./journal.js";
|
|
2
2
|
import { Logger } from "./logger.js";
|
|
3
|
-
import * as http from "node:http";
|
|
3
|
+
import * as http$1 from "node:http";
|
|
4
4
|
|
|
5
5
|
//#region src/search.d.ts
|
|
6
6
|
|
|
@@ -14,9 +14,9 @@ interface SearchFixture {
|
|
|
14
14
|
match: string | RegExp;
|
|
15
15
|
results: SearchResult[];
|
|
16
16
|
}
|
|
17
|
-
declare function handleSearch(req: http.IncomingMessage, res: http.ServerResponse, raw: string, fixtures: SearchFixture[], journal: Journal, defaults: {
|
|
17
|
+
declare function handleSearch(req: http$1.IncomingMessage, res: http$1.ServerResponse, raw: string, fixtures: SearchFixture[], journal: Journal, defaults: {
|
|
18
18
|
logger: Logger;
|
|
19
|
-
}, setCorsHeaders: (res: http.ServerResponse) => void): Promise<void>;
|
|
19
|
+
}, setCorsHeaders: (res: http$1.ServerResponse) => void): Promise<void>;
|
|
20
20
|
//# sourceMappingURL=search.d.ts.map
|
|
21
21
|
//#endregion
|
|
22
22
|
export { SearchFixture, SearchResult, handleSearch };
|
package/dist/search.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","names":[],"sources":["../src/search.ts"],"sourcesContent":[],"mappings":";;;;;;AAwBuB,UATN,YAAA,CASM;EAKD,KAAA,EAAA,MAAA;EAAY,GAAA,EAAA,MAAA;SAC3B,
|
|
1
|
+
{"version":3,"file":"search.d.ts","names":[],"sources":["../src/search.ts"],"sourcesContent":[],"mappings":";;;;;;AAwBuB,UATN,YAAA,CASM;EAKD,KAAA,EAAA,MAAA;EAAY,GAAA,EAAA,MAAA;SAC3B,EAAA,MAAK;OACL,CAAA,EAAA,MAAK;;AAGD,UAZM,aAAA,CAYN;OACW,EAAA,MAAA,GAZJ,MAYI;SACE,EAZb,YAYkB,EAAA;;AACnB,iBARY,YAAA,CAQZ,GAAA,EAPH,MAAA,CAAK,eAOF,EAAA,GAAA,EANH,MAAA,CAAK,cAMF,EAAA,GAAA,EAAA,MAAA,EAAA,QAAA,EAJE,aAIF,EAAA,EAAA,OAAA,EAHC,OAGD,EAAA,QAAA,EAAA;UAFY;yBACE,MAAA,CAAK,0BAC1B"}
|
package/dist/server.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
2
2
|
const require_helpers = require('./helpers.cjs');
|
|
3
|
+
const require_logger = require('./logger.cjs');
|
|
3
4
|
const require_journal = require('./journal.cjs');
|
|
4
5
|
const require_router = require('./router.cjs');
|
|
5
6
|
const require_fixture_loader = require('./fixture-loader.cjs');
|
|
@@ -12,11 +13,15 @@ const require_messages = require('./messages.cjs');
|
|
|
12
13
|
const require_gemini = require('./gemini.cjs');
|
|
13
14
|
const require_bedrock = require('./bedrock.cjs');
|
|
14
15
|
const require_bedrock_converse = require('./bedrock-converse.cjs');
|
|
16
|
+
const require_gemini_interactions = require('./gemini-interactions.cjs');
|
|
15
17
|
const require_embeddings = require('./embeddings.cjs');
|
|
16
18
|
const require_images = require('./images.cjs');
|
|
17
19
|
const require_speech = require('./speech.cjs');
|
|
18
20
|
const require_transcription = require('./transcription.cjs');
|
|
19
21
|
const require_video = require('./video.cjs');
|
|
22
|
+
const require_elevenlabs_audio = require('./elevenlabs-audio.cjs');
|
|
23
|
+
const require_fal_audio = require('./fal-audio.cjs');
|
|
24
|
+
const require_fal = require('./fal.cjs');
|
|
20
25
|
const require_ollama = require('./ollama.cjs');
|
|
21
26
|
const require_cohere = require('./cohere.cjs');
|
|
22
27
|
const require_search = require('./search.cjs');
|
|
@@ -26,7 +31,6 @@ const require_ws_framing = require('./ws-framing.cjs');
|
|
|
26
31
|
const require_ws_responses = require('./ws-responses.cjs');
|
|
27
32
|
const require_ws_realtime = require('./ws-realtime.cjs');
|
|
28
33
|
const require_ws_gemini_live = require('./ws-gemini-live.cjs');
|
|
29
|
-
const require_logger = require('./logger.cjs');
|
|
30
34
|
const require_metrics = require('./metrics.cjs');
|
|
31
35
|
let node_http = require("node:http");
|
|
32
36
|
node_http = require_runtime.__toESM(node_http);
|
|
@@ -48,6 +52,12 @@ const TRANSCRIPTIONS_PATH = "/v1/audio/transcriptions";
|
|
|
48
52
|
const VIDEOS_PATH = "/v1/videos";
|
|
49
53
|
const VIDEOS_STATUS_RE = /^\/v1\/videos\/([^/]+)$/;
|
|
50
54
|
const GEMINI_PREDICT_RE = /^\/v1beta\/models\/([^:]+):predict$/;
|
|
55
|
+
const ELEVENLABS_SOUND_GENERATION_PATH = "/v1/sound-generation";
|
|
56
|
+
const ELEVENLABS_MUSIC_RE = /^\/v1\/music(?:\/(.+))?$/;
|
|
57
|
+
const FAL_QUEUE_SUBMIT_RE = /^\/fal\/queue\/submit\/(.+)$/;
|
|
58
|
+
const FAL_QUEUE_REQUESTS_RE = /^\/fal\/queue\/requests\/(.+)$/;
|
|
59
|
+
const FAL_RUN_RE = /^\/fal\/run\/(.+)$/;
|
|
60
|
+
const FAL_PREFIX_RE = /^\/fal(?:\/.*)?$/;
|
|
51
61
|
const DEFAULT_CHUNK_SIZE = 20;
|
|
52
62
|
const COMPAT_SUFFIXES = [
|
|
53
63
|
"/chat/completions",
|
|
@@ -73,6 +83,7 @@ function normalizeCompatPath(pathname, logger) {
|
|
|
73
83
|
}
|
|
74
84
|
return pathname;
|
|
75
85
|
}
|
|
86
|
+
const GEMINI_INTERACTIONS_PATH = "/v1beta/interactions";
|
|
76
87
|
const GEMINI_PATH_RE = /^\/v1beta\/models\/([^:]+):(generateContent|streamGenerateContent)$/;
|
|
77
88
|
const AZURE_DEPLOYMENT_RE = /^\/openai\/deployments\/([^/]+)\/(chat\/completions|embeddings)$/;
|
|
78
89
|
const BEDROCK_INVOKE_RE = /^\/model\/([^/]+)\/invoke$/;
|
|
@@ -201,6 +212,8 @@ async function handleControlAPI(req, res, pathname, fixtures, journal, videoStat
|
|
|
201
212
|
fixtures.length = 0;
|
|
202
213
|
journal.clear();
|
|
203
214
|
videoStates.clear();
|
|
215
|
+
require_fal_audio.falJobs.clear();
|
|
216
|
+
require_fal.falQueueStates.clear();
|
|
204
217
|
if (defaults.registry) defaults.registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
|
|
205
218
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
206
219
|
res.end(JSON.stringify({ reset: true }));
|
|
@@ -319,6 +332,9 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
319
332
|
} }));
|
|
320
333
|
return;
|
|
321
334
|
}
|
|
335
|
+
const method = req.method ?? "POST";
|
|
336
|
+
const path = req.url ?? COMPLETIONS_PATH;
|
|
337
|
+
const flatHeaders = require_helpers.flattenHeaders(req.headers);
|
|
322
338
|
body._endpointType = "chat";
|
|
323
339
|
const testId = require_helpers.getTestId(req);
|
|
324
340
|
const fixture = require_router.matchFixture(fixtures, body, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform);
|
|
@@ -330,18 +346,40 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
330
346
|
const snippet = typeof lastUserMsg?.content === "string" ? lastUserMsg.content.slice(0, 80) : "";
|
|
331
347
|
defaults.logger.debug(`No fixture matched for request (model=${body.model ?? "?"}, msg="${snippet}")`);
|
|
332
348
|
}
|
|
333
|
-
const
|
|
334
|
-
const
|
|
335
|
-
const flatHeaders = require_helpers.flattenHeaders(req.headers);
|
|
336
|
-
if (require_chaos.applyChaos(res, fixture, defaults.chaos, req.headers, journal, {
|
|
349
|
+
const chaosAction = require_chaos.evaluateChaos(fixture, defaults.chaos, req.headers, defaults.logger);
|
|
350
|
+
const chaosContext = {
|
|
337
351
|
method,
|
|
338
352
|
path,
|
|
339
353
|
headers: flatHeaders,
|
|
340
354
|
body
|
|
341
|
-
}
|
|
355
|
+
};
|
|
356
|
+
if (chaosAction === "drop" || chaosAction === "disconnect") {
|
|
357
|
+
require_chaos.applyChaosAction(chaosAction, res, fixture, journal, chaosContext, fixture ? "fixture" : "proxy", defaults.registry);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (fixture && chaosAction === "malformed") {
|
|
361
|
+
require_chaos.applyChaosAction(chaosAction, res, fixture, journal, chaosContext, "fixture", defaults.registry);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
342
364
|
if (!fixture) {
|
|
343
365
|
if (defaults.record && providerKey) {
|
|
344
|
-
|
|
366
|
+
const hookOptions = chaosAction === "malformed" ? {
|
|
367
|
+
beforeWriteResponse: () => {
|
|
368
|
+
require_chaos.applyChaosAction(chaosAction, res, null, journal, chaosContext, "proxy", defaults.registry);
|
|
369
|
+
return true;
|
|
370
|
+
},
|
|
371
|
+
onHookBypassed: (reason) => {
|
|
372
|
+
defaults.logger.warn(`[chaos] malformed bypassed on proxy: upstream returned SSE (${reason})`);
|
|
373
|
+
defaults.registry?.incrementCounter("aimock_chaos_bypassed_total", {
|
|
374
|
+
action: "malformed",
|
|
375
|
+
source: "proxy",
|
|
376
|
+
reason
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
} : void 0;
|
|
380
|
+
const outcome = await require_recorder.proxyAndRecord(req, res, body, providerKey, req.url ?? COMPLETIONS_PATH, fixtures, defaults, raw, hookOptions);
|
|
381
|
+
if (outcome === "handled_by_hook") return;
|
|
382
|
+
if (outcome === "relayed") {
|
|
345
383
|
journal.add({
|
|
346
384
|
method: req.method ?? "POST",
|
|
347
385
|
path: req.url ?? COMPLETIONS_PATH,
|
|
@@ -394,6 +432,23 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
394
432
|
require_sse_writer.writeErrorResponse(res, status, JSON.stringify(response));
|
|
395
433
|
return;
|
|
396
434
|
}
|
|
435
|
+
if (require_helpers.isAudioResponse(response)) {
|
|
436
|
+
journal.add({
|
|
437
|
+
method: req.method ?? "POST",
|
|
438
|
+
path: req.url ?? COMPLETIONS_PATH,
|
|
439
|
+
headers: require_helpers.flattenHeaders(req.headers),
|
|
440
|
+
body,
|
|
441
|
+
response: {
|
|
442
|
+
status: 422,
|
|
443
|
+
fixture
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
require_sse_writer.writeErrorResponse(res, 422, JSON.stringify({ error: {
|
|
447
|
+
message: "Audio responses are not supported on the chat completions endpoint. Use Gemini generateContent or a dedicated audio endpoint.",
|
|
448
|
+
type: "invalid_request_error"
|
|
449
|
+
} }));
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
397
452
|
if (require_helpers.isContentWithToolCallsResponse(response)) {
|
|
398
453
|
if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
|
|
399
454
|
const overrides = require_helpers.extractOverrides(response);
|
|
@@ -841,7 +896,7 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
|
|
|
841
896
|
const videoStatusMatch = pathname.match(VIDEOS_STATUS_RE);
|
|
842
897
|
if (videoStatusMatch && req.method === "GET") {
|
|
843
898
|
const videoId = videoStatusMatch[1];
|
|
844
|
-
require_video.handleVideoStatus(req, res, videoId, journal, setCorsHeaders, videoStates);
|
|
899
|
+
require_video.handleVideoStatus(req, res, videoId, journal, defaults, setCorsHeaders, videoStates);
|
|
845
900
|
return;
|
|
846
901
|
}
|
|
847
902
|
const geminiPredictMatch = pathname.match(GEMINI_PREDICT_RE);
|
|
@@ -859,6 +914,26 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
|
|
|
859
914
|
}
|
|
860
915
|
return;
|
|
861
916
|
}
|
|
917
|
+
if (pathname === GEMINI_INTERACTIONS_PATH && req.method === "POST") {
|
|
918
|
+
try {
|
|
919
|
+
await require_gemini_interactions.handleGeminiInteractions(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders);
|
|
920
|
+
} catch (err) {
|
|
921
|
+
const msg = err instanceof Error ? err.message : "Internal error";
|
|
922
|
+
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
923
|
+
message: msg,
|
|
924
|
+
type: "server_error"
|
|
925
|
+
} }));
|
|
926
|
+
else if (!res.writableEnded) {
|
|
927
|
+
try {
|
|
928
|
+
res.write(`data: ${JSON.stringify({ error: { message: msg } })}\n\n`);
|
|
929
|
+
} catch (writeErr) {
|
|
930
|
+
logger.debug("Failed to write error recovery response:", writeErr);
|
|
931
|
+
}
|
|
932
|
+
res.end();
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
862
937
|
const geminiMatch = pathname.match(GEMINI_PATH_RE);
|
|
863
938
|
if (geminiMatch && req.method === "POST") {
|
|
864
939
|
const geminiModel = geminiMatch[1];
|
|
@@ -1046,6 +1121,176 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
|
|
|
1046
1121
|
}
|
|
1047
1122
|
return;
|
|
1048
1123
|
}
|
|
1124
|
+
if (pathname === ELEVENLABS_SOUND_GENERATION_PATH && req.method === "POST") {
|
|
1125
|
+
setCorsHeaders(res);
|
|
1126
|
+
try {
|
|
1127
|
+
const raw = await readBody(req);
|
|
1128
|
+
const chaosAction = require_chaos.evaluateChaos(null, defaults.chaos, req.headers, defaults.logger);
|
|
1129
|
+
if (chaosAction) {
|
|
1130
|
+
require_chaos.applyChaosAction(chaosAction, res, null, journal, {
|
|
1131
|
+
method: req.method ?? "POST",
|
|
1132
|
+
path: pathname,
|
|
1133
|
+
headers: require_helpers.flattenHeaders(req.headers),
|
|
1134
|
+
body: {
|
|
1135
|
+
model: "",
|
|
1136
|
+
messages: []
|
|
1137
|
+
}
|
|
1138
|
+
}, "fixture", defaults.registry);
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
await require_elevenlabs_audio.handleElevenLabsAudio(req, res, raw, fixtures, defaults, journal, "sound-generation");
|
|
1142
|
+
} catch (err) {
|
|
1143
|
+
const msg = err instanceof Error ? err.message : "Internal error";
|
|
1144
|
+
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1145
|
+
message: msg,
|
|
1146
|
+
type: "server_error"
|
|
1147
|
+
} }));
|
|
1148
|
+
else if (!res.writableEnded) res.destroy();
|
|
1149
|
+
}
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
const musicMatch = pathname.match(ELEVENLABS_MUSIC_RE);
|
|
1153
|
+
if (musicMatch && req.method === "POST") {
|
|
1154
|
+
setCorsHeaders(res);
|
|
1155
|
+
const musicSubType = musicMatch[1] ?? "music";
|
|
1156
|
+
try {
|
|
1157
|
+
const raw = await readBody(req);
|
|
1158
|
+
const chaosAction = require_chaos.evaluateChaos(null, defaults.chaos, req.headers, defaults.logger);
|
|
1159
|
+
if (chaosAction) {
|
|
1160
|
+
require_chaos.applyChaosAction(chaosAction, res, null, journal, {
|
|
1161
|
+
method: req.method ?? "POST",
|
|
1162
|
+
path: pathname,
|
|
1163
|
+
headers: require_helpers.flattenHeaders(req.headers),
|
|
1164
|
+
body: {
|
|
1165
|
+
model: "",
|
|
1166
|
+
messages: []
|
|
1167
|
+
}
|
|
1168
|
+
}, "fixture", defaults.registry);
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
await require_elevenlabs_audio.handleElevenLabsAudio(req, res, raw, fixtures, defaults, journal, musicSubType);
|
|
1172
|
+
} catch (err) {
|
|
1173
|
+
const msg = err instanceof Error ? err.message : "Internal error";
|
|
1174
|
+
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1175
|
+
message: msg,
|
|
1176
|
+
type: "server_error"
|
|
1177
|
+
} }));
|
|
1178
|
+
else if (!res.writableEnded) res.destroy();
|
|
1179
|
+
}
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
if (FAL_PREFIX_RE.test(pathname) && req.headers["x-fal-target-host"]) {
|
|
1183
|
+
setCorsHeaders(res);
|
|
1184
|
+
try {
|
|
1185
|
+
const raw = req.method === "POST" || req.method === "PUT" ? await readBody(req) : "";
|
|
1186
|
+
const chaosAction = require_chaos.evaluateChaos(null, defaults.chaos, req.headers, defaults.logger);
|
|
1187
|
+
if (chaosAction) {
|
|
1188
|
+
require_chaos.applyChaosAction(chaosAction, res, null, journal, {
|
|
1189
|
+
method: req.method ?? "GET",
|
|
1190
|
+
path: pathname,
|
|
1191
|
+
headers: require_helpers.flattenHeaders(req.headers),
|
|
1192
|
+
body: {
|
|
1193
|
+
model: "",
|
|
1194
|
+
messages: []
|
|
1195
|
+
}
|
|
1196
|
+
}, "fixture", defaults.registry);
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
if (await require_fal.handleFal(req, res, raw, pathname, fixtures, defaults, journal) === "handled") return;
|
|
1200
|
+
} catch (err) {
|
|
1201
|
+
const msg = err instanceof Error ? err.message : "Internal error";
|
|
1202
|
+
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1203
|
+
message: msg,
|
|
1204
|
+
type: "server_error"
|
|
1205
|
+
} }));
|
|
1206
|
+
else if (!res.writableEnded) res.destroy();
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
if (pathname.match(FAL_QUEUE_SUBMIT_RE) && req.method === "POST") {
|
|
1211
|
+
setCorsHeaders(res);
|
|
1212
|
+
try {
|
|
1213
|
+
const raw = await readBody(req);
|
|
1214
|
+
const chaosAction = require_chaos.evaluateChaos(null, defaults.chaos, req.headers, defaults.logger);
|
|
1215
|
+
if (chaosAction) {
|
|
1216
|
+
require_chaos.applyChaosAction(chaosAction, res, null, journal, {
|
|
1217
|
+
method: req.method ?? "POST",
|
|
1218
|
+
path: pathname,
|
|
1219
|
+
headers: require_helpers.flattenHeaders(req.headers),
|
|
1220
|
+
body: {
|
|
1221
|
+
model: "",
|
|
1222
|
+
messages: []
|
|
1223
|
+
}
|
|
1224
|
+
}, "fixture", defaults.registry);
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
await require_fal_audio.handleFalQueue(req, res, raw, pathname, fixtures, defaults, journal);
|
|
1228
|
+
} catch (err) {
|
|
1229
|
+
const msg = err instanceof Error ? err.message : "Internal error";
|
|
1230
|
+
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1231
|
+
message: msg,
|
|
1232
|
+
type: "server_error"
|
|
1233
|
+
} }));
|
|
1234
|
+
else if (!res.writableEnded) res.destroy();
|
|
1235
|
+
}
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
if (pathname.match(FAL_QUEUE_REQUESTS_RE) && (req.method === "GET" || req.method === "POST" || req.method === "PUT")) {
|
|
1239
|
+
setCorsHeaders(res);
|
|
1240
|
+
try {
|
|
1241
|
+
const raw = req.method === "POST" ? await readBody(req) : "{}";
|
|
1242
|
+
const chaosAction = require_chaos.evaluateChaos(null, defaults.chaos, req.headers, defaults.logger);
|
|
1243
|
+
if (chaosAction) {
|
|
1244
|
+
require_chaos.applyChaosAction(chaosAction, res, null, journal, {
|
|
1245
|
+
method: req.method ?? "GET",
|
|
1246
|
+
path: pathname,
|
|
1247
|
+
headers: require_helpers.flattenHeaders(req.headers),
|
|
1248
|
+
body: {
|
|
1249
|
+
model: "",
|
|
1250
|
+
messages: []
|
|
1251
|
+
}
|
|
1252
|
+
}, "fixture", defaults.registry);
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
await require_fal_audio.handleFalQueue(req, res, raw, pathname, fixtures, defaults, journal);
|
|
1256
|
+
} catch (err) {
|
|
1257
|
+
const msg = err instanceof Error ? err.message : "Internal error";
|
|
1258
|
+
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1259
|
+
message: msg,
|
|
1260
|
+
type: "server_error"
|
|
1261
|
+
} }));
|
|
1262
|
+
else if (!res.writableEnded) res.destroy();
|
|
1263
|
+
}
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
if (pathname.match(FAL_RUN_RE) && req.method === "POST") {
|
|
1267
|
+
setCorsHeaders(res);
|
|
1268
|
+
try {
|
|
1269
|
+
const raw = await readBody(req);
|
|
1270
|
+
const chaosAction = require_chaos.evaluateChaos(null, defaults.chaos, req.headers, defaults.logger);
|
|
1271
|
+
if (chaosAction) {
|
|
1272
|
+
require_chaos.applyChaosAction(chaosAction, res, null, journal, {
|
|
1273
|
+
method: req.method ?? "POST",
|
|
1274
|
+
path: pathname,
|
|
1275
|
+
headers: require_helpers.flattenHeaders(req.headers),
|
|
1276
|
+
body: {
|
|
1277
|
+
model: "",
|
|
1278
|
+
messages: []
|
|
1279
|
+
}
|
|
1280
|
+
}, "fixture", defaults.registry);
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
await require_fal_audio.handleFalQueue(req, res, raw, pathname, fixtures, defaults, journal);
|
|
1284
|
+
} catch (err) {
|
|
1285
|
+
const msg = err instanceof Error ? err.message : "Internal error";
|
|
1286
|
+
if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1287
|
+
message: msg,
|
|
1288
|
+
type: "server_error"
|
|
1289
|
+
} }));
|
|
1290
|
+
else if (!res.writableEnded) res.destroy();
|
|
1291
|
+
}
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1049
1294
|
if (pathname !== COMPLETIONS_PATH) {
|
|
1050
1295
|
handleNotFound(res, "Not found");
|
|
1051
1296
|
return;
|