@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.
Files changed (326) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +35 -0
  4. package/README.md +10 -10
  5. package/dist/a2a-mock.d.cts +2 -2
  6. package/dist/a2a-mock.d.cts.map +1 -1
  7. package/dist/a2a-mock.d.ts +2 -2
  8. package/dist/a2a-mock.d.ts.map +1 -1
  9. package/dist/a2a-mock.js +2 -2
  10. package/dist/a2a-mock.js.map +1 -1
  11. package/dist/agui-handler.cjs +120 -5
  12. package/dist/agui-handler.cjs.map +1 -1
  13. package/dist/agui-handler.d.cts +41 -5
  14. package/dist/agui-handler.d.cts.map +1 -1
  15. package/dist/agui-handler.d.ts +41 -5
  16. package/dist/agui-handler.d.ts.map +1 -1
  17. package/dist/agui-handler.js +114 -6
  18. package/dist/agui-handler.js.map +1 -1
  19. package/dist/agui-mock.cjs +18 -7
  20. package/dist/agui-mock.cjs.map +1 -1
  21. package/dist/agui-mock.d.cts +2 -2
  22. package/dist/agui-mock.d.cts.map +1 -1
  23. package/dist/agui-mock.d.ts +2 -2
  24. package/dist/agui-mock.d.ts.map +1 -1
  25. package/dist/agui-mock.js +20 -9
  26. package/dist/agui-mock.js.map +1 -1
  27. package/dist/agui-recorder.cjs +43 -22
  28. package/dist/agui-recorder.cjs.map +1 -1
  29. package/dist/agui-recorder.d.cts +4 -3
  30. package/dist/agui-recorder.d.cts.map +1 -1
  31. package/dist/agui-recorder.d.ts +4 -3
  32. package/dist/agui-recorder.d.ts.map +1 -1
  33. package/dist/agui-recorder.js +45 -24
  34. package/dist/agui-recorder.js.map +1 -1
  35. package/dist/agui-stub.cjs +28 -0
  36. package/dist/agui-stub.d.cts +5 -0
  37. package/dist/agui-stub.d.ts +5 -0
  38. package/dist/agui-stub.js +5 -0
  39. package/dist/agui-types.d.cts +33 -6
  40. package/dist/agui-types.d.cts.map +1 -1
  41. package/dist/agui-types.d.ts +33 -6
  42. package/dist/agui-types.d.ts.map +1 -1
  43. package/dist/aimock-cli.cjs +1 -1
  44. package/dist/aimock-cli.js +1 -1
  45. package/dist/aws-event-stream.d.cts +2 -2
  46. package/dist/aws-event-stream.d.cts.map +1 -1
  47. package/dist/aws-event-stream.d.ts +2 -2
  48. package/dist/aws-event-stream.d.ts.map +1 -1
  49. package/dist/bedrock-converse.cjs +4 -4
  50. package/dist/bedrock-converse.cjs.map +1 -1
  51. package/dist/bedrock-converse.d.cts +3 -3
  52. package/dist/bedrock-converse.d.cts.map +1 -1
  53. package/dist/bedrock-converse.d.ts +3 -3
  54. package/dist/bedrock-converse.d.ts.map +1 -1
  55. package/dist/bedrock-converse.js +4 -4
  56. package/dist/bedrock-converse.js.map +1 -1
  57. package/dist/bedrock.cjs +4 -4
  58. package/dist/bedrock.cjs.map +1 -1
  59. package/dist/bedrock.d.cts +3 -3
  60. package/dist/bedrock.d.cts.map +1 -1
  61. package/dist/bedrock.d.ts +3 -3
  62. package/dist/bedrock.d.ts.map +1 -1
  63. package/dist/bedrock.js +4 -4
  64. package/dist/bedrock.js.map +1 -1
  65. package/dist/chaos.cjs +35 -9
  66. package/dist/chaos.cjs.map +1 -1
  67. package/dist/chaos.d.cts +19 -4
  68. package/dist/chaos.d.cts.map +1 -1
  69. package/dist/chaos.d.ts +19 -4
  70. package/dist/chaos.d.ts.map +1 -1
  71. package/dist/chaos.js +35 -10
  72. package/dist/chaos.js.map +1 -1
  73. package/dist/cli.cjs +6 -5
  74. package/dist/cli.cjs.map +1 -1
  75. package/dist/cli.js +6 -5
  76. package/dist/cli.js.map +1 -1
  77. package/dist/cohere.cjs +2 -2
  78. package/dist/cohere.cjs.map +1 -1
  79. package/dist/cohere.d.cts +2 -2
  80. package/dist/cohere.d.cts.map +1 -1
  81. package/dist/cohere.d.ts +2 -2
  82. package/dist/cohere.d.ts.map +1 -1
  83. package/dist/cohere.js +2 -2
  84. package/dist/cohere.js.map +1 -1
  85. package/dist/config-loader.cjs +3 -3
  86. package/dist/config-loader.d.cts +1 -1
  87. package/dist/config-loader.d.cts.map +1 -1
  88. package/dist/config-loader.d.ts +1 -1
  89. package/dist/config-loader.d.ts.map +1 -1
  90. package/dist/config-loader.js +2 -2
  91. package/dist/convert-vidaimock.cjs +1 -1
  92. package/dist/convert-vidaimock.js +1 -1
  93. package/dist/convert.cjs +1 -1
  94. package/dist/convert.js +1 -1
  95. package/dist/elevenlabs-audio.cjs +212 -0
  96. package/dist/elevenlabs-audio.cjs.map +1 -0
  97. package/dist/elevenlabs-audio.d.cts +11 -0
  98. package/dist/elevenlabs-audio.d.cts.map +1 -0
  99. package/dist/elevenlabs-audio.d.ts +11 -0
  100. package/dist/elevenlabs-audio.d.ts.map +1 -0
  101. package/dist/elevenlabs-audio.js +212 -0
  102. package/dist/elevenlabs-audio.js.map +1 -0
  103. package/dist/embeddings.cjs +2 -2
  104. package/dist/embeddings.cjs.map +1 -1
  105. package/dist/embeddings.d.cts +2 -2
  106. package/dist/embeddings.d.cts.map +1 -1
  107. package/dist/embeddings.d.ts +2 -2
  108. package/dist/embeddings.d.ts.map +1 -1
  109. package/dist/embeddings.js +2 -2
  110. package/dist/embeddings.js.map +1 -1
  111. package/dist/fal-audio.cjs +484 -0
  112. package/dist/fal-audio.cjs.map +1 -0
  113. package/dist/fal-audio.d.cts +10 -0
  114. package/dist/fal-audio.d.cts.map +1 -0
  115. package/dist/fal-audio.d.ts +10 -0
  116. package/dist/fal-audio.d.ts.map +1 -0
  117. package/dist/fal-audio.js +480 -0
  118. package/dist/fal-audio.js.map +1 -0
  119. package/dist/fal.cjs +424 -0
  120. package/dist/fal.cjs.map +1 -0
  121. package/dist/fal.d.cts +39 -0
  122. package/dist/fal.d.cts.map +1 -0
  123. package/dist/fal.d.ts +39 -0
  124. package/dist/fal.d.ts.map +1 -0
  125. package/dist/fal.js +420 -0
  126. package/dist/fal.js.map +1 -0
  127. package/dist/fixture-loader.cjs +16 -3
  128. package/dist/fixture-loader.cjs.map +1 -1
  129. package/dist/fixture-loader.d.cts.map +1 -1
  130. package/dist/fixture-loader.d.ts.map +1 -1
  131. package/dist/fixture-loader.js +17 -4
  132. package/dist/fixture-loader.js.map +1 -1
  133. package/dist/fixtures-remote.cjs +1 -1
  134. package/dist/fixtures-remote.js +1 -1
  135. package/dist/gemini-interactions.cjs +619 -0
  136. package/dist/gemini-interactions.cjs.map +1 -0
  137. package/dist/gemini-interactions.d.cts +46 -0
  138. package/dist/gemini-interactions.d.cts.map +1 -0
  139. package/dist/gemini-interactions.d.ts +46 -0
  140. package/dist/gemini-interactions.d.ts.map +1 -0
  141. package/dist/gemini-interactions.js +618 -0
  142. package/dist/gemini-interactions.js.map +1 -0
  143. package/dist/gemini.cjs +78 -2
  144. package/dist/gemini.cjs.map +1 -1
  145. package/dist/gemini.d.cts +2 -2
  146. package/dist/gemini.d.cts.map +1 -1
  147. package/dist/gemini.d.ts +2 -2
  148. package/dist/gemini.d.ts.map +1 -1
  149. package/dist/gemini.js +79 -3
  150. package/dist/gemini.js.map +1 -1
  151. package/dist/helpers.cjs +28 -1
  152. package/dist/helpers.cjs.map +1 -1
  153. package/dist/helpers.d.cts +13 -3
  154. package/dist/helpers.d.cts.map +1 -1
  155. package/dist/helpers.d.ts +13 -3
  156. package/dist/helpers.d.ts.map +1 -1
  157. package/dist/helpers.js +26 -2
  158. package/dist/helpers.js.map +1 -1
  159. package/dist/images.cjs +2 -2
  160. package/dist/images.cjs.map +1 -1
  161. package/dist/images.d.cts +2 -2
  162. package/dist/images.d.cts.map +1 -1
  163. package/dist/images.d.ts +2 -2
  164. package/dist/images.d.ts.map +1 -1
  165. package/dist/images.js +2 -2
  166. package/dist/images.js.map +1 -1
  167. package/dist/index.cjs +24 -4
  168. package/dist/index.d.cts +12 -8
  169. package/dist/index.d.ts +12 -8
  170. package/dist/index.js +11 -7
  171. package/dist/jest.cjs +1 -1
  172. package/dist/jest.js +1 -1
  173. package/dist/jsonrpc.d.cts +3 -3
  174. package/dist/jsonrpc.d.cts.map +1 -1
  175. package/dist/jsonrpc.d.ts +3 -3
  176. package/dist/jsonrpc.d.ts.map +1 -1
  177. package/dist/llmock.cjs +53 -2
  178. package/dist/llmock.cjs.map +1 -1
  179. package/dist/llmock.d.cts +6 -0
  180. package/dist/llmock.d.cts.map +1 -1
  181. package/dist/llmock.d.ts +6 -0
  182. package/dist/llmock.d.ts.map +1 -1
  183. package/dist/llmock.js +53 -2
  184. package/dist/llmock.js.map +1 -1
  185. package/dist/logger.cjs +5 -4
  186. package/dist/logger.cjs.map +1 -1
  187. package/dist/logger.d.cts +1 -1
  188. package/dist/logger.d.cts.map +1 -1
  189. package/dist/logger.d.ts +1 -1
  190. package/dist/logger.d.ts.map +1 -1
  191. package/dist/logger.js +5 -4
  192. package/dist/logger.js.map +1 -1
  193. package/dist/mcp-mock.d.cts +2 -2
  194. package/dist/mcp-mock.d.cts.map +1 -1
  195. package/dist/mcp-mock.d.ts +2 -2
  196. package/dist/mcp-mock.d.ts.map +1 -1
  197. package/dist/mcp-mock.js +2 -2
  198. package/dist/mcp-mock.js.map +1 -1
  199. package/dist/messages.cjs +2 -2
  200. package/dist/messages.cjs.map +1 -1
  201. package/dist/messages.d.cts +2 -2
  202. package/dist/messages.d.cts.map +1 -1
  203. package/dist/messages.d.ts +2 -2
  204. package/dist/messages.d.ts.map +1 -1
  205. package/dist/messages.js +2 -2
  206. package/dist/messages.js.map +1 -1
  207. package/dist/moderation.d.cts +3 -3
  208. package/dist/moderation.d.cts.map +1 -1
  209. package/dist/moderation.d.ts +3 -3
  210. package/dist/moderation.d.ts.map +1 -1
  211. package/dist/ndjson-writer.d.cts +2 -2
  212. package/dist/ndjson-writer.d.cts.map +1 -1
  213. package/dist/ndjson-writer.d.ts +2 -2
  214. package/dist/ndjson-writer.d.ts.map +1 -1
  215. package/dist/ollama.cjs +4 -4
  216. package/dist/ollama.cjs.map +1 -1
  217. package/dist/ollama.d.cts +3 -3
  218. package/dist/ollama.d.cts.map +1 -1
  219. package/dist/ollama.d.ts +3 -3
  220. package/dist/ollama.d.ts.map +1 -1
  221. package/dist/ollama.js +4 -4
  222. package/dist/ollama.js.map +1 -1
  223. package/dist/recorder.cjs +106 -38
  224. package/dist/recorder.cjs.map +1 -1
  225. package/dist/recorder.d.cts +52 -7
  226. package/dist/recorder.d.cts.map +1 -1
  227. package/dist/recorder.d.ts +52 -7
  228. package/dist/recorder.d.ts.map +1 -1
  229. package/dist/recorder.js +108 -40
  230. package/dist/recorder.js.map +1 -1
  231. package/dist/rerank.d.cts +3 -3
  232. package/dist/rerank.d.cts.map +1 -1
  233. package/dist/rerank.d.ts +3 -3
  234. package/dist/rerank.d.ts.map +1 -1
  235. package/dist/responses.cjs +2 -2
  236. package/dist/responses.cjs.map +1 -1
  237. package/dist/responses.d.cts +2 -2
  238. package/dist/responses.d.cts.map +1 -1
  239. package/dist/responses.d.ts +2 -2
  240. package/dist/responses.d.ts.map +1 -1
  241. package/dist/responses.js +2 -2
  242. package/dist/responses.js.map +1 -1
  243. package/dist/router.cjs +1 -1
  244. package/dist/router.cjs.map +1 -1
  245. package/dist/router.d.cts.map +1 -1
  246. package/dist/router.d.ts.map +1 -1
  247. package/dist/router.js +2 -2
  248. package/dist/router.js.map +1 -1
  249. package/dist/search.d.cts +3 -3
  250. package/dist/search.d.cts.map +1 -1
  251. package/dist/search.d.ts +3 -3
  252. package/dist/search.d.ts.map +1 -1
  253. package/dist/server.cjs +253 -8
  254. package/dist/server.cjs.map +1 -1
  255. package/dist/server.d.cts +2 -2
  256. package/dist/server.d.cts.map +1 -1
  257. package/dist/server.d.ts +2 -2
  258. package/dist/server.d.ts.map +1 -1
  259. package/dist/server.js +257 -12
  260. package/dist/server.js.map +1 -1
  261. package/dist/speech.cjs +20 -11
  262. package/dist/speech.cjs.map +1 -1
  263. package/dist/speech.d.cts +2 -2
  264. package/dist/speech.d.cts.map +1 -1
  265. package/dist/speech.d.ts +2 -2
  266. package/dist/speech.d.ts.map +1 -1
  267. package/dist/speech.js +20 -11
  268. package/dist/speech.js.map +1 -1
  269. package/dist/sse-writer.d.cts +3 -3
  270. package/dist/sse-writer.d.cts.map +1 -1
  271. package/dist/sse-writer.d.ts +3 -3
  272. package/dist/sse-writer.d.ts.map +1 -1
  273. package/dist/stream-collapse.cjs +80 -9
  274. package/dist/stream-collapse.cjs.map +1 -1
  275. package/dist/stream-collapse.d.cts +11 -1
  276. package/dist/stream-collapse.d.cts.map +1 -1
  277. package/dist/stream-collapse.d.ts +11 -1
  278. package/dist/stream-collapse.d.ts.map +1 -1
  279. package/dist/stream-collapse.js +80 -10
  280. package/dist/stream-collapse.js.map +1 -1
  281. package/dist/suite.cjs +1 -1
  282. package/dist/suite.d.cts +2 -2
  283. package/dist/suite.d.ts +2 -2
  284. package/dist/suite.js +1 -1
  285. package/dist/transcription.cjs +2 -2
  286. package/dist/transcription.cjs.map +1 -1
  287. package/dist/transcription.d.cts +2 -2
  288. package/dist/transcription.d.cts.map +1 -1
  289. package/dist/transcription.d.ts +2 -2
  290. package/dist/transcription.d.ts.map +1 -1
  291. package/dist/transcription.js +2 -2
  292. package/dist/transcription.js.map +1 -1
  293. package/dist/types.d.cts +38 -11
  294. package/dist/types.d.cts.map +1 -1
  295. package/dist/types.d.ts +38 -11
  296. package/dist/types.d.ts.map +1 -1
  297. package/dist/vector-mock.d.cts +2 -2
  298. package/dist/vector-mock.d.cts.map +1 -1
  299. package/dist/vector-mock.d.ts +2 -2
  300. package/dist/vector-mock.d.ts.map +1 -1
  301. package/dist/vector-mock.js +2 -2
  302. package/dist/vector-mock.js.map +1 -1
  303. package/dist/vector-types.d.cts.map +1 -1
  304. package/dist/vector-types.d.ts.map +1 -1
  305. package/dist/video.cjs +9 -3
  306. package/dist/video.cjs.map +1 -1
  307. package/dist/video.d.cts +3 -3
  308. package/dist/video.d.cts.map +1 -1
  309. package/dist/video.d.ts +3 -3
  310. package/dist/video.d.ts.map +1 -1
  311. package/dist/video.js +9 -3
  312. package/dist/video.js.map +1 -1
  313. package/dist/vitest.cjs +1 -1
  314. package/dist/vitest.js +1 -1
  315. package/dist/ws-framing.d.cts +2 -2
  316. package/dist/ws-framing.d.cts.map +1 -1
  317. package/dist/ws-framing.d.ts +2 -2
  318. package/dist/ws-framing.d.ts.map +1 -1
  319. package/dist/ws-gemini-live.cjs +145 -2
  320. package/dist/ws-gemini-live.cjs.map +1 -1
  321. package/dist/ws-gemini-live.d.cts.map +1 -1
  322. package/dist/ws-gemini-live.d.ts.map +1 -1
  323. package/dist/ws-gemini-live.js +146 -3
  324. package/dist/ws-gemini-live.js.map +1 -1
  325. package/package.json +16 -2
  326. package/skills/write-fixtures/SKILL.md +10 -10
