@copilotkit/aimock 1.29.0 → 1.31.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 +34 -0
- package/README.md +1 -1
- package/dist/agui-types.d.ts.map +1 -1
- package/dist/bedrock-converse.cjs +63 -31
- 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 +65 -33
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/bedrock.cjs +95 -33
- 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 +97 -35
- package/dist/bedrock.js.map +1 -1
- package/dist/cli.cjs +18 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +18 -1
- package/dist/cli.js.map +1 -1
- package/dist/cohere.cjs +49 -15
- 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 +51 -17
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/elevenlabs-audio.cjs +8 -4
- 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 +10 -6
- package/dist/elevenlabs-audio.js.map +1 -1
- package/dist/embeddings.cjs +4 -3
- 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 +6 -5
- package/dist/embeddings.js.map +1 -1
- package/dist/fal-audio.cjs +18 -8
- 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 +22 -12
- package/dist/fal-audio.js.map +1 -1
- package/dist/fal.cjs +55 -46
- package/dist/fal.cjs.map +1 -1
- package/dist/fal.d.cts +8 -0
- package/dist/fal.d.cts.map +1 -1
- package/dist/fal.d.ts +8 -0
- package/dist/fal.d.ts.map +1 -1
- package/dist/fal.js +58 -49
- package/dist/fal.js.map +1 -1
- package/dist/gemini-embeddings.cjs +4 -3
- package/dist/gemini-embeddings.cjs.map +1 -1
- package/dist/gemini-embeddings.js +6 -5
- package/dist/gemini-embeddings.js.map +1 -1
- package/dist/gemini-interactions.cjs +3 -3
- 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 +5 -5
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +55 -24
- 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 +57 -26
- package/dist/gemini.js.map +1 -1
- package/dist/helpers.cjs +120 -2
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +43 -3
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts +43 -3
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +117 -3
- package/dist/helpers.js.map +1 -1
- package/dist/images.cjs +12 -6
- 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 +14 -8
- package/dist/images.js.map +1 -1
- package/dist/index.cjs +13 -2
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +6 -5
- package/dist/llmock.cjs +2 -1
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +2 -1
- package/dist/llmock.js.map +1 -1
- package/dist/messages.cjs +325 -85
- 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 +327 -87
- package/dist/messages.js.map +1 -1
- package/dist/metrics.cjs +9 -0
- package/dist/metrics.cjs.map +1 -1
- package/dist/metrics.d.cts.map +1 -1
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +7 -1
- package/dist/metrics.js.map +1 -1
- package/dist/model-utils.cjs +68 -0
- package/dist/model-utils.cjs.map +1 -1
- package/dist/model-utils.js +68 -1
- package/dist/model-utils.js.map +1 -1
- package/dist/ollama.cjs +58 -21
- 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 +60 -23
- package/dist/ollama.js.map +1 -1
- package/dist/openrouter-video.cjs +1609 -0
- package/dist/openrouter-video.cjs.map +1 -0
- package/dist/openrouter-video.d.cts +154 -0
- package/dist/openrouter-video.d.cts.map +1 -0
- package/dist/openrouter-video.d.ts +154 -0
- package/dist/openrouter-video.d.ts.map +1 -0
- package/dist/openrouter-video.js +1601 -0
- package/dist/openrouter-video.js.map +1 -0
- package/dist/recorder.cjs +97 -15
- 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 +96 -17
- package/dist/recorder.js.map +1 -1
- package/dist/responses.cjs +26 -12
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.d.cts +1 -1
- package/dist/responses.d.cts.map +1 -1
- package/dist/responses.d.ts +1 -1
- package/dist/responses.d.ts.map +1 -1
- package/dist/responses.js +28 -14
- package/dist/responses.js.map +1 -1
- package/dist/router.cjs +37 -8
- package/dist/router.cjs.map +1 -1
- package/dist/router.d.cts +30 -1
- package/dist/router.d.cts.map +1 -1
- package/dist/router.d.ts +30 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +37 -9
- package/dist/router.js.map +1 -1
- package/dist/server.cjs +173 -19
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +2 -0
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +176 -22
- package/dist/server.js.map +1 -1
- package/dist/speech.cjs +4 -2
- 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 +6 -4
- package/dist/speech.js.map +1 -1
- package/dist/stream-collapse.cjs +44 -1
- package/dist/stream-collapse.cjs.map +1 -1
- package/dist/stream-collapse.d.cts +28 -0
- package/dist/stream-collapse.d.cts.map +1 -1
- package/dist/stream-collapse.d.ts +28 -0
- package/dist/stream-collapse.d.ts.map +1 -1
- package/dist/stream-collapse.js +44 -2
- package/dist/stream-collapse.js.map +1 -1
- package/dist/transcription.cjs +4 -2
- 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 +6 -4
- package/dist/transcription.js.map +1 -1
- package/dist/types.d.cts +123 -8
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +123 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/vector-types.d.cts.map +1 -1
- package/dist/vector-types.d.ts.map +1 -1
- package/dist/video.cjs +4 -2
- 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 +6 -4
- package/dist/video.js.map +1 -1
- package/dist/ws-gemini-live.cjs +4 -3
- package/dist/ws-gemini-live.cjs.map +1 -1
- package/dist/ws-gemini-live.d.cts +2 -2
- 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 +6 -5
- package/dist/ws-gemini-live.js.map +1 -1
- package/dist/ws-realtime.cjs +4 -3
- package/dist/ws-realtime.cjs.map +1 -1
- package/dist/ws-realtime.d.cts +2 -2
- package/dist/ws-realtime.d.cts.map +1 -1
- package/dist/ws-realtime.d.ts.map +1 -1
- package/dist/ws-realtime.js +6 -5
- package/dist/ws-realtime.js.map +1 -1
- package/dist/ws-responses.cjs +8 -6
- package/dist/ws-responses.cjs.map +1 -1
- package/dist/ws-responses.d.cts.map +1 -1
- package/dist/ws-responses.d.ts.map +1 -1
- package/dist/ws-responses.js +10 -8
- package/dist/ws-responses.js.map +1 -1
- package/package.json +2 -2
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 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 * Concatenate the text content of every `system` role message in order.\n * Hosts that build a system context from multiple sources (persona, agent\n * context entries, tool guidance) often emit several system messages in one\n * request; this joins them with newlines so a substring matcher sees the\n * whole context as one body.\n */\nexport function getSystemText(messages: ChatMessage[]): string {\n const parts: string[] = [];\n for (const m of messages) {\n if (m.role !== \"system\") continue;\n const text = getTextContent(m.content);\n if (text) parts.push(text);\n }\n return parts.join(\"\\n\");\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 (\n reqEndpoint &&\n reqEndpoint !== \"chat\" &&\n reqEndpoint !== \"embedding\" &&\n !reqEndpoint.startsWith(\"realtime\")\n ) {\n // Fixture has no endpoint restriction but request is multimedia —\n // only match if the response type is compatible.\n // Function responses cannot be checked statically, so treat them as compatible.\n const r = fixture.response;\n if (typeof r !== \"function\") {\n const compatible =\n (reqEndpoint === \"image\" && isImageResponse(r)) ||\n (reqEndpoint === \"speech\" && isAudioResponse(r)) ||\n (reqEndpoint === \"elevenlabs-tts\" && 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 === \"translation\" && isTranscriptionResponse(r)) ||\n (reqEndpoint === \"video\" && isVideoResponse(r));\n if (!compatible) continue;\n }\n }\n\n // context — opt-in exact match against the request's _context field.\n // If fixture specifies a context, only match requests with that exact context.\n // If fixture omits context, match any request regardless of _context.\n if (match.context !== undefined) {\n if (effective._context !== match.context) 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 // systemMessage — case-sensitive substring, regexp, or array-of-substrings\n // match against the joined text of every system message in the request.\n // Use to gate a fixture on host-supplied context (e.g. agent-context\n // entries) so that when the calling app changes that context the fixture\n // stops matching and the request falls through to the next fixture or\n // upstream proxy.\n //\n // Array form (string[]) requires ALL substrings to be present — useful\n // when the gate must combine multiple non-adjacent tokens (e.g. a default\n // name AND a default activity list whose positions in the serialised\n // context JSON aren't stable).\n if (match.systemMessage !== undefined) {\n const text = getSystemText(effective.messages);\n if (!text) continue;\n const sm = match.systemMessage;\n if (Array.isArray(sm)) {\n // Empty array is treated as \"no constraint\" → effectively matches\n // unconditionally. Validation rejects this at load time for JSON\n // fixtures; programmatic callers that pass [] get the same\n // permissive behaviour as not setting systemMessage at all.\n let allPresent = true;\n for (const needle of sm) {\n if (!text.includes(needle)) {\n allPresent = false;\n break;\n }\n }\n if (!allPresent) continue;\n } else if (typeof sm === \"string\") {\n if (useExactMatch) {\n if (text !== sm) continue;\n } else {\n if (!text.includes(sm)) continue;\n }\n } else {\n sm.lastIndex = 0;\n if (!sm.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 match or prefix + dash-digit boundary for strings (so that\n // \"claude-opus-4\" matches \"claude-opus-4-20250514\" but \"gpt-4\" does NOT\n // match \"gpt-4o\" and \"gpt-4o\" does NOT match \"gpt-4o-mini\"), regexp unchanged\n if (match.model !== undefined) {\n if (typeof match.model === \"string\") {\n if (effective.model !== match.model) {\n if (!effective.model?.startsWith(match.model)) continue;\n const rest = effective.model.slice(match.model.length);\n if (!/^-\\d/.test(rest)) continue;\n }\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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"}
|
|
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 * Concatenate the text content of every `system` role message in order.\n * Hosts that build a system context from multiple sources (persona, agent\n * context entries, tool guidance) often emit several system messages in one\n * request; this joins them with newlines so a substring matcher sees the\n * whole context as one body.\n */\nexport function getSystemText(messages: ChatMessage[]): string {\n const parts: string[] = [];\n for (const m of messages) {\n if (m.role !== \"system\") continue;\n const text = getTextContent(m.content);\n if (text) parts.push(text);\n }\n return parts.join(\"\\n\");\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\n/**\n * Result of {@link matchFixtureDiagnostic}: the matched fixture (or `null`) plus\n * the number of fixtures that matched the request SHAPE (every predicate above\n * the sequenceIndex/turnIndex gates) but were rejected ONLY by the\n * sequenceIndex/turnIndex count state.\n *\n * `skippedBySequenceOrTurn > 0` with `fixture === null` distinguishes a\n * \"sequence/turn exhausted\" miss (candidate fixtures existed but their count\n * gate had moved on) from a true \"no fixture had a matching shape\" miss — used\n * to disambiguate the strict-mode 503 message.\n */\nexport interface MatchFixtureDiagnostic {\n fixture: Fixture | null;\n skippedBySequenceOrTurn: number;\n}\n\n/**\n * Match a fixture against a request, additionally reporting WHY a `null` result\n * occurred. Shares the exact predicate loop with {@link matchFixture}; a fixture\n * that passes every shape predicate but fails ONLY the sequenceIndex or\n * turnIndex gate increments `skippedBySequenceOrTurn`. {@link matchFixture} is a\n * thin wrapper that returns the `.fixture` field, so existing callers are\n * unaffected.\n */\nexport function matchFixtureDiagnostic(\n fixtures: Fixture[],\n req: ChatCompletionRequest,\n matchCounts?: Map<Fixture, number>,\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest,\n): MatchFixtureDiagnostic {\n // Apply transform once before matching — used for stripping dynamic data\n const effective = requestTransform ? requestTransform(req) : req;\n const useExactMatch = !!requestTransform;\n\n let skippedBySequenceOrTurn = 0;\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 (\n reqEndpoint &&\n reqEndpoint !== \"chat\" &&\n reqEndpoint !== \"embedding\" &&\n !reqEndpoint.startsWith(\"realtime\")\n ) {\n // Fixture has no endpoint restriction but request is multimedia —\n // only match if the response type is compatible.\n // Function responses cannot be checked statically, so treat them as compatible.\n const r = fixture.response;\n if (typeof r !== \"function\") {\n const compatible =\n (reqEndpoint === \"image\" && isImageResponse(r)) ||\n (reqEndpoint === \"speech\" && isAudioResponse(r)) ||\n (reqEndpoint === \"elevenlabs-tts\" && 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 === \"translation\" && isTranscriptionResponse(r)) ||\n (reqEndpoint === \"video\" && isVideoResponse(r));\n if (!compatible) continue;\n }\n }\n\n // context — opt-in exact match against the request's _context field.\n // If fixture specifies a context, only match requests with that exact context.\n // If fixture omits context, match any request regardless of _context.\n if (match.context !== undefined) {\n if (effective._context !== match.context) 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 // systemMessage — case-sensitive substring, regexp, or array-of-substrings\n // match against the joined text of every system message in the request.\n // Use to gate a fixture on host-supplied context (e.g. agent-context\n // entries) so that when the calling app changes that context the fixture\n // stops matching and the request falls through to the next fixture or\n // upstream proxy.\n //\n // Array form (string[]) requires ALL substrings to be present — useful\n // when the gate must combine multiple non-adjacent tokens (e.g. a default\n // name AND a default activity list whose positions in the serialised\n // context JSON aren't stable).\n if (match.systemMessage !== undefined) {\n const text = getSystemText(effective.messages);\n if (!text) continue;\n const sm = match.systemMessage;\n if (Array.isArray(sm)) {\n // Empty array is treated as \"no constraint\" → effectively matches\n // unconditionally. Validation rejects this at load time for JSON\n // fixtures; programmatic callers that pass [] get the same\n // permissive behaviour as not setting systemMessage at all.\n let allPresent = true;\n for (const needle of sm) {\n if (!text.includes(needle)) {\n allPresent = false;\n break;\n }\n }\n if (!allPresent) continue;\n } else if (typeof sm === \"string\") {\n if (useExactMatch) {\n if (text !== sm) continue;\n } else {\n if (!text.includes(sm)) continue;\n }\n } else {\n sm.lastIndex = 0;\n if (!sm.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 match or prefix + dash-digit boundary for strings (so that\n // \"claude-opus-4\" matches \"claude-opus-4-20250514\" but \"gpt-4\" does NOT\n // match \"gpt-4o\" and \"gpt-4o\" does NOT match \"gpt-4o-mini\"), regexp unchanged\n if (match.model !== undefined) {\n if (typeof match.model === \"string\") {\n if (effective.model !== match.model) {\n if (!effective.model?.startsWith(match.model)) continue;\n const rest = effective.model.slice(match.model.length);\n if (!/^-\\d/.test(rest)) continue;\n }\n } else {\n match.model.lastIndex = 0;\n if (!match.model.test(effective.model ?? \"\")) continue;\n }\n }\n\n // hasToolResult — request-SHAPE predicate: does the conversation contain a\n // tool result message? Must be evaluated with the other shape predicates\n // ABOVE the sequence/turn state gates so that a fixture whose shape never\n // matched is not miscounted as \"skipped by sequence/turn state\".\n if (match.hasToolResult !== undefined) {\n const hasTool = effective.messages.some((m) => m.role === \"tool\");\n if (hasTool !== match.hasToolResult) continue;\n }\n\n // At this point every SHAPE predicate above has passed. The sequenceIndex\n // and turnIndex gates below reject based on per-test count / turn STATE,\n // not request shape — a fixture that fails only these is a \"candidate that\n // was skipped by sequence/turn state\", which we count separately so callers\n // can disambiguate the strict-mode 503 message.\n let skippedByState = false;\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) skippedByState = true;\n }\n\n if (!skippedByState && match.turnIndex !== undefined) {\n const assistantCount = effective.messages.filter((m) => m.role === \"assistant\").length;\n if (assistantCount !== match.turnIndex) skippedByState = true;\n }\n\n if (skippedByState) {\n skippedBySequenceOrTurn++;\n continue;\n }\n\n return { fixture, skippedBySequenceOrTurn };\n }\n\n return { fixture: null, skippedBySequenceOrTurn };\n}\n\n/**\n * Match a fixture against a request, returning the fixture or `null`. Thin\n * wrapper over {@link matchFixtureDiagnostic} that discards the skip diagnostic\n * — preserves the historical signature for all existing callers.\n */\nexport function matchFixture(\n fixtures: Fixture[],\n req: ChatCompletionRequest,\n matchCounts?: Map<Fixture, number>,\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest,\n): Fixture | null {\n return matchFixtureDiagnostic(fixtures, req, matchCounts, requestTransform).fixture;\n}\n"],"mappings":"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"}
|
package/dist/server.cjs
CHANGED
|
@@ -20,9 +20,10 @@ const require_transcription = require('./transcription.cjs');
|
|
|
20
20
|
const require_images = require('./images.cjs');
|
|
21
21
|
const require_speech = require('./speech.cjs');
|
|
22
22
|
const require_video = require('./video.cjs');
|
|
23
|
-
const require_elevenlabs_audio = require('./elevenlabs-audio.cjs');
|
|
24
|
-
const require_fal = require('./fal.cjs');
|
|
25
23
|
const require_fal_audio = require('./fal-audio.cjs');
|
|
24
|
+
const require_fal = require('./fal.cjs');
|
|
25
|
+
const require_openrouter_video = require('./openrouter-video.cjs');
|
|
26
|
+
const require_elevenlabs_audio = require('./elevenlabs-audio.cjs');
|
|
26
27
|
const require_ollama = require('./ollama.cjs');
|
|
27
28
|
const require_cohere = require('./cohere.cjs');
|
|
28
29
|
const require_search = require('./search.cjs');
|
|
@@ -55,7 +56,6 @@ const SPEECH_PATH = "/v1/audio/speech";
|
|
|
55
56
|
const TRANSCRIPTIONS_PATH = "/v1/audio/transcriptions";
|
|
56
57
|
const TRANSLATIONS_PATH = "/v1/audio/translations";
|
|
57
58
|
const VIDEOS_PATH = "/v1/videos";
|
|
58
|
-
const VIDEOS_STATUS_RE = /^\/v1\/videos\/([^/]+)$/;
|
|
59
59
|
const GEMINI_PREDICT_RE = /^\/v1beta\/models\/([^:]+):predict$/;
|
|
60
60
|
const ELEVENLABS_SOUND_GENERATION_PATH = "/v1/sound-generation";
|
|
61
61
|
const ELEVENLABS_TTS_RE = /^\/v1\/text-to-speech\/([^/]+)$/;
|
|
@@ -106,6 +106,8 @@ const OLLAMA_GENERATE_PATH = "/api/generate";
|
|
|
106
106
|
const OLLAMA_EMBEDDINGS_PATH = "/api/embeddings";
|
|
107
107
|
const OLLAMA_EMBED_PATH = "/api/embed";
|
|
108
108
|
const OLLAMA_TAGS_PATH = "/api/tags";
|
|
109
|
+
const OPENROUTER_VIDEOS_PATH = "/api/v1/videos";
|
|
110
|
+
const OPENROUTER_VIDEO_MODELS_PATH = "/api/v1/videos/models";
|
|
109
111
|
const HEALTH_PATH = "/health";
|
|
110
112
|
const READY_PATH = "/ready";
|
|
111
113
|
const MODELS_PATH = "/v1/models";
|
|
@@ -144,10 +146,11 @@ const CONTROL_PREFIX = "/__aimock";
|
|
|
144
146
|
* `aimock_fixtures_loaded` gauge. Shared by `/reset/fixtures` and the
|
|
145
147
|
* deprecated `/reset` alias.
|
|
146
148
|
*/
|
|
147
|
-
function performFixturesReset(fixtures, journal, videoStates, defaults) {
|
|
149
|
+
function performFixturesReset(fixtures, journal, videoStates, openRouterVideoJobs, defaults) {
|
|
148
150
|
fixtures.length = 0;
|
|
149
151
|
journal.clear();
|
|
150
152
|
videoStates.clear();
|
|
153
|
+
openRouterVideoJobs.clear();
|
|
151
154
|
require_fal_audio.falJobs.clear();
|
|
152
155
|
require_fal.falQueueStates.clear();
|
|
153
156
|
require_gemini_interactions.resetInteractionCounter();
|
|
@@ -158,7 +161,7 @@ function performFixturesReset(fixtures, journal, videoStates, defaults) {
|
|
|
158
161
|
* Handle requests under `/__aimock/`. Returns `true` if the request was
|
|
159
162
|
* handled, `false` if the path doesn't match the control prefix.
|
|
160
163
|
*/
|
|
161
|
-
async function handleControlAPI(req, res, pathname, fixtures, journal, videoStates, defaults) {
|
|
164
|
+
async function handleControlAPI(req, res, pathname, fixtures, journal, videoStates, openRouterVideoJobs, defaults) {
|
|
162
165
|
if (!pathname.startsWith(CONTROL_PREFIX)) return false;
|
|
163
166
|
const subPath = pathname.slice(9);
|
|
164
167
|
setCorsHeaders(res);
|
|
@@ -222,7 +225,7 @@ async function handleControlAPI(req, res, pathname, fixtures, journal, videoStat
|
|
|
222
225
|
return true;
|
|
223
226
|
}
|
|
224
227
|
if (subPath === "/reset/fixtures" && req.method === "POST") {
|
|
225
|
-
performFixturesReset(fixtures, journal, videoStates, defaults);
|
|
228
|
+
performFixturesReset(fixtures, journal, videoStates, openRouterVideoJobs, defaults);
|
|
226
229
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
227
230
|
res.end(JSON.stringify({ reset: true }));
|
|
228
231
|
return true;
|
|
@@ -234,7 +237,7 @@ async function handleControlAPI(req, res, pathname, fixtures, journal, videoStat
|
|
|
234
237
|
return true;
|
|
235
238
|
}
|
|
236
239
|
if (subPath === "/reset" && req.method === "POST") {
|
|
237
|
-
performFixturesReset(fixtures, journal, videoStates, defaults);
|
|
240
|
+
performFixturesReset(fixtures, journal, videoStates, openRouterVideoJobs, defaults);
|
|
238
241
|
const deprecation = "POST /__aimock/reset is deprecated; use POST /__aimock/reset/fixtures (full reset) or POST /__aimock/reset/journal (journal only)";
|
|
239
242
|
defaults.logger.warn("POST /__aimock/reset is deprecated; use /__aimock/reset/fixtures or /__aimock/reset/journal");
|
|
240
243
|
res.writeHead(200, {
|
|
@@ -376,7 +379,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
376
379
|
body._endpointType = "chat";
|
|
377
380
|
body._context = require_helpers.getContext(req);
|
|
378
381
|
const testId = require_helpers.getTestId(req);
|
|
379
|
-
const fixture = require_router.
|
|
382
|
+
const { fixture, skippedBySequenceOrTurn } = require_router.matchFixtureDiagnostic(fixtures, body, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform);
|
|
380
383
|
if (fixture) {
|
|
381
384
|
journal.incrementFixtureMatchCount(fixture, fixtures, testId);
|
|
382
385
|
defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);
|
|
@@ -403,8 +406,8 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
403
406
|
if (!fixture) {
|
|
404
407
|
if (require_helpers.resolveStrictMode(defaults.strict, req.headers)) {
|
|
405
408
|
const strictStatus = 503;
|
|
406
|
-
const strictMessage =
|
|
407
|
-
defaults.logger.error(
|
|
409
|
+
const strictMessage = require_helpers.strictNoMatchMessage(skippedBySequenceOrTurn);
|
|
410
|
+
defaults.logger.error(require_helpers.strictNoMatchLogLine(req.method ?? "POST", req.url ?? COMPLETIONS_PATH, skippedBySequenceOrTurn));
|
|
408
411
|
journal.add({
|
|
409
412
|
method: req.method ?? "POST",
|
|
410
413
|
path: req.url ?? COMPLETIONS_PATH,
|
|
@@ -514,6 +517,8 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
514
517
|
if (require_helpers.isContentWithToolCallsResponse(response)) {
|
|
515
518
|
if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
|
|
516
519
|
const overrides = require_helpers.extractOverrides(response);
|
|
520
|
+
const effectiveStrict = require_helpers.resolveStrictMode(defaults.strict, req.headers);
|
|
521
|
+
const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, body.model, effectiveStrict, defaults.logger);
|
|
517
522
|
const journalEntry = journal.add({
|
|
518
523
|
method: req.method ?? "POST",
|
|
519
524
|
path: req.url ?? COMPLETIONS_PATH,
|
|
@@ -525,11 +530,11 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
525
530
|
}
|
|
526
531
|
});
|
|
527
532
|
if (body.stream !== true) {
|
|
528
|
-
const completion = require_helpers.buildContentWithToolCallsCompletion(response.content, response.toolCalls, body.model,
|
|
533
|
+
const completion = require_helpers.buildContentWithToolCallsCompletion(response.content, response.toolCalls, body.model, effReasoning, overrides, body.messages);
|
|
529
534
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
530
535
|
res.end(JSON.stringify(completion));
|
|
531
536
|
} else {
|
|
532
|
-
const chunks = require_helpers.buildContentWithToolCallsChunks(response.content, response.toolCalls, body.model, chunkSize,
|
|
537
|
+
const chunks = require_helpers.buildContentWithToolCallsChunks(response.content, response.toolCalls, body.model, chunkSize, effReasoning, overrides);
|
|
533
538
|
const completionText = response.content + response.toolCalls.map((tc) => tc.name + tc.arguments).join("");
|
|
534
539
|
const usageChunk = includeUsage ? require_helpers.buildUsageChunk(chunks[0]?.id ?? "chatcmpl-unknown", overrides?.model ?? body.model, chunks[0]?.created ?? Math.floor(Date.now() / 1e3), overrides?.usage ? {
|
|
535
540
|
prompt_tokens: overrides.usage.prompt_tokens ?? 0,
|
|
@@ -561,6 +566,8 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
561
566
|
if (require_helpers.isTextResponse(response)) {
|
|
562
567
|
if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
|
|
563
568
|
const overrides = require_helpers.extractOverrides(response);
|
|
569
|
+
const effectiveStrict = require_helpers.resolveStrictMode(defaults.strict, req.headers);
|
|
570
|
+
const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, body.model, effectiveStrict, defaults.logger);
|
|
564
571
|
const journalEntry = journal.add({
|
|
565
572
|
method: req.method ?? "POST",
|
|
566
573
|
path: req.url ?? COMPLETIONS_PATH,
|
|
@@ -572,11 +579,11 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
572
579
|
}
|
|
573
580
|
});
|
|
574
581
|
if (body.stream !== true) {
|
|
575
|
-
const completion = require_helpers.buildTextCompletion(response.content, body.model,
|
|
582
|
+
const completion = require_helpers.buildTextCompletion(response.content, body.model, effReasoning, overrides, body.messages);
|
|
576
583
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
577
584
|
res.end(JSON.stringify(completion));
|
|
578
585
|
} else {
|
|
579
|
-
const chunks = require_helpers.buildTextChunks(response.content, body.model, chunkSize,
|
|
586
|
+
const chunks = require_helpers.buildTextChunks(response.content, body.model, chunkSize, effReasoning, overrides);
|
|
580
587
|
const usageChunk = includeUsage ? require_helpers.buildUsageChunk(chunks[0]?.id ?? "chatcmpl-unknown", overrides?.model ?? body.model, chunks[0]?.created ?? Math.floor(Date.now() / 1e3), overrides?.usage ? {
|
|
581
588
|
prompt_tokens: overrides.usage.prompt_tokens ?? 0,
|
|
582
589
|
completion_tokens: overrides.usage.completion_tokens ?? 0,
|
|
@@ -607,6 +614,8 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
607
614
|
if (require_helpers.isToolCallResponse(response)) {
|
|
608
615
|
if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
|
|
609
616
|
const overrides = require_helpers.extractOverrides(response);
|
|
617
|
+
const effectiveStrict = require_helpers.resolveStrictMode(defaults.strict, req.headers);
|
|
618
|
+
const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, body.model, effectiveStrict, defaults.logger);
|
|
610
619
|
const journalEntry = journal.add({
|
|
611
620
|
method: req.method ?? "POST",
|
|
612
621
|
path: req.url ?? COMPLETIONS_PATH,
|
|
@@ -618,11 +627,11 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
618
627
|
}
|
|
619
628
|
});
|
|
620
629
|
if (body.stream !== true) {
|
|
621
|
-
const completion = require_helpers.buildToolCallCompletion(response.toolCalls, body.model, overrides, body.messages);
|
|
630
|
+
const completion = require_helpers.buildToolCallCompletion(response.toolCalls, body.model, effReasoning, overrides, body.messages);
|
|
622
631
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
623
632
|
res.end(JSON.stringify(completion));
|
|
624
633
|
} else {
|
|
625
|
-
const chunks = require_helpers.buildToolCallChunks(response.toolCalls, body.model, chunkSize, overrides);
|
|
634
|
+
const chunks = require_helpers.buildToolCallChunks(response.toolCalls, body.model, chunkSize, effReasoning, overrides);
|
|
626
635
|
const completionText = response.toolCalls.map((tc) => tc.name + tc.arguments).join("");
|
|
627
636
|
const usageChunk = includeUsage ? require_helpers.buildUsageChunk(chunks[0]?.id ?? "chatcmpl-unknown", overrides?.model ?? body.model, chunks[0]?.created ?? Math.floor(Date.now() / 1e3), overrides?.usage ? {
|
|
628
637
|
prompt_tokens: overrides.usage.prompt_tokens ?? 0,
|
|
@@ -692,6 +701,9 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
|
|
|
692
701
|
},
|
|
693
702
|
get falQueue() {
|
|
694
703
|
return serverOptions.falQueue;
|
|
704
|
+
},
|
|
705
|
+
get openRouterVideo() {
|
|
706
|
+
return serverOptions.openRouterVideo;
|
|
695
707
|
}
|
|
696
708
|
};
|
|
697
709
|
if (options?.chaos) {
|
|
@@ -711,11 +723,31 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
|
|
|
711
723
|
];
|
|
712
724
|
for (const { name, value } of chaosRates) if (value !== void 0 && (value < 0 || value > 1)) logger.warn(`Chaos ${name} (${value}) is outside 0-1 range — will be clamped at runtime`);
|
|
713
725
|
}
|
|
726
|
+
for (const { name, config } of [{
|
|
727
|
+
name: "falQueue",
|
|
728
|
+
config: options?.falQueue
|
|
729
|
+
}, {
|
|
730
|
+
name: "openRouterVideo",
|
|
731
|
+
config: options?.openRouterVideo
|
|
732
|
+
}]) {
|
|
733
|
+
if (!config) continue;
|
|
734
|
+
for (const field of ["pollsBeforeInProgress", "pollsBeforeCompleted"]) {
|
|
735
|
+
const value = config[field];
|
|
736
|
+
if (value === void 0) continue;
|
|
737
|
+
if (!Number.isFinite(value)) logger.warn(`${name}.${field} (${value}) is not a finite number — treating as unset`);
|
|
738
|
+
else if (!Number.isInteger(value) || value < 0) logger.warn(`${name}.${field} (${value}) is not a non-negative integer — flooring/clamping to a non-negative integer`);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
{
|
|
742
|
+
const cap = options?.record?.openRouterVideo?.maxContentBytes;
|
|
743
|
+
if (cap !== void 0 && (!Number.isInteger(cap) || cap < 0)) logger.warn(`record.openRouterVideo.maxContentBytes (${cap}) is not a non-negative integer — using the default cap (${require_openrouter_video.OPENROUTER_VIDEO_DEFAULT_MAX_CONTENT_BYTES})`);
|
|
744
|
+
}
|
|
714
745
|
const journal = new require_journal.Journal({
|
|
715
746
|
maxEntries: options?.journalMaxEntries ?? 1e3,
|
|
716
747
|
fixtureCountsMaxTestIds: options?.fixtureCountsMaxTestIds ?? 500
|
|
717
748
|
});
|
|
718
749
|
const videoStates = new require_video.VideoStateMap();
|
|
750
|
+
const openRouterVideoJobs = new require_openrouter_video.OpenRouterVideoJobMap();
|
|
719
751
|
if (mounts) for (const { handler } of mounts) {
|
|
720
752
|
if (handler.setJournal) handler.setJournal(journal);
|
|
721
753
|
if (registry && handler.setRegistry) handler.setRegistry(registry);
|
|
@@ -762,7 +794,7 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
|
|
|
762
794
|
}
|
|
763
795
|
});
|
|
764
796
|
if (pathname.startsWith(CONTROL_PREFIX)) {
|
|
765
|
-
await handleControlAPI(req, res, pathname, fixtures, journal, videoStates, defaults);
|
|
797
|
+
await handleControlAPI(req, res, pathname, fixtures, journal, videoStates, openRouterVideoJobs, defaults);
|
|
766
798
|
return;
|
|
767
799
|
}
|
|
768
800
|
if (mounts) {
|
|
@@ -826,6 +858,125 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
|
|
|
826
858
|
res.end(JSON.stringify({ models }));
|
|
827
859
|
return;
|
|
828
860
|
}
|
|
861
|
+
const openRouterVideoContentMatch = pathname.match(require_metrics.OPENROUTER_VIDEO_CONTENT_RE);
|
|
862
|
+
if (openRouterVideoContentMatch && req.method === "GET") {
|
|
863
|
+
try {
|
|
864
|
+
await require_openrouter_video.handleOpenRouterVideoContent(req, res, openRouterVideoContentMatch[1], journal, defaults, setCorsHeaders, openRouterVideoJobs);
|
|
865
|
+
} catch (err) {
|
|
866
|
+
const msg = err instanceof Error ? err.message : "Internal error";
|
|
867
|
+
defaults.logger.error(`openrouter-video content: ${msg}`);
|
|
868
|
+
if (!res.headersSent) {
|
|
869
|
+
try {
|
|
870
|
+
journal.add({
|
|
871
|
+
method: req.method ?? "GET",
|
|
872
|
+
path: req.url ?? pathname,
|
|
873
|
+
headers: require_helpers.flattenHeaders(req.headers),
|
|
874
|
+
body: null,
|
|
875
|
+
response: {
|
|
876
|
+
status: 500,
|
|
877
|
+
fixture: null
|
|
878
|
+
}
|
|
879
|
+
});
|
|
880
|
+
} catch (jErr) {
|
|
881
|
+
defaults.logger.warn(`openrouter-video content: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
882
|
+
}
|
|
883
|
+
require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
884
|
+
message: msg,
|
|
885
|
+
type: "server_error"
|
|
886
|
+
} }));
|
|
887
|
+
} else if (!res.writableEnded) res.destroy();
|
|
888
|
+
}
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
if (pathname === OPENROUTER_VIDEO_MODELS_PATH && req.method === "GET") {
|
|
892
|
+
try {
|
|
893
|
+
await require_openrouter_video.handleOpenRouterVideoModels(req, res, fixtures, journal, defaults, setCorsHeaders);
|
|
894
|
+
} catch (err) {
|
|
895
|
+
const msg = err instanceof Error ? err.message : "Internal error";
|
|
896
|
+
defaults.logger.error(`openrouter-video models: ${msg}`);
|
|
897
|
+
if (!res.headersSent) {
|
|
898
|
+
try {
|
|
899
|
+
journal.add({
|
|
900
|
+
method: req.method ?? "GET",
|
|
901
|
+
path: req.url ?? pathname,
|
|
902
|
+
headers: require_helpers.flattenHeaders(req.headers),
|
|
903
|
+
body: null,
|
|
904
|
+
response: {
|
|
905
|
+
status: 500,
|
|
906
|
+
fixture: null
|
|
907
|
+
}
|
|
908
|
+
});
|
|
909
|
+
} catch (jErr) {
|
|
910
|
+
defaults.logger.warn(`openrouter-video models: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
911
|
+
}
|
|
912
|
+
require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
913
|
+
message: msg,
|
|
914
|
+
type: "server_error"
|
|
915
|
+
} }));
|
|
916
|
+
} else if (!res.writableEnded) res.destroy();
|
|
917
|
+
}
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
const openRouterVideoStatusMatch = pathname.match(require_metrics.OPENROUTER_VIDEO_STATUS_RE);
|
|
921
|
+
if (openRouterVideoStatusMatch && req.method === "GET") {
|
|
922
|
+
try {
|
|
923
|
+
await require_openrouter_video.handleOpenRouterVideoStatus(req, res, openRouterVideoStatusMatch[1], fixtures, journal, defaults, setCorsHeaders, openRouterVideoJobs);
|
|
924
|
+
} catch (err) {
|
|
925
|
+
const msg = err instanceof Error ? err.message : "Internal error";
|
|
926
|
+
defaults.logger.error(`openrouter-video status: ${msg}`);
|
|
927
|
+
if (!res.headersSent) {
|
|
928
|
+
try {
|
|
929
|
+
journal.add({
|
|
930
|
+
method: req.method ?? "GET",
|
|
931
|
+
path: req.url ?? pathname,
|
|
932
|
+
headers: require_helpers.flattenHeaders(req.headers),
|
|
933
|
+
body: null,
|
|
934
|
+
response: {
|
|
935
|
+
status: 500,
|
|
936
|
+
fixture: null
|
|
937
|
+
}
|
|
938
|
+
});
|
|
939
|
+
} catch (jErr) {
|
|
940
|
+
defaults.logger.warn(`openrouter-video status: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
941
|
+
}
|
|
942
|
+
require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
943
|
+
message: msg,
|
|
944
|
+
type: "server_error"
|
|
945
|
+
} }));
|
|
946
|
+
} else if (!res.writableEnded) res.destroy();
|
|
947
|
+
}
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
if (pathname === OPENROUTER_VIDEOS_PATH && req.method === "POST") {
|
|
951
|
+
setCorsHeaders(res);
|
|
952
|
+
try {
|
|
953
|
+
await require_openrouter_video.handleOpenRouterVideoCreate(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders, openRouterVideoJobs);
|
|
954
|
+
} catch (err) {
|
|
955
|
+
const msg = err instanceof Error ? err.message : "Internal error";
|
|
956
|
+
defaults.logger.error(`openrouter-video submit: ${msg}`);
|
|
957
|
+
if (!res.headersSent) {
|
|
958
|
+
try {
|
|
959
|
+
journal.add({
|
|
960
|
+
method: req.method ?? "POST",
|
|
961
|
+
path: req.url ?? pathname,
|
|
962
|
+
headers: require_helpers.flattenHeaders(req.headers),
|
|
963
|
+
body: null,
|
|
964
|
+
response: {
|
|
965
|
+
status: 500,
|
|
966
|
+
fixture: null
|
|
967
|
+
}
|
|
968
|
+
});
|
|
969
|
+
} catch (jErr) {
|
|
970
|
+
defaults.logger.warn(`openrouter-video submit: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
|
|
971
|
+
}
|
|
972
|
+
require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
973
|
+
message: msg,
|
|
974
|
+
type: "server_error"
|
|
975
|
+
} }));
|
|
976
|
+
} else if (!res.writableEnded) res.destroy();
|
|
977
|
+
}
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
829
980
|
let azureDeploymentId;
|
|
830
981
|
const azureMatch = pathname.match(AZURE_DEPLOYMENT_RE);
|
|
831
982
|
if (azureMatch && req.method === "POST") {
|
|
@@ -1101,7 +1252,7 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
|
|
|
1101
1252
|
}
|
|
1102
1253
|
return;
|
|
1103
1254
|
}
|
|
1104
|
-
const videoStatusMatch = pathname.match(
|
|
1255
|
+
const videoStatusMatch = pathname.match(require_metrics.OPENAI_VIDEO_STATUS_RE);
|
|
1105
1256
|
if (videoStatusMatch && req.method === "GET") {
|
|
1106
1257
|
const videoId = videoStatusMatch[1];
|
|
1107
1258
|
require_video.handleVideoStatus(req, res, videoId, journal, defaults, setCorsHeaders, videoStates);
|
|
@@ -1523,6 +1674,8 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
|
|
|
1523
1674
|
server.close = function(callback) {
|
|
1524
1675
|
for (const ws of activeConnections) ws.close(1001, "Server shutting down");
|
|
1525
1676
|
activeConnections.clear();
|
|
1677
|
+
videoStates.clear();
|
|
1678
|
+
openRouterVideoJobs.clear();
|
|
1526
1679
|
originalClose(callback);
|
|
1527
1680
|
return this;
|
|
1528
1681
|
};
|
|
@@ -1543,7 +1696,8 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
|
|
|
1543
1696
|
journal,
|
|
1544
1697
|
url,
|
|
1545
1698
|
defaults,
|
|
1546
|
-
videoStates
|
|
1699
|
+
videoStates,
|
|
1700
|
+
openRouterVideoJobs
|
|
1547
1701
|
});
|
|
1548
1702
|
});
|
|
1549
1703
|
});
|