@copilotkit/aimock 1.43.0 → 1.44.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 (290) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +15 -0
  4. package/README.md +8 -2
  5. package/dist/agui-handler.d.ts.map +1 -1
  6. package/dist/agui-types.d.ts.map +1 -1
  7. package/dist/bedrock-converse.cjs +32 -4
  8. package/dist/bedrock-converse.cjs.map +1 -1
  9. package/dist/bedrock-converse.d.cts.map +1 -1
  10. package/dist/bedrock-converse.d.ts.map +1 -1
  11. package/dist/bedrock-converse.js +32 -4
  12. package/dist/bedrock-converse.js.map +1 -1
  13. package/dist/chaos.cjs +8 -5
  14. package/dist/chaos.cjs.map +1 -1
  15. package/dist/chaos.d.cts.map +1 -1
  16. package/dist/chaos.d.ts.map +1 -1
  17. package/dist/chaos.js +9 -6
  18. package/dist/chaos.js.map +1 -1
  19. package/dist/cli.cjs +89 -7
  20. package/dist/cli.cjs.map +1 -1
  21. package/dist/cli.js +91 -9
  22. package/dist/cli.js.map +1 -1
  23. package/dist/cohere.cjs +27 -1
  24. package/dist/cohere.cjs.map +1 -1
  25. package/dist/cohere.js +27 -1
  26. package/dist/cohere.js.map +1 -1
  27. package/dist/config-loader.cjs +1 -1
  28. package/dist/config-loader.d.ts.map +1 -1
  29. package/dist/config-loader.js +1 -1
  30. package/dist/constants.cjs +8 -0
  31. package/dist/constants.cjs.map +1 -1
  32. package/dist/constants.d.cts +6 -2
  33. package/dist/constants.d.ts +6 -2
  34. package/dist/constants.js +8 -1
  35. package/dist/constants.js.map +1 -1
  36. package/dist/echo-text.cjs +65 -0
  37. package/dist/echo-text.cjs.map +1 -0
  38. package/dist/echo-text.d.cts +22 -0
  39. package/dist/echo-text.d.cts.map +1 -0
  40. package/dist/echo-text.d.ts +22 -0
  41. package/dist/echo-text.d.ts.map +1 -0
  42. package/dist/echo-text.js +65 -0
  43. package/dist/echo-text.js.map +1 -0
  44. package/dist/embeddings.cjs +18 -1
  45. package/dist/embeddings.cjs.map +1 -1
  46. package/dist/embeddings.d.cts +1 -1
  47. package/dist/embeddings.d.cts.map +1 -1
  48. package/dist/embeddings.d.ts +1 -1
  49. package/dist/embeddings.d.ts.map +1 -1
  50. package/dist/embeddings.js +18 -1
  51. package/dist/embeddings.js.map +1 -1
  52. package/dist/files.cjs +9 -11
  53. package/dist/files.cjs.map +1 -1
  54. package/dist/files.d.cts.map +1 -1
  55. package/dist/files.d.ts.map +1 -1
  56. package/dist/files.js +10 -13
  57. package/dist/files.js.map +1 -1
  58. package/dist/fine-tuning.cjs +1 -19
  59. package/dist/fine-tuning.cjs.map +1 -1
  60. package/dist/fine-tuning.d.cts.map +1 -1
  61. package/dist/fine-tuning.d.ts.map +1 -1
  62. package/dist/fine-tuning.js +2 -20
  63. package/dist/fine-tuning.js.map +1 -1
  64. package/dist/fixture-loader-services.cjs +116 -0
  65. package/dist/fixture-loader-services.cjs.map +1 -0
  66. package/dist/fixture-loader-services.d.cts +48 -0
  67. package/dist/fixture-loader-services.d.cts.map +1 -0
  68. package/dist/fixture-loader-services.d.ts +48 -0
  69. package/dist/fixture-loader-services.d.ts.map +1 -0
  70. package/dist/fixture-loader-services.js +114 -0
  71. package/dist/fixture-loader-services.js.map +1 -0
  72. package/dist/fixture-loader.cjs +172 -11
  73. package/dist/fixture-loader.cjs.map +1 -1
  74. package/dist/fixture-loader.d.cts +95 -1
  75. package/dist/fixture-loader.d.cts.map +1 -1
  76. package/dist/fixture-loader.d.ts +95 -1
  77. package/dist/fixture-loader.d.ts.map +1 -1
  78. package/dist/fixture-loader.js +168 -12
  79. package/dist/fixture-loader.js.map +1 -1
  80. package/dist/gemini-embeddings.cjs +42 -2
  81. package/dist/gemini-embeddings.cjs.map +1 -1
  82. package/dist/gemini-embeddings.js +43 -3
  83. package/dist/gemini-embeddings.js.map +1 -1
  84. package/dist/gemini-interactions.cjs +38 -0
  85. package/dist/gemini-interactions.cjs.map +1 -1
  86. package/dist/gemini-interactions.d.cts.map +1 -1
  87. package/dist/gemini-interactions.d.ts.map +1 -1
  88. package/dist/gemini-interactions.js +38 -0
  89. package/dist/gemini-interactions.js.map +1 -1
  90. package/dist/gemini.cjs +8 -1
  91. package/dist/gemini.cjs.map +1 -1
  92. package/dist/gemini.d.cts.map +1 -1
  93. package/dist/gemini.d.ts.map +1 -1
  94. package/dist/gemini.js +8 -1
  95. package/dist/gemini.js.map +1 -1
  96. package/dist/helpers.cjs +276 -5
  97. package/dist/helpers.cjs.map +1 -1
  98. package/dist/helpers.d.cts.map +1 -1
  99. package/dist/helpers.d.ts.map +1 -1
  100. package/dist/helpers.js +275 -7
  101. package/dist/helpers.js.map +1 -1
  102. package/dist/images.cjs +3 -2
  103. package/dist/images.cjs.map +1 -1
  104. package/dist/images.js +3 -2
  105. package/dist/images.js.map +1 -1
  106. package/dist/index.cjs +30 -1
  107. package/dist/index.d.cts +8 -3
  108. package/dist/index.d.ts +8 -3
  109. package/dist/index.js +9 -3
  110. package/dist/jest.cjs +15 -10
  111. package/dist/jest.cjs.map +1 -1
  112. package/dist/jest.d.cts +4 -2
  113. package/dist/jest.d.cts.map +1 -1
  114. package/dist/jest.d.ts +4 -2
  115. package/dist/jest.d.ts.map +1 -1
  116. package/dist/jest.js +16 -11
  117. package/dist/jest.js.map +1 -1
  118. package/dist/journal.cjs +20 -0
  119. package/dist/journal.cjs.map +1 -1
  120. package/dist/journal.d.cts +16 -0
  121. package/dist/journal.d.cts.map +1 -1
  122. package/dist/journal.d.ts +16 -0
  123. package/dist/journal.d.ts.map +1 -1
  124. package/dist/journal.js +20 -0
  125. package/dist/journal.js.map +1 -1
  126. package/dist/llmock.cjs +82 -10
  127. package/dist/llmock.cjs.map +1 -1
  128. package/dist/llmock.d.cts +35 -0
  129. package/dist/llmock.d.cts.map +1 -1
  130. package/dist/llmock.d.ts +35 -0
  131. package/dist/llmock.d.ts.map +1 -1
  132. package/dist/llmock.js +83 -11
  133. package/dist/llmock.js.map +1 -1
  134. package/dist/mcp-fakes-mount.cjs +216 -0
  135. package/dist/mcp-fakes-mount.cjs.map +1 -0
  136. package/dist/mcp-fakes-mount.js +210 -0
  137. package/dist/mcp-fakes-mount.js.map +1 -0
  138. package/dist/mcp-fakes.cjs +1619 -0
  139. package/dist/mcp-fakes.cjs.map +1 -0
  140. package/dist/mcp-fakes.d.cts +101 -0
  141. package/dist/mcp-fakes.d.cts.map +1 -0
  142. package/dist/mcp-fakes.d.ts +101 -0
  143. package/dist/mcp-fakes.d.ts.map +1 -0
  144. package/dist/mcp-fakes.js +1614 -0
  145. package/dist/mcp-fakes.js.map +1 -0
  146. package/dist/mcp-handler.cjs +331 -17
  147. package/dist/mcp-handler.cjs.map +1 -1
  148. package/dist/mcp-handler.js +330 -18
  149. package/dist/mcp-handler.js.map +1 -1
  150. package/dist/mcp-mock.cjs +217 -49
  151. package/dist/mcp-mock.cjs.map +1 -1
  152. package/dist/mcp-mock.d.cts +66 -1
  153. package/dist/mcp-mock.d.cts.map +1 -1
  154. package/dist/mcp-mock.d.ts +66 -1
  155. package/dist/mcp-mock.d.ts.map +1 -1
  156. package/dist/mcp-mock.js +218 -50
  157. package/dist/mcp-mock.js.map +1 -1
  158. package/dist/mcp-stub.cjs +5 -1
  159. package/dist/mcp-stub.d.cts +3 -1
  160. package/dist/mcp-stub.d.ts +3 -1
  161. package/dist/mcp-stub.js +3 -1
  162. package/dist/mcp-types.d.cts +6 -0
  163. package/dist/mcp-types.d.ts +6 -0
  164. package/dist/message-text.cjs +192 -0
  165. package/dist/message-text.cjs.map +1 -0
  166. package/dist/message-text.d.cts +31 -0
  167. package/dist/message-text.d.cts.map +1 -0
  168. package/dist/message-text.d.ts +31 -0
  169. package/dist/message-text.d.ts.map +1 -0
  170. package/dist/message-text.js +184 -0
  171. package/dist/message-text.js.map +1 -0
  172. package/dist/messages.cjs +38 -0
  173. package/dist/messages.cjs.map +1 -1
  174. package/dist/messages.js +38 -0
  175. package/dist/messages.js.map +1 -1
  176. package/dist/metrics.cjs +46 -66
  177. package/dist/metrics.cjs.map +1 -1
  178. package/dist/metrics.d.cts +14 -6
  179. package/dist/metrics.d.cts.map +1 -1
  180. package/dist/metrics.d.ts +14 -6
  181. package/dist/metrics.d.ts.map +1 -1
  182. package/dist/metrics.js +32 -40
  183. package/dist/metrics.js.map +1 -1
  184. package/dist/ollama.cjs +5 -1
  185. package/dist/ollama.cjs.map +1 -1
  186. package/dist/ollama.d.cts.map +1 -1
  187. package/dist/ollama.d.ts.map +1 -1
  188. package/dist/ollama.js +5 -1
  189. package/dist/ollama.js.map +1 -1
  190. package/dist/openapi.cjs +3405 -0
  191. package/dist/openapi.cjs.map +1 -0
  192. package/dist/openapi.d.cts +30 -0
  193. package/dist/openapi.d.cts.map +1 -0
  194. package/dist/openapi.d.ts +30 -0
  195. package/dist/openapi.d.ts.map +1 -0
  196. package/dist/openapi.js +3404 -0
  197. package/dist/openapi.js.map +1 -0
  198. package/dist/openrouter-video.cjs +4 -4
  199. package/dist/openrouter-video.cjs.map +1 -1
  200. package/dist/openrouter-video.d.cts.map +1 -1
  201. package/dist/openrouter-video.d.ts.map +1 -1
  202. package/dist/openrouter-video.js +4 -4
  203. package/dist/openrouter-video.js.map +1 -1
  204. package/dist/recorder.cjs +9 -1
  205. package/dist/recorder.cjs.map +1 -1
  206. package/dist/recorder.d.cts.map +1 -1
  207. package/dist/recorder.d.ts.map +1 -1
  208. package/dist/recorder.js +9 -1
  209. package/dist/recorder.js.map +1 -1
  210. package/dist/responses.cjs +43 -4
  211. package/dist/responses.cjs.map +1 -1
  212. package/dist/responses.d.cts.map +1 -1
  213. package/dist/responses.d.ts.map +1 -1
  214. package/dist/responses.js +43 -4
  215. package/dist/responses.js.map +1 -1
  216. package/dist/route-registry.cjs +951 -0
  217. package/dist/route-registry.cjs.map +1 -0
  218. package/dist/route-registry.d.cts +33 -0
  219. package/dist/route-registry.d.cts.map +1 -0
  220. package/dist/route-registry.d.ts +33 -0
  221. package/dist/route-registry.d.ts.map +1 -0
  222. package/dist/route-registry.js +866 -0
  223. package/dist/route-registry.js.map +1 -0
  224. package/dist/router.cjs +1 -1
  225. package/dist/router.cjs.map +1 -1
  226. package/dist/router.js +1 -1
  227. package/dist/router.js.map +1 -1
  228. package/dist/server.cjs +590 -191
  229. package/dist/server.cjs.map +1 -1
  230. package/dist/server.d.cts +4 -1
  231. package/dist/server.d.cts.map +1 -1
  232. package/dist/server.d.ts +4 -1
  233. package/dist/server.d.ts.map +1 -1
  234. package/dist/server.js +498 -99
  235. package/dist/server.js.map +1 -1
  236. package/dist/speech.cjs +4 -2
  237. package/dist/speech.cjs.map +1 -1
  238. package/dist/speech.js +4 -2
  239. package/dist/speech.js.map +1 -1
  240. package/dist/suite.cjs +1 -1
  241. package/dist/suite.js +1 -1
  242. package/dist/transcription.cjs +2 -2
  243. package/dist/transcription.cjs.map +1 -1
  244. package/dist/transcription.d.cts.map +1 -1
  245. package/dist/transcription.d.ts.map +1 -1
  246. package/dist/transcription.js +2 -2
  247. package/dist/transcription.js.map +1 -1
  248. package/dist/types.cjs +18 -0
  249. package/dist/types.cjs.map +1 -0
  250. package/dist/types.d.cts +202 -2
  251. package/dist/types.d.cts.map +1 -1
  252. package/dist/types.d.ts +202 -2
  253. package/dist/types.d.ts.map +1 -1
  254. package/dist/types.js +17 -0
  255. package/dist/types.js.map +1 -0
  256. package/dist/validate-cli.cjs +89 -31
  257. package/dist/validate-cli.cjs.map +1 -1
  258. package/dist/validate-cli.js +91 -33
  259. package/dist/validate-cli.js.map +1 -1
  260. package/dist/vector-stores.cjs +1180 -0
  261. package/dist/vector-stores.cjs.map +1 -0
  262. package/dist/vector-stores.d.cts +106 -0
  263. package/dist/vector-stores.d.cts.map +1 -0
  264. package/dist/vector-stores.d.ts +106 -0
  265. package/dist/vector-stores.d.ts.map +1 -0
  266. package/dist/vector-stores.js +1164 -0
  267. package/dist/vector-stores.js.map +1 -0
  268. package/dist/vector-types.d.cts.map +1 -1
  269. package/dist/vector-types.d.ts.map +1 -1
  270. package/dist/video.cjs +19 -0
  271. package/dist/video.cjs.map +1 -1
  272. package/dist/video.d.cts.map +1 -1
  273. package/dist/video.d.ts.map +1 -1
  274. package/dist/video.js +19 -0
  275. package/dist/video.js.map +1 -1
  276. package/dist/vitest.cjs +15 -10
  277. package/dist/vitest.cjs.map +1 -1
  278. package/dist/vitest.d.cts +4 -2
  279. package/dist/vitest.d.cts.map +1 -1
  280. package/dist/vitest.d.ts +4 -2
  281. package/dist/vitest.d.ts.map +1 -1
  282. package/dist/vitest.js +16 -11
  283. package/dist/vitest.js.map +1 -1
  284. package/dist/ws-gemini-live.d.ts +2 -2
  285. package/dist/ws-realtime.d.ts +2 -2
  286. package/dist/ws-responses.cjs +14 -2
  287. package/dist/ws-responses.cjs.map +1 -1
  288. package/dist/ws-responses.js +14 -2
  289. package/dist/ws-responses.js.map +1 -1
  290. package/package.json +4 -1