@@ -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":"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"}
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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.cts","names":[],"sources":["../src/router.ts"],"sourcesContent":[],"mappings":";;;;;AAoBA;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"}
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"}
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.ts","names":[],"sources":["../src/router.ts"],"sourcesContent":[],"mappings":";;;;;AAoBA;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"}
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");
@@ -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":"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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 * 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 };
@@ -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,EAAK,MAAA;OACL,CAAA,EAAK,MAAA;;AAGD,UAZM,aAAA,CAYN;OACW,EAAA,MAAA,GAZJ,MAYI;SACE,EAZb,YAYkB,EAAA;;AACnB,iBARY,YAAA,CAQZ,GAAA,EAPH,IAAA,CAAK,eAOF,EAAA,GAAA,EANH,IAAA,CAAK,cAMF,EAAA,GAAA,EAAA,MAAA,EAAA,QAAA,EAJE,aAIF,EAAA,EAAA,OAAA,EAHC,OAGD,EAAA,QAAA,EAAA;UAFY;yBACE,IAAA,CAAK,0BAC1B"}
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 };
@@ -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,EAAK,MAAA;OACL,CAAA,EAAK,MAAA;;AAGD,UAZM,aAAA,CAYN;OACW,EAAA,MAAA,GAZJ,MAYI;SACE,EAZb,YAYkB,EAAA;;AACnB,iBARY,YAAA,CAQZ,GAAA,EAPH,IAAA,CAAK,eAOF,EAAA,GAAA,EANH,IAAA,CAAK,cAMF,EAAA,GAAA,EAAA,MAAA,EAAA,QAAA,EAJE,aAIF,EAAA,EAAA,OAAA,EAHC,OAGD,EAAA,QAAA,EAAA;UAFY;yBACE,IAAA,CAAK,0BAC1B"}
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 method = req.method ?? "POST";
334
- const path = req.url ?? COMPLETIONS_PATH;
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
- }, defaults.registry, defaults.logger)) return;
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
- if (await require_recorder.proxyAndRecord(req, res, body, providerKey, req.url ?? COMPLETIONS_PATH, fixtures, defaults, raw)) {
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;