@copilotkit/aimock 1.42.1 → 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 (442) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +65 -0
  4. package/README.md +108 -16
  5. package/dist/agui-handler.d.ts.map +1 -1
  6. package/dist/agui-mock.cjs +1 -1
  7. package/dist/agui-mock.cjs.map +1 -1
  8. package/dist/agui-mock.d.cts.map +1 -1
  9. package/dist/agui-mock.d.ts.map +1 -1
  10. package/dist/agui-mock.js +1 -1
  11. package/dist/agui-mock.js.map +1 -1
  12. package/dist/agui-recorder.cjs +33 -11
  13. package/dist/agui-recorder.cjs.map +1 -1
  14. package/dist/agui-recorder.d.cts +10 -1
  15. package/dist/agui-recorder.d.cts.map +1 -1
  16. package/dist/agui-recorder.d.ts +10 -1
  17. package/dist/agui-recorder.d.ts.map +1 -1
  18. package/dist/agui-recorder.js +33 -11
  19. package/dist/agui-recorder.js.map +1 -1
  20. package/dist/agui-types.d.cts +1 -0
  21. package/dist/agui-types.d.cts.map +1 -1
  22. package/dist/agui-types.d.ts +1 -0
  23. package/dist/agui-types.d.ts.map +1 -1
  24. package/dist/aimock-cli.cjs +38 -10
  25. package/dist/aimock-cli.cjs.map +1 -1
  26. package/dist/aimock-cli.d.cts +5 -0
  27. package/dist/aimock-cli.d.cts.map +1 -1
  28. package/dist/aimock-cli.d.ts +5 -0
  29. package/dist/aimock-cli.d.ts.map +1 -1
  30. package/dist/aimock-cli.js +38 -10
  31. package/dist/aimock-cli.js.map +1 -1
  32. package/dist/batches.cjs +458 -0
  33. package/dist/batches.cjs.map +1 -0
  34. package/dist/batches.d.cts +57 -0
  35. package/dist/batches.d.cts.map +1 -0
  36. package/dist/batches.d.ts +57 -0
  37. package/dist/batches.d.ts.map +1 -0
  38. package/dist/batches.js +453 -0
  39. package/dist/batches.js.map +1 -0
  40. package/dist/bedrock-converse.cjs +44 -12
  41. package/dist/bedrock-converse.cjs.map +1 -1
  42. package/dist/bedrock-converse.d.cts.map +1 -1
  43. package/dist/bedrock-converse.d.ts.map +1 -1
  44. package/dist/bedrock-converse.js +46 -14
  45. package/dist/bedrock-converse.js.map +1 -1
  46. package/dist/bedrock.cjs +10 -8
  47. package/dist/bedrock.cjs.map +1 -1
  48. package/dist/bedrock.d.cts.map +1 -1
  49. package/dist/bedrock.d.ts.map +1 -1
  50. package/dist/bedrock.js +12 -10
  51. package/dist/bedrock.js.map +1 -1
  52. package/dist/byteplus-video.cjs +3 -3
  53. package/dist/byteplus-video.cjs.map +1 -1
  54. package/dist/byteplus-video.js +4 -4
  55. package/dist/byteplus-video.js.map +1 -1
  56. package/dist/chaos.cjs +625 -72
  57. package/dist/chaos.cjs.map +1 -1
  58. package/dist/chaos.d.cts +164 -7
  59. package/dist/chaos.d.cts.map +1 -1
  60. package/dist/chaos.d.ts +164 -7
  61. package/dist/chaos.d.ts.map +1 -1
  62. package/dist/chaos.js +616 -74
  63. package/dist/chaos.js.map +1 -1
  64. package/dist/cli.cjs +131 -30
  65. package/dist/cli.cjs.map +1 -1
  66. package/dist/cli.js +132 -31
  67. package/dist/cli.js.map +1 -1
  68. package/dist/cohere.cjs +37 -9
  69. package/dist/cohere.cjs.map +1 -1
  70. package/dist/cohere.d.cts.map +1 -1
  71. package/dist/cohere.d.ts.map +1 -1
  72. package/dist/cohere.js +39 -11
  73. package/dist/cohere.js.map +1 -1
  74. package/dist/config-loader.cjs +1 -1
  75. package/dist/config-loader.js +1 -1
  76. package/dist/constants.cjs +8 -0
  77. package/dist/constants.cjs.map +1 -1
  78. package/dist/constants.d.cts +6 -2
  79. package/dist/constants.d.ts +6 -2
  80. package/dist/constants.js +8 -1
  81. package/dist/constants.js.map +1 -1
  82. package/dist/convert.cjs +1 -1
  83. package/dist/convert.cjs.map +1 -1
  84. package/dist/convert.js +1 -1
  85. package/dist/convert.js.map +1 -1
  86. package/dist/echo-text.cjs +65 -0
  87. package/dist/echo-text.cjs.map +1 -0
  88. package/dist/echo-text.d.cts +22 -0
  89. package/dist/echo-text.d.cts.map +1 -0
  90. package/dist/echo-text.d.ts +22 -0
  91. package/dist/echo-text.d.ts.map +1 -0
  92. package/dist/echo-text.js +65 -0
  93. package/dist/echo-text.js.map +1 -0
  94. package/dist/elevenlabs-audio.cjs +3 -3
  95. package/dist/elevenlabs-audio.cjs.map +1 -1
  96. package/dist/elevenlabs-audio.js +4 -4
  97. package/dist/elevenlabs-audio.js.map +1 -1
  98. package/dist/elevenlabs-voice.cjs +1053 -0
  99. package/dist/elevenlabs-voice.cjs.map +1 -0
  100. package/dist/elevenlabs-voice.d.cts +72 -0
  101. package/dist/elevenlabs-voice.d.cts.map +1 -0
  102. package/dist/elevenlabs-voice.d.ts +72 -0
  103. package/dist/elevenlabs-voice.d.ts.map +1 -0
  104. package/dist/elevenlabs-voice.js +1048 -0
  105. package/dist/elevenlabs-voice.js.map +1 -0
  106. package/dist/embeddings.cjs +26 -5
  107. package/dist/embeddings.cjs.map +1 -1
  108. package/dist/embeddings.d.cts +1 -1
  109. package/dist/embeddings.d.cts.map +1 -1
  110. package/dist/embeddings.d.ts +1 -1
  111. package/dist/embeddings.d.ts.map +1 -1
  112. package/dist/embeddings.js +27 -6
  113. package/dist/embeddings.js.map +1 -1
  114. package/dist/fal-audio.cjs +3 -3
  115. package/dist/fal-audio.cjs.map +1 -1
  116. package/dist/fal-audio.js +4 -4
  117. package/dist/fal-audio.js.map +1 -1
  118. package/dist/fal.cjs +28 -4
  119. package/dist/fal.cjs.map +1 -1
  120. package/dist/fal.d.cts +16 -1
  121. package/dist/fal.d.cts.map +1 -1
  122. package/dist/fal.d.ts +16 -1
  123. package/dist/fal.d.ts.map +1 -1
  124. package/dist/fal.js +28 -5
  125. package/dist/fal.js.map +1 -1
  126. package/dist/files.cjs +1069 -0
  127. package/dist/files.cjs.map +1 -0
  128. package/dist/files.d.cts +68 -0
  129. package/dist/files.d.cts.map +1 -0
  130. package/dist/files.d.ts +68 -0
  131. package/dist/files.d.ts.map +1 -0
  132. package/dist/files.js +1056 -0
  133. package/dist/files.js.map +1 -0
  134. package/dist/fine-tuning.cjs +1033 -0
  135. package/dist/fine-tuning.cjs.map +1 -0
  136. package/dist/fine-tuning.d.cts +96 -0
  137. package/dist/fine-tuning.d.cts.map +1 -0
  138. package/dist/fine-tuning.d.ts +96 -0
  139. package/dist/fine-tuning.d.ts.map +1 -0
  140. package/dist/fine-tuning.js +1026 -0
  141. package/dist/fine-tuning.js.map +1 -0
  142. package/dist/fixture-loader-services.cjs +116 -0
  143. package/dist/fixture-loader-services.cjs.map +1 -0
  144. package/dist/fixture-loader-services.d.cts +48 -0
  145. package/dist/fixture-loader-services.d.cts.map +1 -0
  146. package/dist/fixture-loader-services.d.ts +48 -0
  147. package/dist/fixture-loader-services.d.ts.map +1 -0
  148. package/dist/fixture-loader-services.js +114 -0
  149. package/dist/fixture-loader-services.js.map +1 -0
  150. package/dist/fixture-loader.cjs +0 -0
  151. package/dist/fixture-loader.cjs.map +1 -1
  152. package/dist/fixture-loader.d.cts +131 -5
  153. package/dist/fixture-loader.d.cts.map +1 -1
  154. package/dist/fixture-loader.d.ts +131 -5
  155. package/dist/fixture-loader.d.ts.map +1 -1
  156. package/dist/fixture-loader.js +0 -0
  157. package/dist/fixture-loader.js.map +1 -1
  158. package/dist/gemini-embeddings.cjs +44 -4
  159. package/dist/gemini-embeddings.cjs.map +1 -1
  160. package/dist/gemini-embeddings.js +46 -6
  161. package/dist/gemini-embeddings.js.map +1 -1
  162. package/dist/gemini-interactions.cjs +51 -7
  163. package/dist/gemini-interactions.cjs.map +1 -1
  164. package/dist/gemini-interactions.d.cts.map +1 -1
  165. package/dist/gemini-interactions.d.ts.map +1 -1
  166. package/dist/gemini-interactions.js +53 -9
  167. package/dist/gemini-interactions.js.map +1 -1
  168. package/dist/gemini.cjs +19 -8
  169. package/dist/gemini.cjs.map +1 -1
  170. package/dist/gemini.d.cts.map +1 -1
  171. package/dist/gemini.d.ts.map +1 -1
  172. package/dist/gemini.js +21 -10
  173. package/dist/gemini.js.map +1 -1
  174. package/dist/grok-video.cjs +14 -14
  175. package/dist/grok-video.cjs.map +1 -1
  176. package/dist/grok-video.js +15 -15
  177. package/dist/grok-video.js.map +1 -1
  178. package/dist/helpers.cjs +497 -10
  179. package/dist/helpers.cjs.map +1 -1
  180. package/dist/helpers.d.cts +38 -2
  181. package/dist/helpers.d.cts.map +1 -1
  182. package/dist/helpers.d.ts +38 -2
  183. package/dist/helpers.d.ts.map +1 -1
  184. package/dist/helpers.js +487 -12
  185. package/dist/helpers.js.map +1 -1
  186. package/dist/images.cjs +57 -125
  187. package/dist/images.cjs.map +1 -1
  188. package/dist/images.d.cts.map +1 -1
  189. package/dist/images.d.ts.map +1 -1
  190. package/dist/images.js +58 -126
  191. package/dist/images.js.map +1 -1
  192. package/dist/index.cjs +71 -9
  193. package/dist/index.d.cts +17 -7
  194. package/dist/index.d.ts +17 -7
  195. package/dist/index.js +21 -11
  196. package/dist/jest.cjs +24 -12
  197. package/dist/jest.cjs.map +1 -1
  198. package/dist/jest.d.cts +6 -3
  199. package/dist/jest.d.cts.map +1 -1
  200. package/dist/jest.d.ts +6 -3
  201. package/dist/jest.d.ts.map +1 -1
  202. package/dist/jest.js +25 -13
  203. package/dist/jest.js.map +1 -1
  204. package/dist/journal.cjs +61 -4
  205. package/dist/journal.cjs.map +1 -1
  206. package/dist/journal.d.cts +60 -3
  207. package/dist/journal.d.cts.map +1 -1
  208. package/dist/journal.d.ts +60 -3
  209. package/dist/journal.d.ts.map +1 -1
  210. package/dist/journal.js +61 -5
  211. package/dist/journal.js.map +1 -1
  212. package/dist/live-fixture.cjs +1192 -0
  213. package/dist/live-fixture.cjs.map +1 -0
  214. package/dist/live-fixture.js +1187 -0
  215. package/dist/live-fixture.js.map +1 -0
  216. package/dist/live-recorder.cjs +256 -0
  217. package/dist/live-recorder.cjs.map +1 -0
  218. package/dist/live-recorder.js +254 -0
  219. package/dist/live-recorder.js.map +1 -0
  220. package/dist/live-replay.cjs +286 -0
  221. package/dist/live-replay.cjs.map +1 -0
  222. package/dist/live-replay.js +285 -0
  223. package/dist/live-replay.js.map +1 -0
  224. package/dist/live-sanitize.cjs +196 -0
  225. package/dist/live-sanitize.cjs.map +1 -0
  226. package/dist/live-sanitize.js +196 -0
  227. package/dist/live-sanitize.js.map +1 -0
  228. package/dist/live-types.d.cts +71 -0
  229. package/dist/live-types.d.cts.map +1 -0
  230. package/dist/live-types.d.ts +71 -0
  231. package/dist/live-types.d.ts.map +1 -0
  232. package/dist/llmock.cjs +132 -49
  233. package/dist/llmock.cjs.map +1 -1
  234. package/dist/llmock.d.cts +51 -5
  235. package/dist/llmock.d.cts.map +1 -1
  236. package/dist/llmock.d.ts +51 -5
  237. package/dist/llmock.d.ts.map +1 -1
  238. package/dist/llmock.js +133 -50
  239. package/dist/llmock.js.map +1 -1
  240. package/dist/mcp-fakes-mount.cjs +216 -0
  241. package/dist/mcp-fakes-mount.cjs.map +1 -0
  242. package/dist/mcp-fakes-mount.js +210 -0
  243. package/dist/mcp-fakes-mount.js.map +1 -0
  244. package/dist/mcp-fakes.cjs +1619 -0
  245. package/dist/mcp-fakes.cjs.map +1 -0
  246. package/dist/mcp-fakes.d.cts +101 -0
  247. package/dist/mcp-fakes.d.cts.map +1 -0
  248. package/dist/mcp-fakes.d.ts +101 -0
  249. package/dist/mcp-fakes.d.ts.map +1 -0
  250. package/dist/mcp-fakes.js +1614 -0
  251. package/dist/mcp-fakes.js.map +1 -0
  252. package/dist/mcp-handler.cjs +331 -17
  253. package/dist/mcp-handler.cjs.map +1 -1
  254. package/dist/mcp-handler.js +330 -18
  255. package/dist/mcp-handler.js.map +1 -1
  256. package/dist/mcp-mock.cjs +217 -49
  257. package/dist/mcp-mock.cjs.map +1 -1
  258. package/dist/mcp-mock.d.cts +66 -1
  259. package/dist/mcp-mock.d.cts.map +1 -1
  260. package/dist/mcp-mock.d.ts +66 -1
  261. package/dist/mcp-mock.d.ts.map +1 -1
  262. package/dist/mcp-mock.js +218 -50
  263. package/dist/mcp-mock.js.map +1 -1
  264. package/dist/mcp-stub.cjs +5 -1
  265. package/dist/mcp-stub.d.cts +3 -1
  266. package/dist/mcp-stub.d.ts +3 -1
  267. package/dist/mcp-stub.js +3 -1
  268. package/dist/mcp-types.d.cts +6 -0
  269. package/dist/mcp-types.d.ts +6 -0
  270. package/dist/message-text.cjs +192 -0
  271. package/dist/message-text.cjs.map +1 -0
  272. package/dist/message-text.d.cts +31 -0
  273. package/dist/message-text.d.cts.map +1 -0
  274. package/dist/message-text.d.ts +31 -0
  275. package/dist/message-text.d.ts.map +1 -0
  276. package/dist/message-text.js +184 -0
  277. package/dist/message-text.js.map +1 -0
  278. package/dist/messages.cjs +49 -7
  279. package/dist/messages.cjs.map +1 -1
  280. package/dist/messages.d.cts.map +1 -1
  281. package/dist/messages.d.ts.map +1 -1
  282. package/dist/messages.js +51 -9
  283. package/dist/messages.js.map +1 -1
  284. package/dist/metrics.cjs +278 -45
  285. package/dist/metrics.cjs.map +1 -1
  286. package/dist/metrics.d.cts +17 -7
  287. package/dist/metrics.d.cts.map +1 -1
  288. package/dist/metrics.d.ts +17 -7
  289. package/dist/metrics.d.ts.map +1 -1
  290. package/dist/metrics.js +270 -29
  291. package/dist/metrics.js.map +1 -1
  292. package/dist/moderation.cjs +10 -1
  293. package/dist/moderation.cjs.map +1 -1
  294. package/dist/moderation.d.cts.map +1 -1
  295. package/dist/moderation.d.ts.map +1 -1
  296. package/dist/moderation.js +10 -1
  297. package/dist/moderation.js.map +1 -1
  298. package/dist/ollama.cjs +10 -6
  299. package/dist/ollama.cjs.map +1 -1
  300. package/dist/ollama.d.cts.map +1 -1
  301. package/dist/ollama.d.ts.map +1 -1
  302. package/dist/ollama.js +11 -7
  303. package/dist/ollama.js.map +1 -1
  304. package/dist/openapi.cjs +3405 -0
  305. package/dist/openapi.cjs.map +1 -0
  306. package/dist/openapi.d.cts +30 -0
  307. package/dist/openapi.d.cts.map +1 -0
  308. package/dist/openapi.d.ts +30 -0
  309. package/dist/openapi.d.ts.map +1 -0
  310. package/dist/openapi.js +3404 -0
  311. package/dist/openapi.js.map +1 -0
  312. package/dist/openrouter-video.cjs +9 -9
  313. package/dist/openrouter-video.cjs.map +1 -1
  314. package/dist/openrouter-video.d.cts.map +1 -1
  315. package/dist/openrouter-video.d.ts.map +1 -1
  316. package/dist/openrouter-video.js +10 -10
  317. package/dist/openrouter-video.js.map +1 -1
  318. package/dist/recorder.cjs +54 -10
  319. package/dist/recorder.cjs.map +1 -1
  320. package/dist/recorder.d.cts.map +1 -1
  321. package/dist/recorder.d.ts.map +1 -1
  322. package/dist/recorder.js +52 -12
  323. package/dist/recorder.js.map +1 -1
  324. package/dist/responses.cjs +47 -7
  325. package/dist/responses.cjs.map +1 -1
  326. package/dist/responses.d.cts.map +1 -1
  327. package/dist/responses.d.ts.map +1 -1
  328. package/dist/responses.js +48 -9
  329. package/dist/responses.js.map +1 -1
  330. package/dist/route-registry.cjs +951 -0
  331. package/dist/route-registry.cjs.map +1 -0
  332. package/dist/route-registry.d.cts +33 -0
  333. package/dist/route-registry.d.cts.map +1 -0
  334. package/dist/route-registry.d.ts +33 -0
  335. package/dist/route-registry.d.ts.map +1 -0
  336. package/dist/route-registry.js +866 -0
  337. package/dist/route-registry.js.map +1 -0
  338. package/dist/router.cjs +21 -34
  339. package/dist/router.cjs.map +1 -1
  340. package/dist/router.d.cts +4 -1
  341. package/dist/router.d.cts.map +1 -1
  342. package/dist/router.d.ts +4 -1
  343. package/dist/router.d.ts.map +1 -1
  344. package/dist/router.js +21 -35
  345. package/dist/router.js.map +1 -1
  346. package/dist/server.cjs +1252 -777
  347. package/dist/server.cjs.map +1 -1
  348. package/dist/server.d.cts +5 -1
  349. package/dist/server.d.cts.map +1 -1
  350. package/dist/server.d.ts +5 -1
  351. package/dist/server.d.ts.map +1 -1
  352. package/dist/server.js +1185 -710
  353. package/dist/server.js.map +1 -1
  354. package/dist/speech.cjs +6 -4
  355. package/dist/speech.cjs.map +1 -1
  356. package/dist/speech.js +7 -5
  357. package/dist/speech.js.map +1 -1
  358. package/dist/suite.cjs +1 -1
  359. package/dist/suite.js +1 -1
  360. package/dist/transcription.cjs +6 -6
  361. package/dist/transcription.cjs.map +1 -1
  362. package/dist/transcription.d.cts.map +1 -1
  363. package/dist/transcription.d.ts.map +1 -1
  364. package/dist/transcription.js +7 -7
  365. package/dist/transcription.js.map +1 -1
  366. package/dist/types.cjs +18 -0
  367. package/dist/types.cjs.map +1 -0
  368. package/dist/types.d.cts +320 -17
  369. package/dist/types.d.cts.map +1 -1
  370. package/dist/types.d.ts +320 -17
  371. package/dist/types.d.ts.map +1 -1
  372. package/dist/types.js +17 -0
  373. package/dist/types.js.map +1 -0
  374. package/dist/validate-cli.cjs +983 -0
  375. package/dist/validate-cli.cjs.map +1 -0
  376. package/dist/validate-cli.js +982 -0
  377. package/dist/validate-cli.js.map +1 -0
  378. package/dist/vector-stores.cjs +1180 -0
  379. package/dist/vector-stores.cjs.map +1 -0
  380. package/dist/vector-stores.d.cts +106 -0
  381. package/dist/vector-stores.d.cts.map +1 -0
  382. package/dist/vector-stores.d.ts +106 -0
  383. package/dist/vector-stores.d.ts.map +1 -0
  384. package/dist/vector-stores.js +1164 -0
  385. package/dist/vector-stores.js.map +1 -0
  386. package/dist/vector-types.d.cts.map +1 -1
  387. package/dist/vector-types.d.ts.map +1 -1
  388. package/dist/veo-video.cjs +13 -13
  389. package/dist/veo-video.cjs.map +1 -1
  390. package/dist/veo-video.js +14 -14
  391. package/dist/veo-video.js.map +1 -1
  392. package/dist/video.cjs +23 -4
  393. package/dist/video.cjs.map +1 -1
  394. package/dist/video.d.cts +1 -1
  395. package/dist/video.d.cts.map +1 -1
  396. package/dist/video.d.ts +1 -1
  397. package/dist/video.d.ts.map +1 -1
  398. package/dist/video.js +24 -5
  399. package/dist/video.js.map +1 -1
  400. package/dist/vitest.cjs +23 -11
  401. package/dist/vitest.cjs.map +1 -1
  402. package/dist/vitest.d.cts +5 -2
  403. package/dist/vitest.d.cts.map +1 -1
  404. package/dist/vitest.d.ts +5 -2
  405. package/dist/vitest.d.ts.map +1 -1
  406. package/dist/vitest.js +25 -13
  407. package/dist/vitest.js.map +1 -1
  408. package/dist/watcher.cjs +2 -1
  409. package/dist/watcher.cjs.map +1 -1
  410. package/dist/watcher.js +2 -1
  411. package/dist/watcher.js.map +1 -1
  412. package/dist/ws-framing.cjs +150 -35
  413. package/dist/ws-framing.cjs.map +1 -1
  414. package/dist/ws-framing.d.cts +18 -3
  415. package/dist/ws-framing.d.cts.map +1 -1
  416. package/dist/ws-framing.d.ts +18 -3
  417. package/dist/ws-framing.d.ts.map +1 -1
  418. package/dist/ws-framing.js +151 -36
  419. package/dist/ws-framing.js.map +1 -1
  420. package/dist/ws-gemini-live.cjs +2 -2
  421. package/dist/ws-gemini-live.js +2 -2
  422. package/dist/ws-live.cjs +401 -0
  423. package/dist/ws-live.cjs.map +1 -0
  424. package/dist/ws-live.js +400 -0
  425. package/dist/ws-live.js.map +1 -0
  426. package/dist/ws-realtime.cjs +46 -2
  427. package/dist/ws-realtime.cjs.map +1 -1
  428. package/dist/ws-realtime.d.cts.map +1 -1
  429. package/dist/ws-realtime.d.ts.map +1 -1
  430. package/dist/ws-realtime.js +46 -2
  431. package/dist/ws-realtime.js.map +1 -1
  432. package/dist/ws-responses.cjs +16 -4
  433. package/dist/ws-responses.cjs.map +1 -1
  434. package/dist/ws-responses.js +16 -4
  435. package/dist/ws-responses.js.map +1 -1
  436. package/dist/ws-upstream.cjs +118 -0
  437. package/dist/ws-upstream.cjs.map +1 -0
  438. package/dist/ws-upstream.js +115 -0
  439. package/dist/ws-upstream.js.map +1 -0
  440. package/fixtures/openai-live-client.json +390 -0
  441. package/fixtures/openai-live-managed.json +709 -0
  442. package/package.json +4 -1