package/dist/server.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { DEFAULT_TEST_ID } from "./constants.js";
2
+ import { build, msg, quote } from "./message-text.js";
2
3
  import { markAuthenticatedRequest, resolveInboundAuth, validateRequestApiKey, writeApiKeyHttpRejection, writeApiKeyUpgradeRejection } from "./api-key-auth.js";
3
- import { RequestBodyTooLargeError, buildContentWithToolCallsChunks, buildContentWithToolCallsCompletion, buildTextChunks, buildTextCompletion, buildToolCallChunks, buildToolCallCompletion, buildUsageChunk, describeMatch, extractOverrides, flattenHeaders, getContext, getTestId, isAudioResponse, isContentWithToolCallsResponse, isEmbeddingResponse, isErrorResponse, isImageResponse, isJSONResponse, isJsonObject, isResponseFactory, isTextResponse, isToolCallResponse, isTranscriptionResponse, isVideoResponse, markMintedRequestId, readBody, readBodyBufferBounded, resolveFixtureBlockOutcome, resolveReasoningForModel, resolveRequestId, resolveResponse, resolveStrictMode, resolveTestId, resolveUsage, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField, wouldProxyMiss } from "./helpers.js";
4
+ import { RequestBodyTooLargeError, buildContentWithToolCallsChunks, buildContentWithToolCallsCompletion, buildTextChunks, buildTextCompletion, buildToolCallChunks, buildToolCallCompletion, buildUsageChunk, describeMatch, extractOverrides, flattenHeaders, getContext, getTestId, isAudioResponse, isContentWithToolCallsResponse, isEmbeddingResponse, isErrorResponse, isImageResponse, isJSONResponse, isJsonObject, isResponseFactory, isTextResponse, isToolCallResponse, isTranscriptionResponse, isVideoResponse, markMintedRequestId, readBody, readBodyBufferBounded, resolveFixtureBlockOutcome, resolveReasoningForModel, resolveRequestId, resolveResponse, resolveStrictMode, resolveTestId, resolveUsage, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField, validateToolsField, wouldProxyMiss } from "./helpers.js";
4
5
  import { normalizeLiveOptions } from "./live-fixture.js";
