@copilotkit/aimock 1.29.0 → 1.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- 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/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 +8 -4
- 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 +10 -6
- package/dist/fal-audio.js.map +1 -1
- package/dist/fal.cjs +4 -2
- package/dist/fal.cjs.map +1 -1
- package/dist/fal.d.cts.map +1 -1
- package/dist/fal.d.ts.map +1 -1
- package/dist/fal.js +6 -4
- 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 +3 -0
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- 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/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/recorder.cjs +49 -8
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.js +50 -9
- 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 +15 -9
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +17 -11
- 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 +42 -0
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +42 -0
- 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.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.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 +1 -1
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
|
@@ -376,7 +376,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
376
376
|
body._endpointType = "chat";
|
|
377
377
|
body._context = require_helpers.getContext(req);
|
|
378
378
|
const testId = require_helpers.getTestId(req);
|
|
379
|
-
const fixture = require_router.
|
|
379
|
+
const { fixture, skippedBySequenceOrTurn } = require_router.matchFixtureDiagnostic(fixtures, body, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform);
|
|
380
380
|
if (fixture) {
|
|
381
381
|
journal.incrementFixtureMatchCount(fixture, fixtures, testId);
|
|
382
382
|
defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);
|
|
@@ -403,8 +403,8 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
403
403
|
if (!fixture) {
|
|
404
404
|
if (require_helpers.resolveStrictMode(defaults.strict, req.headers)) {
|
|
405
405
|
const strictStatus = 503;
|
|
406
|
-
const strictMessage =
|
|
407
|
-
defaults.logger.error(
|
|
406
|
+
const strictMessage = require_helpers.strictNoMatchMessage(skippedBySequenceOrTurn);
|
|
407
|
+
defaults.logger.error(require_helpers.strictNoMatchLogLine(req.method ?? "POST", req.url ?? COMPLETIONS_PATH, skippedBySequenceOrTurn));
|
|
408
408
|
journal.add({
|
|
409
409
|
method: req.method ?? "POST",
|
|
410
410
|
path: req.url ?? COMPLETIONS_PATH,
|
|
@@ -514,6 +514,8 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
514
514
|
if (require_helpers.isContentWithToolCallsResponse(response)) {
|
|
515
515
|
if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
|
|
516
516
|
const overrides = require_helpers.extractOverrides(response);
|
|
517
|
+
const effectiveStrict = require_helpers.resolveStrictMode(defaults.strict, req.headers);
|
|
518
|
+
const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, body.model, effectiveStrict, defaults.logger);
|
|
517
519
|
const journalEntry = journal.add({
|
|
518
520
|
method: req.method ?? "POST",
|
|
519
521
|
path: req.url ?? COMPLETIONS_PATH,
|
|
@@ -525,11 +527,11 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
525
527
|
}
|
|
526
528
|
});
|
|
527
529
|
if (body.stream !== true) {
|
|
528
|
-
const completion = require_helpers.buildContentWithToolCallsCompletion(response.content, response.toolCalls, body.model,
|
|
530
|
+
const completion = require_helpers.buildContentWithToolCallsCompletion(response.content, response.toolCalls, body.model, effReasoning, overrides, body.messages);
|
|
529
531
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
530
532
|
res.end(JSON.stringify(completion));
|
|
531
533
|
} else {
|
|
532
|
-
const chunks = require_helpers.buildContentWithToolCallsChunks(response.content, response.toolCalls, body.model, chunkSize,
|
|
534
|
+
const chunks = require_helpers.buildContentWithToolCallsChunks(response.content, response.toolCalls, body.model, chunkSize, effReasoning, overrides);
|
|
533
535
|
const completionText = response.content + response.toolCalls.map((tc) => tc.name + tc.arguments).join("");
|
|
534
536
|
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
537
|
prompt_tokens: overrides.usage.prompt_tokens ?? 0,
|
|
@@ -561,6 +563,8 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
561
563
|
if (require_helpers.isTextResponse(response)) {
|
|
562
564
|
if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
|
|
563
565
|
const overrides = require_helpers.extractOverrides(response);
|
|
566
|
+
const effectiveStrict = require_helpers.resolveStrictMode(defaults.strict, req.headers);
|
|
567
|
+
const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, body.model, effectiveStrict, defaults.logger);
|
|
564
568
|
const journalEntry = journal.add({
|
|
565
569
|
method: req.method ?? "POST",
|
|
566
570
|
path: req.url ?? COMPLETIONS_PATH,
|
|
@@ -572,11 +576,11 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
572
576
|
}
|
|
573
577
|
});
|
|
574
578
|
if (body.stream !== true) {
|
|
575
|
-
const completion = require_helpers.buildTextCompletion(response.content, body.model,
|
|
579
|
+
const completion = require_helpers.buildTextCompletion(response.content, body.model, effReasoning, overrides, body.messages);
|
|
576
580
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
577
581
|
res.end(JSON.stringify(completion));
|
|
578
582
|
} else {
|
|
579
|
-
const chunks = require_helpers.buildTextChunks(response.content, body.model, chunkSize,
|
|
583
|
+
const chunks = require_helpers.buildTextChunks(response.content, body.model, chunkSize, effReasoning, overrides);
|
|
580
584
|
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
585
|
prompt_tokens: overrides.usage.prompt_tokens ?? 0,
|
|
582
586
|
completion_tokens: overrides.usage.completion_tokens ?? 0,
|
|
@@ -607,6 +611,8 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
607
611
|
if (require_helpers.isToolCallResponse(response)) {
|
|
608
612
|
if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
|
|
609
613
|
const overrides = require_helpers.extractOverrides(response);
|
|
614
|
+
const effectiveStrict = require_helpers.resolveStrictMode(defaults.strict, req.headers);
|
|
615
|
+
const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, body.model, effectiveStrict, defaults.logger);
|
|
610
616
|
const journalEntry = journal.add({
|
|
611
617
|
method: req.method ?? "POST",
|
|
612
618
|
path: req.url ?? COMPLETIONS_PATH,
|
|
@@ -618,11 +624,11 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
618
624
|
}
|
|
619
625
|
});
|
|
620
626
|
if (body.stream !== true) {
|
|
621
|
-
const completion = require_helpers.buildToolCallCompletion(response.toolCalls, body.model, overrides, body.messages);
|
|
627
|
+
const completion = require_helpers.buildToolCallCompletion(response.toolCalls, body.model, effReasoning, overrides, body.messages);
|
|
622
628
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
623
629
|
res.end(JSON.stringify(completion));
|
|
624
630
|
} else {
|
|
625
|
-
const chunks = require_helpers.buildToolCallChunks(response.toolCalls, body.model, chunkSize, overrides);
|
|
631
|
+
const chunks = require_helpers.buildToolCallChunks(response.toolCalls, body.model, chunkSize, effReasoning, overrides);
|
|
626
632
|
const completionText = response.toolCalls.map((tc) => tc.name + tc.arguments).join("");
|
|
627
633
|
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
634
|
prompt_tokens: overrides.usage.prompt_tokens ?? 0,
|