@copilotkit/aimock 1.43.0 → 1.45.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 (396) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +46 -0
  4. package/README.md +14 -2
  5. package/dist/agui-handler.d.ts.map +1 -1
  6. package/dist/aimock-cli.cjs +31 -5
  7. package/dist/aimock-cli.cjs.map +1 -1
  8. package/dist/aimock-cli.d.cts.map +1 -1
  9. package/dist/aimock-cli.d.ts.map +1 -1
  10. package/dist/aimock-cli.js +31 -5
  11. package/dist/aimock-cli.js.map +1 -1
  12. package/dist/bedrock-converse.cjs +344 -43
  13. package/dist/bedrock-converse.cjs.map +1 -1
  14. package/dist/bedrock-converse.d.cts.map +1 -1
  15. package/dist/bedrock-converse.d.ts.map +1 -1
  16. package/dist/bedrock-converse.js +345 -44
  17. package/dist/bedrock-converse.js.map +1 -1
  18. package/dist/bedrock.cjs +195 -42
  19. package/dist/bedrock.cjs.map +1 -1
  20. package/dist/bedrock.d.cts.map +1 -1
  21. package/dist/bedrock.d.ts.map +1 -1
  22. package/dist/bedrock.js +196 -43
  23. package/dist/bedrock.js.map +1 -1
  24. package/dist/chaos.cjs +9 -5
  25. package/dist/chaos.cjs.map +1 -1
  26. package/dist/chaos.d.cts.map +1 -1
  27. package/dist/chaos.d.ts.map +1 -1
  28. package/dist/chaos.js +10 -7
  29. package/dist/chaos.js.map +1 -1
  30. package/dist/cli.cjs +238 -9
  31. package/dist/cli.cjs.map +1 -1
  32. package/dist/cli.js +242 -13
  33. package/dist/cli.js.map +1 -1
  34. package/dist/cohere.cjs +159 -72
  35. package/dist/cohere.cjs.map +1 -1
  36. package/dist/cohere.d.cts.map +1 -1
  37. package/dist/cohere.d.ts.map +1 -1
  38. package/dist/cohere.js +160 -73
  39. package/dist/cohere.js.map +1 -1
  40. package/dist/config-loader.cjs +63 -5
  41. package/dist/config-loader.cjs.map +1 -1
  42. package/dist/config-loader.d.cts +37 -4
  43. package/dist/config-loader.d.cts.map +1 -1
  44. package/dist/config-loader.d.ts +37 -4
  45. package/dist/config-loader.d.ts.map +1 -1
  46. package/dist/config-loader.js +63 -5
  47. package/dist/config-loader.js.map +1 -1
  48. package/dist/constants.cjs +8 -0
  49. package/dist/constants.cjs.map +1 -1
  50. package/dist/constants.d.cts +6 -2
  51. package/dist/constants.d.ts +6 -2
  52. package/dist/constants.js +8 -1
  53. package/dist/constants.js.map +1 -1
  54. package/dist/echo-text.cjs +65 -0
  55. package/dist/echo-text.cjs.map +1 -0
  56. package/dist/echo-text.d.cts +22 -0
  57. package/dist/echo-text.d.cts.map +1 -0
  58. package/dist/echo-text.d.ts +22 -0
  59. package/dist/echo-text.d.ts.map +1 -0
  60. package/dist/echo-text.js +65 -0
  61. package/dist/echo-text.js.map +1 -0
  62. package/dist/embeddings.cjs +18 -1
  63. package/dist/embeddings.cjs.map +1 -1
  64. package/dist/embeddings.d.cts +1 -1
  65. package/dist/embeddings.d.cts.map +1 -1
  66. package/dist/embeddings.d.ts +1 -1
  67. package/dist/embeddings.d.ts.map +1 -1
  68. package/dist/embeddings.js +18 -1
  69. package/dist/embeddings.js.map +1 -1
  70. package/dist/fakes-plugin.cjs +131 -0
  71. package/dist/fakes-plugin.cjs.map +1 -0
  72. package/dist/fakes-plugin.d.cts +23 -0
  73. package/dist/fakes-plugin.d.cts.map +1 -0
  74. package/dist/fakes-plugin.d.ts +23 -0
  75. package/dist/fakes-plugin.d.ts.map +1 -0
  76. package/dist/fakes-plugin.js +127 -0
  77. package/dist/fakes-plugin.js.map +1 -0
  78. package/dist/files.cjs +9 -11
  79. package/dist/files.cjs.map +1 -1
  80. package/dist/files.d.cts.map +1 -1
  81. package/dist/files.d.ts.map +1 -1
  82. package/dist/files.js +10 -13
  83. package/dist/files.js.map +1 -1
  84. package/dist/fine-tuning.cjs +1 -19
  85. package/dist/fine-tuning.cjs.map +1 -1
  86. package/dist/fine-tuning.d.cts.map +1 -1
  87. package/dist/fine-tuning.d.ts.map +1 -1
  88. package/dist/fine-tuning.js +2 -20
  89. package/dist/fine-tuning.js.map +1 -1
  90. package/dist/fixture-loader-services.cjs +116 -0
  91. package/dist/fixture-loader-services.cjs.map +1 -0
  92. package/dist/fixture-loader-services.d.cts +48 -0
  93. package/dist/fixture-loader-services.d.cts.map +1 -0
  94. package/dist/fixture-loader-services.d.ts +48 -0
  95. package/dist/fixture-loader-services.d.ts.map +1 -0
  96. package/dist/fixture-loader-services.js +114 -0
  97. package/dist/fixture-loader-services.js.map +1 -0
  98. package/dist/fixture-loader.cjs +552 -29
  99. package/dist/fixture-loader.cjs.map +1 -1
  100. package/dist/fixture-loader.d.cts +140 -2
  101. package/dist/fixture-loader.d.cts.map +1 -1
  102. package/dist/fixture-loader.d.ts +140 -2
  103. package/dist/fixture-loader.d.ts.map +1 -1
  104. package/dist/fixture-loader.js +542 -31
  105. package/dist/fixture-loader.js.map +1 -1
  106. package/dist/gemini-embeddings.cjs +42 -2
  107. package/dist/gemini-embeddings.cjs.map +1 -1
  108. package/dist/gemini-embeddings.js +43 -3
  109. package/dist/gemini-embeddings.js.map +1 -1
  110. package/dist/gemini-interactions.cjs +288 -52
  111. package/dist/gemini-interactions.cjs.map +1 -1
  112. package/dist/gemini-interactions.d.cts.map +1 -1
  113. package/dist/gemini-interactions.d.ts.map +1 -1
  114. package/dist/gemini-interactions.js +289 -53
  115. package/dist/gemini-interactions.js.map +1 -1
  116. package/dist/gemini.cjs +234 -38
  117. package/dist/gemini.cjs.map +1 -1
  118. package/dist/gemini.d.cts.map +1 -1
  119. package/dist/gemini.d.ts.map +1 -1
  120. package/dist/gemini.js +235 -39
  121. package/dist/gemini.js.map +1 -1
  122. package/dist/helpers.cjs +1157 -12
  123. package/dist/helpers.cjs.map +1 -1
  124. package/dist/helpers.d.cts +85 -4
  125. package/dist/helpers.d.cts.map +1 -1
  126. package/dist/helpers.d.ts +85 -4
  127. package/dist/helpers.d.ts.map +1 -1
  128. package/dist/helpers.js +1113 -14
  129. package/dist/helpers.js.map +1 -1
  130. package/dist/images.cjs +3 -2
  131. package/dist/images.cjs.map +1 -1
  132. package/dist/images.js +3 -2
  133. package/dist/images.js.map +1 -1
  134. package/dist/index.cjs +39 -1
  135. package/dist/index.d.cts +11 -4
  136. package/dist/index.d.ts +11 -4
  137. package/dist/index.js +11 -4
  138. package/dist/jest.cjs +43 -14
  139. package/dist/jest.cjs.map +1 -1
  140. package/dist/jest.d.cts +47 -5
  141. package/dist/jest.d.cts.map +1 -1
  142. package/dist/jest.d.ts +47 -5
  143. package/dist/jest.d.ts.map +1 -1
  144. package/dist/jest.js +45 -16
  145. package/dist/jest.js.map +1 -1
  146. package/dist/journal.cjs +155 -13
  147. package/dist/journal.cjs.map +1 -1
  148. package/dist/journal.d.cts +54 -4
  149. package/dist/journal.d.cts.map +1 -1
  150. package/dist/journal.d.ts +54 -4
  151. package/dist/journal.d.ts.map +1 -1
  152. package/dist/journal.js +155 -13
  153. package/dist/journal.js.map +1 -1
  154. package/dist/live-sanitize.cjs +4 -32
  155. package/dist/live-sanitize.cjs.map +1 -1
  156. package/dist/live-sanitize.js +4 -32
  157. package/dist/live-sanitize.js.map +1 -1
  158. package/dist/llmock.cjs +204 -39
  159. package/dist/llmock.cjs.map +1 -1
  160. package/dist/llmock.d.cts +51 -1
  161. package/dist/llmock.d.cts.map +1 -1
  162. package/dist/llmock.d.ts +51 -1
  163. package/dist/llmock.d.ts.map +1 -1
  164. package/dist/llmock.js +206 -41
  165. package/dist/llmock.js.map +1 -1
  166. package/dist/mcp-fakes-mount.cjs +217 -0
  167. package/dist/mcp-fakes-mount.cjs.map +1 -0
  168. package/dist/mcp-fakes-mount.js +211 -0
  169. package/dist/mcp-fakes-mount.js.map +1 -0
  170. package/dist/mcp-fakes-report.cjs +111 -0
  171. package/dist/mcp-fakes-report.cjs.map +1 -0
  172. package/dist/mcp-fakes-report.d.cts +40 -0
  173. package/dist/mcp-fakes-report.d.cts.map +1 -0
  174. package/dist/mcp-fakes-report.d.ts +40 -0
  175. package/dist/mcp-fakes-report.d.ts.map +1 -0
  176. package/dist/mcp-fakes-report.js +107 -0
  177. package/dist/mcp-fakes-report.js.map +1 -0
  178. package/dist/mcp-fakes.cjs +1942 -0
  179. package/dist/mcp-fakes.cjs.map +1 -0
  180. package/dist/mcp-fakes.d.cts +504 -0
  181. package/dist/mcp-fakes.d.cts.map +1 -0
  182. package/dist/mcp-fakes.d.ts +504 -0
  183. package/dist/mcp-fakes.d.ts.map +1 -0
  184. package/dist/mcp-fakes.js +1937 -0
  185. package/dist/mcp-fakes.js.map +1 -0
  186. package/dist/mcp-handler.cjs +534 -27
  187. package/dist/mcp-handler.cjs.map +1 -1
  188. package/dist/mcp-handler.d.cts +49 -0
  189. package/dist/mcp-handler.d.cts.map +1 -0
  190. package/dist/mcp-handler.d.ts +49 -0
  191. package/dist/mcp-handler.d.ts.map +1 -0
  192. package/dist/mcp-handler.js +530 -28
  193. package/dist/mcp-handler.js.map +1 -1
  194. package/dist/mcp-mock.cjs +326 -47
  195. package/dist/mcp-mock.cjs.map +1 -1
  196. package/dist/mcp-mock.d.cts +97 -1
  197. package/dist/mcp-mock.d.cts.map +1 -1
  198. package/dist/mcp-mock.d.ts +97 -1
  199. package/dist/mcp-mock.d.ts.map +1 -1
  200. package/dist/mcp-mock.js +327 -48
  201. package/dist/mcp-mock.js.map +1 -1
  202. package/dist/mcp-recorder.cjs +1224 -0
  203. package/dist/mcp-recorder.cjs.map +1 -0
  204. package/dist/mcp-recorder.d.cts +108 -0
  205. package/dist/mcp-recorder.d.cts.map +1 -0
  206. package/dist/mcp-recorder.d.ts +108 -0
  207. package/dist/mcp-recorder.d.ts.map +1 -0
  208. package/dist/mcp-recorder.js +1215 -0
  209. package/dist/mcp-recorder.js.map +1 -0
  210. package/dist/mcp-stub.cjs +5 -1
  211. package/dist/mcp-stub.d.cts +3 -1
  212. package/dist/mcp-stub.d.ts +3 -1
  213. package/dist/mcp-stub.js +3 -1
  214. package/dist/mcp-types.d.cts +6 -0
  215. package/dist/mcp-types.d.ts +6 -0
  216. package/dist/message-text.cjs +192 -0
  217. package/dist/message-text.cjs.map +1 -0
  218. package/dist/message-text.d.cts +31 -0
  219. package/dist/message-text.d.cts.map +1 -0
  220. package/dist/message-text.d.ts +31 -0
  221. package/dist/message-text.d.ts.map +1 -0
  222. package/dist/message-text.js +184 -0
  223. package/dist/message-text.js.map +1 -0
  224. package/dist/messages.cjs +400 -56
  225. package/dist/messages.cjs.map +1 -1
  226. package/dist/messages.d.cts.map +1 -1
  227. package/dist/messages.d.ts.map +1 -1
  228. package/dist/messages.js +401 -58
  229. package/dist/messages.js.map +1 -1
  230. package/dist/metrics.cjs +46 -66
  231. package/dist/metrics.cjs.map +1 -1
  232. package/dist/metrics.d.cts +14 -6
  233. package/dist/metrics.d.cts.map +1 -1
  234. package/dist/metrics.d.ts +14 -6
  235. package/dist/metrics.d.ts.map +1 -1
  236. package/dist/metrics.js +32 -40
  237. package/dist/metrics.js.map +1 -1
  238. package/dist/misbehavior.cjs +1523 -0
  239. package/dist/misbehavior.cjs.map +1 -0
  240. package/dist/misbehavior.d.cts +43 -0
  241. package/dist/misbehavior.d.cts.map +1 -0
  242. package/dist/misbehavior.d.ts +43 -0
  243. package/dist/misbehavior.d.ts.map +1 -0
  244. package/dist/misbehavior.js +1511 -0
  245. package/dist/misbehavior.js.map +1 -0
  246. package/dist/ollama.cjs +315 -113
  247. package/dist/ollama.cjs.map +1 -1
  248. package/dist/ollama.d.cts.map +1 -1
  249. package/dist/ollama.d.ts.map +1 -1
  250. package/dist/ollama.js +316 -114
  251. package/dist/ollama.js.map +1 -1
  252. package/dist/openapi.cjs +3405 -0
  253. package/dist/openapi.cjs.map +1 -0
  254. package/dist/openapi.d.cts +30 -0
  255. package/dist/openapi.d.cts.map +1 -0
  256. package/dist/openapi.d.ts +30 -0
  257. package/dist/openapi.d.ts.map +1 -0
  258. package/dist/openapi.js +3404 -0
  259. package/dist/openapi.js.map +1 -0
  260. package/dist/openrouter-video.cjs +4 -4
  261. package/dist/openrouter-video.cjs.map +1 -1
  262. package/dist/openrouter-video.d.cts.map +1 -1
  263. package/dist/openrouter-video.d.ts.map +1 -1
  264. package/dist/openrouter-video.js +4 -4
  265. package/dist/openrouter-video.js.map +1 -1
  266. package/dist/record-sanitize.cjs +231 -0
  267. package/dist/record-sanitize.cjs.map +1 -0
  268. package/dist/record-sanitize.js +224 -0
  269. package/dist/record-sanitize.js.map +1 -0
  270. package/dist/recorder.cjs +115 -6
  271. package/dist/recorder.cjs.map +1 -1
  272. package/dist/recorder.d.cts +2 -1
  273. package/dist/recorder.d.cts.map +1 -1
  274. package/dist/recorder.d.ts +2 -1
  275. package/dist/recorder.d.ts.map +1 -1
  276. package/dist/recorder.js +116 -8
  277. package/dist/recorder.js.map +1 -1
  278. package/dist/responses.cjs +701 -166
  279. package/dist/responses.cjs.map +1 -1
  280. package/dist/responses.d.cts +5 -13
  281. package/dist/responses.d.cts.map +1 -1
  282. package/dist/responses.d.ts +5 -13
  283. package/dist/responses.d.ts.map +1 -1
  284. package/dist/responses.js +699 -169
  285. package/dist/responses.js.map +1 -1
  286. package/dist/route-registry.cjs +951 -0
  287. package/dist/route-registry.cjs.map +1 -0
  288. package/dist/route-registry.d.cts +33 -0
  289. package/dist/route-registry.d.cts.map +1 -0
  290. package/dist/route-registry.d.ts +33 -0
  291. package/dist/route-registry.d.ts.map +1 -0
  292. package/dist/route-registry.js +866 -0
  293. package/dist/route-registry.js.map +1 -0
  294. package/dist/router.cjs +24 -2
  295. package/dist/router.cjs.map +1 -1
  296. package/dist/router.d.cts.map +1 -1
  297. package/dist/router.d.ts.map +1 -1
  298. package/dist/router.js +25 -4
  299. package/dist/router.js.map +1 -1
  300. package/dist/server.cjs +1159 -264
  301. package/dist/server.cjs.map +1 -1
  302. package/dist/server.d.cts +4 -1
  303. package/dist/server.d.cts.map +1 -1
  304. package/dist/server.d.ts +4 -1
  305. package/dist/server.d.ts.map +1 -1
  306. package/dist/server.js +1067 -172
  307. package/dist/server.js.map +1 -1
  308. package/dist/speech.cjs +4 -2
  309. package/dist/speech.cjs.map +1 -1
  310. package/dist/speech.js +4 -2
  311. package/dist/speech.js.map +1 -1
  312. package/dist/stream-collapse.cjs +236 -34
  313. package/dist/stream-collapse.cjs.map +1 -1
  314. package/dist/stream-collapse.d.cts +39 -4
  315. package/dist/stream-collapse.d.cts.map +1 -1
  316. package/dist/stream-collapse.d.ts +39 -4
  317. package/dist/stream-collapse.d.ts.map +1 -1
  318. package/dist/stream-collapse.js +235 -35
  319. package/dist/stream-collapse.js.map +1 -1
  320. package/dist/suite.cjs +1 -1
  321. package/dist/suite.js +1 -1
  322. package/dist/transcription.cjs +2 -2
  323. package/dist/transcription.cjs.map +1 -1
  324. package/dist/transcription.d.cts.map +1 -1
  325. package/dist/transcription.d.ts.map +1 -1
  326. package/dist/transcription.js +2 -2
  327. package/dist/transcription.js.map +1 -1
  328. package/dist/types.cjs +18 -0
  329. package/dist/types.cjs.map +1 -0
  330. package/dist/types.d.cts +516 -5
  331. package/dist/types.d.cts.map +1 -1
  332. package/dist/types.d.ts +516 -5
  333. package/dist/types.d.ts.map +1 -1
  334. package/dist/types.js +17 -0
  335. package/dist/types.js.map +1 -0
  336. package/dist/validate-cli.cjs +123 -33
  337. package/dist/validate-cli.cjs.map +1 -1
  338. package/dist/validate-cli.js +125 -35
  339. package/dist/validate-cli.js.map +1 -1
  340. package/dist/vector-stores.cjs +1180 -0
  341. package/dist/vector-stores.cjs.map +1 -0
  342. package/dist/vector-stores.d.cts +106 -0
  343. package/dist/vector-stores.d.cts.map +1 -0
  344. package/dist/vector-stores.d.ts +106 -0
  345. package/dist/vector-stores.d.ts.map +1 -0
  346. package/dist/vector-stores.js +1164 -0
  347. package/dist/vector-stores.js.map +1 -0
  348. package/dist/vector-types.d.cts.map +1 -1
  349. package/dist/version.cjs +45 -0
  350. package/dist/version.cjs.map +1 -0
  351. package/dist/version.js +44 -0
  352. package/dist/version.js.map +1 -0
  353. package/dist/video.cjs +19 -0
  354. package/dist/video.cjs.map +1 -1
  355. package/dist/video.d.cts.map +1 -1
  356. package/dist/video.d.ts.map +1 -1
  357. package/dist/video.js +19 -0
  358. package/dist/video.js.map +1 -1
  359. package/dist/vitest.cjs +77 -16
  360. package/dist/vitest.cjs.map +1 -1
  361. package/dist/vitest.d.cts +38 -5
  362. package/dist/vitest.d.cts.map +1 -1
  363. package/dist/vitest.d.ts +38 -5
  364. package/dist/vitest.d.ts.map +1 -1
  365. package/dist/vitest.js +78 -17
  366. package/dist/vitest.js.map +1 -1
  367. package/dist/watcher.cjs +6 -2
  368. package/dist/watcher.cjs.map +1 -1
  369. package/dist/watcher.js +6 -2
  370. package/dist/watcher.js.map +1 -1
  371. package/dist/ws-gemini-live.cjs +210 -101
  372. package/dist/ws-gemini-live.cjs.map +1 -1
  373. package/dist/ws-gemini-live.d.cts +9 -3
  374. package/dist/ws-gemini-live.d.cts.map +1 -1
  375. package/dist/ws-gemini-live.d.ts +9 -3
  376. package/dist/ws-gemini-live.d.ts.map +1 -1
  377. package/dist/ws-gemini-live.js +211 -102
  378. package/dist/ws-gemini-live.js.map +1 -1
  379. package/dist/ws-realtime.cjs +175 -62
  380. package/dist/ws-realtime.cjs.map +1 -1
  381. package/dist/ws-realtime.d.cts +8 -3
  382. package/dist/ws-realtime.d.cts.map +1 -1
  383. package/dist/ws-realtime.d.ts +8 -3
  384. package/dist/ws-realtime.d.ts.map +1 -1
  385. package/dist/ws-realtime.js +176 -63
  386. package/dist/ws-realtime.js.map +1 -1
  387. package/dist/ws-responses.cjs +121 -16
  388. package/dist/ws-responses.cjs.map +1 -1
  389. package/dist/ws-responses.d.cts +8 -12
  390. package/dist/ws-responses.d.cts.map +1 -1
  391. package/dist/ws-responses.d.ts +8 -12
  392. package/dist/ws-responses.d.ts.map +1 -1
  393. package/dist/ws-responses.js +123 -18
  394. package/dist/ws-responses.js.map +1 -1
  395. package/package.json +13 -2
  396. package/skills/write-fixtures/SKILL.md +179 -21