5
6
  import { writeErrorResponse, writeSSEStream } from "./sse-writer.js";
6
7
  import { CHAOS_FIELDS, CHAOS_FIELD_NAMES, applyChaosAction, awaitChaosLatency, describeUnwritableReason, evaluateChaos, isChaosScope, parseChaosField, resetChaosWarnings, resolveChaosConfig, responseGoneReason } from "./chaos.js";
7
- import { INJECTED_STATUS_RANGE, claimOneShotError, clearFixtureQueue, entryToFixture, isInjectableStatus, isOneShotError, queueOneShotError, releaseOneShotError, validateFixtures } from "./fixture-loader.js";
8
+ import { FixtureLoadError, INJECTED_STATUS_RANGE, claimOneShotError, clearFixtureQueue, entryToFixture, isInjectableStatus, isOneShotError, queueOneShotError, releaseOneShotError, validateFixtures } from "./fixture-loader.js";
8
9
  import { matchFixtureDiagnostic, recordMatchOptions } from "./router.js";
9
10
  import { proxyAndRecord } from "./recorder.js";
10
11
  import { Logger } from "./logger.js";
@@ -37,6 +38,7 @@ import { handleSearch } from "./search.js";
37
38
  import { handleRerank } from "./rerank.js";
38
39
  import { handleModeration } from "./moderation.js";
39
40
  import { clearFineTuningStore, handleFineTuningCancel, handleFineTuningCreate, handleFineTuningEvents, handleFineTuningList, handleFineTuningRetrieve } from "./fine-tuning.js";
41
+ import { clearVectorStoreStore, handleVectorFileBatchesCancel, handleVectorFileBatchesCreate, handleVectorFileBatchesFiles, handleVectorFileBatchesRetrieve, handleVectorStoreFilesCreate, handleVectorStoreFilesDelete, handleVectorStoreFilesList, handleVectorStoreFilesRetrieve, handleVectorStoresCreate, handleVectorStoresDelete, handleVectorStoresList, handleVectorStoresModify, handleVectorStoresRetrieve, handleVectorStoresSearch } from "./vector-stores.js";
40
42
  import { FILES_BODY_DRAIN_MAX_BYTES, FILES_BODY_MAX_BYTES, FILES_BODY_OVERSIZED, clearFileStore, handleFilesContent, handleFilesCreate, handleFilesDelete, handleFilesList, handleFilesRetrieve } from "./files.js";
41
43
  import { clearBatchStore, handleBatchesCancel, handleBatchesCreate, handleBatchesList, handleBatchesRetrieve } from "./batches.js";
42
44
  import { upgradeToWebSocket } from "./ws-framing.js";
@@ -44,41 +46,17 @@ import { handleLiveSession } from "./ws-live.js";
44
46
  import { handleWebSocketResponses } from "./ws-responses.js";
45
47
  import { handleWebSocketRealtime } from "./ws-realtime.js";
46
48
  import { handleWebSocketGeminiLive } from "./ws-gemini-live.js";
47
- import { BYTEPLUS_VIDEO_STATUS_RE, BYTEPLUS_VIDEO_SUBMIT_RE, DESTROYED_STATUS_LABEL, FAL_ROUTE_RE, FILES_CONTENT_RE, FILES_ID_RE, GROK_VIDEO_STATUS_RE, GROK_VIDEO_SUBMIT_PATH, OPENROUTER_VIDEO_CONTENT_RE, OPENROUTER_VIDEO_STATUS_RE, VEO_OPERATION_RE, VEO_PREDICT_LRO_RE, createMetricsRegistry, normalizePathLabel } from "./metrics.js";
49
+ import { AZURE_DEPLOYMENT_RE, BATCHES_CANCEL_RE, BATCHES_ID_RE, BATCHES_PATH, BEDROCK_CONVERSE_RE, BEDROCK_CONVERSE_STREAM_RE, BEDROCK_INVOKE_RE, BEDROCK_STREAM_RE, BYTEPLUS_VIDEO_STATUS_RE, BYTEPLUS_VIDEO_SUBMIT_RE, COHERE_CHAT_PATH, COHERE_EMBED_PATH, COMPLETIONS_PATH, CONTROL_PREFIX, ELEVENLABS_MUSIC_RE, ELEVENLABS_SOUND_GENERATION_PATH, ELEVENLABS_TTS_RE, ELEVENLABS_VOICE_CREATE_PATH, ELEVENLABS_VOICE_DESIGN_PATH, ELEVENLABS_VOICE_RE, EMBEDDINGS_PATH, FAL_QUEUE_REQUESTS_RE, FAL_QUEUE_SUBMIT_RE, FAL_ROUTE_RE, FAL_RUN_RE, FILES_CONTENT_RE, FILES_ID_RE, FILES_PATH, FINE_TUNING_CANCEL_RE, FINE_TUNING_EVENTS_RE, FINE_TUNING_ID_RE, FINE_TUNING_JOBS_PATH, GEMINI_EMBED_RE, GEMINI_INTERACTIONS_PATH, GEMINI_LIVE_PATH, GEMINI_PATH_RE, GEMINI_PREDICT_RE, GROK_VIDEO_STATUS_RE, GROK_VIDEO_SUBMIT_PATH, HEALTH_PATH, IMAGES_EDIT_PATH, IMAGES_PATH, IMAGES_VARIATIONS_PATH, LIVE_PATH, MESSAGES_PATH, METRICS_PATH, MODELS_PATH, MODERATIONS_PATH, OLLAMA_CHAT_PATH, OLLAMA_EMBEDDINGS_PATH, OLLAMA_EMBED_PATH, OLLAMA_GENERATE_PATH, OLLAMA_TAGS_PATH, OPENROUTER_CREDITS_PATH, OPENROUTER_KEY_PATH, OPENROUTER_MODELS_PATH, OPENROUTER_VIDEOS_PATH, OPENROUTER_VIDEO_CONTENT_RE, OPENROUTER_VIDEO_MODELS_PATH, OPENROUTER_VIDEO_STATUS_RE, READY_PATH, REALTIME_PATH, REQUESTS_PATH, RERANK_PATH, RESPONSES_PATH, SEARCH_PATH, SPEECH_PATH, TRANSCRIPTIONS_PATH, TRANSLATIONS_PATH, VECTOR_STORES_BATCH_CANCEL_RE, VECTOR_STORES_BATCH_FILES_RE, VECTOR_STORES_BATCH_RE, VECTOR_STORES_FILES_RE, VECTOR_STORES_FILE_BATCHES_RE, VECTOR_STORES_FILE_RE, VECTOR_STORES_ID_RE, VECTOR_STORES_PATH, VECTOR_STORES_SEARCH_RE, VEO_OPERATION_RE, VEO_PREDICT_LRO_RE, VERTEX_AI_RE, VIDEOS_PATH, matchRouteDefinition } from "./route-registry.js";
50
+ import { CATALOG_ROUTES, buildOpenApiDocument } from "./openapi.js";
51
+ import { DESTROYED_STATUS_LABEL, createMetricsRegistry, normalizePathLabel } from "./metrics.js";
52
+ import { MCP_FAKES_DEFAULT_MOUNT, McpFakeStore, McpFakesAddError, blockIdOf } from "./mcp-fakes.js";
53
+ import { MCPMock } from "./mcp-mock.js";
54
+ import { ensureFakeMount, findFakeMount, handOffMcpFakes, isFakeMount, mountConflictDetail } from "./mcp-fakes-mount.js";
48
55
  import * as http$1 from "node:http";
49
56
  import { AsyncLocalStorage } from "node:async_hooks";
50
57
 
51
58
  //#region src/server.ts
