@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,4 +1,4 @@
1
- import { ApiKeyAuthConfig, ChaosConfig, RecordConfig } from "./types.cjs";
1
+ import { ApiKeyAuthConfig, ChaosConfig, McpRecordConfig, MisbehaviorConfig, MisbehaviorFaultId, RecordConfig, ResponsesToolsMode } from "./types.cjs";
2
2
  import { A2AAgentDefinition, A2AArtifact, A2APart, A2AStreamEvent } from "./a2a-types.cjs";
3
3
  import { AGUIEvent } from "./agui-types.cjs";
4
4
  import { LLMock } from "./llmock.cjs";
@@ -83,6 +83,11 @@ interface VectorConfig {
83
83
  path?: string;
84
84
  collections?: VectorConfigCollection[];
85
85
  }
86
+ /** `llm.record` in a config file: the LLM `RecordConfig`, plus MCP record mounts (C10). */
87
+ interface AimockRecordConfig extends RecordConfig {
88
+ /** MR1: mount path → upstream URL, or a full MCP record config. */
89
+ mcp?: Record<string, string | McpRecordConfig>;
90
+ }
86
91
  interface AimockConfig {
87
92
  auth?: ApiKeyAuthConfig;
88
93
  llm?: {
@@ -92,7 +97,24 @@ interface AimockConfig {
92
97
  replaySpeed?: number;
93
98
  logLevel?: "silent" | "warn" | "info" | "debug";
94
99
  chaos?: ChaosConfig;
95
- record?: RecordConfig;
100
+ /**
101
+ * Server-wide misbehavior defaults. Read only when misbehavior is enabled
102
+ * (`llm.enableMisbehavior: true` or `aimock --misbehavior`); otherwise the
103
+ * key is ignored with a warning, as 1.44.0 ignored it.
104
+ */
105
+ misbehavior?: MisbehaviorConfig | MisbehaviorFaultId;
106
+ /** See `MockServerOptions.enableMisbehavior`. */
107
+ enableMisbehavior?: boolean;
108
+ record?: AimockRecordConfig;
109
+ /**
110
+ * Turns on `llm.record.mcp`. Without it the key is ignored with a warning,
111
+ * as 1.44.0 ignored it.
112
+ */
113
+ enableMcpRecording?: boolean;
114
+ /** See `MockServerOptions.strictToolArguments`. */
115
+ strictToolArguments?: boolean;
116
+ /** See `MockServerOptions.responsesTools`. */
117
+ responsesTools?: ResponsesToolsMode;
96
118
  };
97
119
  mcp?: MCPConfig;
98
120
  a2a?: A2AConfig;
@@ -109,10 +131,21 @@ interface AimockConfig {
109
131
  host?: string;
110
132
  }
111
133
  declare function loadConfig(configPath: string): AimockConfig;
112
- declare function startFromConfig(config: AimockConfig, overrides?: {
134
+ /**
135
+ * Command-line overrides for `startFromConfig`. The opt-in flags win over the
136
+ * matching `llm.*` config keys.
137
+ */
138
+ interface StartFromConfigOverrides {
113
139
  port?: number;
114
140
  host?: string;
115
- }): Promise<{
141
+ /** `aimock --misbehavior`: overrides `llm.enableMisbehavior`. */
142
+ enableMisbehavior?: boolean;
143
+ /** `aimock --strict-tool-arguments`: overrides `llm.strictToolArguments`. */
144
+ strictToolArguments?: boolean;
145
+ /** `aimock --responses-tools`: overrides `llm.responsesTools`. */
146
+ responsesTools?: ResponsesToolsMode;
147
+ }
148
+ declare function startFromConfig(config: AimockConfig, overrides?: StartFromConfigOverrides): Promise<{
116
149
  llmock: LLMock;
117
150
  url: string;
118
151
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"config-loader.d.cts","names":[],"sources":["../src/config-loader.ts"],"sourcesContent":[],"mappings":";;;;;;;;UAgBiB,aAAA,SAAsB;;AAAvC;AAIiB,UAAA,iBAAA,CAAiB;EASjB,GAAA,EAAA,MAAA;EAAgB,IAAA,EAAA,MAAA;UAEnB,CAAA,EAAA,MAAA;aAF2B,CAAA,EAAA,MAAA;EAAmB,IAAA,CAAA,EAAA,MAAA;EAM3C,IAAA,CAAA,EAAA,MAAS;;AAGhB,UATO,eAAA,SAAwB,mBAS/B,CAAA;QACI,CAAA,EAAA;IACF,QAAA,EATE,KASF,CAAA;MAAe,IAAA,EAAA,MAAA;MAGV,OAAA,EAAA;QAAgB,IAAA,EAAA,MAAA;QAEvB,IAAA,EAAA,MAAA;MACI,CAAA;IACH,CAAA,CAAA;EAAc,CAAA;AAIzB;AAAgC,UAhBf,SAAA,CAgBe;MACnB,CAAA,EAAA,MAAA;YACH,CAAA,EAAA;IACS,IAAA,EAAA,MAAA;IAHqB,OAAA,EAAA,MAAA;EAAkB,CAAA;EAMzC,KAAA,CAAA,EAnBP,aAmBgB,EAAA;EAKT,SAAA,CAAA,EAvBH,iBAuBoB,EAAA;EAOjB,OAAA,CAAA,EA7BL,eA+BC,EAAA;AAGb;AAAuC,UA/BtB,gBAAA,CA+BsB;SAMxB,EAAA,MAAA;OAHH,CAAA,EAhCF,OAgCE,EAAA;WAKK,CAAA,EApCH,WAoCG,EAAA;EAAW,MAAA,CAAA,EAnCjB,cAmCiB,EAAA;EAGX,OAAA,CAAA,EAAA,MAAY;AAK7B;AAA6B,UAvCZ,cAAA,SAAuB,kBAuCX,CAAA;UACpB,CAAA,EAvCI,gBAuCJ,EAAA;OAOG,CAAA,EA7CF,gBA6CE,EAAA;gBACC,CAAA,EA7CM,gBA6CN,EAAA;;AAGL,UA7CS,SAAA,CA6CT;MACC,CAAA,EAAA,MAAA;QACE,CAAA,EA7CA,cA6CA,EAAA;;AAQK,UAlDC,iBAAA,CAkD+B;EAK1B,KAAA,EAAA;IAAe,OAAA,CAAA,EAAA,MAAA;IAC3B,UAAA,CAAA,EAAA,MAAA;IAEW,QAAA,CAAA,EAAA,MAAA;IAAlB,QAAA,CAAA,EAAA,MAAA;EAAO,CAAA;;WAvDC;;;UAIM,UAAA;;aAEJ;;UAGI,sBAAA;;;YAGL;;;eAGG;;iBAEE;;UAGA,YAAA;;gBAED;;UAGC,YAAA;SACR;;;;;;;YAOG;aACC;;QAEL;QACA;SACC;WACE;;;;;;;;;;;iBAQK,UAAA,sBAAgC;iBAK1B,eAAA,SACZ;;;IAEP;UAAkB"}
1
+ {"version":3,"file":"config-loader.d.cts","names":[],"sources":["../src/config-loader.ts"],"sourcesContent":[],"mappings":";;;;;;;;UA4BiB,aAAA,SAAsB;;AAAvC;AAIiB,UAAA,iBAAA,CAAiB;EASjB,GAAA,EAAA,MAAA;EAAgB,IAAA,EAAA,MAAA;UAEnB,CAAA,EAAA,MAAA;aAF2B,CAAA,EAAA,MAAA;EAAmB,IAAA,CAAA,EAAA,MAAA;EAM3C,IAAA,CAAA,EAAA,MAAS;;AAGhB,UATO,eAAA,SAAwB,mBAS/B,CAAA;QACI,CAAA,EAAA;IACF,QAAA,EATE,KASF,CAAA;MAAe,IAAA,EAAA,MAAA;MAGV,OAAA,EAAA;QAAgB,IAAA,EAAA,MAAA;QAEvB,IAAA,EAAA,MAAA;MACI,CAAA;IACH,CAAA,CAAA;EAAc,CAAA;AAIzB;AAAgC,UAhBf,SAAA,CAgBe;MACnB,CAAA,EAAA,MAAA;YACH,CAAA,EAAA;IACS,IAAA,EAAA,MAAA;IAHqB,OAAA,EAAA,MAAA;EAAkB,CAAA;EAMzC,KAAA,CAAA,EAnBP,aAmBgB,EAAA;EAKT,SAAA,CAAA,EAvBH,iBAuBoB,EAAA;EAOjB,OAAA,CAAA,EA7BL,eA+BC,EAAA;AAGb;AAAuC,UA/BtB,gBAAA,CA+BsB;SAMxB,EAAA,MAAA;OAHH,CAAA,EAhCF,OAgCE,EAAA;WAKK,CAAA,EApCH,WAoCG,EAAA;EAAW,MAAA,CAAA,EAnCjB,cAmCiB,EAAA;EAGX,OAAA,CAAA,EAAA,MAAY;AAM7B;AAAoC,UAxCnB,cAAA,SAAuB,kBAwCJ,CAAA;UAEJ,CAAA,EAzCnB,gBAyCmB,EAAA;OAAxB,CAAA,EAxCE,gBAwCF,EAAA;gBAFoC,CAAA,EArCzB,gBAqCyB,EAAA;;AAK3B,UAvCA,SAAA,CAuCY;EAAA,IAAA,CAAA,EAAA,MAAA;QACpB,CAAA,EAtCE,cAsCF,EAAA;;AAaS,UAhDD,iBAAA,CAgDC;OAAoB,EAAA;IAGzB,OAAA,CAAA,EAAA,MAAA;IASQ,UAAA,CAAA,EAAA,MAAA;IAEb,QAAA,CAAA,EAAA,MAAA;IACA,QAAA,CAAA,EAAA,MAAA;;MAEG,CAAA,EAAA,MAAA;EAAY,MAAA,CAAA,EA9DZ,SA8DY,EAAA;EAgBP,OAAA,CAAA,EAAA,MAAU;AAgB1B;AAWsB,UArGL,UAAA,CAqGoB;EAAA,IAAA,CAAA,EAAA,MAAA;UAC3B,CAAA,EApGG,iBAoGH,EAAA;;AAEW,UAnGJ,sBAAA,CAmGI;MAAlB,EAAA,MAAA;EAAO,SAAA,EAAA,MAAA;YAhGE;;;eAGG;;iBAEE;;UAGA,YAAA;;gBAED;;;UAIC,kBAAA,SAA2B;;QAEpC,wBAAwB;;UAGf,YAAA;SACR;;;;;;;YAOG;;;;;;kBAMM,oBAAoB;;;aAGzB;;;;;;;;;qBASQ;;QAEb;QACA;SACC;WACE;;;;;;;;;;;iBAgBK,UAAA,sBAAgC;;;;;UAgB/B,wBAAA;;;;;;;;mBAQE;;iBAGG,eAAA,SACZ,0BACI,2BACX;UAAkB"}
@@ -1,4 +1,4 @@
1
- import { ApiKeyAuthConfig, ChaosConfig, RecordConfig } from "./types.js";
1
+ import { ApiKeyAuthConfig, ChaosConfig, McpRecordConfig, MisbehaviorConfig, MisbehaviorFaultId, RecordConfig, ResponsesToolsMode } from "./types.js";
2
2
  import { A2AAgentDefinition, A2AArtifact, A2APart, A2AStreamEvent } from "./a2a-types.js";
3
3
  import { AGUIEvent } from "./agui-types.js";
4
4
  import { LLMock } from "./llmock.js";
@@ -83,6 +83,11 @@ interface VectorConfig {
83
83
  path?: string;
84
84
  collections?: VectorConfigCollection[];
85
85
  }
86
+ /** `llm.record` in a config file: the LLM `RecordConfig`, plus MCP record mounts (C10). */
87
+ interface AimockRecordConfig extends RecordConfig {
88
+ /** MR1: mount path → upstream URL, or a full MCP record config. */
89
+ mcp?: Record<string, string | McpRecordConfig>;
90
+ }
86
91
  interface AimockConfig {
87
92
  auth?: ApiKeyAuthConfig;
88
93
  llm?: {
@@ -92,7 +97,24 @@ interface AimockConfig {
92
97
  replaySpeed?: number;
93
98
  logLevel?: "silent" | "warn" | "info" | "debug";
94
99
  chaos?: ChaosConfig;
95
- record?: RecordConfig;
100
+ /**
101
+ * Server-wide misbehavior defaults. Read only when misbehavior is enabled
102
+ * (`llm.enableMisbehavior: true` or `aimock --misbehavior`); otherwise the
103
+ * key is ignored with a warning, as 1.44.0 ignored it.
104
+ */
105
+ misbehavior?: MisbehaviorConfig | MisbehaviorFaultId;
106
+ /** See `MockServerOptions.enableMisbehavior`. */
107
+ enableMisbehavior?: boolean;
108
+ record?: AimockRecordConfig;
109
+ /**
110
+ * Turns on `llm.record.mcp`. Without it the key is ignored with a warning,
111
+ * as 1.44.0 ignored it.
112
+ */
113
+ enableMcpRecording?: boolean;
114
+ /** See `MockServerOptions.strictToolArguments`. */
115
+ strictToolArguments?: boolean;
116
+ /** See `MockServerOptions.responsesTools`. */
117
+ responsesTools?: ResponsesToolsMode;
96
118
  };
97
119
  mcp?: MCPConfig;
98
120
  a2a?: A2AConfig;
@@ -109,10 +131,21 @@ interface AimockConfig {
109
131
  host?: string;
110
132
  }
111
133
  declare function loadConfig(configPath: string): AimockConfig;
112
- declare function startFromConfig(config: AimockConfig, overrides?: {
134
+ /**
135
+ * Command-line overrides for `startFromConfig`. The opt-in flags win over the
136
+ * matching `llm.*` config keys.
137
+ */
138
+ interface StartFromConfigOverrides {
113
139
  port?: number;
114
140
  host?: string;
115
- }): Promise<{
141
+ /** `aimock --misbehavior`: overrides `llm.enableMisbehavior`. */
142
+ enableMisbehavior?: boolean;
143
+ /** `aimock --strict-tool-arguments`: overrides `llm.strictToolArguments`. */
144
+ strictToolArguments?: boolean;
145
+ /** `aimock --responses-tools`: overrides `llm.responsesTools`. */
146
+ responsesTools?: ResponsesToolsMode;
147
+ }
148
+ declare function startFromConfig(config: AimockConfig, overrides?: StartFromConfigOverrides): Promise<{
116
149
  llmock: LLMock;
117
150
  url: string;
118
151
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"config-loader.d.ts","names":[],"sources":["../src/config-loader.ts"],"sourcesContent":[],"mappings":";;;;;;;;UAgBiB,aAAA,SAAsB;;AAAvC;AAIiB,UAAA,iBAAA,CAAiB;EASjB,GAAA,EAAA,MAAA;EAAgB,IAAA,EAAA,MAAA;UAEnB,CAAA,EAAA,MAAA;aAF2B,CAAA,EAAA,MAAA;EAAmB,IAAA,CAAA,EAAA,MAAA;EAM3C,IAAA,CAAA,EAAA,MAAS;;AAGhB,UATO,eAAA,SAAwB,mBAS/B,CAAA;QACI,CAAA,EAAA;IACF,QAAA,EATE,KASF,CAAA;MAAe,IAAA,EAAA,MAAA;MAGV,OAAA,EAAA;QAAgB,IAAA,EAAA,MAAA;QAEvB,IAAA,EAAA,MAAA;MACI,CAAA;IACH,CAAA,CAAA;EAAc,CAAA;AAIzB;AAAgC,UAhBf,SAAA,CAgBe;MACnB,CAAA,EAAA,MAAA;YACH,CAAA,EAAA;IACS,IAAA,EAAA,MAAA;IAHqB,OAAA,EAAA,MAAA;EAAkB,CAAA;EAMzC,KAAA,CAAA,EAnBP,aAmBgB,EAEf;EAGM,SAAA,CAAA,EAvBH,iBAuBoB,EAGvB;EAIM,OAAA,CAAA,EA7BL,eA+BC,EAAA;AAGb;AAAuC,UA/BtB,gBAAA,CA+BsB;SAMxB,EAAA,MAAA;OAHH,CAAA,EAhCF,OAgCE,EAAA;WAKK,CAAA,EApCH,WAoCG,EAAA;EAAW,MAAA,CAAA,EAnCjB,cAmCiB,EAAA;EAGX,OAAA,CAAA,EAAA,MAAY;AAK7B;AAA6B,UAvCZ,cAAA,SAAuB,kBAuCX,CAAA;UACpB,CAAA,EAvCI,gBAuCJ,EAAA;OAOG,CAAA,EA7CF,gBA6CE,EAAA;gBACC,CAAA,EA7CM,gBA6CN,EAAA;;AAGL,UA7CS,SAAA,CA6CT;MACC,CAAA,EAAA,MAAA;QACE,CAAA,EA7CA,cA6CA,EAAA;;AAQK,UAlDC,iBAAA,CAkD+B;EAK1B,KAAA,EAAA;IAAe,OAAA,CAAA,EAAA,MAAA;IAC3B,UAAA,CAAA,EAAA,MAAA;IAEW,QAAA,CAAA,EAAA,MAAA;IAAlB,QAAA,CAAA,EAAA,MAAA;EAAO,CAAA;;WAvDC;;;UAIM,UAAA;;aAEJ;;UAGI,sBAAA;;;YAGL;;;eAGG;;iBAEE;;UAGA,YAAA;;gBAED;;UAGC,YAAA;SACR;;;;;;;YAOG;aACC;;QAEL;QACA;SACC;WACE;;;;;;;;;;;iBAQK,UAAA,sBAAgC;iBAK1B,eAAA,SACZ;;;IAEP;UAAkB"}
1
+ {"version":3,"file":"config-loader.d.ts","names":[],"sources":["../src/config-loader.ts"],"sourcesContent":[],"mappings":";;;;;;;;UA4BiB,aAAA,SAAsB;;AAAvC;AAIiB,UAAA,iBAAA,CAAiB;EASjB,GAAA,EAAA,MAAA;EAAgB,IAAA,EAAA,MAAA;UAEnB,CAAA,EAAA,MAAA;aAF2B,CAAA,EAAA,MAAA;EAAmB,IAAA,CAAA,EAAA,MAAA;EAM3C,IAAA,CAAA,EAAA,MAAS;;AAGhB,UATO,eAAA,SAAwB,mBAS/B,CAAA;QACI,CAAA,EAAA;IACF,QAAA,EATE,KASF,CAAA;MAAe,IAAA,EAAA,MAAA;MAGV,OAAA,EAAA;QAAgB,IAAA,EAAA,MAAA;QAEvB,IAAA,EAAA,MAAA;MACI,CAAA;IACH,CAAA,CAAA;EAAc,CAAA;AAIzB;AAAgC,UAhBf,SAAA,CAgBe;MACnB,CAAA,EAAA,MAAA;YACH,CAAA,EAAA;IACS,IAAA,EAAA,MAAA;IAHqB,OAAA,EAAA,MAAA;EAAkB,CAAA;EAMzC,KAAA,CAAA,EAnBP,aAmBgB,EAEf;EAGM,SAAA,CAAA,EAvBH,iBAuBoB,EAGvB;EAIM,OAAA,CAAA,EA7BL,eA+BC,EAAA;AAGb;AAAuC,UA/BtB,gBAAA,CA+BsB;SAMxB,EAAA,MAAA;OAHH,CAAA,EAhCF,OAgCE,EAAA;WAKK,CAAA,EApCH,WAoCG,EAAA;EAAW,MAAA,CAAA,EAnCjB,cAmCiB,EAAA;EAGX,OAAA,CAAA,EAAA,MAAY;AAM7B;AAAoC,UAxCnB,cAAA,SAAuB,kBAwCJ,CAAA;UAEJ,CAAA,EAzCnB,gBAyCmB,EAAA;OAAxB,CAAA,EAxCE,gBAwCF,EAAA;gBAFoC,CAAA,EArCzB,gBAqCyB,EAAA;;AAK3B,UAvCA,SAAA,CAuCY;EAAA,IAAA,CAAA,EAAA,MAAA;QACpB,CAAA,EAtCE,cAsCF,EAAA;;AAaS,UAhDD,iBAAA,CAgDC;OAAoB,EAAA;IAGzB,OAAA,CAAA,EAAA,MAAA;IASQ,UAAA,CAAA,EAAA,MAAA;IAEb,QAAA,CAAA,EAAA,MAAA;IACA,QAAA,CAAA,EAAA,MAAA;;MAEG,CAAA,EAAA,MAAA;EAAY,MAAA,CAAA,EA9DZ,SA8DY,EAAA;EAgBP,OAAA,CAAA,EAAA,MAAU;AAgB1B;AAWsB,UArGL,UAAA,CAqGoB;EAAA,IAAA,CAAA,EAAA,MAAA;UAC3B,CAAA,EApGG,iBAoGH,EAAA;;AAEW,UAnGJ,sBAAA,CAmGI;MAAlB,EAAA,MAAA;EAAO,SAAA,EAAA,MAAA;YAhGE;;;eAGG;;iBAEE;;UAGA,YAAA;;gBAED;;;UAIC,kBAAA,SAA2B;;QAEpC,wBAAwB;;UAGf,YAAA;SACR;;;;;;;YAOG;;;;;;kBAMM,oBAAoB;;;aAGzB;;;;;;;;;qBASQ;;QAEb;QACA;SACC;WACE;;;;;;;;;;;iBAgBK,UAAA,sBAAgC;;;;;UAgB/B,wBAAA;;;;;;;;mBAQE;;iBAGG,eAAA,SACZ,0BACI,2BACX;UAAkB"}
@@ -1,21 +1,58 @@
1
1
  import { resolveInboundAuth, selectInboundAuthSource } from "./api-key-auth.js";
2
2
  import { A2AMock } from "./a2a-mock.js";
3
3
  import { buildTextResponse } from "./agui-handler.js";
4
+ import { parseMisbehavior } from "./misbehavior.js";
4
5
  import { Logger } from "./logger.js";
5
6
  import { AGUIMock } from "./agui-mock.js";
6
- import { createLLMockWithResolvedAuth } from "./llmock.js";
7
+ import { configToolHandlers } from "./mcp-handler.js";
8
+ import { wireMcpRecording } from "./mcp-recorder.js";
7
9
  import { MCPMock } from "./mcp-mock.js";
10
+ import { createLLMockWithResolvedAuth } from "./llmock.js";
8
11
  import { VectorMock } from "./vector-mock.js";
9
12
  import * as fs from "node:fs";
10
13
  import * as path from "node:path";
11
14
 
12
15
  //#region src/config-loader.ts
16
+ /** C10: the LLM part of `llm.record`. `mcp` is MCP-only; no `providers` means no LLM recording. */
17
+ function llmRecordOf(record) {
18
+ if (!record) return void 0;
19
+ const { mcp: _mcp, ...llm } = record;
20
+ return llm.providers === void 0 ? void 0 : llm;
21
+ }
13
22
  function loadConfig(configPath) {
14
23
  const raw = fs.readFileSync(configPath, "utf-8");
15
24
  return JSON.parse(raw);
16
25
  }
26
+ /** C3: `llm.record.mcp` is an object with at least one mount. */
27
+ function hasMcpRecordMount(mcp) {
28
+ return typeof mcp === "object" && mcp !== null && !Array.isArray(mcp) && Object.keys(mcp).length > 0;
29
+ }
17
30
  async function startFromConfig(config, overrides) {
18
31
  const logger = new Logger("info");
32
+ const booleanOptIn = (key) => {
33
+ const value = config.llm?.[key];
34
+ if (value === void 0 || typeof value === "boolean") return value;
35
+ logger.warn(`Ignoring llm.${key} because it must be true or false, got ${JSON.stringify(value)}.`);
36
+ };
37
+ const configEnableMisbehavior = booleanOptIn("enableMisbehavior");
38
+ const configStrictToolArguments = booleanOptIn("strictToolArguments");
39
+ const enableMcpRecording = booleanOptIn("enableMcpRecording") === true;
40
+ const mcpRecord = enableMcpRecording ? config.llm?.record?.mcp : void 0;
41
+ const enableMisbehavior = (overrides?.enableMisbehavior ?? configEnableMisbehavior) === true;
42
+ let misbehavior;
43
+ if (config.llm?.misbehavior !== void 0) if (!enableMisbehavior) logger.warn("Ignoring llm.misbehavior because misbehavior is not enabled. Set llm.enableMisbehavior: true or pass --misbehavior to use it.");
44
+ else {
45
+ const parsed = parseMisbehavior(config.llm.misbehavior, "llm.misbehavior");
46
+ if (!parsed.ok) throw new TypeError(`${parsed.issue.rule}: ${parsed.issue.path}: ${parsed.issue.message}`);
47
+ misbehavior = parsed.config;
48
+ }
49
+ if (config.llm?.record?.mcp !== void 0 && !enableMcpRecording) logger.warn("Ignoring llm.record.mcp because MCP recording is not enabled. Set llm.enableMcpRecording: true to use it.");
50
+ let responsesTools = config.llm?.responsesTools;
51
+ if (responsesTools !== void 0 && responsesTools !== "legacy" && responsesTools !== "extended") {
52
+ const effective = overrides?.responsesTools !== void 0 ? `"${overrides.responsesTools}" from --responses-tools` : "\"legacy\"";
53
+ logger.warn(`Ignoring llm.responsesTools because it must be "legacy" or "extended", got ${JSON.stringify(responsesTools)}. Using ${effective}.`);
54
+ responsesTools = void 0;
55
+ }
19
56
  let replaySpeed = config.llm?.replaySpeed;
20
57
  if (replaySpeed != null && (!Number.isFinite(replaySpeed) || replaySpeed <= 0)) {
21
58
  logger.warn(`llm.replaySpeed must be a positive number, got ${replaySpeed}. Ignoring.`);
@@ -28,12 +65,17 @@ async function startFromConfig(config, overrides) {
28
65
  latency: config.llm?.latency,
29
66
  chunkSize: config.llm?.chunkSize,
30
67
  replaySpeed,
31
- logLevel: config.llm?.logLevel,
68
+ logLevel: config.llm?.logLevel ?? (hasMcpRecordMount(mcpRecord) ? "warn" : void 0),
32
69
  chaos: config.llm?.chaos,
33
- record: config.llm?.record,
70
+ misbehavior,
71
+ ...enableMisbehavior ? { enableMisbehavior: true } : {},
72
+ record: llmRecordOf(config.llm?.record),
34
73
  metrics: config.metrics,
35
- strict: config.strict
74
+ strict: config.strict,
75
+ strictToolArguments: overrides?.strictToolArguments ?? configStrictToolArguments,
76
+ responsesTools: overrides?.responsesTools ?? responsesTools
36
77
  }, resolvedAuth);
78
+ const configMounts = /* @__PURE__ */ new Map();
37
79
  if (config.llm?.fixtures) {
38
80
  const fixturePath = path.resolve(config.llm.fixtures);
39
81
  if (fs.statSync(fixturePath).isDirectory()) llmock.loadFixtureDir(fixturePath);
@@ -45,7 +87,11 @@ async function startFromConfig(config, overrides) {
45
87
  if (mcpConfig.tools) for (const tool of mcpConfig.tools) {
46
88
  const { result, ...def } = tool;
47
89
  mcp.addTool(def);
48
- if (result !== void 0) mcp.onToolCall(def.name, () => result);
90
+ if (result !== void 0) {
91
+ const answer = () => result;
92
+ configToolHandlers.add(answer);
93
+ mcp.onToolCall(def.name, answer);
94
+ }
49
95
  }
50
96
  if (mcpConfig.resources) for (const res of mcpConfig.resources) mcp.addResource({
51
97
  uri: res.uri,
@@ -64,6 +110,7 @@ async function startFromConfig(config, overrides) {
64
110
  }
65
111
  const mcpPath = mcpConfig.path ?? "/mcp";
66
112
  llmock.mount(mcpPath, mcp);
113
+ configMounts.set(mcpPath, mcp);
67
114
  logger.info(`MCPMock mounted at ${mcpPath}`);
68
115
  }
69
116
  if (config.a2a) {
@@ -78,6 +125,7 @@ async function startFromConfig(config, overrides) {
78
125
  }
79
126
  const a2aPath = a2aConfig.path ?? "/a2a";
80
127
  llmock.mount(a2aPath, a2a);
128
+ configMounts.set(a2aPath, a2a);
81
129
  logger.info(`A2AMock mounted at ${a2aPath}`);
82
130
  }
83
131
  if (config.agui) {
@@ -109,6 +157,7 @@ async function startFromConfig(config, overrides) {
109
157
  }
110
158
  const aguiPath = aguiConfig.path ?? "/agui";
111
159
  llmock.mount(aguiPath, agui);
160
+ configMounts.set(aguiPath, agui);
112
161
  logger.info(`AGUIMock mounted at ${aguiPath}`);
113
162
  }
114
163
  if (config.vector) {
@@ -121,8 +170,17 @@ async function startFromConfig(config, overrides) {
121
170
  }
122
171
  const vectorPath = vectorConfig.path ?? "/vector";
123
172
  llmock.mount(vectorPath, vector);
173
+ configMounts.set(vectorPath, vector);
124
174
  logger.info(`VectorMock mounted at ${vectorPath}`);
125
175
  }
176
+ if (mcpRecord !== void 0) wireMcpRecording(llmock, mcpRecord, {
177
+ mounted: configMounts,
178
+ mcpMock: MCPMock,
179
+ fixtures: config.llm?.fixtures,
180
+ fixturePath: config.llm?.record?.fixturePath,
181
+ strict: config.strict,
182
+ env: process.env
183
+ });
126
184
  if (config.services) {
127
185
  if (config.services.search) {
128
186
  llmock.onSearch(/.*/, []);
@@ -1 +1 @@
1
- {"version":3,"file":"config-loader.js","names":[],"sources":["../src/config-loader.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { LLMock, createLLMockWithResolvedAuth } from \"./llmock.js\";\nimport { resolveInboundAuth, selectInboundAuthSource } from \"./api-key-auth.js\";\nimport { MCPMock } from \"./mcp-mock.js\";\nimport { A2AMock } from \"./a2a-mock.js\";\nimport { AGUIMock } from \"./agui-mock.js\";\nimport type { ApiKeyAuthConfig, ChaosConfig, RecordConfig } from \"./types.js\";\nimport type { MCPToolDefinition, MCPPromptDefinition } from \"./mcp-types.js\";\nimport type { A2AAgentDefinition, A2APart, A2AArtifact, A2AStreamEvent } from \"./a2a-types.js\";\nimport type { AGUIEvent } from \"./agui-types.js\";\nimport { buildTextResponse } from \"./agui-handler.js\";\nimport { VectorMock } from \"./vector-mock.js\";\nimport type { QueryResult } from \"./vector-types.js\";\nimport { Logger } from \"./logger.js\";\n\nexport interface MCPConfigTool extends MCPToolDefinition {\n result?: string;\n}\n\nexport interface MCPConfigResource {\n uri: string;\n name: string;\n mimeType?: string;\n description?: string;\n text?: string;\n blob?: string;\n}\n\nexport interface MCPConfigPrompt extends MCPPromptDefinition {\n result?: {\n messages: Array<{ role: string; content: { type: string; text: string } }>;\n };\n}\n\nexport interface MCPConfig {\n path?: string;\n serverInfo?: { name: string; version: string };\n tools?: MCPConfigTool[];\n resources?: MCPConfigResource[];\n prompts?: MCPConfigPrompt[];\n}\n\nexport interface A2AConfigPattern {\n pattern: string;\n parts?: A2APart[];\n artifacts?: A2AArtifact[];\n events?: A2AStreamEvent[];\n delayMs?: number;\n}\n\nexport interface A2AConfigAgent extends A2AAgentDefinition {\n messages?: A2AConfigPattern[];\n tasks?: A2AConfigPattern[];\n streamingTasks?: A2AConfigPattern[];\n}\n\nexport interface A2AConfig {\n path?: string;\n agents?: A2AConfigAgent[];\n}\n\nexport interface AGUIConfigFixture {\n match: { message?: string; toolCallId?: string; toolName?: string; stateKey?: string };\n text?: string; // shorthand: uses buildTextResponse\n events?: AGUIEvent[]; // raw events\n delayMs?: number;\n}\n\nexport interface AGUIConfig {\n path?: string; // mount path, default \"/agui\"\n fixtures?: AGUIConfigFixture[];\n}\n\nexport interface VectorConfigCollection {\n name: string;\n dimension: number;\n vectors?: Array<{\n id: string;\n values: number[];\n metadata?: Record<string, unknown>;\n }>;\n queryResults?: QueryResult[];\n}\n\nexport interface VectorConfig {\n path?: string;\n collections?: VectorConfigCollection[];\n}\n\nexport interface AimockConfig {\n auth?: ApiKeyAuthConfig;\n llm?: {\n fixtures?: string;\n latency?: number;\n chunkSize?: number;\n replaySpeed?: number;\n logLevel?: \"silent\" | \"warn\" | \"info\" | \"debug\";\n chaos?: ChaosConfig;\n record?: RecordConfig;\n };\n mcp?: MCPConfig;\n a2a?: A2AConfig;\n agui?: AGUIConfig;\n vector?: VectorConfig;\n services?: { search?: boolean; rerank?: boolean; moderate?: boolean };\n metrics?: boolean;\n strict?: boolean;\n port?: number;\n host?: string;\n}\n\nexport function loadConfig(configPath: string): AimockConfig {\n const raw = fs.readFileSync(configPath, \"utf-8\");\n return JSON.parse(raw) as AimockConfig;\n}\n\nexport async function startFromConfig(\n config: AimockConfig,\n overrides?: { port?: number; host?: string },\n): Promise<{ llmock: LLMock; url: string }> {\n const logger = new Logger(\"info\");\n\n // A non-positive replaySpeed fails calculateDelay's `speed > 0` check and applies the\n // full delay rather than none. Mirrors the fixture-level guard in fixture-loader.ts.\n let replaySpeed = config.llm?.replaySpeed;\n if (replaySpeed != null && (!Number.isFinite(replaySpeed) || replaySpeed <= 0)) {\n logger.warn(`llm.replaySpeed must be a positive number, got ${replaySpeed}. Ignoring.`);\n replaySpeed = undefined;\n }\n\n // Load fixtures if specified\n const resolvedAuth = resolveInboundAuth(selectInboundAuthSource(config.auth));\n const llmock = createLLMockWithResolvedAuth(\n {\n port: overrides?.port ?? config.port ?? 0,\n host: overrides?.host ?? config.host ?? \"127.0.0.1\",\n latency: config.llm?.latency,\n chunkSize: config.llm?.chunkSize,\n replaySpeed,\n logLevel: config.llm?.logLevel,\n chaos: config.llm?.chaos,\n record: config.llm?.record,\n metrics: config.metrics,\n strict: config.strict,\n },\n resolvedAuth,\n );\n\n if (config.llm?.fixtures) {\n const fixturePath = path.resolve(config.llm.fixtures);\n const stat = fs.statSync(fixturePath);\n if (stat.isDirectory()) {\n llmock.loadFixtureDir(fixturePath);\n } else {\n llmock.loadFixtureFile(fixturePath);\n }\n }\n\n // MCP\n if (config.mcp) {\n const mcpConfig = config.mcp;\n const mcp = new MCPMock({\n serverInfo: mcpConfig.serverInfo,\n });\n\n if (mcpConfig.tools) {\n for (const tool of mcpConfig.tools) {\n const { result, ...def } = tool;\n mcp.addTool(def);\n if (result !== undefined) {\n mcp.onToolCall(def.name, () => result);\n }\n }\n }\n\n if (mcpConfig.resources) {\n for (const res of mcpConfig.resources) {\n mcp.addResource(\n { uri: res.uri, name: res.name, mimeType: res.mimeType, description: res.description },\n res.text !== undefined || res.blob !== undefined\n ? { text: res.text, blob: res.blob, mimeType: res.mimeType }\n : undefined,\n );\n }\n }\n\n if (mcpConfig.prompts) {\n for (const prompt of mcpConfig.prompts) {\n const { result, ...def } = prompt;\n if (result) {\n mcp.addPrompt(def, () => result as import(\"./mcp-types.js\").MCPPromptResult);\n } else {\n mcp.addPrompt(def);\n }\n }\n }\n\n const mcpPath = mcpConfig.path ?? \"/mcp\";\n llmock.mount(mcpPath, mcp);\n logger.info(`MCPMock mounted at ${mcpPath}`);\n }\n\n // A2A\n if (config.a2a) {\n const a2aConfig = config.a2a;\n const a2a = new A2AMock();\n\n if (a2aConfig.agents) {\n for (const agentConfig of a2aConfig.agents) {\n const { messages, tasks, streamingTasks, ...def } = agentConfig;\n a2a.registerAgent(def);\n\n if (messages) {\n for (const m of messages) {\n a2a.onMessage(def.name, m.pattern, m.parts ?? [{ text: \"\" }]);\n }\n }\n\n if (tasks) {\n for (const t of tasks) {\n a2a.onTask(def.name, t.pattern, t.artifacts ?? []);\n }\n }\n\n if (streamingTasks) {\n for (const s of streamingTasks) {\n a2a.onStreamingTask(def.name, s.pattern, s.events ?? [], s.delayMs);\n }\n }\n }\n }\n\n const a2aPath = a2aConfig.path ?? \"/a2a\";\n llmock.mount(a2aPath, a2a);\n logger.info(`A2AMock mounted at ${a2aPath}`);\n }\n\n // AG-UI\n if (config.agui) {\n const aguiConfig = config.agui;\n const agui = new AGUIMock();\n\n if (aguiConfig.fixtures) {\n for (const f of aguiConfig.fixtures) {\n if (f.match.toolCallId && f.text && !f.events) {\n logger.warn(\n `AG-UI fixture uses text shorthand with toolCallId — text shorthand ignores toolCallId matching; use events[] instead (match: ${JSON.stringify(f.match)})`,\n );\n }\n if (f.text) {\n if (f.match.message !== undefined) {\n agui.onMessage(f.match.message, f.text, { delayMs: f.delayMs });\n } else {\n // No message pattern — register via addFixture so it only matches\n // on other criteria (toolCallId, toolName, stateKey) instead of\n // becoming a catch-all that matches every request.\n agui.addFixture({\n match: {\n toolCallId: f.match.toolCallId,\n toolName: f.match.toolName,\n stateKey: f.match.stateKey,\n },\n events: buildTextResponse(f.text),\n delayMs: f.delayMs,\n });\n }\n } else if (f.events) {\n agui.addFixture({\n match: {\n message: f.match.message,\n toolCallId: f.match.toolCallId,\n toolName: f.match.toolName,\n stateKey: f.match.stateKey,\n },\n events: f.events,\n delayMs: f.delayMs,\n });\n } else {\n logger.warn(\n `AG-UI fixture has neither text nor events — it will be skipped (match: ${JSON.stringify(f.match)})`,\n );\n }\n }\n }\n\n const aguiPath = aguiConfig.path ?? \"/agui\";\n llmock.mount(aguiPath, agui);\n logger.info(`AGUIMock mounted at ${aguiPath}`);\n }\n\n // Vector\n if (config.vector) {\n const vectorConfig = config.vector;\n const vector = new VectorMock();\n\n if (vectorConfig.collections) {\n for (const col of vectorConfig.collections) {\n vector.addCollection(col.name, { dimension: col.dimension });\n\n if (col.vectors && col.vectors.length > 0) {\n vector.upsert(col.name, col.vectors);\n }\n\n if (col.queryResults) {\n vector.onQuery(col.name, col.queryResults);\n }\n }\n }\n\n const vectorPath = vectorConfig.path ?? \"/vector\";\n llmock.mount(vectorPath, vector);\n logger.info(`VectorMock mounted at ${vectorPath}`);\n }\n\n // Services — configure default catch-all responses\n if (config.services) {\n if (config.services.search) {\n llmock.onSearch(/.*/, []);\n logger.info(\"Search service enabled with default empty results\");\n }\n if (config.services.rerank) {\n llmock.onRerank(/.*/, []);\n logger.info(\"Rerank service enabled with default empty results\");\n }\n if (config.services.moderate) {\n llmock.onModerate(/.*/, { flagged: false, categories: {} });\n logger.info(\"Moderation service enabled with default unflagged results\");\n }\n }\n\n const url = await llmock.start();\n return { llmock, url };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"config-loader.js","names":[],"sources":["../src/config-loader.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { LLMock, createLLMockWithResolvedAuth } from \"./llmock.js\";\nimport { resolveInboundAuth, selectInboundAuthSource } from \"./api-key-auth.js\";\nimport { MCPMock } from \"./mcp-mock.js\";\nimport { configToolHandlers } from \"./mcp-handler.js\";\nimport { A2AMock } from \"./a2a-mock.js\";\nimport { AGUIMock } from \"./agui-mock.js\";\nimport type {\n ApiKeyAuthConfig,\n ChaosConfig,\n McpRecordConfig,\n MisbehaviorConfig,\n MisbehaviorFaultId,\n Mountable,\n RecordConfig,\n ResponsesToolsMode,\n} from \"./types.js\";\nimport { parseMisbehavior } from \"./misbehavior.js\";\nimport type { MCPToolDefinition, MCPPromptDefinition } from \"./mcp-types.js\";\nimport type { A2AAgentDefinition, A2APart, A2AArtifact, A2AStreamEvent } from \"./a2a-types.js\";\nimport type { AGUIEvent } from \"./agui-types.js\";\nimport { buildTextResponse } from \"./agui-handler.js\";\nimport { VectorMock } from \"./vector-mock.js\";\nimport type { QueryResult } from \"./vector-types.js\";\nimport { Logger } from \"./logger.js\";\nimport { wireMcpRecording } from \"./mcp-recorder.js\";\n\nexport interface MCPConfigTool extends MCPToolDefinition {\n result?: string;\n}\n\nexport interface MCPConfigResource {\n uri: string;\n name: string;\n mimeType?: string;\n description?: string;\n text?: string;\n blob?: string;\n}\n\nexport interface MCPConfigPrompt extends MCPPromptDefinition {\n result?: {\n messages: Array<{ role: string; content: { type: string; text: string } }>;\n };\n}\n\nexport interface MCPConfig {\n path?: string;\n serverInfo?: { name: string; version: string };\n tools?: MCPConfigTool[];\n resources?: MCPConfigResource[];\n prompts?: MCPConfigPrompt[];\n}\n\nexport interface A2AConfigPattern {\n pattern: string;\n parts?: A2APart[];\n artifacts?: A2AArtifact[];\n events?: A2AStreamEvent[];\n delayMs?: number;\n}\n\nexport interface A2AConfigAgent extends A2AAgentDefinition {\n messages?: A2AConfigPattern[];\n tasks?: A2AConfigPattern[];\n streamingTasks?: A2AConfigPattern[];\n}\n\nexport interface A2AConfig {\n path?: string;\n agents?: A2AConfigAgent[];\n}\n\nexport interface AGUIConfigFixture {\n match: { message?: string; toolCallId?: string; toolName?: string; stateKey?: string };\n text?: string; // shorthand: uses buildTextResponse\n events?: AGUIEvent[]; // raw events\n delayMs?: number;\n}\n\nexport interface AGUIConfig {\n path?: string; // mount path, default \"/agui\"\n fixtures?: AGUIConfigFixture[];\n}\n\nexport interface VectorConfigCollection {\n name: string;\n dimension: number;\n vectors?: Array<{\n id: string;\n values: number[];\n metadata?: Record<string, unknown>;\n }>;\n queryResults?: QueryResult[];\n}\n\nexport interface VectorConfig {\n path?: string;\n collections?: VectorConfigCollection[];\n}\n\n/** `llm.record` in a config file: the LLM `RecordConfig`, plus MCP record mounts (C10). */\nexport interface AimockRecordConfig extends RecordConfig {\n /** MR1: mount path → upstream URL, or a full MCP record config. */\n mcp?: Record<string, string | McpRecordConfig>;\n}\n\nexport interface AimockConfig {\n auth?: ApiKeyAuthConfig;\n llm?: {\n fixtures?: string;\n latency?: number;\n chunkSize?: number;\n replaySpeed?: number;\n logLevel?: \"silent\" | \"warn\" | \"info\" | \"debug\";\n chaos?: ChaosConfig;\n /**\n * Server-wide misbehavior defaults. Read only when misbehavior is enabled\n * (`llm.enableMisbehavior: true` or `aimock --misbehavior`); otherwise the\n * key is ignored with a warning, as 1.44.0 ignored it.\n */\n misbehavior?: MisbehaviorConfig | MisbehaviorFaultId;\n /** See `MockServerOptions.enableMisbehavior`. */\n enableMisbehavior?: boolean;\n record?: AimockRecordConfig;\n /**\n * Turns on `llm.record.mcp`. Without it the key is ignored with a warning,\n * as 1.44.0 ignored it.\n */\n enableMcpRecording?: boolean;\n /** See `MockServerOptions.strictToolArguments`. */\n strictToolArguments?: boolean;\n /** See `MockServerOptions.responsesTools`. */\n responsesTools?: ResponsesToolsMode;\n };\n mcp?: MCPConfig;\n a2a?: A2AConfig;\n agui?: AGUIConfig;\n vector?: VectorConfig;\n services?: { search?: boolean; rerank?: boolean; moderate?: boolean };\n metrics?: boolean;\n strict?: boolean;\n port?: number;\n host?: string;\n}\n\n/** C10: the LLM part of `llm.record`. `mcp` is MCP-only; no `providers` means no LLM recording. */\nfunction llmRecordOf(record: AimockRecordConfig | undefined): RecordConfig | undefined {\n if (!record) return undefined;\n const { mcp: _mcp, ...llm } = record;\n void _mcp; // repo pattern for an intentionally unused binding (byteplus-video.ts:1379-1380)\n return llm.providers === undefined ? undefined : llm;\n}\n\nexport function loadConfig(configPath: string): AimockConfig {\n const raw = fs.readFileSync(configPath, \"utf-8\");\n return JSON.parse(raw) as AimockConfig;\n}\n\n/** C3: `llm.record.mcp` is an object with at least one mount. */\nfunction hasMcpRecordMount(mcp: unknown): boolean {\n return (\n typeof mcp === \"object\" && mcp !== null && !Array.isArray(mcp) && Object.keys(mcp).length > 0\n );\n}\n\n/**\n * Command-line overrides for `startFromConfig`. The opt-in flags win over the\n * matching `llm.*` config keys.\n */\nexport interface StartFromConfigOverrides {\n port?: number;\n host?: string;\n /** `aimock --misbehavior`: overrides `llm.enableMisbehavior`. */\n enableMisbehavior?: boolean;\n /** `aimock --strict-tool-arguments`: overrides `llm.strictToolArguments`. */\n strictToolArguments?: boolean;\n /** `aimock --responses-tools`: overrides `llm.responsesTools`. */\n responsesTools?: ResponsesToolsMode;\n}\n\nexport async function startFromConfig(\n config: AimockConfig,\n overrides?: StartFromConfigOverrides,\n): Promise<{ llmock: LLMock; url: string }> {\n const logger = new Logger(\"info\");\n // 1.44.0 ignored these keys, so a non-boolean value (such as the string\n // \"true\") is ignored with a warning, as `llm.misbehavior` is, and the\n // option stays off unless a CLI flag turns it on (`llm.enableMcpRecording`\n // has no CLI flag).\n const booleanOptIn = (\n key: \"enableMisbehavior\" | \"enableMcpRecording\" | \"strictToolArguments\",\n ): boolean | undefined => {\n const value: unknown = config.llm?.[key];\n if (value === undefined || typeof value === \"boolean\") return value;\n logger.warn(\n `Ignoring llm.${key} because it must be true or false, got ${JSON.stringify(value)}.`,\n );\n return undefined;\n };\n const configEnableMisbehavior = booleanOptIn(\"enableMisbehavior\");\n const configStrictToolArguments = booleanOptIn(\"strictToolArguments\");\n // MCP recording also needs an explicit opt-in. 1.44.0 ignored\n // `llm.record.mcp`, so without `llm.enableMcpRecording: true` the key is\n // ignored (with a warning): nothing is mounted or validated and the log level\n // is unchanged. With the opt-in, an invalid value fails startup.\n const enableMcpRecording = booleanOptIn(\"enableMcpRecording\") === true;\n const mcpRecord = enableMcpRecording ? config.llm?.record?.mcp : undefined;\n // Misbehavior needs an explicit opt-in that a 1.44.0 config cannot contain.\n // 1.44.0 ignored `llm.misbehavior`, so without the opt-in the key is ignored\n // (with a warning) and the server serves exactly what 1.44.0 served. With the\n // opt-in, an invalid value fails startup like any other enabled-mode error.\n const enableMisbehavior = (overrides?.enableMisbehavior ?? configEnableMisbehavior) === true;\n let misbehavior: MisbehaviorConfig | undefined;\n if (config.llm?.misbehavior !== undefined) {\n if (!enableMisbehavior) {\n logger.warn(\n \"Ignoring llm.misbehavior because misbehavior is not enabled. Set llm.enableMisbehavior: true or pass --misbehavior to use it.\",\n );\n } else {\n const parsed = parseMisbehavior(config.llm.misbehavior, \"llm.misbehavior\");\n if (!parsed.ok) {\n throw new TypeError(`${parsed.issue.rule}: ${parsed.issue.path}: ${parsed.issue.message}`);\n }\n misbehavior = parsed.config;\n }\n }\n if (config.llm?.record?.mcp !== undefined && !enableMcpRecording) {\n logger.warn(\n \"Ignoring llm.record.mcp because MCP recording is not enabled. Set llm.enableMcpRecording: true to use it.\",\n );\n }\n\n // 1.44.0 ignored `llm.responsesTools`, so an invalid value is ignored with a\n // warning (as `llm.misbehavior` is) and the server runs in \"legacy\" mode,\n // unless `--responses-tools` sets the mode. The warning names the mode that\n // actually runs.\n let responsesTools = config.llm?.responsesTools;\n if (\n responsesTools !== undefined &&\n responsesTools !== \"legacy\" &&\n responsesTools !== \"extended\"\n ) {\n const effective =\n overrides?.responsesTools !== undefined\n ? `\"${overrides.responsesTools}\" from --responses-tools`\n : '\"legacy\"';\n logger.warn(\n `Ignoring llm.responsesTools because it must be \"legacy\" or \"extended\", got ${JSON.stringify(responsesTools)}. Using ${effective}.`,\n );\n responsesTools = undefined;\n }\n\n // A non-positive replaySpeed fails calculateDelay's `speed > 0` check and applies the\n // full delay rather than none. Mirrors the fixture-level guard in fixture-loader.ts.\n let replaySpeed = config.llm?.replaySpeed;\n if (replaySpeed != null && (!Number.isFinite(replaySpeed) || replaySpeed <= 0)) {\n logger.warn(`llm.replaySpeed must be a positive number, got ${replaySpeed}. Ignoring.`);\n replaySpeed = undefined;\n }\n\n // Load fixtures if specified\n const resolvedAuth = resolveInboundAuth(selectInboundAuthSource(config.auth));\n const llmock = createLLMockWithResolvedAuth(\n {\n port: overrides?.port ?? config.port ?? 0,\n host: overrides?.host ?? config.host ?? \"127.0.0.1\",\n latency: config.llm?.latency,\n chunkSize: config.llm?.chunkSize,\n replaySpeed,\n // llm.record.mcp reports skipped or failed recordings only as MCP-RECORD: warnings,\n // so an enabled config with at least one mount defaults to \"warn\" rather than the silent\n // default. An empty llm.record.mcp records nothing. An explicit logLevel wins.\n logLevel: config.llm?.logLevel ?? (hasMcpRecordMount(mcpRecord) ? \"warn\" : undefined),\n chaos: config.llm?.chaos,\n misbehavior,\n ...(enableMisbehavior ? { enableMisbehavior: true } : {}),\n record: llmRecordOf(config.llm?.record),\n metrics: config.metrics,\n strict: config.strict,\n strictToolArguments: overrides?.strictToolArguments ?? configStrictToolArguments,\n responsesTools: overrides?.responsesTools ?? responsesTools,\n },\n resolvedAuth,\n );\n\n // What this function mounted, by path (MR1: llm.record.mcp records through an MCP mount).\n const configMounts = new Map<string, Mountable>();\n\n if (config.llm?.fixtures) {\n const fixturePath = path.resolve(config.llm.fixtures);\n const stat = fs.statSync(fixturePath);\n if (stat.isDirectory()) {\n llmock.loadFixtureDir(fixturePath);\n } else {\n llmock.loadFixtureFile(fixturePath);\n }\n }\n\n // MCP\n if (config.mcp) {\n const mcpConfig = config.mcp;\n const mcp = new MCPMock({\n serverInfo: mcpConfig.serverInfo,\n });\n\n if (mcpConfig.tools) {\n for (const tool of mcpConfig.tools) {\n const { result, ...def } = tool;\n mcp.addTool(def);\n if (result !== undefined) {\n const answer = () => result;\n configToolHandlers.add(answer);\n mcp.onToolCall(def.name, answer);\n }\n }\n }\n\n if (mcpConfig.resources) {\n for (const res of mcpConfig.resources) {\n mcp.addResource(\n { uri: res.uri, name: res.name, mimeType: res.mimeType, description: res.description },\n res.text !== undefined || res.blob !== undefined\n ? { text: res.text, blob: res.blob, mimeType: res.mimeType }\n : undefined,\n );\n }\n }\n\n if (mcpConfig.prompts) {\n for (const prompt of mcpConfig.prompts) {\n const { result, ...def } = prompt;\n if (result) {\n mcp.addPrompt(def, () => result as import(\"./mcp-types.js\").MCPPromptResult);\n } else {\n mcp.addPrompt(def);\n }\n }\n }\n\n const mcpPath = mcpConfig.path ?? \"/mcp\";\n llmock.mount(mcpPath, mcp);\n configMounts.set(mcpPath, mcp);\n logger.info(`MCPMock mounted at ${mcpPath}`);\n }\n\n // A2A\n if (config.a2a) {\n const a2aConfig = config.a2a;\n const a2a = new A2AMock();\n\n if (a2aConfig.agents) {\n for (const agentConfig of a2aConfig.agents) {\n const { messages, tasks, streamingTasks, ...def } = agentConfig;\n a2a.registerAgent(def);\n\n if (messages) {\n for (const m of messages) {\n a2a.onMessage(def.name, m.pattern, m.parts ?? [{ text: \"\" }]);\n }\n }\n\n if (tasks) {\n for (const t of tasks) {\n a2a.onTask(def.name, t.pattern, t.artifacts ?? []);\n }\n }\n\n if (streamingTasks) {\n for (const s of streamingTasks) {\n a2a.onStreamingTask(def.name, s.pattern, s.events ?? [], s.delayMs);\n }\n }\n }\n }\n\n const a2aPath = a2aConfig.path ?? \"/a2a\";\n llmock.mount(a2aPath, a2a);\n configMounts.set(a2aPath, a2a);\n logger.info(`A2AMock mounted at ${a2aPath}`);\n }\n\n // AG-UI\n if (config.agui) {\n const aguiConfig = config.agui;\n const agui = new AGUIMock();\n\n if (aguiConfig.fixtures) {\n for (const f of aguiConfig.fixtures) {\n if (f.match.toolCallId && f.text && !f.events) {\n logger.warn(\n `AG-UI fixture uses text shorthand with toolCallId — text shorthand ignores toolCallId matching; use events[] instead (match: ${JSON.stringify(f.match)})`,\n );\n }\n if (f.text) {\n if (f.match.message !== undefined) {\n agui.onMessage(f.match.message, f.text, { delayMs: f.delayMs });\n } else {\n // No message pattern — register via addFixture so it only matches\n // on other criteria (toolCallId, toolName, stateKey) instead of\n // becoming a catch-all that matches every request.\n agui.addFixture({\n match: {\n toolCallId: f.match.toolCallId,\n toolName: f.match.toolName,\n stateKey: f.match.stateKey,\n },\n events: buildTextResponse(f.text),\n delayMs: f.delayMs,\n });\n }\n } else if (f.events) {\n agui.addFixture({\n match: {\n message: f.match.message,\n toolCallId: f.match.toolCallId,\n toolName: f.match.toolName,\n stateKey: f.match.stateKey,\n },\n events: f.events,\n delayMs: f.delayMs,\n });\n } else {\n logger.warn(\n `AG-UI fixture has neither text nor events — it will be skipped (match: ${JSON.stringify(f.match)})`,\n );\n }\n }\n }\n\n const aguiPath = aguiConfig.path ?? \"/agui\";\n llmock.mount(aguiPath, agui);\n configMounts.set(aguiPath, agui);\n logger.info(`AGUIMock mounted at ${aguiPath}`);\n }\n\n // Vector\n if (config.vector) {\n const vectorConfig = config.vector;\n const vector = new VectorMock();\n\n if (vectorConfig.collections) {\n for (const col of vectorConfig.collections) {\n vector.addCollection(col.name, { dimension: col.dimension });\n\n if (col.vectors && col.vectors.length > 0) {\n vector.upsert(col.name, col.vectors);\n }\n\n if (col.queryResults) {\n vector.onQuery(col.name, col.queryResults);\n }\n }\n }\n\n const vectorPath = vectorConfig.path ?? \"/vector\";\n llmock.mount(vectorPath, vector);\n configMounts.set(vectorPath, vector);\n logger.info(`VectorMock mounted at ${vectorPath}`);\n }\n\n // MR1: llm.record.mcp — record mounts (auto-mount an MCPMock where none is).\n // Read only with llm.enableMcpRecording: true. Any value that is present is then\n // validated, so null, false, 0 and \"\" fail at start too.\n if (mcpRecord !== undefined) {\n wireMcpRecording(llmock, mcpRecord, {\n mounted: configMounts,\n mcpMock: MCPMock,\n fixtures: config.llm?.fixtures,\n fixturePath: config.llm?.record?.fixturePath,\n strict: config.strict,\n env: process.env,\n });\n }\n\n // Services — configure default catch-all responses\n if (config.services) {\n if (config.services.search) {\n llmock.onSearch(/.*/, []);\n logger.info(\"Search service enabled with default empty results\");\n }\n if (config.services.rerank) {\n llmock.onRerank(/.*/, []);\n logger.info(\"Rerank service enabled with default empty results\");\n }\n if (config.services.moderate) {\n llmock.onModerate(/.*/, { flagged: false, categories: {} });\n logger.info(\"Moderation service enabled with default unflagged results\");\n }\n }\n\n const url = await llmock.start();\n return { llmock, url };\n}\n"],"mappings":"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"}
@@ -2,7 +2,15 @@
2
2
  //#region src/constants.ts
3
3
  /** Sentinel testId used when no explicit test scope is provided. */
4
4
  const DEFAULT_TEST_ID = "__default__";
5
+ /**
6
+ * Longest text (in characters) an `echo` of one value produces in an error
7
+ * message, a bad-block detail or a `firstDifference` side. A longer value is
8
+ * cut so that the kept part, `…` and the count of chars left out together
9
+ * fit in this limit. Exported from mcp-fakes.ts as well.
10
+ */
11
+ const MCP_FAKES_ECHO_LIMIT = 200;
5
12
 
6
13
  //#endregion
7
14
  exports.DEFAULT_TEST_ID = DEFAULT_TEST_ID;
15
+ exports.MCP_FAKES_ECHO_LIMIT = MCP_FAKES_ECHO_LIMIT;
8
16
  //# sourceMappingURL=constants.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.cjs","names":[],"sources":["../src/constants.ts"],"sourcesContent":["/** Sentinel testId used when no explicit test scope is provided. */\nexport const DEFAULT_TEST_ID = \"__default__\";\n"],"mappings":";;;AACA,MAAa,kBAAkB"}
1
+ {"version":3,"file":"constants.cjs","names":[],"sources":["../src/constants.ts"],"sourcesContent":["/** Sentinel testId used when no explicit test scope is provided. */\nexport const DEFAULT_TEST_ID = \"__default__\";\n\n/**\n * Longest text (in characters) an `echo` of one value produces in an error\n * message, a bad-block detail or a `firstDifference` side. A longer value is\n * cut so that the kept part, `…` and the count of chars left out together\n * fit in this limit. Exported from mcp-fakes.ts as well.\n */\nexport const MCP_FAKES_ECHO_LIMIT = 200;\n"],"mappings":";;;AACA,MAAa,kBAAkB;;;;;;;AAQ/B,MAAa,uBAAuB"}
@@ -1,8 +1,12 @@
1
1
  //#region src/constants.d.ts
2
2
  /** Sentinel testId used when no explicit test scope is provided. */
3
3
  declare const DEFAULT_TEST_ID = "__default__";
4
- //# sourceMappingURL=constants.d.ts.map
5
-
4
+ /**
5
+ * Longest text (in characters) an `echo` of one value produces in an error
6
+ * message, a bad-block detail or a `firstDifference` side. A longer value is
7
+ * cut so that the kept part, `…` and the count of chars left out together
8
+ * fit in this limit. Exported from mcp-fakes.ts as well.
9
+ */
6
10
  //#endregion
7
11
  export { DEFAULT_TEST_ID };
8
12
  //# sourceMappingURL=constants.d.cts.map
@@ -1,8 +1,12 @@
1
1
  //#region src/constants.d.ts
2
2
  /** Sentinel testId used when no explicit test scope is provided. */
3
3
  declare const DEFAULT_TEST_ID = "__default__";
4
- //# sourceMappingURL=constants.d.ts.map
5
-
4
+ /**
5
+ * Longest text (in characters) an `echo` of one value produces in an error
6
+ * message, a bad-block detail or a `firstDifference` side. A longer value is
7
+ * cut so that the kept part, `…` and the count of chars left out together
8
+ * fit in this limit. Exported from mcp-fakes.ts as well.
9
+ */
6
10
  //#endregion
7
11
  export { DEFAULT_TEST_ID };
8
12
  //# sourceMappingURL=constants.d.ts.map
package/dist/constants.js CHANGED
@@ -1,7 +1,14 @@
1
1
  //#region src/constants.ts
2
2
  /** Sentinel testId used when no explicit test scope is provided. */
3
3
  const DEFAULT_TEST_ID = "__default__";
4
+ /**
5
+ * Longest text (in characters) an `echo` of one value produces in an error
6
+ * message, a bad-block detail or a `firstDifference` side. A longer value is
7
+ * cut so that the kept part, `…` and the count of chars left out together
8
+ * fit in this limit. Exported from mcp-fakes.ts as well.
9
+ */
10
+ const MCP_FAKES_ECHO_LIMIT = 200;
4
11
 
5
12
  //#endregion
6
- export { DEFAULT_TEST_ID };
13
+ export { DEFAULT_TEST_ID, MCP_FAKES_ECHO_LIMIT };
7
14
  //# sourceMappingURL=constants.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","names":[],"sources":["../src/constants.ts"],"sourcesContent":["/** Sentinel testId used when no explicit test scope is provided. */\nexport const DEFAULT_TEST_ID = \"__default__\";\n"],"mappings":";;AACA,MAAa,kBAAkB"}
1
+ {"version":3,"file":"constants.js","names":[],"sources":["../src/constants.ts"],"sourcesContent":["/** Sentinel testId used when no explicit test scope is provided. */\nexport const DEFAULT_TEST_ID = \"__default__\";\n\n/**\n * Longest text (in characters) an `echo` of one value produces in an error\n * message, a bad-block detail or a `firstDifference` side. A longer value is\n * cut so that the kept part, `…` and the count of chars left out together\n * fit in this limit. Exported from mcp-fakes.ts as well.\n */\nexport const MCP_FAKES_ECHO_LIMIT = 200;\n"],"mappings":";;AACA,MAAa,kBAAkB;;;;;;;AAQ/B,MAAa,uBAAuB"}
@@ -0,0 +1,65 @@
1
+ const require_constants = require('./constants.cjs');
2
+
3
+ //#region src/echo-text.ts
4
+ /**
5
+ * The one cut rule for text that MCP fakes put into a message. The message
6
+ * builder (`build` in message-text.ts) cuts every user part of a message with
7
+ * it, once; helpers.ts also cuts the `MCP_INVALID_UNDECLARED` `raw` field
8
+ * with it. A leaf module (it imports only constants.ts), so every one of
9
+ * those can use it without an import cycle.
10
+ */
11
+ /**
12
+ * `text` cut to at most `limit` chars in all: the kept part, then `…` and the
13
+ * count of chars left out. When even that count does not fit in `limit`, the
14
+ * kept part is followed by a bare `…`. A `limit` below 1 has no room even for
15
+ * that `…`, so a text longer than it is cut to `""`; the message builder
16
+ * never asks for that (each of its user parts gets at least 1 char). The
17
+ * kept part is a prefix of `text` that never ends inside a unit of `units`. A
18
+ * `limit` that is not an integer is rounded down, a negative one
19
+ * (`-Infinity` too) is 0, and a `NaN` limit is a `RangeError`.
20
+ *
21
+ * @internal
22
+ */
23
+ function cutText(text, limit = require_constants.MCP_FAKES_ECHO_LIMIT, units = "text") {
24
+ if (Number.isNaN(limit)) throw new RangeError("cutText limit must be a number, got NaN");
25
+ limit = Math.max(0, Math.floor(limit));
26
+ if (text.length <= limit) return text;
27
+ let keep = limit;
28
+ let suffix = "";
29
+ for (;;) {
30
+ suffix = `… (${text.length - keep} more chars)`;
31
+ const room = Math.max(0, limit - suffix.length);
32
+ if (keep <= room) break;
33
+ keep = room;
34
+ }
35
+ if (keep + suffix.length > limit) return limit < 1 ? "" : text.slice(0, safeEnd(text, limit - 1, units)) + "…";
36
+ keep = safeEnd(text, keep, units);
37
+ return text.slice(0, keep) + `… (${text.length - keep} more chars)`;
38
+ }
39
+ /** `keep`, moved back to a point that does not split a unit of `units`. */
40
+ function safeEnd(text, keep, units) {
41
+ return surrogateSafeEnd(text, units === "json" ? escapeSafeEnd(text, keep) : keep);
42
+ }
43
+ /** `keep`, or one less when the char before it is a high surrogate. */
44
+ function surrogateSafeEnd(text, keep) {
45
+ if (keep <= 0) return keep;
46
+ const code = text.charCodeAt(keep - 1);
47
+ return code >= 55296 && code <= 56319 ? keep - 1 : keep;
48
+ }
49
+ /**
50
+ * `keep`, or the start of the JSON escape (`\x` or `\uXXXX`) that a cut at
51
+ * `keep` would split.
52
+ */
53
+ function escapeSafeEnd(text, keep) {
54
+ for (let i = 0; i < keep; i++) {
55
+ if (text.charCodeAt(i) !== 92) continue;
56
+ const end = i + (text[i + 1] === "u" ? 6 : 2);
57
+ if (end > keep) return i;
58
+ i = end - 1;
59
+ }
60
+ return keep;
61
+ }
62
+
63
+ //#endregion
64
+ exports.cutText = cutText;
65
+ //# sourceMappingURL=echo-text.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"echo-text.cjs","names":["MCP_FAKES_ECHO_LIMIT"],"sources":["../src/echo-text.ts"],"sourcesContent":["/**\n * The one cut rule for text that MCP fakes put into a message. The message\n * builder (`build` in message-text.ts) cuts every user part of a message with\n * it, once; helpers.ts also cuts the `MCP_INVALID_UNDECLARED` `raw` field\n * with it. A leaf module (it imports only constants.ts), so every one of\n * those can use it without an import cycle.\n */\nimport { MCP_FAKES_ECHO_LIMIT } from \"./constants.js\";\n\n/**\n * What a cut must not split. `\"text\"`: a surrogate pair. `\"json\"`: a\n * surrogate pair or a JSON escape (`\\x` or `\\uXXXX`); for JSON text, where a\n * backslash only ever starts an escape.\n */\nexport type CutUnits = \"text\" | \"json\";\n\n/**\n * `text` cut to at most `limit` chars in all: the kept part, then `…` and the\n * count of chars left out. When even that count does not fit in `limit`, the\n * kept part is followed by a bare `…`. A `limit` below 1 has no room even for\n * that `…`, so a text longer than it is cut to `\"\"`; the message builder\n * never asks for that (each of its user parts gets at least 1 char). The\n * kept part is a prefix of `text` that never ends inside a unit of `units`. A\n * `limit` that is not an integer is rounded down, a negative one\n * (`-Infinity` too) is 0, and a `NaN` limit is a `RangeError`.\n *\n * @internal\n */\nexport function cutText(\n text: string,\n limit: number = MCP_FAKES_ECHO_LIMIT,\n units: CutUnits = \"text\",\n): string {\n if (Number.isNaN(limit)) throw new RangeError(\"cutText limit must be a number, got NaN\");\n // A negative limit (`-Infinity` included) keeps nothing, as a limit of 0 does.\n limit = Math.max(0, Math.floor(limit));\n if (text.length <= limit) return text;\n let keep = limit;\n let suffix = \"\";\n for (;;) {\n suffix = `… (${text.length - keep} more chars)`;\n const room = Math.max(0, limit - suffix.length);\n if (keep <= room) break;\n keep = room;\n }\n if (keep + suffix.length > limit) {\n // Not even the count fits: keep what fits before a bare `…`.\n return limit < 1 ? \"\" : text.slice(0, safeEnd(text, limit - 1, units)) + \"…\";\n }\n // Moving the cut back by d chars grows the count by d, which adds at most\n // one digit to the suffix, so the total stays within `limit`.\n keep = safeEnd(text, keep, units);\n return text.slice(0, keep) + `… (${text.length - keep} more chars)`;\n}\n\n/** `keep`, moved back to a point that does not split a unit of `units`. */\nfunction safeEnd(text: string, keep: number, units: CutUnits): number {\n return surrogateSafeEnd(text, units === \"json\" ? escapeSafeEnd(text, keep) : keep);\n}\n\n/** `keep`, or one less when the char before it is a high surrogate. */\nfunction surrogateSafeEnd(text: string, keep: number): number {\n if (keep <= 0) return keep;\n const code = text.charCodeAt(keep - 1);\n return code >= 0xd800 && code <= 0xdbff ? keep - 1 : keep;\n}\n\n/**\n * `keep`, or the start of the JSON escape (`\\x` or `\\uXXXX`) that a cut at\n * `keep` would split.\n */\nfunction escapeSafeEnd(text: string, keep: number): number {\n for (let i = 0; i < keep; i++) {\n if (text.charCodeAt(i) !== 0x5c) continue;\n const end = i + (text[i + 1] === \"u\" ? 6 : 2);\n if (end > keep) return i;\n i = end - 1;\n }\n return keep;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,QACd,MACA,QAAgBA,wCAChB,QAAkB,QACV;AACR,KAAI,OAAO,MAAM,MAAM,CAAE,OAAM,IAAI,WAAW,0CAA0C;AAExF,SAAQ,KAAK,IAAI,GAAG,KAAK,MAAM,MAAM,CAAC;AACtC,KAAI,KAAK,UAAU,MAAO,QAAO;CACjC,IAAI,OAAO;CACX,IAAI,SAAS;AACb,UAAS;AACP,WAAS,MAAM,KAAK,SAAS,KAAK;EAClC,MAAM,OAAO,KAAK,IAAI,GAAG,QAAQ,OAAO,OAAO;AAC/C,MAAI,QAAQ,KAAM;AAClB,SAAO;;AAET,KAAI,OAAO,OAAO,SAAS,MAEzB,QAAO,QAAQ,IAAI,KAAK,KAAK,MAAM,GAAG,QAAQ,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG;AAI3E,QAAO,QAAQ,MAAM,MAAM,MAAM;AACjC,QAAO,KAAK,MAAM,GAAG,KAAK,GAAG,MAAM,KAAK,SAAS,KAAK;;;AAIxD,SAAS,QAAQ,MAAc,MAAc,OAAyB;AACpE,QAAO,iBAAiB,MAAM,UAAU,SAAS,cAAc,MAAM,KAAK,GAAG,KAAK;;;AAIpF,SAAS,iBAAiB,MAAc,MAAsB;AAC5D,KAAI,QAAQ,EAAG,QAAO;CACtB,MAAM,OAAO,KAAK,WAAW,OAAO,EAAE;AACtC,QAAO,QAAQ,SAAU,QAAQ,QAAS,OAAO,IAAI;;;;;;AAOvD,SAAS,cAAc,MAAc,MAAsB;AACzD,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,KAAK;AAC7B,MAAI,KAAK,WAAW,EAAE,KAAK,GAAM;EACjC,MAAM,MAAM,KAAK,KAAK,IAAI,OAAO,MAAM,IAAI;AAC3C,MAAI,MAAM,KAAM,QAAO;AACvB,MAAI,MAAM;;AAEZ,QAAO"}
@@ -0,0 +1,22 @@
1
+ //#region src/echo-text.d.ts
2
+ /**
3
+ * What a cut must not split. `"text"`: a surrogate pair. `"json"`: a
4
+ * surrogate pair or a JSON escape (`\x` or `\uXXXX`); for JSON text, where a
5
+ * backslash only ever starts an escape.
6
+ */
7
+ type CutUnits = "text" | "json";
8
+ /**
9
+ * `text` cut to at most `limit` chars in all: the kept part, then `…` and the
10
+ * count of chars left out. When even that count does not fit in `limit`, the
11
+ * kept part is followed by a bare `…`. A `limit` below 1 has no room even for
12
+ * that `…`, so a text longer than it is cut to `""`; the message builder
13
+ * never asks for that (each of its user parts gets at least 1 char). The
14
+ * kept part is a prefix of `text` that never ends inside a unit of `units`. A
15
+ * `limit` that is not an integer is rounded down, a negative one
16
+ * (`-Infinity` too) is 0, and a `NaN` limit is a `RangeError`.
17
+ *
18
+ * @internal
19
+ */
20
+ //#endregion
21
+ export { CutUnits };
22
+ //# sourceMappingURL=echo-text.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"echo-text.d.cts","names":[],"sources":["../src/echo-text.ts"],"sourcesContent":[],"mappings":";;AAcA;;;;KAAY,QAAA"}
@@ -0,0 +1,22 @@
1
+ //#region src/echo-text.d.ts
2
+ /**
3
+ * What a cut must not split. `"text"`: a surrogate pair. `"json"`: a
4
+ * surrogate pair or a JSON escape (`\x` or `\uXXXX`); for JSON text, where a
5
+ * backslash only ever starts an escape.
6
+ */
7
+ type CutUnits = "text" | "json";
8
+ /**
9
+ * `text` cut to at most `limit` chars in all: the kept part, then `…` and the
10
+ * count of chars left out. When even that count does not fit in `limit`, the
11
+ * kept part is followed by a bare `…`. A `limit` below 1 has no room even for
12
+ * that `…`, so a text longer than it is cut to `""`; the message builder
13
+ * never asks for that (each of its user parts gets at least 1 char). The
14
+ * kept part is a prefix of `text` that never ends inside a unit of `units`. A
15
+ * `limit` that is not an integer is rounded down, a negative one
16
+ * (`-Infinity` too) is 0, and a `NaN` limit is a `RangeError`.
17
+ *
18
+ * @internal
19
+ */
20
+ //#endregion
21
+ export { CutUnits };
22
+ //# sourceMappingURL=echo-text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"echo-text.d.ts","names":[],"sources":["../src/echo-text.ts"],"sourcesContent":[],"mappings":";;AAcA;;;;KAAY,QAAA"}