@@ -1,6 +1,9 @@
1
- import { isAudioResponse, isContentWithToolCallsResponse, isEmbeddingResponse, isErrorResponse, isImageResponse, isJSONResponse, isLiveResponse, isTextResponse, isToolCallResponse, isTranscriptionResponse, isVideoResponse } from "./helpers.js";
1
+ import { MCP_FAKES_ECHO_LIMIT } from "./constants.js";
2
+ import { build, fixed, jsonText, msg, quote, within } from "./message-text.js";
3
+ import { fixtureListTarget, isAudioResponse, isCombinedFixtureResponse, isEmbeddingResponse, isErrorResponse, isImageResponse, isJSONResponse, isLiveResponse, isTextResponse, isToolCallResponse, isTranscriptionResponse, isVideoResponse } from "./helpers.js";
2
4
  import { normalizeLiveFixture } from "./live-fixture.js";
3
5
  import { CHAOS_FIELDS, CHAOS_FIELD_NAMES, parseChaosField } from "./chaos.js";
6
+ import { copyFixtureMisbehaviorPosition, parseMisbehavior, setFixtureMisbehaviorPosition, validateFixtureMisbehavior } from "./misbehavior.js";
4
7
  import { readFileSync, readdirSync, statSync } from "node:fs";
5
8
  import { join } from "node:path";