52
59
  const liveClosers = /* @__PURE__ */ new WeakMap();
53
- const COMPLETIONS_PATH = "/v1/chat/completions";
54
- const RESPONSES_PATH = "/v1/responses";
55
- const LIVE_PATH = "/v1/live/sessions";
56
- const REALTIME_PATH = "/v1/realtime";
57
- const GEMINI_LIVE_PATH = "/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent";
58
- const MESSAGES_PATH = "/v1/messages";
59
- const EMBEDDINGS_PATH = "/v1/embeddings";
60
- const COHERE_CHAT_PATH = "/v2/chat";
61
- const COHERE_EMBED_PATH = "/v2/embed";
62
- const SEARCH_PATH = "/search";
63
- const RERANK_PATH = "/v2/rerank";
64
- const MODERATIONS_PATH = "/v1/moderations";
65
- const IMAGES_PATH = "/v1/images/generations";
66
- const IMAGES_EDIT_PATH = "/v1/images/edits";
67
- const IMAGES_VARIATIONS_PATH = "/v1/images/variations";
68
- const SPEECH_PATH = "/v1/audio/speech";
69
- const TRANSCRIPTIONS_PATH = "/v1/audio/transcriptions";
70
- const TRANSLATIONS_PATH = "/v1/audio/translations";
71
- const VIDEOS_PATH = "/v1/videos";
72
- const GEMINI_PREDICT_RE = /^\/v1beta\/models\/([^:]+):predict$/;
73
- const ELEVENLABS_SOUND_GENERATION_PATH = "/v1/sound-generation";
74
- const ELEVENLABS_TTS_RE = /^\/v1\/text-to-speech\/([^/]+)$/;
75
- const ELEVENLABS_MUSIC_RE = /^\/v1\/music(?:\/(.+))?$/;
76
- const ELEVENLABS_VOICE_DESIGN_PATH = "/v1/text-to-voice/design";
77
- const ELEVENLABS_VOICE_CREATE_PATH = "/v1/text-to-voice";
78
- const ELEVENLABS_VOICE_RE = /^\/v1\/voices\/([^/]+)$/;
79
- const FAL_QUEUE_SUBMIT_RE = /^\/fal\/queue\/submit\/(.+)$/;
80
- const FAL_QUEUE_REQUESTS_RE = /^\/fal\/queue\/requests\/(.+)$/;
81
- const FAL_RUN_RE = /^\/fal\/run\/(.+)$/;
82
60
  const DEFAULT_CHUNK_SIZE = 20;
83
61
  const COMPAT_SUFFIXES = [
84
62
  "/chat/completions",
@@ -107,34 +85,6 @@ function normalizeCompatPath(pathname, logger) {
107
85
  }
108
86
  return pathname;
109
87
  }
110
- const GEMINI_INTERACTIONS_PATH = "/v1beta/interactions";
111
- const GEMINI_PATH_RE = /^\/v1beta\/models\/([^:]+):(generateContent|streamGenerateContent)$/;
112
- const GEMINI_EMBED_RE = /^\/v1beta\/models\/([^:]+):embedContent$/;
113
- const AZURE_DEPLOYMENT_RE = /^\/openai\/deployments\/([^/]+)\/(chat\/completions|embeddings)$/;
114
- const BEDROCK_INVOKE_RE = /^\/model\/([^/]+)\/invoke$/;
115
- const BEDROCK_STREAM_RE = /^\/model\/([^/]+)\/invoke-with-response-stream$/;
116
- const BEDROCK_CONVERSE_RE = /^\/model\/([^/]+)\/converse$/;
117
- const BEDROCK_CONVERSE_STREAM_RE = /^\/model\/([^/]+)\/converse-stream$/;
118
- const VERTEX_AI_RE = /^\/v1\/projects\/[^/]+\/locations\/[^/]+\/publishers\/google\/models\/([^/:]+):(generateContent|streamGenerateContent)$/;
119
- const OLLAMA_CHAT_PATH = "/api/chat";
120
- const OLLAMA_GENERATE_PATH = "/api/generate";
121
- const OLLAMA_EMBEDDINGS_PATH = "/api/embeddings";
122
- const OLLAMA_EMBED_PATH = "/api/embed";
123
- const OLLAMA_TAGS_PATH = "/api/tags";
124
- const OPENROUTER_VIDEOS_PATH = "/api/v1/videos";
125
- const OPENROUTER_VIDEO_MODELS_PATH = "/api/v1/videos/models";
126
- const HEALTH_PATH = "/health";
127
- const READY_PATH = "/ready";
128
- const MODELS_PATH = "/v1/models";
129
- const REQUESTS_PATH = "/v1/_requests";
130
- const BATCHES_PATH = "/v1/batches";
131
- const BATCHES_ID_RE = /^\/v1\/batches\/([^/]+)$/;
132
- const BATCHES_CANCEL_RE = /^\/v1\/batches\/([^/]+)\/cancel$/;
133
- const FILES_PATH = "/v1/files";
134
- const FINE_TUNING_JOBS_PATH = "/v1/fine_tuning/jobs";
135
- const FINE_TUNING_ID_RE = /^\/v1\/fine_tuning\/jobs\/([^/]+)$/;
136
- const FINE_TUNING_CANCEL_RE = /^\/v1\/fine_tuning\/jobs\/([^/]+)\/cancel$/;
137
- const FINE_TUNING_EVENTS_RE = /^\/v1\/fine_tuning\/jobs\/([^/]+)\/events$/;
138
88
  const DEFAULT_MODELS = [
139
89
  "gpt-4",
140
90
  "gpt-4o",
@@ -144,7 +94,7 @@ const DEFAULT_MODELS = [
144
94
  ];
145
95
  const CORS_HEADERS = {
146
96
  "Access-Control-Allow-Origin": "*",
147
- "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
97
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
148
98
  "Access-Control-Allow-Headers": "*",
149
99
  "Access-Control-Expose-Headers": "X-Total-Count, X-Request-Id"
150
100
  };
@@ -163,7 +113,6 @@ function handleNotFound(res, message) {
163
113
  type: "not_found"
164
114
  } }));
165
115
  }
166
- const CONTROL_PREFIX = "/__aimock";
167
116
  /** The complete `GET /__aimock/fixtures` query-param vocabulary; anything else 400s. */
168
117
  const FIXTURES_PARAMS = new Set(["include"]);
169
118
  /** The complete `GET /__aimock/journal` query-param vocabulary; anything else 400s. */