@@ -0,0 +1,1619 @@
1
+ const require_constants = require('./constants.cjs');
2
+ const require_message_text = require('./message-text.cjs');
3
+ const require_fixture_loader = require('./fixture-loader.cjs');
4
+
5
+ //#region src/mcp-fakes.ts
6
+ /**
7
+ * MCP fakes: the fake model core.
8
+ *
9
+ * Pure module, no I/O. It owns block validation (the bad-block cases (a)-(j)
10
+ * listed in `MCP_FAKES_LOAD_RULES`), entry ids and the per-mount counters in
11
+ * them, block-id and entry-id uniqueness per mount, scope matching, scope
12
+ * tiers, argument equality, the synchronous claim step with its mismatch and
13
+ * exhausted details (`firstDifference`), consumption state per test id with
14
+ * its FIFO cap, the undeclared-tool policy, and the fake tools for
15
+ * `tools/list`. One `McpFakeStore` serves one MCP mount.
16
+ */
17
+ /** Default `mount` path of a block. */
18
+ const MCP_FAKES_DEFAULT_MOUNT = "/mcp";
19
+ /**
20
+ * Name of the consumption state shared by requests that supply no test id:
21
+ * the journal's `DEFAULT_TEST_ID`. The store keeps that state apart from an
22
+ * explicitly supplied test id equal to this string;
23
+ * `resetState(MCP_FAKES_DEFAULT_TEST_ID)` resets both.
24
+ */
25
+ const MCP_FAKES_DEFAULT_TEST_ID = require_constants.DEFAULT_TEST_ID;
26
+ /**
27
+ * Default FIFO cap on distinct test ids (`McpFakeStoreOptions.maxTestIds`),
28
+ * for a mount with no journal. A mount with a journal mirrors the journal's
29
+ * effective cap (`fixtureCountsMaxTestIdsCap`): 500 under `createServer` unless
30
+ * the option is set, and `0` (unbounded) for a bare `Journal` or an explicit `0`.
31
+ */
32
+ const MCP_FAKES_DEFAULT_MAX_TEST_IDS = 500;
33
+ /**
34
+ * Deepest nesting of objects and arrays accepted in an `mcpFakes` value: the
35
+ * value itself (a block, or the array of blocks) is level 1. A container
36
+ * below this level is not copied; the input snapshot holds a "nested deeper
37
+ * than 64 levels" value there, which the check of the enclosing field reports
38
+ * as its own bad block.
39
+ */
40
+ const MCP_FAKES_MAX_DEPTH = 64;
41
+ /** The reason a container below `MCP_FAKES_MAX_DEPTH` levels is not copied. */
42
+ const DEPTH_REASON = require_message_text.msg`nested deeper than ${MCP_FAKES_MAX_DEPTH} levels`;
43
+ /** Longest `firstDifference` text (path plus both values), cut the same way. */
44
+ const DIFF_LIMIT = 1e3;
45
+ const BLOCK_KEYS = new Set([
46
+ "scope",
47
+ "mount",
48
+ "undeclaredTools",
49
+ "tools"
50
+ ]);
51
+ const TOOL_KEYS = new Set([
52
+ "name",
53
+ "description",
54
+ "inputSchema",
55
+ "calls"
56
+ ]);
57
+ const CALL_KEYS = new Set([
58
+ "args",
59
+ "anyArgs",
60
+ "result",
61
+ "error",
62
+ "id"
63
+ ]);
64
+ const SCOPE_KEYS = new Set(["testId", "context"]);
65
+ const TIER_ORDER = [
66
+ "testId+context",
67
+ "testId",
68
+ "context",
69
+ "shared"
70
+ ];
71
+ /**
72
+ * The error a failed add throws. It is a `FixtureLoadError` whose `rule`,
73
+ * `file`, `blockId` and `entryId` are those of the first error, and whose
74
+ * `message` is the first error's message plus the count of the others.
75
+ * `errors` holds every error found in the input, in order (the first
76
+ * included; see `validateBlock` for the checks that stop early), and
77
+ * `warnings` the shadowed-entry warnings of the blocks that passed every
78
+ * check. Both are frozen, non-writable arrays; `toJSON()` returns copies.
79
+ */
80
+ var McpFakesAddError = class extends require_fixture_loader.FixtureLoadError {
81
+ name = "McpFakesAddError";
82
+ constructor(errors, warnings) {
83
+ const first = errors[0];
84
+ if (!first) throw new TypeError(require_message_text.build(require_message_text.msg`McpFakesAddError needs at least one error`));
85
+ super({
86
+ rule: first.rule,
87
+ file: first.file,
88
+ blockId: first.blockId,
89
+ entryId: first.entryId,
90
+ detail: ""
91
+ });
92
+ const more = errors.length - 1;
93
+ this.message = more > 0 ? require_message_text.build(require_message_text.msg`${require_message_text.fixed(first.message)} (and ${more} more ${require_message_text.fixed(more === 1 ? "error" : "errors")})`) : first.message;
94
+ Object.defineProperty(this, "errors", {
95
+ value: Object.freeze([...errors]),
96
+ enumerable: true,
97
+ writable: false
98
+ });
99
+ Object.defineProperty(this, "warnings", {
100
+ value: Object.freeze(warnings.map((w) => Object.freeze({ ...w }))),
101
+ enumerable: true,
102
+ writable: false
103
+ });
104
+ }
105
+ toJSON() {
106
+ return {
107
+ ...super.toJSON(),
108
+ name: this.name,
109
+ errors: this.errors.map((e) => e.toJSON()),
110
+ warnings: this.warnings.map((w) => ({ ...w }))
111
+ };
112
+ }
113
+ };
114
+ /** An object whose prototype is `Object.prototype` or `null` (not an array). */
115
+ function isPlainObject(value) {
116
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
117
+ const proto = Object.getPrototypeOf(value);
118
+ return proto === Object.prototype || proto === null;
119
+ }
120
+ /**
121
+ * The one own-key listing for input data: own, enumerable, string keys.
122
+ * Inherited names (`constructor`, `toString`, `__proto__`) are never listed.
123
+ *
124
+ * @internal
125
+ */
126
+ function ownKeys(obj) {
127
+ return Object.keys(obj);
128
+ }
129
+ /**
130
+ * The one key check: `key` is an own property of `obj`. Unlike `key in obj`,
131
+ * it never sees the prototype chain, so `hasOwnKey({}, "constructor")` is
132
+ * false.
133
+ *
134
+ * @internal
135
+ */
136
+ function hasOwnKey(obj, key) {
137
+ return Object.prototype.hasOwnProperty.call(obj, key);
138
+ }
139
+ /**
140
+ * A user value as one message part: a string is JSON-quoted (quotes and
141
+ * control chars escaped), any other value is rendered as JSON (or as
142
+ * `undefined`, `NaN`, `10n`, ... where JSON cannot at the top level), and a
143
+ * snapshot placeholder, a nested bigint, a cycle, a container too deep, a
144
+ * symbol or function, and a value whose reading throws each as a `<reason>`
145
+ * placeholder whose user text is quoted (see `render`). The builder cuts it
146
+ * once, to its share and to no more than its max, never inside a JSON escape
147
+ * (`\n`, `\u0001`). Whatever the caller's code throws is caught; an error of
148
+ * aimock's own is not.
149
+ */
150
+ function shown(value, max = require_constants.MCP_FAKES_ECHO_LIMIT) {
151
+ return require_message_text.jsonText(render(value), max);
152
+ }
153
+ function render(value) {
154
+ if (value === void 0) return "undefined";
155
+ if (typeof value === "number" && !Number.isFinite(value)) return String(value);
156
+ if (typeof value === "bigint") return `${value}n`;
157
+ return renderMember(() => value, "", []) ?? notJson(value);
158
+ }
159
+ /**
160
+ * Thrown by `userCode` when code the caller supplied throws: a `toJSON`, a
161
+ * getter, a `Proxy` trap, a `toString`. The renderer catches only this, so a
162
+ * defect in aimock's own rendering code is never hidden as a placeholder.
163
+ */
164
+ var UserCodeThrew = class {
165
+ constructor(error) {
166
+ this.error = error;
167
+ }
168
+ };
169
+ /**
170
+ * `run()`, which touches the caller's value and so may run the caller's code;
171
+ * whatever that code throws comes out as a `UserCodeThrew`.
172
+ */
173
+ function userCode(run) {
174
+ try {
175
+ return run();
176
+ } catch (error) {
177
+ throw new UserCodeThrew(error);
178
+ }
179
+ }
180
+ /** A placeholder in a rendered value: `<reason>`, the reason's text whole. */
181
+ function placeholder(reason) {
182
+ return `<${require_message_text.fullText(reason)}>`;
183
+ }
184
+ /** The reason a rendered value holds where it reaches one of its own containers. */
185
+ const CIRCULAR_REASON = require_message_text.msg`a circular reference`;
186
+ /**
187
+ * A value that `renderJson` gives no JSON for (a symbol, a function): a
188
+ * placeholder that names its type and, when it has one, quotes its text.
189
+ */
190
+ function notJson(value) {
191
+ const reason = require_message_text.msg`not a JSON value (got ${require_message_text.fixed(typeof value)})`;
192
+ let text;
193
+ try {
194
+ text = userCode(() => String(value));
195
+ } catch (err) {
196
+ if (!(err instanceof UserCodeThrew)) throw err;
197
+ return placeholder(reason);
198
+ }
199
+ return placeholder(require_message_text.msg`${reason}: ${require_message_text.quote(text)}`);
200
+ }
201
+ /**
202
+ * One value of a rendered value, read by `read` (which may run the caller's
203
+ * code), rendered by `renderJson`. Where the caller's code throws while it is
204
+ * read or rendered, it renders as an "unreadable value" placeholder that
205
+ * quotes what was thrown, and the rest of the value is kept.
206
+ */
207
+ function renderMember(read, key, stack) {
208
+ try {
209
+ return renderJson(userCode(read), key, stack);
210
+ } catch (err) {
211
+ if (!(err instanceof UserCodeThrew)) throw err;
212
+ return placeholder(require_message_text.msg`an unreadable value (reading it threw ${describeThrown(err.error)})`);
213
+ }
214
+ }
215
+ /**
216
+ * `value` as `JSON.stringify` renders it, except that a snapshot placeholder,
217
+ * a bigint below the top level, a cycle (found through `stack`, the
218
+ * containers being rendered) and a container more than `MCP_FAKES_MAX_DEPTH`
219
+ * levels down each render as a `<reason>` placeholder. A placeholder is known
220
+ * by its class, never by its text, so no user value can pose as one.
221
+ * `undefined` where `JSON.stringify` gives `undefined`. Every part it joins
222
+ * is `JSON.stringify` output or placeholder text. Every read of the caller's
223
+ * value goes through `userCode`, so the only error it lets out from the
224
+ * caller's code is a `UserCodeThrew`.
225
+ */
226
+ function renderJson(value, key, stack) {
227
+ if (userCode(() => value instanceof NotPlain)) return placeholder(value.reason);
228
+ if (value !== null && (typeof value === "object" || typeof value === "bigint")) {
229
+ const toJSON = userCode(() => value.toJSON);
230
+ if (typeof toJSON === "function") value = userCode(() => toJSON.call(value, key));
231
+ }
232
+ if (typeof value === "bigint") return placeholder(require_message_text.msg`a bigint (${require_message_text.fixed(`${value}n`)})`);
233
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
234
+ const container = value;
235
+ if (userCode(() => container instanceof Number || container instanceof String || container instanceof Boolean)) return userCode(() => JSON.stringify(container));
236
+ if (stack.includes(container)) return placeholder(CIRCULAR_REASON);
237
+ if (stack.length >= MCP_FAKES_MAX_DEPTH) return placeholder(DEPTH_REASON);
238
+ stack.push(container);
239
+ try {
240
+ if (userCode(() => Array.isArray(container))) {
241
+ const array = container;
242
+ const length = userCode(() => array.length);
243
+ const items = [];
244
+ for (let i = 0; i < length; i++) items.push(renderMember(() => array[i], String(i), stack) ?? "null");
245
+ return `[${items.join(",")}]`;
246
+ }
247
+ const record = container;
248
+ const members = [];
249
+ for (const k of userCode(() => ownKeys(record))) {
250
+ const item = renderMember(() => record[k], k, stack);
251
+ if (item !== void 0) members.push(`${JSON.stringify(k)}:${item}`);
252
+ }
253
+ return `{${members.join(",")}}`;
254
+ } finally {
255
+ stack.pop();
256
+ }
257
+ }
258
+ /**
259
+ * A thrown value as a message part: its text (user text: a Proxy trap's or a
260
+ * `toJSON`'s message) JSON-quoted; never throws.
261
+ */
262
+ function describeThrown(err) {
263
+ try {
264
+ return require_message_text.quote(err instanceof Error ? `${err.name}: ${err.message}` : String(err));
265
+ } catch {
266
+ return require_message_text.fixed("an unprintable value");
267
+ }
268
+ }
269
+ /**
270
+ * A built-in `Object.prototype.toString` tag (`[object Date]`). Any other tag
271
+ * can come from a user `Symbol.toStringTag`, so a message JSON-quotes it: it
272
+ * then cannot pose as JSON structure or a `<...>` placeholder, and holds no
273
+ * backslash that is not a JSON escape.
274
+ */
275
+ const BUILTIN_TAG = /^\[object [A-Za-z][A-Za-z0-9]*\]$/;
276
+ /** A `toString` tag as a message part: a built-in one as is, any other quoted. */
277
+ function tagPart(tag) {
278
+ return BUILTIN_TAG.test(tag) ? require_message_text.fixed(tag) : require_message_text.quote(tag);
279
+ }
280
+ /**
281
+ * A place in the caller's input that the boundary snapshot could not copy as
282
+ * plain data. It replaces that value in the snapshot, so every later check
283
+ * sees a non-plain value and reports its own rule with `reason`.
284
+ */
285
+ var NotPlain = class {
286
+ constructor(reason) {
287
+ this.reason = reason;
288
+ Object.freeze(this);
289
+ }
290
+ };
291
+ /**
292
+ * The input boundary: one plain-data copy of a caller's value, made once at
293
+ * `McpFakeStore.add` / `validateMcpFakes` entry; every check after it reads
294
+ * only the copy. It reads own, enumerable, string-keyed data properties only:
295
+ * getters are never run and inherited fields never seen. Never throws. Where
296
+ * the value is not plain data, the copy holds a `NotPlain`: a non-plain
297
+ * object or array (`Date`, `Map`, a class instance, an object with a custom
298
+ * prototype), a symbol key, an accessor property, a sparse array, an array
299
+ * with a non-index key, a cycle, a function, symbol or bigint, or a value
300
+ * whose reading throws (a hostile or revoked `Proxy`). Strings, numbers
301
+ * (non-finite ones too), booleans, `null` and `undefined` are kept as is.
302
+ * Copied objects have a `null` prototype.
303
+ *
304
+ * An object or array below `maxDepth` levels (the root is level 1) is not
305
+ * read: the copy holds a "nested deeper than `MCP_FAKES_MAX_DEPTH` levels"
306
+ * `NotPlain` there, so the copy never recurses deeper than that. Only the
307
+ * reflective reads of the caller's value (`readShape`) are guarded by a
308
+ * `catch`; a throw from aimock's own copy code is a bug and propagates.
309
+ *
310
+ * Each caller object is read once, and copied once per level it is reached
311
+ * at: an object reached through several paths (shared references) is not
312
+ * copied once per path, so the time is linear in the number of objects, not
313
+ * exponential in the depth. The copy keeps that sharing. An object on a
314
+ * cycle is still never copied without a placeholder somewhere under it.
315
+ */
316
+ function snapshotInput(value, maxDepth = MCP_FAKES_MAX_DEPTH) {
317
+ return snapshotValue(value, {
318
+ maxDepth,
319
+ ancestors: [],
320
+ shapes: /* @__PURE__ */ new Map(),
321
+ copies: /* @__PURE__ */ new Map()
322
+ });
323
+ }
324
+ function snapshotValue(value, state) {
325
+ if (value === null || value === void 0 || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
326
+ if (typeof value !== "object") return new NotPlain(require_message_text.msg`not a JSON value (got ${require_message_text.fixed(typeof value)})`);
327
+ const { ancestors } = state;
328
+ if (ancestors.includes(value)) return new NotPlain(require_message_text.msg`a circular reference`);
329
+ const level = ancestors.length;
330
+ if (level >= state.maxDepth) return new NotPlain(DEPTH_REASON);
331
+ let byLevel = state.copies.get(value);
332
+ if (byLevel?.has(level)) return byLevel.get(level);
333
+ let shape = state.shapes.get(value);
334
+ if (shape === void 0) {
335
+ shape = readShape(value);
336
+ state.shapes.set(value, shape);
337
+ }
338
+ if (shape instanceof NotPlain) return shape;
339
+ ancestors.push(value);
340
+ let copy;
341
+ try {
342
+ copy = copyShape(shape, state);
343
+ } finally {
344
+ ancestors.pop();
345
+ }
346
+ if (!byLevel) {
347
+ byLevel = /* @__PURE__ */ new Map();
348
+ state.copies.set(value, byLevel);
349
+ }
350
+ byLevel.set(level, copy);
351
+ return copy;
352
+ }
353
+ /**
354
+ * Every reflective read of one caller object, and nothing else: the only
355
+ * code a hostile or revoked `Proxy` can make throw. A throw here is the
356
+ * caller's value being unreadable, so it becomes a `NotPlain`.
357
+ */
358
+ function readShape(value) {
359
+ try {
360
+ const isArray = Array.isArray(value);
361
+ const proto = Object.getPrototypeOf(value);
362
+ if (isArray ? proto !== Array.prototype : proto !== Object.prototype && proto !== null) {
363
+ const tag = Object.prototype.toString.call(value);
364
+ return new NotPlain(require_message_text.msg`not a plain JSON ${require_message_text.fixed(isArray ? "array" : "object")} (got ${tagPart(tag)})`);
365
+ }
366
+ const data = [];
367
+ for (const key of Reflect.ownKeys(value)) {
368
+ const desc = Reflect.getOwnPropertyDescriptor(value, key);
369
+ if (!desc?.enumerable) continue;
370
+ if (typeof key === "symbol") return new NotPlain(require_message_text.msg`an object with a symbol key`);
371
+ data.push([key, hasOwnKey(desc, "value") ? desc.value : ACCESSOR]);
372
+ }
373
+ const lengthDesc = isArray ? Reflect.getOwnPropertyDescriptor(value, "length") : void 0;
374
+ return {
375
+ isArray,
376
+ data,
377
+ length: typeof lengthDesc?.value === "number" ? lengthDesc.value : 0
378
+ };
379
+ } catch (err) {
380
+ return new NotPlain(require_message_text.msg`an unreadable value (reading it threw ${describeThrown(err)})`);
381
+ }
382
+ }
383
+ /**
384
+ * Most holes a sparse array may have and still be copied element by element.
385
+ * Its `length` costs the caller nothing, so a longer one is not walked.
386
+ */
387
+ const SPARSE_HOLE_LIMIT = 1e4;
388
+ /**
389
+ * Copy what `readShape` read; recurses through `snapshotValue`. A hole in a
390
+ * sparse array becomes a placeholder in its own place, so the array is still
391
+ * an array and every other element is still checked.
392
+ */
393
+ function copyShape(shape, state) {
394
+ const { data, length } = shape;
395
+ if (shape.isArray) {
396
+ const own = new Map(data);
397
+ for (const [key] of data) {
398
+ const n = Number(key);
399
+ if (key !== String(n) || !Number.isInteger(n) || n < 0 || n >= length) return new NotPlain(require_message_text.msg`an array with a non-index key ${require_message_text.quote(key)}`);
400
+ }
401
+ const holes = length - own.size;
402
+ if (holes > SPARSE_HOLE_LIMIT) {
403
+ let hole = 0;
404
+ while (own.has(String(hole))) hole += 1;
405
+ return new NotPlain(require_message_text.msg`a sparse array with ${holes} holes (the first at [${hole}])`);
406
+ }
407
+ const out = [];
408
+ for (let i = 0; i < length; i++) {
409
+ const key = String(i);
410
+ out.push(own.has(key) ? snapshotField(own.get(key), state) : new NotPlain(require_message_text.msg`a hole in a sparse array`));
411
+ }
412
+ return out;
413
+ }
414
+ const out = Object.create(null);
415
+ for (const [key, item] of data) Object.defineProperty(out, key, {
416
+ value: snapshotField(item, state),
417
+ enumerable: true,
418
+ writable: true,
419
+ configurable: true
420
+ });
421
+ return out;
422
+ }
423
+ /** Marks an accessor property in `readShape` (its getter is never run). */
424
+ const ACCESSOR = Symbol("accessor");
425
+ function snapshotField(item, state) {
426
+ return item === ACCESSOR ? new NotPlain(require_message_text.msg`an accessor property (getters are not read)`) : snapshotValue(item, state);
427
+ }
428
+ /**
429
+ * A copy of `value`, a snapshot copy, with every container below `levels`
430
+ * levels (the value itself is level 1) replaced by the depth placeholder.
431
+ * Used by `McpFakeStore.add` for a block of an array-form `mcpFakes`, whose
432
+ * array is level 1. It does not change `value`: a snapshot can share one copy
433
+ * between paths at different levels. Each container is copied once per level
434
+ * (`memo`), so shared references stay linear.
435
+ */
436
+ function capDepth(value, levels, memo = /* @__PURE__ */ new Map()) {
437
+ if (value instanceof NotPlain || typeof value !== "object" || value === null) return value;
438
+ if (levels < 1) return new NotPlain(DEPTH_REASON);
439
+ let byLevel = memo.get(value);
440
+ if (byLevel?.has(levels)) return byLevel.get(levels);
441
+ let out;
442
+ if (Array.isArray(value)) out = value.map((item) => capDepth(item, levels - 1, memo));
443
+ else {
444
+ const src = value;
445
+ const obj = Object.create(null);
446
+ for (const key of ownKeys(src)) Object.defineProperty(obj, key, {
447
+ value: capDepth(src[key], levels - 1, memo),
448
+ enumerable: true,
449
+ writable: true,
450
+ configurable: true
451
+ });
452
+ out = obj;
453
+ }
454
+ if (!byLevel) {
455
+ byLevel = /* @__PURE__ */ new Map();
456
+ memo.set(value, byLevel);
457
+ }
458
+ byLevel.set(levels, out);
459
+ return out;
460
+ }
461
+ /**
462
+ * Freeze `value` and everything under it (plain data only). A frozen object
463
+ * is not walked again: this module freezes children before their parent, and
464
+ * a shared child is then frozen once.
465
+ */
466
+ function deepFreeze(value) {
467
+ if (typeof value === "object" && value !== null && !Object.isFrozen(value)) {
468
+ const obj = value;
469
+ for (const key of ownKeys(obj)) deepFreeze(obj[key]);
470
+ Object.freeze(value);
471
+ }
472
+ return value;
473
+ }
474
+ function isNonEmptyString(value) {
475
+ return typeof value === "string" && value.length > 0;
476
+ }
477
+ /** A field is present when it is an own key whose value is not `undefined`. */
478
+ function has(raw, key) {
479
+ return hasOwnKey(raw, key) && raw[key] !== void 0;
480
+ }
481
+ /** Own keys whose value is not `undefined` (an `undefined` field is absent). */
482
+ function presentKeys(raw) {
483
+ return ownKeys(raw).filter((key) => raw[key] !== void 0);
484
+ }
485
+ /**
486
+ * The first place under `value` that is not plain JSON (as `JSON.parse` could
487
+ * produce it), or `null`. An object field set to `undefined` is absent, as in
488
+ * `JSON.stringify`. Rejects an `undefined` array item, functions, symbols,
489
+ * bigints, non-finite numbers, sparse-array holes and objects whose prototype
490
+ * is not `Object.prototype` or `null` (`Date`, `Map`, class instances). Its
491
+ * input is always the input snapshot, which holds no cycle (`snapshotInput`
492
+ * puts a placeholder there) and no container deeper than
493
+ * `MCP_FAKES_MAX_DEPTH`, so the recursion is bounded. A container found clean
494
+ * is not checked again (`clean`), so a shared one is walked once.
495
+ */
496
+ function jsonProblem(value, path, clean = /* @__PURE__ */ new WeakSet()) {
497
+ if (value instanceof NotPlain) return require_message_text.msg`${pathPart(path)} is ${value.reason}`;
498
+ if (value === null || typeof value === "string" || typeof value === "boolean") return null;
499
+ if (typeof value === "number") return Number.isFinite(value) ? null : require_message_text.msg`${pathPart(path)} must be a finite number, got ${shown(value)}`;
500
+ if (typeof value !== "object") return require_message_text.msg`${pathPart(path)} is not a JSON value (got ${require_message_text.fixed(typeof value)})`;
501
+ if (clean.has(value)) return null;
502
+ let problem = null;
503
+ if (Array.isArray(value)) for (let i = 0; i < value.length && !problem; i++) problem = hasOwnKey(value, String(i)) ? jsonProblem(value[i], `${path}[${i}]`, clean) : require_message_text.msg`${pathPart(`${path}[${i}]`)} is a hole in a sparse array`;
504
+ else {
505
+ const proto = Object.getPrototypeOf(value);
506
+ if (proto !== Object.prototype && proto !== null) {
507
+ const tag = Object.prototype.toString.call(value);
508
+ problem = require_message_text.msg`${pathPart(path)} must be a plain JSON object, got ${tagPart(tag)}`;
509
+ } else {
510
+ const obj = value;
511
+ for (const key of presentKeys(obj)) {
512
+ problem = jsonProblem(obj[key], pathKey(path, key), clean);
513
+ if (problem) break;
514
+ }
515
+ }
516
+ }
517
+ if (!problem) clean.add(value);
518
+ return problem;
519
+ }
520
+ /**
521
+ * Deep copy of a plain JSON value (checked by `jsonProblem`), optionally
522
+ * deep-frozen. Keys are defined, not assigned, so `"__proto__"` stays data;
523
+ * a field set to `undefined` is left out. A container reached through several
524
+ * paths is copied once (`memo`), and the copy shares it the same way.
525
+ */
526
+ function copyJson(value, freeze = false, memo = /* @__PURE__ */ new Map()) {
527
+ if (typeof value !== "object" || value === null) return value;
528
+ if (memo.has(value)) return memo.get(value);
529
+ let out;
530
+ if (Array.isArray(value)) out = value.map((item) => copyJson(item, freeze, memo));
531
+ else {
532
+ const src = value;
533
+ const obj = {};
534
+ for (const key of presentKeys(src)) Object.defineProperty(obj, key, {
535
+ value: copyJson(src[key], freeze, memo),
536
+ enumerable: true,
537
+ writable: true,
538
+ configurable: true
539
+ });
540
+ out = obj;
541
+ }
542
+ if (freeze) Object.freeze(out);
543
+ memo.set(value, out);
544
+ return out;
545
+ }
546
+ /** Block id: `<source><load>[<block>]`, with no `[<block>]` for a single-object `mcpFakes`. */
547
+ function blockIdOf(source, loadSuffix, blockIndex) {
548
+ return `${source}${loadSuffix}${blockIndex === null ? "" : `[${blockIndex}]`}`;
549
+ }
550
+ function localOf(toolName, index, userId) {
551
+ return isNonEmptyString(userId) ? userId : `${toolName}#${index}`;
552
+ }
553
+ function tierOf(scope) {
554
+ if (scope === "shared") return "shared";
555
+ if (scope.testId !== void 0 && scope.context !== void 0) return "testId+context";
556
+ if (scope.testId !== void 0) return "testId";
557
+ return "context";
558
+ }
559
+ /** Scope match: exact, case-sensitive, no normalization; `"shared"` matches every request. */
560
+ function scopeMatches(scope, identity) {
561
+ if (scope === "shared") return true;
562
+ if (scope.testId !== void 0 && identity.testId !== scope.testId) return false;
563
+ if (scope.context !== void 0 && identity.context !== scope.context) return false;
564
+ return true;
565
+ }
566
+ function badBlock(ctx, letter, detail, entryId = null) {
567
+ return new require_fixture_loader.FixtureLoadError({
568
+ rule: `mcp-fakes/bad-block:${letter}`,
569
+ file: ctx.source,
570
+ blockId: ctx.blockId,
571
+ entryId,
572
+ detail
573
+ });
574
+ }
575
+ /** Every scope error goes to `errors`; returns the scope only when it is clean. */
576
+ function validateScope(raw, ctx, errors) {
577
+ if (raw === void 0) {
578
+ errors.push(badBlock(ctx, "a", require_message_text.msg`block has no scope`));
579
+ return null;
580
+ }
581
+ if (raw === "shared") return "shared";
582
+ if (!isPlainObject(raw)) {
583
+ errors.push(badBlock(ctx, "b", require_message_text.msg`scope must be "shared" or an object, got ${shown(raw)}`));
584
+ return null;
585
+ }
586
+ const before = errors.length;
587
+ for (const key of presentKeys(raw)) if (!SCOPE_KEYS.has(key)) errors.push(badBlock(ctx, "b", require_message_text.msg`scope has unknown key ${shown(key)}`));
588
+ if (!has(raw, "testId") && !has(raw, "context")) errors.push(badBlock(ctx, "b", require_message_text.msg`scope object has neither testId nor context`));
589
+ for (const key of ["testId", "context"]) if (has(raw, key) && !isNonEmptyString(raw[key])) errors.push(badBlock(ctx, "b", require_message_text.msg`scope.${require_message_text.fixed(key)} must be a non-empty string, got ${shown(raw[key])}`));
590
+ if (errors.length > before) return null;
591
+ const testId = raw.testId;
592
+ const context = raw.context;
593
+ if (testId !== void 0 && context !== void 0) return Object.freeze({
594
+ testId,
595
+ context
596
+ });
597
+ if (testId !== void 0) return Object.freeze({ testId });
598
+ return Object.freeze({ context });
599
+ }
600
+ const mustBeString = (v, p) => typeof v === "string" ? null : require_message_text.msg`${pathPart(p)} must be a string, got ${shown(v)}`;
601
+ const mustBeNumber = (v, p) => typeof v === "number" ? null : require_message_text.msg`${pathPart(p)} must be a number, got ${shown(v)}`;
602
+ const mustBeBoolean = (v, p) => typeof v === "boolean" ? null : require_message_text.msg`${pathPart(p)} must be a boolean, got ${shown(v)}`;
603
+ const mustBeObject = (v, p) => isPlainObject(v) ? null : require_message_text.msg`${pathPart(p)} must be a JSON object, got ${shown(v)}`;
604
+ /** Base64 as the SDK's `Base64Schema` accepts it (`atob` does not throw). */
605
+ const mustBeBase64 = (v, p) => {
606
+ if (typeof v === "string") try {
607
+ atob(v);
608
+ return null;
609
+ } catch {}
610
+ return require_message_text.msg`${pathPart(p)} must be a base64 string, got ${shown(v)}`;
611
+ };
612
+ function oneOf(values) {
613
+ const list = values.map((v) => require_message_text.quoteJson(v)).join(", ");
614
+ return (v, p) => typeof v === "string" && values.includes(v) ? null : require_message_text.msg`${pathPart(p)} must be one of ${require_message_text.fixed(list)}, got ${shown(v)}`;
615
+ }
616
+ function arrayOf(item, noun = "an array") {
617
+ return (v, p) => {
618
+ if (!Array.isArray(v)) return require_message_text.msg`${pathPart(p)} must be ${require_message_text.fixed(noun)}, got ${shown(v)}`;
619
+ for (let i = 0; i < v.length; i++) {
620
+ const problem = item(v[i], `${p}[${i}]`);
621
+ if (problem) return problem;
622
+ }
623
+ return null;
624
+ };
625
+ }
626
+ const mustBeStringArray = arrayOf(mustBeString, "an array of strings");
627
+ /**
628
+ * An object with `required` fields and `optional` ones (an `undefined` field
629
+ * is absent). Other keys are not checked.
630
+ */
631
+ function shape(required, optional = {}) {
632
+ return (v, p) => {
633
+ if (!isPlainObject(v)) return require_message_text.msg`${pathPart(p)} must be a JSON object, got ${shown(v)}`;
634
+ for (const key of ownKeys(required)) {
635
+ const check = required[key];
636
+ const problem = check(has(v, key) ? v[key] : void 0, pathKey(p, key));
637
+ if (problem) return problem;
638
+ }
639
+ for (const key of ownKeys(optional)) {
640
+ const check = optional[key];
641
+ if (!has(v, key)) continue;
642
+ const problem = check(v[key], pathKey(p, key));
643
+ if (problem) return problem;
644
+ }
645
+ return null;
646
+ };
647
+ }
648
+ /**
649
+ * RFC 3339 date-time with seconds and a `Z` or `±hh:mm` offset: the pattern of
650
+ * zod's `z.iso.datetime({ offset: true })`, which the SDK uses for
651
+ * `Annotations.lastModified`.
652
+ */
653
+ const ISO_DATETIME = /^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/;
654
+ const ANNOTATIONS = shape({}, {
655
+ audience: arrayOf(oneOf(["user", "assistant"])),
656
+ priority: (v, p) => typeof v === "number" && v >= 0 && v <= 1 ? null : require_message_text.msg`${pathPart(p)} must be a number from 0 to 1, got ${shown(v)}`,
657
+ lastModified: (v, p) => typeof v === "string" && ISO_DATETIME.test(v) ? null : require_message_text.msg`${pathPart(p)} must be an ISO 8601 date-time with seconds and an offset, got ${shown(v)}`
658
+ });
659
+ const ICON = shape({ src: mustBeString }, {
660
+ mimeType: mustBeString,
661
+ sizes: mustBeStringArray,
662
+ theme: oneOf(["light", "dark"])
663
+ });
664
+ /** MCP `TextResourceContents` / `BlobResourceContents`: exactly one of text / blob. */
665
+ const RESOURCE_CONTENTS = (v, p) => {
666
+ if (!isPlainObject(v)) return require_message_text.msg`${pathPart(p)} must be a JSON object with uri and text or blob, got ${shown(v)}`;
667
+ if (has(v, "text") === has(v, "blob")) return require_message_text.msg`${pathPart(p)} needs exactly one of text / blob`;
668
+ return shape({
669
+ uri: mustBeString,
670
+ ...has(v, "text") ? { text: mustBeString } : { blob: mustBeBase64 }
671
+ }, {
672
+ mimeType: mustBeString,
673
+ _meta: mustBeObject
674
+ })(v, p);
675
+ };
676
+ /** Fields every content block may carry. */
677
+ const CONTENT_COMMON = {
678
+ annotations: ANNOTATIONS,
679
+ _meta: mustBeObject
680
+ };
681
+ /** Each MCP content block type, with its fields. */
682
+ const CONTENT_TYPES = new Map([
683
+ ["text", shape({ text: mustBeString }, CONTENT_COMMON)],
684
+ ["image", shape({
685
+ data: mustBeBase64,
686
+ mimeType: mustBeString
687
+ }, CONTENT_COMMON)],
688
+ ["audio", shape({
689
+ data: mustBeBase64,
690
+ mimeType: mustBeString
691
+ }, CONTENT_COMMON)],
692
+ ["resource_link", shape({
693
+ uri: mustBeString,
694
+ name: mustBeString
695
+ }, {
696
+ title: mustBeString,
697
+ description: mustBeString,
698
+ mimeType: mustBeString,
699
+ size: mustBeNumber,
700
+ icons: arrayOf(ICON),
701
+ ...CONTENT_COMMON
702
+ })],
703
+ ["resource", shape({ resource: RESOURCE_CONTENTS }, CONTENT_COMMON)]
704
+ ]);
705
+ const CONTENT_TYPE_LIST = [...CONTENT_TYPES.keys()].map((type) => require_message_text.quoteJson(type)).join(", ");
706
+ /** One MCP content block, checked by its `type`. */
707
+ const CONTENT_BLOCK = (v, p) => {
708
+ if (!isPlainObject(v)) return require_message_text.msg`${pathPart(p)} must be a content object, got ${shown(v)}`;
709
+ const check = typeof v.type === "string" ? CONTENT_TYPES.get(v.type) : void 0;
710
+ if (!check) return require_message_text.msg`${pathPart(p)}.type must be one of ${require_message_text.fixed(CONTENT_TYPE_LIST)}, got ${shown(v.type)}`;
711
+ return check(v, p);
712
+ };
713
+ const CONTENT = arrayOf(CONTENT_BLOCK, "an array of content blocks");
714
+ /** The SDK client's `RequestMeta` keys on a result's `_meta`; other keys pass through. */
715
+ const RESULT_META = shape({}, {
716
+ progressToken: (v, p) => typeof v === "string" || Number.isSafeInteger(v) ? null : require_message_text.msg`${pathPart(p)} must be a string or an integer, got ${shown(v)}`,
717
+ "io.modelcontextprotocol/related-task": shape({ taskId: mustBeString })
718
+ });
719
+ /**
720
+ * The keys of a full `CallToolResult`. An object `result` with any of
721
+ * them is that form, and must have a `content` array; any other object is
722
+ * structured data.
723
+ */
724
+ const FULL_RESULT_KEYS = new Set([
725
+ "content",
726
+ "isError",
727
+ "structuredContent",
728
+ "_meta"
729
+ ]);
730
+ const FULL_RESULT = shape({ content: CONTENT }, {
731
+ isError: mustBeBoolean,
732
+ structuredContent: mustBeObject,
733
+ _meta: RESULT_META
734
+ });
735
+ function isFullResult(obj) {
736
+ return [...FULL_RESULT_KEYS].some((key) => has(obj, key));
737
+ }
738
+ /**
739
+ * A valid `result`: a string; a content array; a full `CallToolResult` (`content`
740
+ * array, optional boolean `isError`, optional object `structuredContent` and
741
+ * `_meta`, nothing else); or a JSON object with none of those keys (served as
742
+ * text plus `structuredContent`).
743
+ */
744
+ function validateResult(result) {
745
+ if (typeof result === "string") return null;
746
+ if (!Array.isArray(result) && !isPlainObject(result)) return require_message_text.msg`result must be a string, a content array or an object, got ${shown(result)}`;
747
+ const json = jsonProblem(result, "result");
748
+ if (json) return json;
749
+ if (Array.isArray(result)) return CONTENT(result, "result");
750
+ if (!isFullResult(result)) return null;
751
+ for (const key of presentKeys(result)) if (!FULL_RESULT_KEYS.has(key)) return require_message_text.msg`result has unknown key ${shown(key)}`;
752
+ return FULL_RESULT(result, "result");
753
+ }
754
+ /**
755
+ * MCP `Tool.inputSchema`: a JSON Schema object with `type: "object"` at the
756
+ * root; `properties`, when present, maps names to schema objects, and
757
+ * `required`, when present, lists names. Any other JSON Schema keyword is
758
+ * allowed.
759
+ */
760
+ const INPUT_SCHEMA = shape({ type: (v, p) => v === "object" ? null : require_message_text.msg`${pathPart(p)} must be "object", got ${shown(v)}` }, {
761
+ properties: (v, p) => {
762
+ if (!isPlainObject(v)) return mustBeObject(v, p);
763
+ for (const key of presentKeys(v)) {
764
+ const problem = mustBeObject(v[key], pathKey(p, key));
765
+ if (problem) return problem;
766
+ }
767
+ return null;
768
+ },
769
+ required: mustBeStringArray
770
+ });
771
+ function inputSchemaProblem(schema) {
772
+ if (!isPlainObject(schema)) return mustBeObject(schema, "inputSchema");
773
+ return jsonProblem(schema, "inputSchema") ?? INPUT_SCHEMA(schema, "inputSchema");
774
+ }
775
+ /**
776
+ * Validate one call entry of the input snapshot. Every error goes to
777
+ * `errors`; returns its entry id whether or not it is clean, so that the
778
+ * block's entry-id uniqueness check also sees invalid entries, and the frozen call
779
+ * only when it is clean. `toolName` is the tool's name, or a `tools[<i>]`
780
+ * placeholder when that name is invalid.
781
+ */
782
+ function validateCall(raw, toolName, label, index, ctx, errors) {
783
+ const idRaw = isPlainObject(raw) ? raw.id : void 0;
784
+ const entryId = `${ctx.blockId}:${localOf(toolName, index, idRaw)}`;
785
+ const where = require_message_text.msg`${label} calls[${index}]`;
786
+ const bad = (letter, detail) => {
787
+ errors.push(badBlock(ctx, letter, require_message_text.msg`${where} ${detail}`, entryId));
788
+ };
789
+ if (!isPlainObject(raw)) {
790
+ bad("d", require_message_text.msg`must be an object, got ${shown(raw)}`);
791
+ return {
792
+ entryId,
793
+ call: null
794
+ };
795
+ }
796
+ const before = errors.length;
797
+ for (const key of presentKeys(raw)) if (!CALL_KEYS.has(key)) bad("h", require_message_text.msg`has unknown key ${shown(key)}`);
798
+ if (has(raw, "id") && !isNonEmptyString(raw.id)) bad("d", require_message_text.msg`id must be a non-empty string, got ${shown(raw.id)}`);
799
+ if (has(raw, "anyArgs") && raw.anyArgs !== true) bad("j", require_message_text.msg`anyArgs must be true, got ${shown(raw.anyArgs)}`);
800
+ const hasArgs = has(raw, "args");
801
+ if (hasArgs === has(raw, "anyArgs")) bad("d", require_message_text.msg`needs exactly one of args / anyArgs`);
802
+ if (hasArgs) {
803
+ const problem = isPlainObject(raw.args) ? jsonProblem(raw.args, "args") : require_message_text.msg`args must be a JSON object, got ${shown(raw.args)}`;
804
+ if (problem) bad("d", problem);
805
+ }
806
+ const hasResult = has(raw, "result");
807
+ const hasError = has(raw, "error");
808
+ if (hasResult === hasError) bad("d", require_message_text.msg`needs exactly one of result / error`);
809
+ if (hasError && typeof raw.error !== "string") bad("d", require_message_text.msg`error must be a string, got ${shown(raw.error)}`);
810
+ if (hasResult) {
811
+ const problem = validateResult(raw.result);
812
+ if (problem) bad("d", problem);
813
+ }
814
+ if (errors.length > before) return {
815
+ entryId,
816
+ call: null
817
+ };
818
+ const match = hasArgs ? { args: copyJson(raw.args, true) } : { anyArgs: true };
819
+ const answer = hasResult ? { result: copyJson(raw.result, true) } : { error: raw.error };
820
+ return {
821
+ entryId,
822
+ call: Object.freeze({
823
+ id: entryId,
824
+ index,
825
+ ...match,
826
+ ...answer
827
+ })
828
+ };
829
+ }
830
+ /**
831
+ * Validate one tool of the input snapshot. Every error goes to `errors`, and
832
+ * the entry ids of all its calls (valid or not; with an invalid tool name,
833
+ * only the user ids) to `entryIds`; returns the frozen tool only when it is
834
+ * clean.
835
+ */
836
+ function validateTool(raw, index, ctx, errors, entryIds) {
837
+ const where = `tools[${index}]`;
838
+ const place = require_message_text.msg`tools[${index}]`;
839
+ if (!isPlainObject(raw)) {
840
+ errors.push(badBlock(ctx, "g", require_message_text.msg`${place} must be an object, got ${shown(raw)}`));
841
+ return null;
842
+ }
843
+ const before = errors.length;
844
+ for (const key of presentKeys(raw)) if (!TOOL_KEYS.has(key)) errors.push(badBlock(ctx, "h", require_message_text.msg`${place} has unknown key ${shown(key)}`));
845
+ const name = isNonEmptyString(raw.name) ? raw.name : null;
846
+ if (name === null) errors.push(badBlock(ctx, "g", require_message_text.msg`${place} needs a string name, got ${shown(raw.name)}`));
847
+ const tw = name === null ? place : require_message_text.msg`tool ${require_message_text.quote(name)}`;
848
+ if (has(raw, "description") && typeof raw.description !== "string") errors.push(badBlock(ctx, "g", require_message_text.msg`${tw} description must be a string, got ${shown(raw.description)}`));
849
+ if (has(raw, "inputSchema")) {
850
+ const problem = inputSchemaProblem(raw.inputSchema);
851
+ if (problem) errors.push(badBlock(ctx, "g", require_message_text.msg`${tw} ${problem}`));
852
+ }
853
+ const calls = [];
854
+ if (!Array.isArray(raw.calls) || raw.calls.length === 0) errors.push(badBlock(ctx, "g", require_message_text.msg`${tw} calls must be a non-empty array, got ${shown(raw.calls)}`));
855
+ else raw.calls.forEach((rawCall, i) => {
856
+ const { entryId, call } = validateCall(rawCall, name ?? where, tw, i, ctx, errors);
857
+ if (name !== null || isPlainObject(rawCall) && isNonEmptyString(rawCall.id)) entryIds.push(entryId);
858
+ if (call) calls.push(call);
859
+ });
860
+ if (errors.length > before || name === null) return null;
861
+ const tool = {
862
+ name,
863
+ calls: Object.freeze(calls)
864
+ };
865
+ if (typeof raw.description === "string") tool.description = raw.description;
866
+ if (isPlainObject(raw.inputSchema)) tool.inputSchema = copyJson(raw.inputSchema, true);
867
+ return Object.freeze(tool);
868
+ }
869
+ /**
870
+ * Validate one block of the input snapshot. Every check runs and each problem
871
+ * goes to `errors` (no first-stop), with two limits: a block, tool or call
872
+ * entry that is not an object is not checked further, and one `result` or
873
+ * `inputSchema` value reports only its first problem. Entry-id uniqueness
874
+ * inside the block is checked here over every declared entry, valid or not;
875
+ * uniqueness across blocks is the caller's.
876
+ */
877
+ function validateBlock(raw, source, blockIndex, load, errors) {
878
+ const ctx = {
879
+ source,
880
+ load,
881
+ blockIndex,
882
+ blockId: blockIdOf(source, load, blockIndex)
883
+ };
884
+ if (!isPlainObject(raw)) {
885
+ errors.push(badBlock(ctx, "i", require_message_text.msg`mcpFakes block must be an object, got ${shown(raw)}`));
886
+ return {
887
+ block: null,
888
+ entryIds: [],
889
+ warnings: []
890
+ };
891
+ }
892
+ const before = errors.length;
893
+ for (const key of presentKeys(raw)) if (!BLOCK_KEYS.has(key)) errors.push(badBlock(ctx, "h", require_message_text.msg`block has unknown key ${shown(key)}`));
894
+ const scope = validateScope(raw.scope, ctx, errors);
895
+ let undeclared;
896
+ if (has(raw, "undeclaredTools")) if (raw.undeclaredTools === "allow" || raw.undeclaredTools === "deny") undeclared = raw.undeclaredTools;
897
+ else errors.push(badBlock(ctx, "g", require_message_text.msg`undeclaredTools must be "allow" or "deny", got ${shown(raw.undeclaredTools)}`));
898
+ if (scope === "shared" && undeclared === "deny") errors.push(badBlock(ctx, "c", require_message_text.msg`scope "shared" cannot have undeclaredTools "deny"`));
899
+ let mount = MCP_FAKES_DEFAULT_MOUNT;
900
+ if (has(raw, "mount")) if (typeof raw.mount === "string" && raw.mount.startsWith("/")) mount = raw.mount;
901
+ else errors.push(badBlock(ctx, "g", require_message_text.msg`mount must be a string starting with "/", got ${shown(raw.mount)}`));
902
+ const tools = [];
903
+ const declaredIds = [];
904
+ if (!Array.isArray(raw.tools)) errors.push(badBlock(ctx, "g", require_message_text.msg`tools must be an array, got ${shown(raw.tools)}`));
905
+ else if (raw.tools.length === 0) {
906
+ if (undeclared !== "deny" || scope === "shared") errors.push(badBlock(ctx, "g", require_message_text.msg`tools is empty; an empty tools array (tools: []) is allowed only on a scoped (not "shared") block with undeclaredTools "deny"`));
907
+ } else {
908
+ const names = /* @__PURE__ */ new Set();
909
+ raw.tools.forEach((rawTool, i) => {
910
+ const name = isPlainObject(rawTool) && isNonEmptyString(rawTool.name) ? rawTool.name : null;
911
+ const duplicate = name !== null && names.has(name);
912
+ if (duplicate) errors.push(badBlock(ctx, "e", require_message_text.msg`tool ${require_message_text.quote(name)} is declared twice in one block`));
913
+ else if (name !== null) names.add(name);
914
+ const tool = validateTool(rawTool, i, ctx, errors, duplicate ? [] : declaredIds);
915
+ if (tool && !duplicate) tools.push(tool);
916
+ });
917
+ }
918
+ const seen = /* @__PURE__ */ new Set();
919
+ for (const id of declaredIds) {
920
+ if (seen.has(id)) errors.push(badBlock(ctx, "f", require_message_text.msg`entry id ${require_message_text.quote(id)} is not unique`, id));
921
+ seen.add(id);
922
+ }
923
+ const entryIds = [...seen];
924
+ if (errors.length > before || scope === null) return {
925
+ block: null,
926
+ entryIds,
927
+ warnings: []
928
+ };
929
+ const warnings = [];
930
+ for (const tool of tools) {
931
+ let lastAny = null;
932
+ for (const call of tool.calls) if (call.anyArgs) lastAny = call;
933
+ else if (lastAny) warnings.push({
934
+ file: source,
935
+ blockId: ctx.blockId,
936
+ entryId: call.id,
937
+ message: require_message_text.build(require_message_text.msg`entry ${require_message_text.quote(call.id)} is shadowed until the preceding anyArgs entry ${require_message_text.quote(lastAny.id)} is consumed`)
938
+ });
939
+ }
940
+ return {
941
+ block: Object.freeze({
942
+ source,
943
+ load,
944
+ blockIndex,
945
+ blockId: ctx.blockId,
946
+ scope,
947
+ tier: tierOf(scope),
948
+ mount,
949
+ ...undeclared !== void 0 ? { undeclaredTools: undeclared } : {},
950
+ tools: Object.freeze(tools)
951
+ }),
952
+ entryIds,
953
+ warnings
954
+ };
955
+ }
956
+ /**
957
+ * Standalone validation of a raw `mcpFakes` value (one object, or an array of
958
+ * blocks) from one `source`. It takes the plain-data snapshot of `raw`
959
+ * (getters are not run, inherited fields not read) and reports every
960
+ * bad-block case (a)-(j) found as a `FixtureLoadError` naming the source and
961
+ * block; entry-id collisions inside a block are (f). Never throws. Ids use the
962
+ * first-load form (empty `load`). The store does not use this function:
963
+ * `McpFakeStore.add` runs the same per-block checks (`validateBlock`) with its
964
+ * own load suffix, and adds the entry-id check across blocks (against the
965
+ * mount and the earlier blocks of the same input). `blocks` holds only the
966
+ * clean blocks (deeply frozen), `warnings` only their shadowed-entry warnings.
967
+ */
968
+ function validateMcpFakes(raw, source) {
969
+ const errors = [];
970
+ const warnings = [];
971
+ const blocks = [];
972
+ const input = snapshotInput(raw);
973
+ let items;
974
+ if (Array.isArray(input) && input.length === 0) {
975
+ errors.push(badBlock({
976
+ source,
977
+ blockId: null
978
+ }, "i", require_message_text.msg`mcpFakes is an empty array`));
979
+ return {
980
+ blocks,
981
+ errors,
982
+ warnings
983
+ };
984
+ } else if (Array.isArray(input)) items = input.map((item, i) => ({
985
+ raw: item,
986
+ blockIndex: i
987
+ }));
988
+ else if (isPlainObject(input)) items = [{
989
+ raw: input,
990
+ blockIndex: null
991
+ }];
992
+ else {
993
+ errors.push(badBlock({
994
+ source,
995
+ blockId: null
996
+ }, "i", require_message_text.msg`mcpFakes must be an object or an array, got ${shown(input)}`));
997
+ return {
998
+ blocks,
999
+ errors,
1000
+ warnings
1001
+ };
1002
+ }
1003
+ for (const item of items) {
1004
+ const outcome = validateBlock(item.raw, source, item.blockIndex, "", errors);
1005
+ if (outcome.block) {
1006
+ blocks.push(outcome.block);
1007
+ warnings.push(...outcome.warnings);
1008
+ }
1009
+ }
1010
+ return {
1011
+ blocks,
1012
+ errors,
1013
+ warnings
1014
+ };
1015
+ }
1016
+ function blockEntryIds(block) {
1017
+ return block.tools.flatMap((t) => t.calls.map((c) => c.id));
1018
+ }
1019
+ /**
1020
+ * Turn a call entry's `result` or `error` into a `tools/call` result. The
1021
+ * returned object is a fresh copy the caller may change. A full
1022
+ * `CallToolResult` (validation guarantees any object with one of its keys
1023
+ * has a `content` array) is copied unchanged, with no `isError` added;
1024
+ * every other shape sets `isError`. Throws when the entry has
1025
+ * neither (validation makes that a bad block (d)).
1026
+ */
1027
+ function toCallToolResult(call) {
1028
+ if (call.error !== void 0) return {
1029
+ content: [{
1030
+ type: "text",
1031
+ text: call.error
1032
+ }],
1033
+ isError: true
1034
+ };
1035
+ const result = call.result;
1036
+ if (result === void 0 || result === null) throw new Error(require_message_text.build(require_message_text.msg`toCallToolResult: call entry has neither result nor error`));
1037
+ if (typeof result === "string") return {
1038
+ content: [{
1039
+ type: "text",
1040
+ text: result
1041
+ }],
1042
+ isError: false
1043
+ };
1044
+ if (Array.isArray(result)) return {
1045
+ content: copyJson(result),
1046
+ isError: false
1047
+ };
1048
+ const obj = copyJson(result);
1049
+ if (Array.isArray(obj.content)) return {
1050
+ ...obj,
1051
+ content: obj.content
1052
+ };
1053
+ return {
1054
+ content: [{
1055
+ type: "text",
1056
+ text: JSON.stringify(obj)
1057
+ }],
1058
+ structuredContent: obj,
1059
+ isError: false
1060
+ };
1061
+ }
1062
+ function jsonEqual(a, b) {
1063
+ if (a === b) return true;
1064
+ if (Array.isArray(a) || Array.isArray(b)) {
1065
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
1066
+ return a.every((v, i) => jsonEqual(v, b[i]));
1067
+ }
1068
+ if (isPlainObject(a) && isPlainObject(b)) {
1069
+ const ak = ownKeys(a);
1070
+ const bk = ownKeys(b);
1071
+ if (ak.length !== bk.length) return false;
1072
+ return ak.every((k) => hasOwnKey(b, k) && jsonEqual(a[k], b[k]));
1073
+ }
1074
+ return false;
1075
+ }
1076
+ /**
1077
+ * Argument equality: key order ignored, array order matters, no coercion,
1078
+ * numbers equal as parsed JSON, absent `arguments` is `{}`, non-object
1079
+ * `arguments` never equals an `args` entry (only an `anyArgs` entry matches
1080
+ * it).
1081
+ */
1082
+ function mcpArgsEqual(expected, received) {
1083
+ const actual = received === void 0 ? {} : received;
1084
+ if (!isPlainObject(actual)) return false;
1085
+ return jsonEqual(expected, actual);
1086
+ }
1087
+ /**
1088
+ * `path` and one more key: `.key` for a short identifier, else `[<key>]` with
1089
+ * the key JSON-quoted whole. The path is cut once, where a message shows it
1090
+ * (`pathPart`).
1091
+ */
1092
+ function pathKey(path, key) {
1093
+ return key.length <= require_constants.MCP_FAKES_ECHO_LIMIT && /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? `${path}.${key}` : `${path}[${require_message_text.quoteJson(key)}]`;
1094
+ }
1095
+ /**
1096
+ * A JSON path (built by `pathKey`) as a message part: user data, cut as JSON
1097
+ * text, so the cut never splits an escape of a quoted key.
1098
+ */
1099
+ function pathPart(path) {
1100
+ return require_message_text.jsonText(path);
1101
+ }
1102
+ function diffAt(path, expected, received) {
1103
+ if (Array.isArray(expected)) {
1104
+ if (!Array.isArray(received)) return require_message_text.msg`${pathPart(path)}: expected an array, received ${shown(received)}`;
1105
+ if (expected.length !== received.length) return require_message_text.msg`${pathPart(path)}: expected an array of length ${expected.length}, received length ${received.length}`;
1106
+ for (let i = 0; i < expected.length; i++) {
1107
+ const d = diffAt(`${path}[${i}]`, expected[i], received[i]);
1108
+ if (d) return d;
1109
+ }
1110
+ return null;
1111
+ }
1112
+ if (isPlainObject(expected)) {
1113
+ if (!isPlainObject(received)) return require_message_text.msg`${pathPart(path)}: expected an object, received ${shown(received)}`;
1114
+ for (const key of ownKeys(expected)) {
1115
+ const p = pathKey(path, key);
1116
+ if (!hasOwnKey(received, key)) return require_message_text.msg`${pathPart(p)}: expected ${shown(expected[key])}, received nothing (key missing)`;
1117
+ const d = diffAt(p, expected[key], received[key]);
1118
+ if (d) return d;
1119
+ }
1120
+ for (const key of ownKeys(received)) if (!hasOwnKey(expected, key)) return require_message_text.msg`${pathPart(pathKey(path, key))}: unexpected key, received ${shown(received[key])}`;
1121
+ return null;
1122
+ }
1123
+ if (expected === received) return null;
1124
+ return require_message_text.msg`${pathPart(path)}: expected ${shown(expected)}, received ${shown(received)}`;
1125
+ }
1126
+ /**
1127
+ * A JSON path to the first unequal value, with both values (the mismatch
1128
+ * detail). Returns `""` when `mcpArgsEqual(expected, received)` holds. The
1129
+ * path and each value are cut at `MCP_FAKES_ECHO_LIMIT` chars and the whole
1130
+ * text at 1000, each part once.
1131
+ */
1132
+ function firstDifference(expected, received) {
1133
+ const diff = diffAt("$", expected, received === void 0 ? {} : received);
1134
+ return diff ? require_message_text.build(diff, DIFF_LIMIT) : "";
1135
+ }
1136
+ /**
1137
+ * Approximate count of differences, used to pick the closest declared entry.
1138
+ * A key or array element present on one side only counts as 1, whatever its
1139
+ * subtree size.
1140
+ */
1141
+ function diffCount(expected, received) {
1142
+ if (Array.isArray(expected) && Array.isArray(received)) {
1143
+ const n = Math.max(expected.length, received.length);
1144
+ let count = 0;
1145
+ for (let i = 0; i < n; i++) if (i >= expected.length || i >= received.length) count += 1;
1146
+ else count += diffCount(expected[i], received[i]);
1147
+ return count;
1148
+ }
1149
+ if (isPlainObject(expected) && isPlainObject(received)) {
1150
+ let count = 0;
1151
+ for (const key of new Set([...ownKeys(expected), ...ownKeys(received)])) {
1152
+ const inE = hasOwnKey(expected, key);
1153
+ const inR = hasOwnKey(received, key);
1154
+ count += inE && inR ? diffCount(expected[key], received[key]) : 1;
1155
+ }
1156
+ return count;
1157
+ }
1158
+ return jsonEqual(expected, received) ? 0 : 1;
1159
+ }
1160
+ function callMatches(call, received) {
1161
+ return call.anyArgs === true || call.args !== void 0 && mcpArgsEqual(call.args, received);
1162
+ }
1163
+ var McpFakeStore = class {
1164
+ blocks = [];
1165
+ /** Entry id → the block id that owns it, for every loaded entry. */
1166
+ ids = /* @__PURE__ */ new Map();
1167
+ /** Block id → its `source`, for every loaded block. */
1168
+ blockIds = /* @__PURE__ */ new Map();
1169
+ /**
1170
+ * Consumption state: supplied test id → consumed entry ids. Map insertion
1171
+ * order is the FIFO order of the cap.
1172
+ */
1173
+ consumed = /* @__PURE__ */ new Map();
1174
+ /** Consumed entry ids for requests with no test id. Never evicted. */
1175
+ untagged = /* @__PURE__ */ new Set();
1176
+ /**
1177
+ * Test ids whose state the cap evicted. A claim from one of them, of a tool
1178
+ * an applicable block declares, is `evicted` until the test id is reset; a
1179
+ * mark never ages out, because a forgotten mark would silently replay
1180
+ * entries the test id already consumed. Memory stays bounded per evicted
1181
+ * test id: one id string, against the entry-id set the cap dropped. The set only grows with distinct test ids, and every
1182
+ * reset door clears it (`resetState`, `clear`). A cap of `0` evicts
1183
+ * nothing, so marks made under an earlier cap stay until reset.
1184
+ */
1185
+ evicted = /* @__PURE__ */ new Set();
1186
+ /**
1187
+ * The last `<n>` of `code#<n>` / `control-api#<n>` used by a run-time
1188
+ * addition (see `nextRuntimeNumber`). Never rewound.
1189
+ */
1190
+ runtimeAdds = 0;
1191
+ /**
1192
+ * The `<k>` of `@<k>`: loads per source string, skipping a `@<k>` that a
1193
+ * loaded block id already starts (see `add`). Never rewound.
1194
+ */
1195
+ loadCounts = /* @__PURE__ */ new Map();
1196
+ maxTestIds;
1197
+ constructor(options = {}) {
1198
+ this.maxTestIds = checkCap(options.maxTestIds ?? MCP_FAKES_DEFAULT_MAX_TEST_IDS);
1199
+ }
1200
+ /**
1201
+ * Set the FIFO cap on distinct test ids; an MCP mount can pass the
1202
+ * journal's `fixtureCountsMaxTestIdsCap`. A lower cap evicts the oldest test
1203
+ * ids at once. `0` is unbounded: nothing is evicted, and test ids already
1204
+ * marked evicted stay marked until reset. An invalid cap throws a
1205
+ * `RangeError` and leaves the cap unchanged.
1206
+ */
1207
+ setMaxTestIds(cap) {
1208
+ this.maxTestIds = checkCap(cap);
1209
+ this.enforceCap();
1210
+ }
1211
+ /**
1212
+ * Take one plain-data snapshot of `sources` and `origin` (getters are not
1213
+ * run, inherited fields not read, holes, symbol keys, proxies that throw and
1214
+ * non-plain objects are errors), validate the whole snapshot, derive entry
1215
+ * ids and their counters, check that every block id and every entry id is
1216
+ * unique against the mount and inside the input (bad block (f), naming the
1217
+ * block that already owns the id and, for a block id, that block's source),
1218
+ * then append. Every block is checked,
1219
+ * valid or not (no first-stop; `validateBlock` says which checks stop
1220
+ * early). On any error, throws one `McpFakesAddError` carrying every error
1221
+ * found, and the warnings of the blocks that passed every check, and adds
1222
+ * nothing (no counter is consumed); it never throws anything else for a bad
1223
+ * input (a throw from a defect in aimock itself is not caught). An empty
1224
+ * input, an invalid `origin` or an invalid source item (not an object, an
1225
+ * empty `source`, a `blockIndex` that is not `null` or a non-negative
1226
+ * integer) is a bad block (i).
1227
+ *
1228
+ * With `kind: "file"`, consecutive sources with the same `source` string
1229
+ * and increasing `blockIndex` form one load (one `@<k>`). A load whose
1230
+ * derived block ids a loaded block already has (a file named `"a@2"`
1231
+ * starts the ids of `@2`; a file named `"a[0]"` has the id of block 0 of
1232
+ * the first load) takes the next free `@<k>`, so a file name never blocks
1233
+ * later loads of another source. A file loaded after the block whose id
1234
+ * its name spells is still the bad block (f). With a run-time origin,
1235
+ * blocks are numbered per `McpFakeAddOrigin`; a block given with a null
1236
+ * `blockIndex` keeps the single-object depth limit.
1237
+ */
1238
+ add(sources, origin) {
1239
+ const originData = snapshotInput(origin);
1240
+ const kind = isPlainObject(originData) ? originData.kind : void 0;
1241
+ if (kind !== "file" && kind !== "code" && kind !== "control-api") throw new McpFakesAddError([badBlock({
1242
+ source: null,
1243
+ blockId: null
1244
+ }, "i", isPlainObject(originData) ? require_message_text.msg`origin.kind must be "file", "code" or "control-api", got ${shown(kind)}` : require_message_text.msg`origin must be an object { kind }, got ${shown(originData)}`)], []);
1245
+ const runtimeNumber = kind === "file" ? null : this.nextRuntimeNumber(kind);
1246
+ const runtimeSource = runtimeNumber === null ? null : `${kind}#${runtimeNumber}`;
1247
+ const input = snapshotInput(sources, MCP_FAKES_MAX_DEPTH + 2);
1248
+ if (!Array.isArray(input) || input.length === 0) {
1249
+ const detail = Array.isArray(input) ? require_message_text.msg`mcpFakes holds no blocks` : require_message_text.msg`the blocks to add must be an array, got ${shown(input)}`;
1250
+ throw new McpFakesAddError([badBlock({
1251
+ source: runtimeSource,
1252
+ blockId: null
1253
+ }, "i", detail)], []);
1254
+ }
1255
+ const errors = [];
1256
+ const warnings = [];
1257
+ const accepted = [];
1258
+ /** Entry id → owning block id, for this input. */
1259
+ const newIds = /* @__PURE__ */ new Map();
1260
+ /** Block id → source, for this input. */
1261
+ const newBlockIds = /* @__PURE__ */ new Map();
1262
+ const pendingLoads = /* @__PURE__ */ new Map();
1263
+ const items = [];
1264
+ input.forEach((item, position) => {
1265
+ const fail = (detail, file = runtimeSource) => {
1266
+ errors.push(badBlock({
1267
+ source: file,
1268
+ blockId: null
1269
+ }, "i", require_message_text.msg`blocks[${position}] ${detail}`));
1270
+ };
1271
+ if (!isPlainObject(item)) {
1272
+ fail(require_message_text.msg`must be an object { source, blockIndex, raw }, got ${shown(item)}`);
1273
+ return;
1274
+ }
1275
+ const { source, blockIndex } = item;
1276
+ if (runtimeSource === null && !isNonEmptyString(source)) {
1277
+ fail(require_message_text.msg`source must be a non-empty string, got ${shown(source)}`);
1278
+ return;
1279
+ }
1280
+ const sourceText = typeof source === "string" ? source : "";
1281
+ const index = blockIndex === null || typeof blockIndex === "number" && Number.isSafeInteger(blockIndex) && blockIndex >= 0 ? blockIndex : void 0;
1282
+ if (index === void 0) {
1283
+ fail(require_message_text.msg`blockIndex must be null or a non-negative integer, got ${shown(blockIndex)}`, runtimeSource ?? sourceText);
1284
+ return;
1285
+ }
1286
+ items.push({
1287
+ position,
1288
+ source: sourceText,
1289
+ blockIndex: index,
1290
+ raw: item.raw
1291
+ });
1292
+ });
1293
+ const singleObject = input.length === 1 && items.length === 1 && items[0].blockIndex === null;
1294
+ /** Two consecutive file items with one source and a rising block index are one load. */
1295
+ const sameLoad = (prev, item) => prev !== void 0 && prev.source === item.source && prev.blockIndex !== null && item.blockIndex !== null && item.blockIndex > prev.blockIndex;
1296
+ let load = "";
1297
+ for (const [at, item] of items.entries()) {
1298
+ const source = runtimeSource ?? item.source;
1299
+ let blockIndex = item.blockIndex;
1300
+ if (runtimeSource === null) {
1301
+ if (!sameLoad(items[at - 1], item)) {
1302
+ const indices = [];
1303
+ for (let j = at; j < items.length && (j === at || sameLoad(items[j - 1], items[j])); j++) {
1304
+ const index = items[j].blockIndex;
1305
+ if (index !== null) indices.push(index);
1306
+ }
1307
+ let k = (pendingLoads.get(source) ?? this.loadCounts.get(source) ?? 0) + 1;
1308
+ while (k > 1 ? this.blockIdPrefixTaken(blockIdOf(source, `@${k}`, null), newBlockIds) : indices.some((index) => {
1309
+ const derived = blockIdOf(source, "", index);
1310
+ return (this.blockIds.get(derived) ?? newBlockIds.get(derived)) === derived;
1311
+ })) k += 1;
1312
+ pendingLoads.set(source, k);
1313
+ load = k === 1 ? "" : `@${k}`;
1314
+ }
1315
+ } else blockIndex = singleObject ? null : item.position;
1316
+ const outcome = validateBlock(item.blockIndex === null ? item.raw : capDepth(item.raw, MCP_FAKES_MAX_DEPTH - 1), source, blockIndex, load, errors);
1317
+ const blockId = blockIdOf(source, load, blockIndex);
1318
+ const blockOwner = this.blockIds.get(blockId) ?? newBlockIds.get(blockId);
1319
+ let collided = blockOwner !== void 0;
1320
+ if (blockOwner !== void 0) errors.push(badBlock({
1321
+ source,
1322
+ blockId
1323
+ }, "f", blockCollisionDetail(source, blockOwner, this.blockIds.has(blockId))));
1324
+ else newBlockIds.set(blockId, source);
1325
+ for (const id of outcome.entryIds) {
1326
+ const loaded = this.ids.get(id);
1327
+ const earlier = newIds.get(id);
1328
+ if (!newIds.has(id)) newIds.set(id, blockId);
1329
+ if (blockOwner !== void 0) continue;
1330
+ const where = loaded !== void 0 ? require_message_text.msg`is already loaded on this mount, in block ${require_message_text.quote(loaded)}` : earlier !== void 0 ? require_message_text.msg`collides with an entry of block ${require_message_text.quote(earlier)} earlier in the same input` : null;
1331
+ if (where === null) continue;
1332
+ errors.push(badBlock({
1333
+ source,
1334
+ blockId
1335
+ }, "f", require_message_text.msg`entry id ${require_message_text.quote(id)} ${where}`, id));
1336
+ collided = true;
1337
+ }
1338
+ if (outcome.block && !collided) {
1339
+ accepted.push(outcome.block);
1340
+ warnings.push(...outcome.warnings);
1341
+ }
1342
+ }
1343
+ if (errors.length > 0) throw new McpFakesAddError(errors, warnings);
1344
+ for (const [source, k] of pendingLoads) this.loadCounts.set(source, k);
1345
+ if (runtimeNumber !== null) this.runtimeAdds = runtimeNumber;
1346
+ for (const [id, owner] of newIds) this.ids.set(id, owner);
1347
+ for (const [blockId, source] of newBlockIds) this.blockIds.set(blockId, source);
1348
+ this.blocks.push(...accepted);
1349
+ return { warnings };
1350
+ }
1351
+ /**
1352
+ * The `<n>` for the next run-time add of `kind`: one more than the last one
1353
+ * used, skipping any number whose `<kind>#<n>` already starts a loaded block
1354
+ * id (and so every entry id of that block). A file source may be spelled
1355
+ * `code#1`; without the skip, every later run-time add would collide with it
1356
+ * and fail, and a failed add does not advance `<n>`. Blocks loaded after a
1357
+ * run-time add still collide with it as usual (the later block fails).
1358
+ */
1359
+ nextRuntimeNumber(kind) {
1360
+ let n = this.runtimeAdds + 1;
1361
+ while (this.blockIdPrefixTaken(`${kind}#${n}`)) n += 1;
1362
+ return n;
1363
+ }
1364
+ /**
1365
+ * A loaded block id (or one in `pending`, the blocks earlier in the same
1366
+ * input) starts with `prefix` and does not go on with a digit, so `prefix`
1367
+ * would start a colliding block or entry id (`a@2` is taken by `a@2` and
1368
+ * `a@2[0]`, not by `a@23`).
1369
+ */
1370
+ blockIdPrefixTaken(prefix, pending) {
1371
+ for (const ids of [this.blockIds, pending ?? /* @__PURE__ */ new Map()]) for (const blockId of ids.keys()) if (blockId.startsWith(prefix) && !/^[0-9]/.test(blockId.slice(prefix.length))) return true;
1372
+ return false;
1373
+ }
1374
+ /** Unload every fake and its state. The id counters (`<n>`, `<k>`) are kept. */
1375
+ clear() {
1376
+ this.blocks = [];
1377
+ this.ids.clear();
1378
+ this.blockIds.clear();
1379
+ this.resetState();
1380
+ }
1381
+ /**
1382
+ * Reset consumption state for one test id, or all. Also clears an
1383
+ * `evicted` mark. `MCP_FAKES_DEFAULT_TEST_ID` resets the untagged state as
1384
+ * well as an explicit test id of that name. That matches
1385
+ * `Journal.clearMatchCounts` (which `LLMock.resetMatchCounts` calls), where
1386
+ * untagged requests and an explicit `__default__` share one key.
1387
+ */
1388
+ resetState(testId) {
1389
+ if (testId === void 0) {
1390
+ this.consumed.clear();
1391
+ this.untagged.clear();
1392
+ this.evicted.clear();
1393
+ return;
1394
+ }
1395
+ this.consumed.delete(testId);
1396
+ this.evicted.delete(testId);
1397
+ if (testId === MCP_FAKES_DEFAULT_TEST_ID) this.untagged.clear();
1398
+ }
1399
+ /** Every entry id on this mount, in load order. */
1400
+ allEntryIds() {
1401
+ return this.blocks.flatMap(blockEntryIds);
1402
+ }
1403
+ /** The blocks whose scope matches `identity`, in load order. A frozen list of frozen blocks. */
1404
+ applicable(identity) {
1405
+ return Object.freeze(this.blocks.filter((b) => scopeMatches(b.scope, identity)));
1406
+ }
1407
+ /** The candidate entries for `tool` from the most specific tier declaring it. */
1408
+ selectTier(tool, identity) {
1409
+ const blocks = this.applicable(identity);
1410
+ for (const tier of TIER_ORDER) {
1411
+ const entries = [];
1412
+ for (const block of blocks) {
1413
+ if (block.tier !== tier) continue;
1414
+ const t = block.tools.find((x) => x.name === tool);
1415
+ if (t) for (const entry of t.calls) entries.push({
1416
+ block,
1417
+ entry
1418
+ });
1419
+ }
1420
+ if (entries.length > 0) return {
1421
+ tier,
1422
+ entries
1423
+ };
1424
+ }
1425
+ return null;
1426
+ }
1427
+ /**
1428
+ * Find the first unconsumed matching entry of the selected tier (load order,
1429
+ * then `calls` order) and mark it consumed, synchronously (no `await`
1430
+ * between find and mark), so concurrent calls never share an entry. The
1431
+ * result is built and checked to serialize as JSON before the entry is
1432
+ * marked, so a result that cannot be sent throws and leaves the entry
1433
+ * unconsumed. An `anyArgs` entry answers any `arguments`, a non-object
1434
+ * value included; only an `args` entry needs an object.
1435
+ */
1436
+ claim(tool, args, identity) {
1437
+ const selection = this.selectTier(tool, identity);
1438
+ if (!selection) return Object.freeze({ kind: "none" });
1439
+ const testId = identity.testId;
1440
+ if (testId !== null && this.evicted.has(testId)) return Object.freeze({
1441
+ kind: "evicted",
1442
+ tool,
1443
+ testId,
1444
+ maxTestIds: this.maxTestIds
1445
+ });
1446
+ const used = this.stateOf(testId);
1447
+ const received = args === void 0 ? {} : args;
1448
+ for (const { block, entry } of selection.entries) {
1449
+ if (used?.has(entry.id)) continue;
1450
+ if (!callMatches(entry, received)) continue;
1451
+ let result;
1452
+ try {
1453
+ result = toCallToolResult(entry);
1454
+ } catch (err) {
1455
+ throw new Error(require_message_text.build(require_message_text.msg`MCP fake entry ${require_message_text.quote(entry.id)}: its tools/call result cannot be built: ${describeThrown(err)}`), { cause: err });
1456
+ }
1457
+ try {
1458
+ JSON.stringify(result);
1459
+ } catch (err) {
1460
+ throw new Error(require_message_text.build(require_message_text.msg`MCP fake entry ${require_message_text.quote(entry.id)}: result cannot be serialized as JSON: ${describeThrown(err)}`), { cause: err });
1461
+ }
1462
+ this.stateFor(testId).add(entry.id);
1463
+ return Object.freeze({
1464
+ kind: "answer",
1465
+ tool,
1466
+ entry,
1467
+ block,
1468
+ tier: selection.tier,
1469
+ result
1470
+ });
1471
+ }
1472
+ const matching = selection.entries.filter(({ entry }) => callMatches(entry, received));
1473
+ if (matching.length > 0) return Object.freeze({
1474
+ kind: "exhausted",
1475
+ tool,
1476
+ received,
1477
+ matchingDeclared: matching.length,
1478
+ matchingConsumed: matching.filter(({ entry }) => used?.has(entry.id)).length,
1479
+ matchingIds: Object.freeze(matching.map(({ entry }) => entry.id))
1480
+ });
1481
+ let closest = null;
1482
+ let best = Infinity;
1483
+ for (const { entry } of selection.entries) {
1484
+ if (!entry.args) continue;
1485
+ const score = diffCount(entry.args, received);
1486
+ if (score < best) {
1487
+ best = score;
1488
+ closest = entry;
1489
+ }
1490
+ }
1491
+ return Object.freeze({
1492
+ kind: "mismatch",
1493
+ tool,
1494
+ received,
1495
+ declared: deepFreeze(selection.entries.map(({ entry }) => declaredEntry(entry, used))),
1496
+ firstDifference: closest?.args ? firstDifference(closest.args, received) : ""
1497
+ });
1498
+ }
1499
+ /**
1500
+ * The undeclared-tool policy, first of: `override` (`null` or omitted falls
1501
+ * back to `identity.undeclared`, the request's or session's
1502
+ * `X-AIMock-MCP-Undeclared` / `?undeclared=` value) → `deny` when any
1503
+ * applicable scoped block says `deny` → `allow`.
1504
+ */
1505
+ policy(identity, override = null) {
1506
+ const effective = override ?? identity.undeclared;
1507
+ if (effective) return effective;
1508
+ for (const block of this.applicable(identity)) if (block.scope !== "shared" && block.undeclaredTools === "deny") return "deny";
1509
+ return "allow";
1510
+ }
1511
+ /** Tool names any applicable block (any tier) declares, in load order. */
1512
+ declaredTools(identity) {
1513
+ const names = /* @__PURE__ */ new Set();
1514
+ for (const block of this.applicable(identity)) for (const t of block.tools) names.add(t.name);
1515
+ return [...names];
1516
+ }
1517
+ /**
1518
+ * Fake tools for `tools/list`, metadata from the most specific
1519
+ * tier declaring each tool; inside that tier, the first block in load order
1520
+ * that declares it. A tool with no `inputSchema` lists `{ type: "object" }`.
1521
+ * Merging with registered tools is the mount's job.
1522
+ */
1523
+ listTools(identity) {
1524
+ return this.declaredTools(identity).map((name) => {
1525
+ const tool = this.selectTier(name, identity)?.entries[0].block.tools.find((t) => t.name === name);
1526
+ const listing = {
1527
+ name,
1528
+ inputSchema: tool?.inputSchema ? copyJson(tool.inputSchema) : { type: "object" }
1529
+ };
1530
+ if (tool?.description !== void 0) listing.description = tool.description;
1531
+ return listing;
1532
+ });
1533
+ }
1534
+ /**
1535
+ * For `GET /__aimock/mcp/fakes`: applicable blocks with consumed state for
1536
+ * `testId`. For a test id the cap evicted, every block says `evicted: true`.
1537
+ * Deeply frozen.
1538
+ */
1539
+ snapshot(testId, context) {
1540
+ const used = this.stateOf(testId);
1541
+ const evicted = testId !== null && this.evicted.has(testId);
1542
+ return deepFreeze(this.applicable({
1543
+ testId,
1544
+ context,
1545
+ undeclared: null
1546
+ }).map((block) => ({
1547
+ blockId: block.blockId,
1548
+ source: block.source,
1549
+ mount: block.mount,
1550
+ scope: block.scope,
1551
+ undeclaredTools: block.undeclaredTools ?? "allow",
1552
+ evicted,
1553
+ tools: block.tools.map((t) => ({
1554
+ name: t.name,
1555
+ entries: t.calls.map((c) => declaredEntry(c, used))
1556
+ }))
1557
+ })));
1558
+ }
1559
+ /** The consumed ids for `testId` (`null`: untagged), without creating state. */
1560
+ stateOf(testId) {
1561
+ return testId === null ? this.untagged : this.consumed.get(testId);
1562
+ }
1563
+ stateFor(testId) {
1564
+ if (testId === null) return this.untagged;
1565
+ let set = this.consumed.get(testId);
1566
+ if (!set) {
1567
+ set = /* @__PURE__ */ new Set();
1568
+ this.consumed.set(testId, set);
1569
+ this.enforceCap();
1570
+ }
1571
+ return set;
1572
+ }
1573
+ /**
1574
+ * FIFO cap: evict the oldest test ids until the cap holds, marking each
1575
+ * evicted. A cap of `0` evicts nothing. The marks are not capped (see
1576
+ * `evicted`).
1577
+ */
1578
+ enforceCap() {
1579
+ if (this.maxTestIds === 0) return;
1580
+ for (const oldest of this.consumed.keys()) {
1581
+ if (this.consumed.size <= this.maxTestIds) break;
1582
+ this.consumed.delete(oldest);
1583
+ this.evicted.add(oldest);
1584
+ }
1585
+ }
1586
+ };
1587
+ /**
1588
+ * The detail of a block-id collision (bad block (f)). The message prefix
1589
+ * already names the block id, so the detail names the two sources.
1590
+ */
1591
+ function blockCollisionDetail(source, owner, loaded) {
1592
+ return loaded ? require_message_text.msg`block id (this block's source: ${require_message_text.quote(source)}) is already loaded on this mount by source ${require_message_text.quote(owner)}` : require_message_text.msg`block id (this block's source: ${require_message_text.quote(source)}) collides with a block earlier in the same input, from source ${require_message_text.quote(owner)}`;
1593
+ }
1594
+ /** The test-id cap: a non-negative safe integer, `0` meaning unbounded. Anything else throws. */
1595
+ function checkCap(cap) {
1596
+ if (!Number.isSafeInteger(cap) || cap < 0) throw new RangeError(require_message_text.build(require_message_text.msg`MCP fakes maxTestIds must be a non-negative integer (0 = unbounded), got ${shown(cap)}`));
1597
+ return cap;
1598
+ }
1599
+ function declaredEntry(entry, used) {
1600
+ const consumed = used?.has(entry.id) ?? false;
1601
+ return entry.args ? {
1602
+ id: entry.id,
1603
+ args: copyJson(entry.args),
1604
+ consumed
1605
+ } : {
1606
+ id: entry.id,
1607
+ anyArgs: true,
1608
+ consumed
1609
+ };
1610
+ }
1611
+
1612
+ //#endregion
1613
+ exports.MCP_FAKES_DEFAULT_MOUNT = MCP_FAKES_DEFAULT_MOUNT;
1614
+ exports.MCP_FAKES_DEFAULT_TEST_ID = MCP_FAKES_DEFAULT_TEST_ID;
1615
+ exports.McpFakeStore = McpFakeStore;
1616
+ exports.McpFakesAddError = McpFakesAddError;
1617
+ exports.blockIdOf = blockIdOf;
1618
+ exports.validateMcpFakes = validateMcpFakes;
1619
+ //# sourceMappingURL=mcp-fakes.cjs.map