6
9
 
@@ -32,7 +35,35 @@ function normalizeResponse(raw, liveOptions) {
32
35
  });
33
36
  return response;
34
37
  }
35
- function entryToFixture(entry, logger, liveOptions) {
38
+ /**
39
+ * @internal Normalize `responsesBlocks` and `customToolCalls` in place, for a
40
+ * server with `responsesTools: "extended"`: stringify object `arguments` on
41
+ * `toolCall` blocks, and give a `customToolCalls` entry with no `type` the
42
+ * type `"custom"`. A custom call's free-text `input` is never stringified.
43
+ * {@link normalizeResponse} leaves both keys as written, as 1.44.0 did.
44
+ */
45
+ function normalizeResponsesToolsKeys(response) {
46
+ if (Array.isArray(response.responsesBlocks)) response.responsesBlocks = response.responsesBlocks.map((block) => {
47
+ const b = block;
48
+ if (b !== null && typeof b === "object" && b.type === "toolCall" && typeof b.arguments === "object" && b.arguments !== null) return {
49
+ ...b,
50
+ arguments: JSON.stringify(b.arguments)
51
+ };
52
+ return block;
53
+ });
54
+ if (Array.isArray(response.customToolCalls)) response.customToolCalls = response.customToolCalls.map((tc) => {
55
+ const entry = tc;
56
+ return entry !== null && typeof entry === "object" && entry.type === void 0 ? {
57
+ ...entry,
58
+ type: "custom"
59
+ } : tc;
60
+ });
61
+ }
62
+ var InvalidFixtureMatchError = class extends TypeError {};
63
+ function entryToFixture(entry, logger, liveOptions, source) {
64
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) throw new TypeError("Fixture entry must be an object");
65
+ if (entry.match == null) throw new TypeError("Fixture match must be an object");
66
+ if (typeof entry.match !== "object" || Array.isArray(entry.match)) throw new InvalidFixtureMatchError("Fixture match must be an object");
36
67
  const fixture = {
37
68
  match: {
38
69
  userMessage: entry.match.userMessage,
@@ -61,6 +92,19 @@ function entryToFixture(entry, logger, liveOptions) {
61
92
  ...entry.openRouterProcessing !== void 0 && { openRouterProcessing: entry.openRouterProcessing },
62
93
  ...entry.metadata !== void 0 && { metadata: entry.metadata }
63
94
  };
95
+ if (source) setFixtureMisbehaviorPosition(fixture, `${source.file}#${source.index}`);
96
+ const heldToolNamespace = entry.match.toolNamespace !== void 0;
97
+ const response = fixture.response;
98
+ const heldResponseKeys = entry.match.endpoint !== "openai-live" && !isLiveResponse(entry.response) && typeof response === "object" && response !== null && ("customToolCalls" in response || "responsesBlocks" in response);
99
+ if (heldToolNamespace || heldResponseKeys) heldResponsesTools.set(fixture, {
100
+ ...heldToolNamespace && { toolNamespace: entry.match.toolNamespace },
101
+ normalize: heldResponseKeys
102
+ });
103
+ if (entry.misbehavior !== void 0) heldFixtureMisbehavior.set(fixture, {
104
+ value: entry.misbehavior,
105
+ path: source ? `fixtures[${source.index}].misbehavior` : "misbehavior",
106
+ file: source?.file ?? null
107
+ });
64
108
  if (fixture.recordedTimings) {
65
109
  const rt = fixture.recordedTimings;
66
110
  if (!Number.isFinite(rt.ttftMs) || rt.ttftMs < 0) rt.ttftMs = 0;
@@ -91,34 +135,100 @@ function warn(logger, msg, ...rest) {
91
135
  if (logger) logger.warn(msg, ...rest);
92
136
  else console.warn(`[fixture-loader] ${msg}`, ...rest);
93
137
  }
94
- function loadFixtureFile(filePath, logger, liveOptions) {
138
+ /**
139
+ * A parsed JSON value, or `null` when the file could not be read or parsed (warned).
140
+ *
141
+ * @internal Shared with fixture-loader-services.ts.
142
+ */
143
+ function readFixtureJson(filePath, logger) {
95
144
  let raw;
96
145
  try {
97
146
  raw = readFileSync(filePath, "utf-8");
98
147
  } catch (err) {
99
148
  warn(logger, `Could not read file ${filePath}:`, err);
100
- return [];
149
+ return null;
101
150
  }
102
- let parsed;
103
151
  try {
104
- parsed = JSON.parse(raw);
152
+ return { value: JSON.parse(raw) };
105
153
  } catch (err) {
106
154
  warn(logger, `Invalid JSON in ${filePath}:`, err);
107
- return [];
155
+ return null;
108
156
  }
157
+ }
158
+ /**
159
+ * `parsed` is a JSON object with an own top-level `mcpFakes` key.
160
+ *
161
+ * @internal Shared with fixture-loader-services.ts.
162
+ */
163
+ function hasMcpFakesKey(parsed) {
164
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && Object.prototype.hasOwnProperty.call(parsed, "mcpFakes");
165
+ }
166
+ /**
167
+ * The LLM fixtures of a parsed file. A missing or non-array `fixtures` is
168
+ * warned and gives none, except that a file with `mcpFakes` may leave
169
+ * `fixtures` out (`fixturesOptional`, spec 5.4). An entry whose `match` is
170
+ * not an object is skipped with a warning; any other bad entry throws.
171
+ *
172
+ * @internal Shared with fixture-loader-services.ts.
173
+ */
174
+ function fixturesOf(parsed, filePath, logger, liveOptions, fixturesOptional, source = filePath) {
175
+ if (fixturesOptional && typeof parsed === "object" && parsed !== null && !Object.prototype.hasOwnProperty.call(parsed, "fixtures")) return [];
109
176
  if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.fixtures)) {
110
177
  warn(logger, `Missing or invalid "fixtures" array in ${filePath}`);
111
178
  return [];
112
179
  }
113
- return parsed.fixtures.map((e) => entryToFixture(e, logger, liveOptions));
180
+ const fixtures = [];
181
+ for (const [index, entry] of parsed.fixtures.entries()) try {
182
+ fixtures.push(entryToFixture(entry, logger, liveOptions, {
183
+ file: source,
184
+ index
185
+ }));
186
+ } catch (error) {
187
+ if (!(error instanceof InvalidFixtureMatchError)) throw error;
188
+ warn(logger, `Skipping fixture at index ${index} in ${filePath}: ${error.message}`);
189
+ }
190
+ return fixtures;
114
191
  }
115
- function loadFixturesFromDir(dirPath, logger, liveOptions) {
192
+ /**
193
+ * B13: the old loaders return LLM fixtures only, so a file with `mcpFakes`
194
+ * fails loud instead of losing its fakes.
195
+ */
196
+ function cannotCarryFakes(filePath) {
197
+ return new FixtureLoadError({
198
+ rule: "mcp-fakes/loader-cannot-carry-fakes",
199
+ file: filePath,
200
+ blockId: null,
201
+ entryId: null,
202
+ detail: msg`top-level "mcpFakes" key: loadFixtureFile and loadFixturesFromDir cannot carry MCP fakes; use loadFixtureFileWithServices or loadFixturesFromDirWithServices, or LLMock.loadFixtureFile or LLMock.loadFixtureDir`
203
+ });
204
+ }
205
+ /**
206
+ * Load the LLM fixtures of one file. Throws a `FixtureLoadError` (rule
207
+ * `mcp-fakes/loader-cannot-carry-fakes`) when the file has a top-level
208
+ * `mcpFakes` key; use `loadFixtureFileWithServices` (fixture-loader-services.ts) for such files.
209
+ */
210
+ function loadFixtureFile(filePath, logger, liveOptions) {
211
+ return loadLlmFixtureFile(filePath, filePath, logger, liveOptions);
212
+ }
213
+ function loadLlmFixtureFile(filePath, source, logger, liveOptions) {
214
+ const read = readFixtureJson(filePath, logger);
215
+ if (read === null) return [];
216
+ if (hasMcpFakesKey(read.value)) throw cannotCarryFakes(filePath);
217
+ return fixturesOf(read.value, filePath, logger, liveOptions, false, source);
218
+ }
219
+ /**
220
+ * The `.json` files (sorted) and the subdirectories (sorted) of `dirPath`,
221
+ * or `null` when it cannot be read (warned).
222
+ *
223
+ * @internal Shared with fixture-loader-services.ts.
224
+ */
225
+ function listFixtureDir(dirPath, logger) {
116
226
  let entries;
117
227
  try {
118
228
  entries = readdirSync(dirPath);
119
229
  } catch (err) {
120
230
  warn(logger, `Could not read directory ${dirPath}:`, err);
121
- return [];
231
+ return null;
122
232
  }
123
233
  const jsonFiles = [];
124
234
  const subdirs = [];
@@ -136,15 +246,34 @@ function loadFixturesFromDir(dirPath, logger, liveOptions) {
136
246
  if (name.endsWith(".json")) jsonFiles.push(name);
137
247
  }
138
248
  jsonFiles.sort();
249
+ subdirs.sort();
250
+ return {
251
+ jsonFiles,
252
+ subdirs
253
+ };
254
+ }
255
+ /**
256
+ * Load the LLM fixtures of every `.json` file under `dirPath`. Throws a
257
+ * `FixtureLoadError` (rule `mcp-fakes/loader-cannot-carry-fakes`) on the
258
+ * first file with a top-level `mcpFakes` key; use
259
+ * `loadFixturesFromDirWithServices` (fixture-loader-services.ts) for such
260
+ * directories.
261
+ */
262
+ function loadFixturesFromDir(dirPath, logger, liveOptions) {
263
+ return loadLlmFixtureDir(dirPath, "", logger, liveOptions);
264
+ }
265
+ function loadLlmFixtureDir(dirPath, relDir, logger, liveOptions) {
266
+ const listing = listFixtureDir(dirPath, logger);
267
+ if (listing === null) return [];
139
268
  const fixtures = [];
140
- for (const name of jsonFiles) {
269
+ for (const name of listing.jsonFiles) {
141
270
  const filePath = join(dirPath, name);
142
- fixtures.push(...loadFixtureFile(filePath, logger, liveOptions));
271
+ fixtures.push(...loadLlmFixtureFile(filePath, relDir + name, logger, liveOptions));
143
272
  }
144
- subdirs.sort();
145
- for (const sub of subdirs) fixtures.push(...loadFixturesFromDir(join(dirPath, sub), logger, liveOptions));
273
+ for (const sub of listing.subdirs) fixtures.push(...loadLlmFixtureDir(join(dirPath, sub), `${relDir}${sub}/`, logger, liveOptions));
146
274
  return fixtures;
147
275
  }
276
+ const INVALID_ARGUMENTS_HINT = "to send invalid JSON on purpose, use `misbehavior: tool-args-invalid-json`";
148
277
  /**
149
278
  * Render a ref-carrying finding's message with each named index passed through
150
279
  * `renderIndex`. With `String` it reproduces the message `validateFixtures`
@@ -195,6 +324,98 @@ function validateWebSearches(response, fixtureIndex, results) {
195
324
  });
196
325
  }
197
326
  }
327
+ /**
328
+ * Path (`customToolCalls[j]` or `responsesBlocks[j]`) of the first custom
329
+ * tool call a static fixture response carries, or undefined. Factory
330
+ * responses are skipped.
331
+ */
332
+ function firstCustomToolCallPath(response) {
333
+ if (response === null || typeof response !== "object") return void 0;
334
+ const r = response;
335
+ if (Array.isArray(r.customToolCalls) && r.customToolCalls.length > 0) return "customToolCalls[0]";
336
+ if (Array.isArray(r.responsesBlocks)) {
337
+ const j = r.responsesBlocks.findIndex((b) => b !== null && typeof b === "object" && b.type === "customToolCall");
338
+ if (j >= 0) return `responsesBlocks[${j}]`;
339
+ }
340
+ }
341
+ /**
342
+ * Checks for the OpenAI Responses keys `customToolCalls` and
343
+ * `responsesBlocks`. Both are new, so their findings never touch a fixture an
344
+ * earlier release accepted. A custom tool call takes a non-empty string
345
+ * `name`, a string `input` (empty: warning), no `arguments`, a string `id`
346
+ * when present and a non-empty string `namespace` when present.
347
+ * `responsesBlocks` follows the `blocks` rules plus `customToolCall` blocks,
348
+ * and may not be combined with `blocks`.
349
+ */
350
+ function validateResponsesToolKeys(response, fixtureIndex, results) {
351
+ if (response === null || typeof response !== "object") return;
352
+ const r = response;
353
+ const error = (message) => results.push({
354
+ severity: "error",
355
+ fixtureIndex,
356
+ message
357
+ });
358
+ const warning = (message) => results.push({
359
+ severity: "warning",
360
+ fixtureIndex,
361
+ message
362
+ });
363
+ const checkCustom = (tc, path) => {
364
+ if (typeof tc.name !== "string" || tc.name === "") error(`${path}.name must be a non-empty string`);
365
+ if (typeof tc.input !== "string") error(`${path}.input must be a string for a custom tool call`);
366
+ else if (tc.input === "") warning(`${path}.input is empty`);
367
+ if (tc.arguments !== void 0) error(`${path}.arguments is not valid on a custom tool call; use input`);
368
+ if (tc.id !== void 0 && typeof tc.id !== "string") error(`${path}.id must be a string, got ${typeof tc.id}`);
369
+ if (tc.namespace !== void 0 && (typeof tc.namespace !== "string" || tc.namespace === "")) error(`${path}.namespace must be a non-empty string`);
370
+ };
371
+ if (r.customToolCalls !== void 0) if (!Array.isArray(r.customToolCalls)) error(`customToolCalls must be an array, got ${typeof r.customToolCalls}`);
372
+ else r.customToolCalls.forEach((entry, j) => {
373
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
374
+ error(`customToolCalls[${j}] must be an object`);
375
+ return;
376
+ }
377
+ const tc = entry;
378
+ if (tc.type !== void 0 && tc.type !== "custom") error(`customToolCalls[${j}].type must be "custom" when present`);
379
+ checkCustom(tc, `customToolCalls[${j}]`);
380
+ });
381
+ if (r.responsesBlocks === void 0) return;
382
+ if (!Array.isArray(r.responsesBlocks)) {
383
+ error(`responsesBlocks must be an array, got ${typeof r.responsesBlocks}`);
384
+ return;
385
+ }
386
+ if (Array.isArray(r.blocks) && r.blocks.length > 0 && r.responsesBlocks.length > 0) error("blocks and responsesBlocks cannot both be set; use responsesBlocks alone");
387
+ r.responsesBlocks.forEach((entry, j) => {
388
+ const path = `responsesBlocks[${j}]`;
389
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
390
+ error(`${path} must be an object`);
391
+ return;
392
+ }
393
+ const block = entry;
394
+ if (block.type === "text") {
395
+ if (typeof block.text !== "string") error(`${path}.text must be a string`);
396
+ return;
397
+ }
398
+ if (block.type === "customToolCall") {
399
+ checkCustom(block, path);
400
+ return;
401
+ }
402
+ if (block.type !== "toolCall") {
403
+ error(`${path}.type must be "text", "toolCall" or "customToolCall", got ${JSON.stringify(block.type)}`);
404
+ return;
405
+ }
406
+ if (typeof block.name !== "string" || block.name === "") error(`${path}.name must be a non-empty string`);
407
+ if (block.id !== void 0 && typeof block.id !== "string") error(`${path}.id must be a string, got ${typeof block.id}`);
408
+ if (block.input !== void 0) error(`${path}.input is only valid on a "customToolCall" block`);
409
+ if (block.namespace !== void 0 && (typeof block.namespace !== "string" || block.namespace === "")) error(`${path}.namespace must be a non-empty string`);
410
+ const args = block.arguments;
411
+ if (typeof args === "string") try {
412
+ JSON.parse(args);
413
+ } catch {
414
+ error(`${path}.arguments is not valid JSON: ${args}; ${INVALID_ARGUMENTS_HINT}`);
415
+ }
416
+ else if (args === null || typeof args !== "object") error(`${path}.arguments must be a string or an object`);
417
+ });
418
+ }
198
419
  function validateBlocks(response, fixtureIndex, results) {
199
420
  if (response.blocks === void 0) return;
200
421
  if (!Array.isArray(response.blocks)) {
@@ -246,7 +467,7 @@ function validateBlocks(response, fixtureIndex, results) {
246
467
  results.push({
247
468
  severity: "error",
248
469
  fixtureIndex,
249
- message: `blocks[${j}].arguments is not valid JSON: ${block.arguments}`
470
+ message: `blocks[${j}].arguments is not valid JSON: ${block.arguments}; ${INVALID_ARGUMENTS_HINT}`
250
471
  });
251
472
  }
252
473
  else if (typeof block.arguments !== "object" || block.arguments === null) results.push({
@@ -366,13 +587,14 @@ function claimOneShotError(fixtures, fixture) {
366
587
  * generation means the queue was cleared or reset while the claim was parked
367
588
  * (in the chaos-latency await) and the claim is stale — re-arming it would
368
589
  * plant the previous test's injection in the next test's freshly reset queue.
369
- * Keyed by array identity: every clear path preserves the array reference
370
- * (`length = 0`), so the counter follows the live queue.
590
+ * Keyed by the caller's array (a server's own list resolves to it): every
591
+ * clear path preserves the array reference (`length = 0`), so the counter
592
+ * follows the live queue.
371
593
  */
372
594
  const fixtureQueueGeneration = /* @__PURE__ */ new WeakMap();
373
595
  const oneShotClaimGeneration = /* @__PURE__ */ new WeakMap();
374
596
  function queueGeneration(fixtures) {
375
- return fixtureQueueGeneration.get(fixtures) ?? 0;
597
+ return fixtureQueueGeneration.get(fixtureListTarget(fixtures)) ?? 0;
376
598
  }
377
599
  /**
378
600
  * Empty a fixture queue in place (array identity preserved — the running
@@ -384,7 +606,7 @@ function queueGeneration(fixtures) {
384
606
  */
385
607
  function clearFixtureQueue(fixtures) {
386
608
  fixtures.length = 0;
387
- fixtureQueueGeneration.set(fixtures, queueGeneration(fixtures) + 1);
609
+ fixtureQueueGeneration.set(fixtureListTarget(fixtures), queueGeneration(fixtures) + 1);
388
610
  }
389
611
  /**
390
612
  * Re-arm a one-shot error that was claimed for a request which then ended
@@ -428,8 +650,25 @@ function validateFixtures(fixtures, liveOptions) {
428
650
  const results = [];
429
651
  const seenUserMessages = /* @__PURE__ */ new Map();
430
652
  for (let i = 0; i < fixtures.length; i++) {
431
- const f = fixtures[i];
653
+ const original = fixtures[i];
654
+ const f = responsesToolsExtendedFixtures.has(original) ? original : withoutResponsesToolsKeys(original);
432
655
  const response = f.response;
656
+ const misbehaviorIssue = misbehaviorEnabledFixtures.has(original) ? validateFixtureMisbehavior(original, `fixtures[${i}].misbehavior`) : void 0;
657
+ if (misbehaviorIssue) results.push({
658
+ severity: "error",
659
+ fixtureIndex: i,
660
+ message: misbehaviorIssue.message
661
+ });
662
+ else if (misbehaviorEnabledFixtures.has(original) && f.misbehavior !== void 0) {
663
+ const parsed = parseMisbehavior(f.misbehavior);
664
+ if (parsed.ok && parsed.config.seed === void 0) {
665
+ for (const fault of parsed.config.faults) if (fault.rate !== void 0 && fault.rate < 1 && fault.times === void 0) results.push({
666
+ severity: "warning",
667
+ fixtureIndex: i,
668
+ message: "probabilistic fault without a seed is reproducible only per test id order; set seed to pin it"
669
+ });
670
+ }
671
+ }
433
672
  if (typeof response !== "function" && (isLiveResponse(response) || f.match.endpoint === "openai-live")) try {
434
673
  normalizeLiveFixture(response, liveOptions);
435
674
  } catch (error) {
@@ -440,12 +679,13 @@ function validateFixtures(fixtures, liveOptions) {
440
679
  });
441
680
  }
442
681
  if (typeof response === "function") {} else {
443
- if (!isContentWithToolCallsResponse(response) && !isTextResponse(response) && !isToolCallResponse(response) && !isErrorResponse(response) && !isEmbeddingResponse(response) && !isImageResponse(response) && !isAudioResponse(response) && !isTranscriptionResponse(response) && !isVideoResponse(response) && !isJSONResponse(response) && !isLiveResponse(response)) results.push({
682
+ if (!isCombinedFixtureResponse(response) && !isTextResponse(response) && !isToolCallResponse(response) && !isErrorResponse(response) && !isEmbeddingResponse(response) && !isImageResponse(response) && !isAudioResponse(response) && !isTranscriptionResponse(response) && !isVideoResponse(response) && !isJSONResponse(response) && !isLiveResponse(response)) results.push({
444
683
  severity: "error",
445
684
  fixtureIndex: i,
446
685
  message: "response is not a recognized type (must have content, toolCalls, error, embedding, image, audio, transcription, video, json, or live)"
447
686
  });
448
- const hasNonEmptyBlocks = Array.isArray(response.blocks) && response.blocks.length > 0;
687
+ const hasNonEmptyBlocks = Array.isArray(response.blocks) && response.blocks.length > 0 || Array.isArray(response.responsesBlocks) && response.responsesBlocks.length > 0;
688
+ const hasCustomToolCalls = Array.isArray(response.customToolCalls) && response.customToolCalls.length > 0;
449
689
  if (isTextResponse(response)) {
450
690
  if (response.content === "" && !hasNonEmptyBlocks) results.push({
451
691
  severity: "error",
@@ -455,7 +695,7 @@ function validateFixtures(fixtures, liveOptions) {
455
695
  validateReasoning(response, i, results);
456
696
  validateWebSearches(response, i, results);
457
697
  }
458
- if (isContentWithToolCallsResponse(response)) {
698
+ if (isCombinedFixtureResponse(response)) {
459
699
  if (typeof response.content === "string") {
460
700
  if (response.content === "" && !hasNonEmptyBlocks) results.push({
461
701
  severity: "error",
@@ -464,7 +704,7 @@ function validateFixtures(fixtures, liveOptions) {
464
704
  });
465
705
  }
466
706
  if (Array.isArray(response.toolCalls)) {
467
- if (response.toolCalls.length === 0) results.push({
707
+ if (response.toolCalls.length === 0 && !hasCustomToolCalls) results.push({
468
708
  severity: "warning",
469
709
  fixtureIndex: i,
470
710
  message: "toolCalls array is empty — fixture will never produce tool calls"
@@ -482,7 +722,7 @@ function validateFixtures(fixtures, liveOptions) {
482
722
  results.push({
483
723
  severity: "error",
484
724
  fixtureIndex: i,
485
- message: `toolCalls[${j}].arguments is not valid JSON: ${tc.arguments}`
725
+ message: `toolCalls[${j}].arguments is not valid JSON: ${tc.arguments}; ${INVALID_ARGUMENTS_HINT}`
486
726
  });
487
727
  }
488
728
  }
@@ -491,8 +731,9 @@ function validateFixtures(fixtures, liveOptions) {
491
731
  validateWebSearches(response, i, results);
492
732
  }
493
733
  validateBlocks(response, i, results);
734
+ validateResponsesToolKeys(response, i, results);
494
735
  if (isToolCallResponse(response)) {
495
- if (response.toolCalls.length === 0) results.push({
736
+ if (response.toolCalls.length === 0 && !hasCustomToolCalls) results.push({
496
737
  severity: "warning",
497
738
  fixtureIndex: i,
498
739
  message: "toolCalls array is empty — fixture will never produce tool calls"
@@ -510,7 +751,7 @@ function validateFixtures(fixtures, liveOptions) {
510
751
  results.push({
511
752
  severity: "error",
512
753
  fixtureIndex: i,
513
- message: `toolCalls[${j}].arguments is not valid JSON: ${tc.arguments}`
754
+ message: `toolCalls[${j}].arguments is not valid JSON: ${tc.arguments}; ${INVALID_ARGUMENTS_HINT}`
514
755
  });
515
756
  }
516
757
  }
@@ -561,7 +802,7 @@ function validateFixtures(fixtures, liveOptions) {
561
802
  message: `audio.contentType must be a string, got ${typeof audioObj.contentType}`
562
803
  });
563
804
  }
564
- if (isTextResponse(response) || isToolCallResponse(response) || isContentWithToolCallsResponse(response)) {
805
+ if (isTextResponse(response) || isToolCallResponse(response) || isCombinedFixtureResponse(response)) {
565
806
  const r = response;
566
807
  if (r.id !== void 0 && typeof r.id !== "string") results.push({
567
808
  severity: "error",
@@ -731,6 +972,29 @@ function validateFixtures(fixtures, liveOptions) {
731
972
  message: "match.toolResultContains must be a non-empty string"
732
973
  });
733
974
  }
975
+ if (f.match.toolNamespace !== void 0) {
976
+ if (typeof f.match.toolNamespace !== "string") results.push({
977
+ severity: "error",
978
+ fixtureIndex: i,
979
+ message: `match.toolNamespace must be a string, got ${typeof f.match.toolNamespace}`
980
+ });
981
+ else if (f.match.toolNamespace.length === 0) results.push({
982
+ severity: "error",
983
+ fixtureIndex: i,
984
+ message: "match.toolNamespace must be a non-empty string"
985
+ });
986
+ }
987
+ if (typeof f.match.toolNamespace === "string" && f.match.toolNamespace !== "" && f.match.endpoint !== void 0 && f.match.endpoint !== "chat") results.push({
988
+ severity: "warning",
989
+ fixtureIndex: i,
990
+ message: `match.toolNamespace never matches with endpoint ${JSON.stringify(f.match.endpoint)} — only OpenAI Responses requests (endpoint "chat") carry tool namespaces`
991
+ });
992
+ const customCallPath = f.match.endpoint !== void 0 && f.match.endpoint !== "chat" ? firstCustomToolCallPath(f.response) : void 0;
993
+ if (customCallPath !== void 0) results.push({
994
+ severity: "warning",
995
+ fixtureIndex: i,
996
+ message: `${customCallPath} is a custom tool call, which only OpenAI Responses requests (endpoint "chat") can carry — endpoint ${JSON.stringify(f.match.endpoint)} never serves it, so a request that matches this fixture gets an error instead`
997
+ });
734
998
  if (f.match.systemMessage !== void 0) {
735
999
  const sm = f.match.systemMessage;
736
1000
  if (typeof sm === "string") {} else if (Array.isArray(sm)) {
@@ -771,7 +1035,8 @@ function validateFixtures(fixtures, liveOptions) {
771
1035
  m.context,
772
1036
  m.sequenceIndex,
773
1037
  m.turnIndex,
774
- m.hasToolResult
1038
+ m.hasToolResult,
1039
+ ...m.toolNamespace !== void 0 ? [`toolNamespace=${m.toolNamespace}`] : []
775
1040
  ].map((v) => v === void 0 ? "" : String(v)).join("|");
776
1041
  const prev = seenUserMessages.get(dedupKey);
777
1042
  if (prev !== void 0) {
@@ -789,7 +1054,7 @@ function validateFixtures(fixtures, liveOptions) {
789
1054
  } else seenUserMessages.set(dedupKey, i);
790
1055
  }
791
1056
  const match = f.match;
792
- if (!(match.endpoint !== void 0 || match.context !== void 0 || match.userMessage !== void 0 || match.systemMessage !== void 0 || match.inputText !== void 0 || match.responseFormat !== void 0 || match.toolCallId !== void 0 || match.toolResultContains !== void 0 || match.toolName !== void 0 || match.model !== void 0 || match.predicate !== void 0 || match.turnIndex !== void 0 || match.sequenceIndex !== void 0 || match.hasToolResult !== void 0) && i < fixtures.length - 1) {
1057
+ if (!(match.endpoint !== void 0 || match.context !== void 0 || match.userMessage !== void 0 || match.systemMessage !== void 0 || match.inputText !== void 0 || match.responseFormat !== void 0 || match.toolCallId !== void 0 || match.toolResultContains !== void 0 || match.toolName !== void 0 || match.toolNamespace !== void 0 || match.model !== void 0 || match.predicate !== void 0 || match.turnIndex !== void 0 || match.sequenceIndex !== void 0 || match.hasToolResult !== void 0) && i < fixtures.length - 1) {
793
1058
  const ref = {
794
1059
  rule: "catch-all-not-last",
795
1060
  shadowsFrom: i + 1,
@@ -805,7 +1070,253 @@ function validateFixtures(fixtures, liveOptions) {
805
1070
  }
806
1071
  return results;
807
1072
  }
1073
+ /**
1074
+ * `text` with every run of line breaks, and the spaces around it, folded to
1075
+ * one space: CR, LF, VT, FF, NEL, and the Unicode line and paragraph
1076
+ * separators.
1077
+ */
1078
+ function oneLine(text) {
1079
+ return text.replace(/\s*[\r\n\v\f\u0085\u2028\u2029]+\s*/g, " ");
1080
+ }
1081
+ /**
1082
+ * The `detail` as a `FixtureLoadError` message adds it: built in a budget of
1083
+ * its own, cut to at most `MCP_FAKES_ECHO_LIMIT` chars in all, with each of
1084
+ * its user parts cut once. A text detail is folded onto one line, gets the
1085
+ * escaping of every JSON text part (`jsonText`: each control char left, C0,
1086
+ * DEL or C1, and the line and paragraph separators as `\uXXXX`) and is cut
1087
+ * as JSON text (it is
1088
+ * made of fixed text, paths and quoted values, where a backslash only starts
1089
+ * a JSON escape; a stray one only moves the cut back a few chars).
1090
+ */
1091
+ function capPart(detail) {
1092
+ return within(MCP_FAKES_ECHO_LIMIT, typeof detail === "string" ? msg`${jsonText(oneLine(detail))}` : detail);
1093
+ }
1094
+ /**
1095
+ * One `file`, `blockId` or `entryId` as the message names it: JSON-quoted
1096
+ * (control chars escaped, so a name can neither break the line nor pose as
1097
+ * the message layout), and cut once to `MCP_FAKES_ECHO_LIMIT` chars at most.
1098
+ * An empty string is `""`, so the message names every part `toJSON()` keeps.
1099
+ */
1100
+ function namePart(text) {
1101
+ return msg`${quote(text)}`;
1102
+ }
1103
+ /**
1104
+ * The message of a `FixtureLoadError` or `MisbehaviorConfigError`: the
1105
+ * `file`, every non-null `blockId` and `entryId`, then `[rule] detail`.
1106
+ */
1107
+ function loadErrorMessage(init) {
1108
+ return oneLine(build(msg`${init.file === null ? msg`<no file>` : namePart(init.file)}${init.blockId == null ? msg`` : msg`, block ${namePart(init.blockId)}`}${init.entryId == null ? msg`` : msg`, entry ${namePart(init.entryId)}`}: [${fixed(init.rule)}] ${capPart(init.detail)}`));
1109
+ }
1110
+ /**
1111
+ * Thrown for a fixture-load case aimock cannot honor (fail-loud rule). Only
1112
+ * MCP fakes use it (misbehavior throws its own `MisbehaviorConfigError`);
1113
+ * other fixture-loader paths log warnings.
1114
+ * `blockId` and `entryId` are own properties only when supplied, so
1115
+ * `toJSON()` (the control API's HTTP 400 `details`) omits them otherwise.
1116
+ * The message names `file` and every non-null `blockId` and `entryId`, an
1117
+ * empty string as `""`, so it agrees with `toJSON()`. `options.cause` (the
1118
+ * standard `ErrorOptions`) keeps the underlying error, such as a JSON parse
1119
+ * failure; `toJSON()` leaves it out.
1120
+ */
1121
+ var FixtureLoadError = class extends Error {
1122
+ /** Typed `string` so a subclass can set its own name. */
1123
+ name = "FixtureLoadError";
1124
+ rule;
1125
+ file;
1126
+ constructor(init, options) {
1127
+ super(loadErrorMessage(init), options);
1128
+ this.rule = init.rule;
1129
+ this.file = init.file;
1130
+ if (init.blockId !== void 0) Object.defineProperty(this, "blockId", {
1131
+ value: init.blockId,
1132
+ enumerable: true,
1133
+ writable: false
1134
+ });
1135
+ if (init.entryId !== void 0) Object.defineProperty(this, "entryId", {
1136
+ value: init.entryId,
1137
+ enumerable: true,
1138
+ writable: false
1139
+ });
1140
+ }
1141
+ toJSON() {
1142
+ const json = {
1143
+ name: this.name,
1144
+ rule: this.rule,
1145
+ file: this.file,
1146
+ message: this.message
1147
+ };
1148
+ if (this.blockId !== void 0) json.blockId = this.blockId;
1149
+ if (this.entryId !== void 0) json.entryId = this.entryId;
1150
+ return json;
1151
+ }
1152
+ };
1153
+ /**
1154
+ * Thrown for an invalid fixture `misbehavior` key, or one that cannot apply
1155
+ * to its fixture, on a server with misbehavior enabled (`enableMisbehavior:
1156
+ * true`, CLI `--misbehavior`). Without that opt-in the key is unused data
1157
+ * and nothing throws. The message has the `FixtureLoadError` layout:
1158
+ * `"<file>": [<rule>] <detail>`.
1159
+ */
1160
+ var MisbehaviorConfigError = class extends Error {
1161
+ name = "MisbehaviorConfigError";
1162
+ rule;
1163
+ file;
1164
+ constructor(init) {
1165
+ super(loadErrorMessage(init));
1166
+ this.rule = init.rule;
1167
+ this.file = init.file;
1168
+ }
1169
+ toJSON() {
1170
+ return {
1171
+ name: this.name,
1172
+ rule: this.rule,
1173
+ file: this.file,
1174
+ message: this.message
1175
+ };
1176
+ }
1177
+ };
1178
+ const heldFixtureMisbehavior = /* @__PURE__ */ new WeakMap();
1179
+ const responsesToolsExtendedFixtures = /* @__PURE__ */ new WeakSet();
1180
+ const heldResponsesTools = /* @__PURE__ */ new WeakMap();
1181
+ /**
1182
+ * @internal Mark a fixture for a server with `responsesTools: "extended"`,
1183
+ * where that server made the fixture itself (its own load, `--validate`, the
1184
+ * control API): the held `match.toolNamespace` is applied and
1185
+ * `customToolCalls` / `responsesBlocks` are normalized in place, and
1186
+ * `validateFixtures` checks those keys. Idempotent. A fixture the caller
1187
+ * passed in is never marked: an extended server reads it through
1188
+ * {@link responsesToolsExtendedView}, so another server sharing it still sees
1189
+ * it as 1.44.0 loaded it.
1190
+ */
1191
+ function markFixtureResponsesToolsExtended(fixture) {
1192
+ responsesToolsExtendedFixtures.add(fixture);
1193
+ const held = heldResponsesTools.get(fixture);
1194
+ if (held === void 0) return;
1195
+ heldResponsesTools.delete(fixture);
1196
+ if (held.toolNamespace !== void 0) fixture.match = withHeldToolNamespace(fixture.match, held.toolNamespace);
1197
+ if (held.normalize) normalizeResponsesToolsKeys(fixture.response);
1198
+ }
1199
+ /**
1200
+ * `match` with `toolNamespace` after `toolName`, in the same key order the
1201
+ * listing and journal have always shown in extended mode.
1202
+ */
1203
+ function withHeldToolNamespace(match, toolNamespace) {
1204
+ const rebuilt = {};
1205
+ for (const [key, value] of Object.entries(match)) {
1206
+ rebuilt[key] = value;
1207
+ if (key === "toolName") rebuilt.toolNamespace = toolNamespace;
1208
+ }
1209
+ if (!("toolNamespace" in rebuilt)) rebuilt.toolNamespace = toolNamespace;
1210
+ return rebuilt;
1211
+ }
1212
+ const extendedViews = /* @__PURE__ */ new WeakMap();
1213
+ /** Each extended view's fixture, so identity bookkeeping can name the caller's object. */
1214
+ const extendedViewSources = /* @__PURE__ */ new WeakMap();
1215
+ /**
1216
+ * @internal The fixture an extended view was made from
1217
+ * ({@link responsesToolsExtendedView}); any other fixture is itself. An
1218
+ * extended server counts and journals the caller's fixture, not its view, so
1219
+ * the identity APIs (`getFixtureMatchCount`, `findByFixture`, the count-map
1220
+ * keys, `JournalEntry.response.fixture`) take the object the caller passed in.
1221
+ */
1222
+ function responsesToolsViewSource(fixture) {
1223
+ return extendedViewSources.get(fixture) ?? fixture;
1224
+ }
1225
+ /**
1226
+ * @internal The fixture as a server with `responsesTools: "extended"` reads
1227
+ * it. A fixture with held keys gets a separate object, one per fixture, with
1228
+ * `match.toolNamespace` applied and `customToolCalls` / `responsesBlocks`
1229
+ * normalized, as {@link markFixtureResponsesToolsExtended} would leave it.
1230
+ * The fixture itself is not changed, so a legacy server that shares it is
1231
+ * unaffected. Every other property is read from the fixture on each call.
1232
+ * A fixture with nothing held is returned as it is.
1233
+ */
1234
+ function responsesToolsExtendedView(fixture) {
1235
+ const held = heldResponsesTools.get(fixture);
1236
+ if (held === void 0) return fixture;
1237
+ let cached = extendedViews.get(fixture);
1238
+ if (cached === void 0) {
1239
+ cached = { view: {} };
1240
+ responsesToolsExtendedFixtures.add(cached.view);
1241
+ extendedViewSources.set(cached.view, fixture);
1242
+ extendedViews.set(fixture, cached);
1243
+ }
1244
+ if (cached.viewMatch === void 0 || cached.match !== fixture.match) {
1245
+ cached.match = fixture.match;
1246
+ cached.viewMatch = held.toolNamespace !== void 0 ? withHeldToolNamespace(fixture.match, held.toolNamespace) : fixture.match;
1247
+ }
1248
+ if (!("viewResponse" in cached) || cached.response !== fixture.response) {
1249
+ cached.response = fixture.response;
1250
+ let viewResponse = fixture.response;
1251
+ if (held.normalize && typeof viewResponse === "object" && viewResponse !== null) {
1252
+ const copy = { ...viewResponse };
1253
+ normalizeResponsesToolsKeys(copy);
1254
+ viewResponse = copy;
1255
+ }
1256
+ cached.viewResponse = viewResponse;
1257
+ }
1258
+ const view = cached.view;
1259
+ const source = fixture;
1260
+ for (const key of Object.keys(view)) if (!(key in source)) delete view[key];
1261
+ for (const key of Object.keys(source)) view[key] = key === "match" ? cached.viewMatch : key === "response" ? cached.viewResponse : source[key];
1262
+ copyFixtureMisbehaviorPosition(fixture, cached.view);
1263
+ return cached.view;
1264
+ }
1265
+ /**
1266
+ * The fixture as 1.44.0 saw it: without `match.toolNamespace`,
1267
+ * `customToolCalls` and `responsesBlocks`. Returns the same object when none
1268
+ * is set; factory responses are left as they are.
1269
+ */
1270
+ function withoutResponsesToolsKeys(fixture) {
1271
+ let match = fixture.match;
1272
+ if ("toolNamespace" in match) {
1273
+ const copy = { ...match };
1274
+ delete copy.toolNamespace;
1275
+ match = copy;
1276
+ }
1277
+ let response = fixture.response;
1278
+ if (typeof response !== "function" && response !== null && typeof response === "object" && ("customToolCalls" in response || "responsesBlocks" in response)) {
1279
+ const copy = { ...response };
1280
+ delete copy.customToolCalls;
1281
+ delete copy.responsesBlocks;
1282
+ response = copy;
1283
+ }
1284
+ if (match === fixture.match && response === fixture.response) return fixture;
1285
+ return {
1286
+ ...fixture,
1287
+ match,
1288
+ response
1289
+ };
1290
+ }
1291
+ const misbehaviorEnabledFixtures = /* @__PURE__ */ new WeakSet();
1292
+ /**
1293
+ * @internal Recognize the `misbehavior` key a loaded fixture entry carried,
1294
+ * for a server with misbehavior enabled: parse it onto the fixture (in place;
1295
+ * the loader made the object) and check it applies, or throw
1296
+ * `MisbehaviorConfigError`. A fixture with no held key is left unchanged.
1297
+ */
1298
+ function enableHeldFixtureMisbehavior(fixture) {
1299
+ const held = heldFixtureMisbehavior.get(fixture);
1300
+ if (held === void 0) return;
1301
+ const parsed = parseMisbehavior(held.value, held.path);
1302
+ if (parsed.ok) fixture.misbehavior = parsed.config;
1303
+ const issue = parsed.ok ? validateFixtureMisbehavior(fixture, held.path) : parsed.issue;
1304
+ if (issue) throw new MisbehaviorConfigError({
1305
+ rule: issue.rule,
1306
+ file: held.file,
1307
+ detail: issue.message
1308
+ });
1309
+ heldFixtureMisbehavior.delete(fixture);
1310
+ misbehaviorEnabledFixtures.add(fixture);
1311
+ }
1312
+ /**
1313
+ * @internal Mark a fixture whose `misbehavior` a server with misbehavior
1314
+ * enabled has recognized, so `validateFixtures` checks it.
1315
+ */
1316
+ function markFixtureMisbehaviorEnabled(fixture) {
1317
+ misbehaviorEnabledFixtures.add(fixture);
1318
+ }
808
1319
 
809
1320
  //#endregion
810
- export { INJECTED_STATUS_RANGE, claimOneShotError, clearFixtureQueue, entryToFixture, isInjectableStatus, isOneShotError, loadFixtureFile, loadFixturesFromDir, normalizeResponse, queueOneShotError, releaseOneShotError, renderValidationRef, validateFixtures };
1321
+ export { FixtureLoadError, INJECTED_STATUS_RANGE, MisbehaviorConfigError, claimOneShotError, clearFixtureQueue, enableHeldFixtureMisbehavior, entryToFixture, fixturesOf, hasMcpFakesKey, isInjectableStatus, isOneShotError, listFixtureDir, loadFixtureFile, loadFixturesFromDir, markFixtureMisbehaviorEnabled, markFixtureResponsesToolsExtended, normalizeResponse, normalizeResponsesToolsKeys, queueOneShotError, readFixtureJson, releaseOneShotError, renderValidationRef, responsesToolsExtendedView, responsesToolsViewSource, validateFixtures };
811
1322
  //# sourceMappingURL=fixture-loader.js.map