@@ -202,6 +151,7 @@ function performFullReset(fixtures, targets) {
202
151
  clearElevenLabsVoices();
203
152
  clearFileStore();
204
153
  clearFineTuningStore();
154
+ clearVectorStoreStore();
205
155
  resetInteractionCounter();
206
156
  resetEventIdCounter();
207
157
  if (!targets) return;
@@ -214,6 +164,7 @@ function performFullReset(fixtures, targets) {
214
164
  targets.veoVideoJobs.clear();
215
165
  targets.grokVideoJobs.clear();
216
166
  targets.bytePlusVideoJobs.clear();
167
+ for (const { handler } of targets.mounts ?? []) handler.clearMcpFakes?.();
217
168
  if (targets.defaults.registry) targets.defaults.registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
218
169
  }
219
170
  /**
@@ -268,9 +219,14 @@ function fixtureResponseKind(response) {
268
219
  * scoping path does not also do. (Splitting on "," additionally broke every
269
220
  * legitimately comma-bearing id.) A raw `path.includes("testId=t1")` both
270
221
  * prefix-collides with `t10` and matches unrelated params like `notTestId`.
222
+ *
223
+ * An MCP mount records the test id it resolved on the entry (I9, B3): a
224
+ * decoded header or query value, or the one bound to the session at
225
+ * `initialize`. That value wins; LLM entries carry none and are attributed as
226
+ * before.
271
227
  */
272
228
  function journalEntryTestId(entry) {
273
- return resolveTestId(entry.headers, entry.path);
229
+ return entry.testId ?? resolveTestId(entry.headers, entry.path);
274
230
  }
275
231
  /**
276
232
  * The testId a chaos override is stored under, and read back with — resolved by
@@ -309,19 +265,186 @@ function effectiveChaos(defaults, scopeId) {
309
265
  if (!current) return {};
310
266
  return isChaosScope(current) ? current.base ?? {} : current;
311
267
  }
268
+ /** The `GET /__aimock/mcp/fakes` query-param vocabulary; anything else 400s. */
269
+ const MCP_FAKES_PARAMS = new Set([
270
+ "testId",
271
+ "context",
272
+ "mount"
273
+ ]);
274
+ /** A block's `mount` path, read without running getters; the add validates it. */
275
+ function controlFakeMountOf(raw) {
276
+ if (typeof raw !== "object" || raw === null) return MCP_FAKES_DEFAULT_MOUNT;
277
+ const value = Object.getOwnPropertyDescriptor(raw, "mount")?.value;
278
+ return typeof value === "string" ? value : MCP_FAKES_DEFAULT_MOUNT;
279
+ }
280
+ /**
281
+ * Check one `POST /__aimock/fixtures` `mcpFakes` value against the live
282
+ * mounts (F10, W6) without changing anything. Returns every error in input
283
+ * order (an empty list when the value can be added), and `apply`, which adds
284
+ * the blocks (auto-mounting an MCPMock where no mount serves a block's path)
285
+ * and returns the number of blocks added.
286
+ *
287
+ * The whole value is checked before any mount is touched, and the check
288
+ * never adds to a real mount: each group is added to a throwaway store,
289
+ * whose errors are the ones reported (it numbers the add `control-api#1`, as
290
+ * a new mount would; for an existing MCPMock the rejected group is re-run on
291
+ * its atomic add, so the errors name that mount's next `<n>`, I7). A group
292
+ * whose path no request to it can reach (a mount that is not an MCPMock
293
+ * serves it, an MCPMock answers it as its root, or it is under the control
294
+ * prefix) is a mount conflict (one error per block).
295
+ */
296
+ function planControlApiFakes(raw, mounts) {
297
+ const items = Array.isArray(raw) ? raw.map((block, position) => ({
298
+ position,
299
+ source: {
300
+ source: "control-api",
301
+ blockIndex: position,
302
+ raw: block
303
+ }
304
+ })) : [{
305
+ position: 0,
306
+ source: {
307
+ source: "control-api",
308
+ blockIndex: null,
309
+ raw
310
+ }
311
+ }];
312
+ const groups = /* @__PURE__ */ new Map();
313
+ if (items.length === 0) groups.set(MCP_FAKES_DEFAULT_MOUNT, {
314
+ path: MCP_FAKES_DEFAULT_MOUNT,
315
+ positions: [],
316
+ sources: []
317
+ });
318
+ for (const { position, source } of items) {
319
+ const path = controlFakeMountOf(source.raw);
320
+ const group = groups.get(path) ?? {
321
+ path,
322
+ positions: [],
323
+ sources: []
324
+ };
325
+ group.positions.push(position);
326
+ group.sources.push(source);
327
+ groups.set(path, group);
328
+ }
329
+ const found = [];
330
+ /** The paths `apply` auto-mounts, in order. */
331
+ const pending = [];
332
+ for (const group of groups.values()) {
333
+ const target = findFakeMount(mounts, group.path, pending);
334
+ if (target.kind === "conflict") {
335
+ group.positions.forEach((position) => {
336
+ const where = Array.isArray(raw) ? msg`mcpFakes[${position}]` : msg`mcpFakes`;
337
+ found.push({
338
+ position,
339
+ error: new FixtureLoadError({
340
+ rule: "mcp-fakes/mount-conflict",
341
+ file: null,
342
+ blockId: null,
343
+ entryId: null,
344
+ detail: msg`${where}: ${mountConflictDetail(group.path, target)}`
345
+ })
346
+ });
347
+ });
348
+ continue;
349
+ }
350
+ let failure = addFailure(() => new McpFakeStore().add(group.sources, { kind: "control-api" }));
351
+ if (target.kind === "none") pending.push(group.path);
352
+ if (!failure) continue;
353
+ if (target.kind === "mcp" && target.handler.addMcpFakes === MCPMock.prototype.addMcpFakes) {
354
+ const handler = target.handler;
355
+ failure = addFailure(() => handler.addMcpFakes?.(group.sources, { kind: "control-api" })) ?? failure;
356
+ }
357
+ for (const error of failure.errors) found.push({
358
+ position: positionOf(error, group),
359
+ error
360
+ });
361
+ }
362
+ found.sort((a, b) => a.position - b.position);
363
+ return {
364
+ errors: found.map((f) => f.error),
365
+ apply(wiring) {
366
+ let added = 0;
367
+ for (const group of groups.values()) {
368
+ const target = ensureFakeMount(mounts, group.path, {
369
+ journal: wiring.journal,
370
+ registry: wiring.defaults.registry,
371
+ logger: wiring.defaults.logger
372
+ }, wiring.defaults.logger);
373
+ if ("conflict" in target) continue;
374
+ const result = target.handler.addMcpFakes?.(group.sources, { kind: "control-api" });
375
+ for (const warning of result?.warnings ?? []) wiring.defaults.logger.warn(warning.message);
376
+ added += group.sources.length;
377
+ }
378
+ return added;
379
+ }
380
+ };
381
+ }
382
+ /** The `McpFakesAddError` that `add` throws, or `null` when it adds. */
383
+ function addFailure(add) {
384
+ try {
385
+ add();
386
+ return null;
387
+ } catch (err) {
388
+ if (err instanceof McpFakesAddError) return err;
389
+ throw err;
390
+ }
391
+ }
392
+ /**
393
+ * The request position of the block an add error names. The add numbers a
394
+ * run-time group's blocks by their place in the group (`[<j>]`), or gives no
395
+ * index to a single-object value; an error tied to no block sorts with the
396
+ * group's first block.
397
+ */
398
+ function positionOf(error, group) {
399
+ const first = group.positions[0] ?? 0;
400
+ if (error.blockId == null || error.file === null) return first;
401
+ const at = group.positions.findIndex((_, j) => error.blockId === blockIdOf(error.file ?? "", "", j));
402
+ return at === -1 ? first : group.positions[at];
403
+ }
404
+ function hasFakesSnapshot(handler) {
405
+ return "fakesSnapshot" in handler && typeof handler.fakesSnapshot === "function";
406
+ }
407
+ /**
408
+ * `GET /__aimock/mcp/fakes` (spec 6.5, I9): for each MCP mount (or the one
409
+ * named by `mount`), the blocks that apply to the requested test id and
410
+ * context, with every entry id and its consumed state for that test id.
411
+ * A mount is listed when it takes fakes (it implements `addMcpFakes`, the
412
+ * check every other fakes path uses); one with no `fakesSnapshot` lists no
413
+ * blocks.
414
+ */
415
+ function listControlApiFakes(mounts, testId, context, mount) {
416
+ const listed = mounts.flatMap(({ path, handler }) => isFakeMount(handler) && (mount === null || path === mount) ? [{
417
+ mount: path,
418
+ blocks: hasFakesSnapshot(handler) ? handler.fakesSnapshot(testId, context) : []
419
+ }] : []);
420
+ return mount !== null && listed.length === 0 ? null : listed;
421
+ }
312
422
  /**
313
423
  * Handle requests under `/__aimock/`. Returns `true` if the request was
314
424
  * handled, `false` if the path doesn't match the control prefix.
315
425
  */
316
- async function handleControlAPI(req, res, pathname, searchParams, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, bytePlusVideoJobs, defaults) {
426
+ async function handleControlAPI(req, res, pathname, searchParams, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, bytePlusVideoJobs, defaults, mounts) {
317
427
  if (!pathname.startsWith(CONTROL_PREFIX)) return false;
318
- const subPath = pathname.slice(9);
428
+ const subPath = pathname.slice(CONTROL_PREFIX.length);
319
429
  setCorsHeaders(res);
320
430
  if (subPath === "/health" && req.method === "GET") {
321
431
  res.writeHead(200, { "Content-Type": "application/json" });
322
432
  res.end(JSON.stringify({ status: "ok" }));
323
433
  return true;
324
434
  }
435
+ if (subPath === "/openapi.json" && req.method === "GET") {
436
+ res.writeHead(200, { "Content-Type": "application/json" });
437
+ res.end(JSON.stringify(buildOpenApiDocument()));
438
+ return true;
439
+ }
440
+ if (subPath === "/routes" && req.method === "GET") {
441
+ res.writeHead(200, { "Content-Type": "application/json" });
442
+ res.end(JSON.stringify({
443
+ count: CATALOG_ROUTES.length,
444
+ routes: CATALOG_ROUTES
445
+ }));
446
+ return true;
447
+ }
325
448
  if (subPath === "/journal" && req.method === "GET") {
326
449
  const bad = (message) => {
327
450
  res.writeHead(400, { "Content-Type": "application/json" });
@@ -472,6 +595,24 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
472
595
  res.end(JSON.stringify({ chaos: effectiveChaos(defaults, scopeId) }));
473
596
  return true;
474
597
  }
598
+ if (subPath === "/mcp/fakes" && req.method === "GET") {
599
+ const reply = (status, body) => {
600
+ res.writeHead(status, { "Content-Type": "application/json" });
601
+ res.end(JSON.stringify(body));
602
+ return true;
603
+ };
604
+ for (const key of searchParams.keys()) if (!MCP_FAKES_PARAMS.has(key)) return reply(400, { error: `Unknown query parameter: '${key}'. Supported: ${[...MCP_FAKES_PARAMS].join(", ")}` });
605
+ const testId = searchParams.get("testId") || null;
606
+ const context = searchParams.get("context") || null;
607
+ const mount = searchParams.get("mount") || null;
608
+ const listed = listControlApiFakes(mounts, testId, context, mount);
609
+ if (listed === null) return reply(404, { error: build(msg`No MCP mount at ${quote(mount ?? "")}`) });
610
+ return reply(200, {
611
+ testId,
612
+ context,
613
+ mounts: listed
614
+ });
615
+ }
475
616
  if (subPath === "/fixtures" && req.method === "POST") {
476
617
  let raw;
477
618
  try {
@@ -493,29 +634,60 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
493
634
  res.end(JSON.stringify({ error: `Invalid JSON: ${msg}` }));
494
635
  return true;
495
636
  }
496
- if (!Array.isArray(parsed.fixtures)) {
637
+ const withFakes = typeof parsed === "object" && parsed !== null && Object.hasOwn(parsed, "mcpFakes");
638
+ if (parsed === null || !Array.isArray(parsed.fixtures) && !(withFakes && parsed.fixtures === void 0)) {
497
639
  res.writeHead(400, { "Content-Type": "application/json" });
498
640
  res.end(JSON.stringify({ error: "Missing or invalid \"fixtures\" array" }));
499
641
  return true;
500
642
  }
501
- const converted = parsed.fixtures.map((e) => entryToFixture(e));
643
+ const entries = Array.isArray(parsed.fixtures) ? parsed.fixtures : [];
644
+ const missingMatchIndex = entries.findIndex((entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry) && entry.match == null);
645
+ if (missingMatchIndex !== -1) {
646
+ res.writeHead(400, { "Content-Type": "application/json" });
647
+ res.end(JSON.stringify({ error: `Fixture at index ${missingMatchIndex} is missing match` }));
648
+ return true;
649
+ }
650
+ const invalidMatchIndex = entries.findIndex((entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry) && entry.match != null && (typeof entry.match !== "object" || Array.isArray(entry.match)));
651
+ if (invalidMatchIndex !== -1) {
652
+ res.writeHead(400, { "Content-Type": "application/json" });
653
+ res.end(JSON.stringify({ error: `Fixture at index ${invalidMatchIndex}: match must be an object` }));
654
+ return true;
655
+ }
656
+ const converted = entries.map((e) => entryToFixture(e));
502
657
  const errors = validateFixtures(converted).filter((i) => i.severity === "error");
503
- if (errors.length > 0) {
658
+ const fakes = withFakes ? planControlApiFakes(parsed.mcpFakes, mounts) : null;
659
+ const fakeErrors = fakes?.errors ?? [];
660
+ const validationFailed = (details) => {
504
661
  res.writeHead(400, { "Content-Type": "application/json" });
505
662
  res.end(JSON.stringify({
506
663
  error: "Validation failed",
507
- details: errors
664
+ details
508
665
  }));
509
666
  return true;
667
+ };
668
+ if (errors.length > 0 || fakeErrors.length > 0) return validationFailed([...errors, ...fakeErrors.map((e) => e.toJSON())]);
669
+ let mcpFakesAdded = 0;
670
+ if (fakes) try {
671
+ mcpFakesAdded = fakes.apply({
672
+ journal,
673
+ defaults
674
+ });
675
+ } catch (err) {
676
+ if (!(err instanceof McpFakesAddError)) throw err;
677
+ return validationFailed(err.errors.map((e) => e.toJSON()));
510
678
  }
511
679
  fixtures.push(...converted);
512
680
  if (defaults.registry) defaults.registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
513
681
  res.writeHead(200, { "Content-Type": "application/json" });
514
- res.end(JSON.stringify({ added: converted.length }));
682
+ res.end(JSON.stringify(withFakes ? {
683
+ added: converted.length,
684
+ mcpFakesAdded
685
+ } : { added: converted.length }));
515
686
  return true;
516
687
  }
517
688
  if (subPath === "/fixtures" && req.method === "DELETE") {
518
689
  clearFixtureQueue(fixtures);
690
+ for (const { handler } of mounts) handler.clearMcpFakes?.();
519
691
  if (defaults.registry) defaults.registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
520
692
  res.writeHead(200, { "Content-Type": "application/json" });
521
693
  res.end(JSON.stringify({ cleared: true }));
@@ -528,7 +700,8 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
528
700
  veoVideoJobs,
529
701
  grokVideoJobs,
530
702
  bytePlusVideoJobs,
531
- defaults
703
+ defaults,
704
+ mounts
532
705
  });
533
706
  if (subPath === "/reset" && req.method === "POST") {
534
707
  performFullReset(fixtures, resetTargets());
@@ -737,6 +910,51 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
737
910
  } }));
738
911
  return;
739
912
  }
913
+ const nullMessageIndex = body.messages.findIndex((message) => message === null);
914
+ if (nullMessageIndex !== -1) {
915
+ const message = `Invalid request: messages[${nullMessageIndex}] must be an object`;
916
+ journal.add({
917
+ method: req.method ?? "POST",
918
+ path: req.url ?? COMPLETIONS_PATH,
919
+ headers: flattenHeaders(req.headers),
920
+ body: null,
921
+ response: {
922
+ status: 400,
923
+ fixture: null
924
+ }
925
+ });
926
+ writeErrorResponse(res, 400, openRouter ? serializeOpenRouterError(400, message) : JSON.stringify({ error: {
927
+ message,
928
+ type: "invalid_request_error",
929
+ param: null,
930
+ code: null
931
+ } }));
932
+ return;
933
+ }
934
+ if (new URL(req.url ?? COMPLETIONS_PATH, "http://localhost").pathname === COMPLETIONS_PATH) {
935
+ let toolsError = validateToolsField(body.tools);
936
+ if (!toolsError && Array.isArray(body.tools)) {
937
+ const nullFunctionIndex = body.tools.findIndex((tool) => tool?.function === null);
938
+ if (nullFunctionIndex !== -1) toolsError = `tools[${nullFunctionIndex}].function must not be null`;
939
+ }
940
+ if (toolsError) {
941
+ journal.add({
942
+ method: req.method ?? "POST",
943
+ path: req.url ?? COMPLETIONS_PATH,
944
+ headers: flattenHeaders(req.headers),
945
+ body: null,
946
+ response: {
947
+ status: 400,
948
+ fixture: null
949
+ }
950
+ });
951
+ writeErrorResponse(res, 400, JSON.stringify({ error: {
952
+ message: toolsError,
953
+ type: "invalid_request_error"
954
+ } }));
955
+ return;
956
+ }
957
+ }
740
958
  const method = req.method ?? "POST";
741
959
  const path = req.url ?? COMPLETIONS_PATH;
742
960
  const flatHeaders = flattenHeaders(req.headers);
@@ -1146,9 +1364,24 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
1146
1364
  }
1147
1365
  /** @internal Config startup uses this to avoid parsing a selected auth source twice. */
1148
1366
  async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mounts, serviceFixtures) {
1367
+ const logger = new Logger(options?.logLevel ?? "silent");
1368
+ const mountList = mounts ?? [];
1369
+ const handOff = serviceFixtures?.mcpFakes && serviceFixtures.mcpFakes.length > 0 ? handOffMcpFakes(mountList, serviceFixtures.mcpFakes, logger) : null;
1370
+ try {
1371
+ return await startServer(fixtures, options, resolvedAuth, mountList, logger, serviceFixtures, () => handOff?.commit());
1372
+ } catch (err) {
1373
+ handOff?.undo();
1374
+ throw err;
1375
+ }
1376
+ }
1377
+ /**
1378
+ * The server of `createServerWithResolvedAuth`, after the MCP fakes hand-off.
1379
+ * `commitHandOff` runs once the server listens; if it throws, the server is
1380
+ * closed and start rejects.
1381
+ */
1382
+ async function startServer(fixtures, options, resolvedAuth, mountList, logger, serviceFixtures, commitHandOff) {
1149
1383
  const host = options?.host ?? "127.0.0.1";
1150
1384
  const port = options?.port ?? 0;
1151
- const logger = new Logger(options?.logLevel ?? "silent");
1152
1385
  const registry = options?.metrics ? createMetricsRegistry() : void 0;
1153
1386
  const serverOptions = options ?? {};
1154
1387
  const chaosByTestId = /* @__PURE__ */ new Map();
@@ -1437,9 +1670,10 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1437
1670
  else res.destroy();
1438
1671
  }
1439
1672
  }
1440
- if (mounts) for (const { handler } of mounts) {
1673
+ for (const { handler } of mountList) {
1441
1674
  if (handler.setJournal) handler.setJournal(journal);
1442
1675
  if (registry && handler.setRegistry) handler.setRegistry(registry);
1676
+ if (handler.setLogger) handler.setLogger(logger);
1443
1677
  }
1444
1678
  if (registry) registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
1445
1679
  const server = http$1.createServer((req, res) => {
@@ -1462,7 +1696,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1462
1696
  if (recorded) return;
1463
1697
  recorded = true;
1464
1698
  try {
1465
- const normalizedPath = normalizePathLabel(pathname, mounts ? mounts.map((m) => m.path) : []);
1699
+ const normalizedPath = normalizePathLabel(pathname, mountList.map((m) => m.path));
1466
1700
  const method = req.method ?? "UNKNOWN";
1467
1701
  registry.incrementCounter("aimock_requests_total", {
1468
1702
  method,
@@ -1483,7 +1717,36 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1483
1717
  if (!res.writableFinished) recordOutcome(DESTROYED_STATUS_LABEL);
1484
1718
  });
1485
1719
  }
1486
- const parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
1720
+ let parsedUrl;
1721
+ try {
1722
+ parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
1723
+ } catch (err) {
1724
+ const route = `${req.method ?? "?"} ${req.url ?? "?"}`;
1725
+ const msg = err instanceof Error ? err.message : "Internal error";
1726
+ logger.error(`${route}: ${msg}`);
1727
+ if (err instanceof Error && err.stack) logger.debug(err.stack);
1728
+ try {
1729
+ journal.add({
1730
+ method: req.method ?? "?",
1731
+ path: req.url ?? "?",
1732
+ headers: flattenHeaders(req.headers),
1733
+ body: null,
1734
+ response: {
1735
+ status: 400,
1736
+ fixture: null,
1737
+ source: "internal"
1738
+ }
1739
+ });
1740
+ } catch (jErr) {
1741
+ logger.warn(`${route}: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
1742
+ }
1743
+ setCorsHeaders(res);
1744
+ writeErrorResponse(res, 400, JSON.stringify({ error: {
1745
+ message: "Invalid request target",
1746
+ type: "invalid_request_error"
1747
+ } }));
1748
+ return;
1749
+ }
1487
1750
  pathname = parsedUrl.pathname;
1488
1751
  const originalPathname = pathname;
1489
1752
  const isOpenRouter = isOpenRouterPath(originalPathname);
@@ -1492,7 +1755,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1492
1755
  handleOptions(res);
1493
1756
  return;
1494
1757
  }
1495
- if (!(req.method === "GET" && (pathname === HEALTH_PATH || pathname === READY_PATH || pathname === "/metrics"))) {
1758
+ if (!(req.method === "GET" && (pathname === HEALTH_PATH || pathname === READY_PATH || pathname === METRICS_PATH))) {
1496
1759
  if (!validateRequestApiKey(req, resolvedAuth.policy).ok) {
1497
1760
  writeApiKeyHttpRejection(res, setCorsHeaders);
1498
1761
  return;
@@ -1500,14 +1763,12 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1500
1763
  markAuthenticatedRequest(req, resolvedAuth.policy);
1501
1764
  }
1502
1765
  if (pathname.startsWith(CONTROL_PREFIX)) {
1503
- await handleControlAPI(req, res, pathname, parsedUrl.searchParams, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, bytePlusVideoJobs, defaults);
1766
+ await handleControlAPI(req, res, pathname, parsedUrl.searchParams, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, bytePlusVideoJobs, defaults, mountList);
1504
1767
  return;
1505
1768
  }
1506
- if (mounts) {
1507
- for (const { path: mountPath, handler } of mounts) if (pathname === mountPath || pathname.startsWith(mountPath + "/")) {
1508
- const subPath = pathname.slice(mountPath.length) || "/";
1509
- if (await handler.handleRequest(req, res, subPath)) return;
1510
- }
1769
+ for (const { path: mountPath, handler } of mountList) if (pathname === mountPath || pathname.startsWith(mountPath + "/")) {
1770
+ const subPath = pathname.slice(mountPath.length) || "/";
1771
+ if (await handler.handleRequest(req, res, subPath)) return;
1511
1772
  }
1512
1773
  if (pathname === OLLAMA_CHAT_PATH && req.method === "POST") {
1513
1774
  try {
@@ -1602,15 +1863,15 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1602
1863
  }
1603
1864
  return;
1604
1865
  }
1605
- if (pathname === "/api/v1/models" && req.method === "GET") {
1866
+ if (pathname === OPENROUTER_MODELS_PATH && req.method === "GET") {
1606
1867
  handleOpenRouterModels(req, res, fixtures, journal, defaults, setCorsHeaders);
1607
1868
  return;
1608
1869
  }
1609
- if (pathname === "/api/v1/key" && req.method === "GET") {
1870
+ if (pathname === OPENROUTER_KEY_PATH && req.method === "GET") {
1610
1871
  handleOpenRouterKey(req, res, journal, setCorsHeaders);
1611
1872
  return;
1612
1873
  }
1613
- if (pathname === "/api/v1/credits" && req.method === "GET") {
1874
+ if (pathname === OPENROUTER_CREDITS_PATH && req.method === "GET") {
1614
1875
  handleOpenRouterCredits(req, res, journal, setCorsHeaders);
1615
1876
  return;
1616
1877
  }
@@ -1623,12 +1884,12 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1623
1884
  if (!azureDeploymentId) pathname = normalizeCompatPath(pathname, logger);
1624
1885
  if (pathname === HEALTH_PATH && req.method === "GET") {
1625
1886
  setCorsHeaders(res);
1626
- if (mounts && mounts.length > 0) {
1887
+ if (mountList.length > 0) {
1627
1888
  const services = { llm: {
1628
1889
  status: "ok",
1629
1890
  fixtures: fixtures.length
1630
1891
  } };
1631
- for (const { path: mountPath, handler } of mounts) if (handler.health) {
1892
+ for (const { path: mountPath, handler } of mountList) if (handler.health) {
1632
1893
  const name = mountPath.replace(/^\//, "");
1633
1894
  services[name] = handler.health();
1634
1895
  }
@@ -1649,7 +1910,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1649
1910
  res.end(JSON.stringify({ status: "ready" }));
1650
1911
  return;
1651
1912
  }
1652
- if (pathname === "/metrics" && req.method === "GET") {
1913
+ if (pathname === METRICS_PATH && req.method === "GET") {
1653
1914
  if (!registry) {
1654
1915
  handleNotFound(res, "Not found");
1655
1916
  return;
@@ -1797,14 +2058,140 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1797
2058
  }
1798
2059
  return;
1799
2060
  }
2061
+ const vsSearchMatch = pathname.match(VECTOR_STORES_SEARCH_RE);
2062
+ if (vsSearchMatch && req.method === "POST") {
2063
+ try {
2064
+ const raw = await readBody(req);
2065
+ await handleVectorStoresSearch(req, res, vsSearchMatch[1], raw, journal, defaults, setCorsHeaders);
2066
+ } catch (err) {
2067
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2068
+ }
2069
+ return;
2070
+ }
2071
+ const vsBatchFilesMatch = pathname.match(VECTOR_STORES_BATCH_FILES_RE);
2072
+ if (vsBatchFilesMatch && req.method === "GET") {
2073
+ try {
2074
+ await handleVectorFileBatchesFiles(req, res, vsBatchFilesMatch[1], vsBatchFilesMatch[2], journal, defaults, setCorsHeaders);
2075
+ } catch (err) {
2076
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2077
+ }
2078
+ return;
2079
+ }
2080
+ const vsBatchCancelMatch = pathname.match(VECTOR_STORES_BATCH_CANCEL_RE);
2081
+ if (vsBatchCancelMatch && req.method === "POST") {
2082
+ try {
2083
+ await readBody(req);
2084
+ await handleVectorFileBatchesCancel(req, res, vsBatchCancelMatch[1], vsBatchCancelMatch[2], journal, defaults, setCorsHeaders);
2085
+ } catch (err) {
2086
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2087
+ }
2088
+ return;
2089
+ }
2090
+ const vsBatchMatch = pathname.match(VECTOR_STORES_BATCH_RE);
2091
+ if (vsBatchMatch && req.method === "GET") {
2092
+ try {
2093
+ await handleVectorFileBatchesRetrieve(req, res, vsBatchMatch[1], vsBatchMatch[2], journal, defaults, setCorsHeaders);
2094
+ } catch (err) {
2095
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2096
+ }
2097
+ return;
2098
+ }
2099
+ const vsFileMatch = pathname.match(VECTOR_STORES_FILE_RE);
2100
+ if (vsFileMatch && req.method === "GET") {
2101
+ try {
2102
+ await handleVectorStoreFilesRetrieve(req, res, vsFileMatch[1], vsFileMatch[2], journal, defaults, setCorsHeaders);
2103
+ } catch (err) {
2104
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2105
+ }
2106
+ return;
2107
+ }
2108
+ if (vsFileMatch && req.method === "DELETE") {
2109
+ try {
2110
+ await handleVectorStoreFilesDelete(req, res, vsFileMatch[1], vsFileMatch[2], journal, defaults, setCorsHeaders);
2111
+ } catch (err) {
2112
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2113
+ }
2114
+ return;
2115
+ }
2116
+ const vsIdMatch = pathname.match(VECTOR_STORES_ID_RE);
2117
+ if (vsIdMatch && req.method === "GET") {
2118
+ try {
2119
+ await handleVectorStoresRetrieve(req, res, vsIdMatch[1], journal, defaults, setCorsHeaders);
2120
+ } catch (err) {
2121
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2122
+ }
2123
+ return;
2124
+ }
2125
+ if (vsIdMatch && req.method === "POST") {
2126
+ try {
2127
+ const raw = await readBody(req);
2128
+ await handleVectorStoresModify(req, res, vsIdMatch[1], raw, journal, defaults, setCorsHeaders);
2129
+ } catch (err) {
2130
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2131
+ }
2132
+ return;
2133
+ }
2134
+ if (vsIdMatch && req.method === "DELETE") {
2135
+ try {
2136
+ await handleVectorStoresDelete(req, res, vsIdMatch[1], journal, defaults, setCorsHeaders);
2137
+ } catch (err) {
2138
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2139
+ }
2140
+ return;
2141
+ }
2142
+ if (VECTOR_STORES_FILES_RE.test(pathname) && req.method === "POST") {
2143
+ try {
2144
+ const raw = await readBody(req);
2145
+ const storeId = pathname.split("/")[3];
2146
+ await handleVectorStoreFilesCreate(req, res, storeId, raw, journal, defaults, setCorsHeaders);
2147
+ } catch (err) {
2148
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2149
+ }
2150
+ return;
2151
+ }
2152
+ if (VECTOR_STORES_FILES_RE.test(pathname) && req.method === "GET") {
2153
+ try {
2154
+ const storeId = pathname.split("/")[3];
2155
+ await handleVectorStoreFilesList(req, res, storeId, journal, defaults, setCorsHeaders);
2156
+ } catch (err) {
2157
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2158
+ }
2159
+ return;
2160
+ }
2161
+ if (VECTOR_STORES_FILE_BATCHES_RE.test(pathname) && req.method === "POST") {
2162
+ try {
2163
+ const raw = await readBody(req);
2164
+ const storeId = pathname.split("/")[3];
2165
+ await handleVectorFileBatchesCreate(req, res, storeId, raw, journal, defaults, setCorsHeaders);
2166
+ } catch (err) {
2167
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2168
+ }
2169
+ return;
2170
+ }
2171
+ if (pathname === VECTOR_STORES_PATH && req.method === "GET") {
2172
+ try {
2173
+ await handleVectorStoresList(req, res, journal, defaults, setCorsHeaders);
2174
+ } catch (err) {
2175
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2176
+ }
2177
+ return;
2178
+ }
2179
+ if (pathname === VECTOR_STORES_PATH && req.method === "POST") {
2180
+ try {
2181
+ await handleVectorStoresCreate(req, res, await readBody(req), journal, defaults, setCorsHeaders);
2182
+ } catch (err) {
2183
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2184
+ }
2185
+ return;
2186
+ }
1800
2187
  if (pathname === REQUESTS_PATH) {
1801
2188
  setCorsHeaders(res);
1802
2189
  if (req.method === "GET") {
1803
2190
  const limitParam = parsedUrl.searchParams.get("limit");
1804
2191
  let opts;
1805
2192
  if (limitParam) {
1806
- const limit = parseInt(limitParam, 10);
1807
- if (Number.isNaN(limit) || limit <= 0) {
2193
+ const limit = Number(limitParam);
2194
+ if (!/^\+?\d+$/.test(limitParam.trim()) || !Number.isSafeInteger(limit) || limit <= 0) {
1808
2195
  writeErrorResponse(res, 400, JSON.stringify({ error: {
1809
2196
  message: `Invalid limit parameter: "${limitParam}"`,
1810
2197
  type: "invalid_request_error"
@@ -1873,7 +2260,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1873
2260
  } catch (err) {
1874
2261
  if (!(err instanceof SyntaxError)) defaults.logger.error(`Unexpected error in Azure model injection: ${err instanceof Error ? err.message : String(err)}`);
1875
2262
  }
1876
- await handleEmbeddings(req, res, raw, fixtures, journal, defaults, setCorsHeaders, embeddingsProvider);
2263
+ await handleEmbeddings(req, res, raw, fixtures, journal, defaults, setCorsHeaders, embeddingsProvider, originalPathname === EMBEDDINGS_PATH);
1877
2264
  } catch (err) {
1878
2265
  routeError(req, res, err, pathname);
1879
2266
  }
@@ -2244,6 +2631,8 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2244
2631
  return;
2245
2632
  }
2246
2633
  if (pathname !== COMPLETIONS_PATH) {
2634
+ const catalogMatch = matchRouteDefinition(req.method ?? "", pathname);
2635
+ if (catalogMatch && !(pathname === METRICS_PATH && !registry)) defaults.logger.warn(`Route drift: ${req.method ?? "?"} ${pathname} matches the route catalog (${catalogMatch.method} ${catalogMatch.path} — ${catalogMatch.description}) but no dispatcher branch served it. The catalog entry or the dispatcher is stale — see route-registry.ts.`);
2247
2636
  handleNotFound(res, "Not found");
2248
2637
  return;
2249
2638
  }
@@ -2323,17 +2712,23 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2323
2712
  server.emit("connection", socket);
2324
2713
  return;
2325
2714
  }
2326
- const parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
2715
+ let parsedUrl;
2716
+ try {
2717
+ parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
2718
+ } catch (err) {
2719
+ const msg = err instanceof Error ? err.message : "Internal error";
2720
+ defaults.logger.warn(`Unhandled upgrade error: ${msg}`);
2721
+ socket.end("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n");
2722
+ return;
2723
+ }
2327
2724
  let pathname = parsedUrl.pathname;
2328
2725
  if (!validateRequestApiKey(req, resolvedAuth.policy).ok) {
2329
2726
  writeApiKeyUpgradeRejection(socket);
2330
2727
  return;
2331
2728
  }
2332
- if (mounts) {
2333
- for (const { path: mountPath, handler } of mounts) if ((pathname === mountPath || pathname.startsWith(mountPath + "/")) && handler.handleUpgrade) {
2334
- const subPath = pathname.slice(mountPath.length) || "/";
2335
- if (await handler.handleUpgrade(socket, head, subPath)) return;
2336
- }
2729
+ for (const { path: mountPath, handler } of mountList) if ((pathname === mountPath || pathname.startsWith(mountPath + "/")) && handler.handleUpgrade) {
2730
+ const subPath = pathname.slice(mountPath.length) || "/";
2731
+ if (await handler.handleUpgrade(socket, head, subPath)) return;
2337
2732
  }
2338
2733
  if (!pathname.match(AZURE_DEPLOYMENT_RE)) pathname = normalizeCompatPath(pathname, logger);
2339
2734
  if (pathname !== RESPONSES_PATH && pathname !== REALTIME_PATH && pathname !== GEMINI_LIVE_PATH && pathname !== LIVE_PATH) {
@@ -2443,9 +2838,13 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2443
2838
  return;
2444
2839
  }
2445
2840
  const url = `http://${addr.address}:${addr.port}`;
2446
- if (mounts) {
2447
- for (const { path: mountPath, handler } of mounts) if (handler.setBaseUrl) handler.setBaseUrl(url + mountPath);
2841
+ try {
2842
+ commitHandOff();
2843
+ } catch (err) {
2844
+ server.close(() => reject(err));
2845
+ return;
2448
2846
  }
2847
+ for (const { path: mountPath, handler } of mountList) if (handler.setBaseUrl) handler.setBaseUrl(url + mountPath);
2449
2848
  resolve({
2450
2849
  closeLiveSessions,
2451
2850
  server,