@copilotkit/aimock 1.44.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 (272) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +31 -0
  4. package/README.md +6 -0
  5. package/dist/agui-types.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 +312 -39
  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 +313 -40
  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 +1 -0
  25. package/dist/chaos.js +1 -1
  26. package/dist/cli.cjs +152 -5
  27. package/dist/cli.cjs.map +1 -1
  28. package/dist/cli.js +155 -8
  29. package/dist/cli.js.map +1 -1
  30. package/dist/cohere.cjs +132 -71
  31. package/dist/cohere.cjs.map +1 -1
  32. package/dist/cohere.d.cts.map +1 -1
  33. package/dist/cohere.d.ts.map +1 -1
  34. package/dist/cohere.js +133 -72
  35. package/dist/cohere.js.map +1 -1
  36. package/dist/config-loader.cjs +62 -4
  37. package/dist/config-loader.cjs.map +1 -1
  38. package/dist/config-loader.d.cts +37 -4
  39. package/dist/config-loader.d.cts.map +1 -1
  40. package/dist/config-loader.d.ts +37 -4
  41. package/dist/config-loader.d.ts.map +1 -1
  42. package/dist/config-loader.js +62 -4
  43. package/dist/config-loader.js.map +1 -1
  44. package/dist/fakes-plugin.cjs +131 -0
  45. package/dist/fakes-plugin.cjs.map +1 -0
  46. package/dist/fakes-plugin.d.cts +23 -0
  47. package/dist/fakes-plugin.d.cts.map +1 -0
  48. package/dist/fakes-plugin.d.ts +23 -0
  49. package/dist/fakes-plugin.d.ts.map +1 -0
  50. package/dist/fakes-plugin.js +127 -0
  51. package/dist/fakes-plugin.js.map +1 -0
  52. package/dist/fixture-loader-services.cjs +1 -1
  53. package/dist/fixture-loader-services.cjs.map +1 -1
  54. package/dist/fixture-loader-services.js +1 -1
  55. package/dist/fixture-loader-services.js.map +1 -1
  56. package/dist/fixture-loader.cjs +389 -27
  57. package/dist/fixture-loader.cjs.map +1 -1
  58. package/dist/fixture-loader.d.cts +47 -3
  59. package/dist/fixture-loader.d.cts.map +1 -1
  60. package/dist/fixture-loader.d.ts +47 -3
  61. package/dist/fixture-loader.d.ts.map +1 -1
  62. package/dist/fixture-loader.js +384 -29
  63. package/dist/fixture-loader.js.map +1 -1
  64. package/dist/gemini-interactions.cjs +250 -52
  65. package/dist/gemini-interactions.cjs.map +1 -1
  66. package/dist/gemini-interactions.d.cts.map +1 -1
  67. package/dist/gemini-interactions.d.ts.map +1 -1
  68. package/dist/gemini-interactions.js +251 -53
  69. package/dist/gemini-interactions.js.map +1 -1
  70. package/dist/gemini.cjs +226 -37
  71. package/dist/gemini.cjs.map +1 -1
  72. package/dist/gemini.d.cts.map +1 -1
  73. package/dist/gemini.d.ts.map +1 -1
  74. package/dist/gemini.js +227 -38
  75. package/dist/gemini.js.map +1 -1
  76. package/dist/helpers.cjs +881 -7
  77. package/dist/helpers.cjs.map +1 -1
  78. package/dist/helpers.d.cts +85 -4
  79. package/dist/helpers.d.cts.map +1 -1
  80. package/dist/helpers.d.ts +85 -4
  81. package/dist/helpers.d.ts.map +1 -1
  82. package/dist/helpers.js +839 -8
  83. package/dist/helpers.js.map +1 -1
  84. package/dist/index.cjs +9 -0
  85. package/dist/index.d.cts +7 -5
  86. package/dist/index.d.ts +7 -5
  87. package/dist/index.js +4 -3
  88. package/dist/jest.cjs +31 -7
  89. package/dist/jest.cjs.map +1 -1
  90. package/dist/jest.d.cts +43 -3
  91. package/dist/jest.d.cts.map +1 -1
  92. package/dist/jest.d.ts +43 -3
  93. package/dist/jest.d.ts.map +1 -1
  94. package/dist/jest.js +33 -9
  95. package/dist/jest.js.map +1 -1
  96. package/dist/journal.cjs +135 -13
  97. package/dist/journal.cjs.map +1 -1
  98. package/dist/journal.d.cts +38 -4
  99. package/dist/journal.d.cts.map +1 -1
  100. package/dist/journal.d.ts +38 -4
  101. package/dist/journal.d.ts.map +1 -1
  102. package/dist/journal.js +135 -13
  103. package/dist/journal.js.map +1 -1
  104. package/dist/live-sanitize.cjs +4 -32
  105. package/dist/live-sanitize.cjs.map +1 -1
  106. package/dist/live-sanitize.js +4 -32
  107. package/dist/live-sanitize.js.map +1 -1
  108. package/dist/llmock.cjs +123 -30
  109. package/dist/llmock.cjs.map +1 -1
  110. package/dist/llmock.d.cts +16 -1
  111. package/dist/llmock.d.cts.map +1 -1
  112. package/dist/llmock.d.ts +16 -1
  113. package/dist/llmock.d.ts.map +1 -1
  114. package/dist/llmock.js +125 -32
  115. package/dist/llmock.js.map +1 -1
  116. package/dist/mcp-fakes-mount.cjs +1 -0
  117. package/dist/mcp-fakes-mount.cjs.map +1 -1
  118. package/dist/mcp-fakes-mount.js +1 -0
  119. package/dist/mcp-fakes-mount.js.map +1 -1
  120. package/dist/mcp-fakes-report.cjs +111 -0
  121. package/dist/mcp-fakes-report.cjs.map +1 -0
  122. package/dist/mcp-fakes-report.d.cts +40 -0
  123. package/dist/mcp-fakes-report.d.cts.map +1 -0
  124. package/dist/mcp-fakes-report.d.ts +40 -0
  125. package/dist/mcp-fakes-report.d.ts.map +1 -0
  126. package/dist/mcp-fakes-report.js +107 -0
  127. package/dist/mcp-fakes-report.js.map +1 -0
  128. package/dist/mcp-fakes.cjs +331 -8
  129. package/dist/mcp-fakes.cjs.map +1 -1
  130. package/dist/mcp-fakes.d.cts +406 -3
  131. package/dist/mcp-fakes.d.cts.map +1 -1
  132. package/dist/mcp-fakes.d.ts +406 -3
  133. package/dist/mcp-fakes.d.ts.map +1 -1
  134. package/dist/mcp-fakes.js +331 -8
  135. package/dist/mcp-fakes.js.map +1 -1
  136. package/dist/mcp-handler.cjs +209 -16
  137. package/dist/mcp-handler.cjs.map +1 -1
  138. package/dist/mcp-handler.d.cts +49 -0
  139. package/dist/mcp-handler.d.cts.map +1 -0
  140. package/dist/mcp-handler.d.ts +49 -0
  141. package/dist/mcp-handler.d.ts.map +1 -0
  142. package/dist/mcp-handler.js +207 -17
  143. package/dist/mcp-handler.js.map +1 -1
  144. package/dist/mcp-mock.cjs +112 -1
  145. package/dist/mcp-mock.cjs.map +1 -1
  146. package/dist/mcp-mock.d.cts +33 -2
  147. package/dist/mcp-mock.d.cts.map +1 -1
  148. package/dist/mcp-mock.d.ts +33 -2
  149. package/dist/mcp-mock.d.ts.map +1 -1
  150. package/dist/mcp-mock.js +112 -1
  151. package/dist/mcp-mock.js.map +1 -1
  152. package/dist/mcp-recorder.cjs +1224 -0
  153. package/dist/mcp-recorder.cjs.map +1 -0
  154. package/dist/mcp-recorder.d.cts +108 -0
  155. package/dist/mcp-recorder.d.cts.map +1 -0
  156. package/dist/mcp-recorder.d.ts +108 -0
  157. package/dist/mcp-recorder.d.ts.map +1 -0
  158. package/dist/mcp-recorder.js +1215 -0
  159. package/dist/mcp-recorder.js.map +1 -0
  160. package/dist/messages.cjs +362 -56
  161. package/dist/messages.cjs.map +1 -1
  162. package/dist/messages.d.cts.map +1 -1
  163. package/dist/messages.d.ts.map +1 -1
  164. package/dist/messages.js +363 -58
  165. package/dist/messages.js.map +1 -1
  166. package/dist/misbehavior.cjs +1523 -0
  167. package/dist/misbehavior.cjs.map +1 -0
  168. package/dist/misbehavior.d.cts +43 -0
  169. package/dist/misbehavior.d.cts.map +1 -0
  170. package/dist/misbehavior.d.ts +43 -0
  171. package/dist/misbehavior.d.ts.map +1 -0
  172. package/dist/misbehavior.js +1511 -0
  173. package/dist/misbehavior.js.map +1 -0
  174. package/dist/ollama.cjs +310 -112
  175. package/dist/ollama.cjs.map +1 -1
  176. package/dist/ollama.d.cts.map +1 -1
  177. package/dist/ollama.d.ts.map +1 -1
  178. package/dist/ollama.js +311 -113
  179. package/dist/ollama.js.map +1 -1
  180. package/dist/record-sanitize.cjs +231 -0
  181. package/dist/record-sanitize.cjs.map +1 -0
  182. package/dist/record-sanitize.js +224 -0
  183. package/dist/record-sanitize.js.map +1 -0
  184. package/dist/recorder.cjs +106 -5
  185. package/dist/recorder.cjs.map +1 -1
  186. package/dist/recorder.d.cts +2 -1
  187. package/dist/recorder.d.cts.map +1 -1
  188. package/dist/recorder.d.ts +2 -1
  189. package/dist/recorder.d.ts.map +1 -1
  190. package/dist/recorder.js +107 -7
  191. package/dist/recorder.js.map +1 -1
  192. package/dist/responses.cjs +667 -171
  193. package/dist/responses.cjs.map +1 -1
  194. package/dist/responses.d.cts +5 -13
  195. package/dist/responses.d.cts.map +1 -1
  196. package/dist/responses.d.ts +5 -13
  197. package/dist/responses.d.ts.map +1 -1
  198. package/dist/responses.js +665 -174
  199. package/dist/responses.js.map +1 -1
  200. package/dist/router.cjs +24 -2
  201. package/dist/router.cjs.map +1 -1
  202. package/dist/router.d.cts.map +1 -1
  203. package/dist/router.d.ts.map +1 -1
  204. package/dist/router.js +25 -4
  205. package/dist/router.js.map +1 -1
  206. package/dist/server.cjs +572 -76
  207. package/dist/server.cjs.map +1 -1
  208. package/dist/server.d.cts.map +1 -1
  209. package/dist/server.d.ts.map +1 -1
  210. package/dist/server.js +574 -78
  211. package/dist/server.js.map +1 -1
  212. package/dist/stream-collapse.cjs +236 -34
  213. package/dist/stream-collapse.cjs.map +1 -1
  214. package/dist/stream-collapse.d.cts +39 -4
  215. package/dist/stream-collapse.d.cts.map +1 -1
  216. package/dist/stream-collapse.d.ts +39 -4
  217. package/dist/stream-collapse.d.ts.map +1 -1
  218. package/dist/stream-collapse.js +235 -35
  219. package/dist/stream-collapse.js.map +1 -1
  220. package/dist/types.cjs.map +1 -1
  221. package/dist/types.d.cts +315 -4
  222. package/dist/types.d.cts.map +1 -1
  223. package/dist/types.d.ts +315 -4
  224. package/dist/types.d.ts.map +1 -1
  225. package/dist/types.js.map +1 -1
  226. package/dist/validate-cli.cjs +36 -4
  227. package/dist/validate-cli.cjs.map +1 -1
  228. package/dist/validate-cli.js +37 -5
  229. package/dist/validate-cli.js.map +1 -1
  230. package/dist/vector-types.d.ts.map +1 -1
  231. package/dist/version.cjs +45 -0
  232. package/dist/version.cjs.map +1 -0
  233. package/dist/version.js +44 -0
  234. package/dist/version.js.map +1 -0
  235. package/dist/vitest.cjs +66 -10
  236. package/dist/vitest.cjs.map +1 -1
  237. package/dist/vitest.d.cts +35 -4
  238. package/dist/vitest.d.cts.map +1 -1
  239. package/dist/vitest.d.ts +35 -4
  240. package/dist/vitest.d.ts.map +1 -1
  241. package/dist/vitest.js +67 -11
  242. package/dist/vitest.js.map +1 -1
  243. package/dist/watcher.cjs +6 -2
  244. package/dist/watcher.cjs.map +1 -1
  245. package/dist/watcher.js +6 -2
  246. package/dist/watcher.js.map +1 -1
  247. package/dist/ws-gemini-live.cjs +210 -101
  248. package/dist/ws-gemini-live.cjs.map +1 -1
  249. package/dist/ws-gemini-live.d.cts +9 -3
  250. package/dist/ws-gemini-live.d.cts.map +1 -1
  251. package/dist/ws-gemini-live.d.ts +9 -3
  252. package/dist/ws-gemini-live.d.ts.map +1 -1
  253. package/dist/ws-gemini-live.js +211 -102
  254. package/dist/ws-gemini-live.js.map +1 -1
  255. package/dist/ws-realtime.cjs +175 -62
  256. package/dist/ws-realtime.cjs.map +1 -1
  257. package/dist/ws-realtime.d.cts +8 -3
  258. package/dist/ws-realtime.d.cts.map +1 -1
  259. package/dist/ws-realtime.d.ts +8 -3
  260. package/dist/ws-realtime.d.ts.map +1 -1
  261. package/dist/ws-realtime.js +176 -63
  262. package/dist/ws-realtime.js.map +1 -1
  263. package/dist/ws-responses.cjs +109 -16
  264. package/dist/ws-responses.cjs.map +1 -1
  265. package/dist/ws-responses.d.cts +8 -12
  266. package/dist/ws-responses.d.cts.map +1 -1
  267. package/dist/ws-responses.d.ts +8 -12
  268. package/dist/ws-responses.d.ts.map +1 -1
  269. package/dist/ws-responses.js +111 -18
  270. package/dist/ws-responses.js.map +1 -1
  271. package/package.json +10 -2
  272. package/skills/write-fixtures/SKILL.md +179 -21
@@ -1 +1 @@
1
- {"version":3,"file":"llmock.d.ts","names":[],"sources":["../src/llmock.ts"],"sourcesContent":[],"mappings":"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"}
1
+ {"version":3,"file":"llmock.d.ts","names":[],"sources":["../src/llmock.ts"],"sourcesContent":[],"mappings":"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"}
package/dist/llmock.js CHANGED
@@ -1,9 +1,12 @@
1
+ import { DEFAULT_TEST_ID } from "./constants.js";
1
2
  import { build, msg, quote } from "./message-text.js";
2
- import { isLiveResponse } from "./helpers.js";
3
+ import { isLiveResponse, markServerOwnedFixtures } from "./helpers.js";
3
4
  import { normalizeLiveFixture, normalizeLiveOptions } from "./live-fixture.js";
4
- import { INJECTED_STATUS_RANGE, clearFixtureQueue, entryToFixture, isInjectableStatus, normalizeResponse, queueOneShotError, validateFixtures } from "./fixture-loader.js";
5
+ import { getFixtureMisbehaviorPosition, parseMisbehavior, resolveMisbehaviorSeed, setFixtureMisbehaviorPosition, validateFixtureMisbehavior } from "./misbehavior.js";
6
+ import { INJECTED_STATUS_RANGE, MisbehaviorConfigError, clearFixtureQueue, enableHeldFixtureMisbehavior, entryToFixture, isInjectableStatus, markFixtureMisbehaviorEnabled, markFixtureResponsesToolsExtended, normalizeResponse, normalizeResponsesToolsKeys, queueOneShotError, validateFixtures } from "./fixture-loader.js";
5
7
  import { imageResponseToFalJson, videoResponseToFalJson } from "./fal.js";
6
8
  import { voiceDesignToJson } from "./elevenlabs-voice.js";
9
+ import { MCPMock } from "./mcp-mock.js";
7
10
  import { ensureFakeMount, isAutoMount, planFileFakes } from "./mcp-fakes-mount.js";
8
11
  import { createServer, createServerWithResolvedAuth, performFullReset } from "./server.js";
9
12
  import { loadFixtureFileWithServices, loadFixturesFromDirWithServices } from "./fixture-loader-services.js";
@@ -11,6 +14,13 @@ import { loadFixtureFileWithServices, loadFixturesFromDirWithServices } from "./
11
14
  //#region src/llmock.ts
12
15
  var LLMock = class LLMock {
13
16
  fixtures = [];
17
+ fixtureAddition = 0;
18
+ fixtureCountOrigins = /* @__PURE__ */ new WeakMap();
19
+ /**
20
+ * Fixtures this LLMock built itself (on() and the other helpers), so the
21
+ * caller never held them: the stored object is their identity.
22
+ */
23
+ ownFixtures = /* @__PURE__ */ new WeakSet();
14
24
  searchFixtures = [];
15
25
  rerankFixtures = [];
16
26
  moderationFixtures = [];
@@ -22,16 +32,46 @@ var LLMock = class LLMock {
22
32
  options;
23
33
  resolvedInboundAuth;
24
34
  constructor(options, resolvedInboundAuth) {
35
+ markServerOwnedFixtures(this.fixtures);
25
36
  this.options = options ?? {};
26
37
  if (this.options.live !== void 0) normalizeLiveOptions(this.options.live);
38
+ if (this.misbehaviorEnabled && this.options.misbehavior !== void 0) this.setMisbehavior(this.options.misbehavior);
27
39
  this.resolvedInboundAuth = resolvedInboundAuth;
28
40
  }
41
+ /** Misbehavior (fixture keys, header, `misbehavior` option) needs `enableMisbehavior: true`. */
42
+ get misbehaviorEnabled() {
43
+ return this.options.enableMisbehavior === true;
44
+ }
29
45
  normalizeFixture(fixture) {
30
- if (isLiveResponse(fixture.response) || fixture.match.endpoint === "openai-live" && typeof fixture.response !== "function") return {
46
+ const misbehaviorEnabled = this.misbehaviorEnabled;
47
+ if (misbehaviorEnabled) enableHeldFixtureMisbehavior(fixture);
48
+ const own = this.ownFixtures.delete(fixture);
49
+ const countIdentity = own ? void 0 : this.fixtureCountOrigins.get(fixture) ?? fixture;
50
+ const previousPosition = getFixtureMisbehaviorPosition(fixture);
51
+ const isNewAddition = previousPosition === void 0 || /^code#\d+$/.test(previousPosition);
52
+ const position = isNewAddition ? `code#${this.fixtureAddition++}` : previousPosition;
53
+ if (isNewAddition && misbehaviorEnabled && !own) fixture = { ...fixture };
54
+ if (misbehaviorEnabled && fixture.misbehavior !== void 0) {
55
+ const parsed = parseMisbehavior(fixture.misbehavior);
56
+ if (parsed.ok) fixture = {
57
+ ...fixture,
58
+ misbehavior: parsed.config
59
+ };
60
+ const issue = parsed.ok ? validateFixtureMisbehavior(fixture) : parsed.issue;
61
+ if (issue) throw new MisbehaviorConfigError({
62
+ rule: issue.rule,
63
+ file: position,
64
+ detail: issue.message
65
+ });
66
+ }
67
+ if (isLiveResponse(fixture.response) || fixture.match.endpoint === "openai-live" && typeof fixture.response !== "function") fixture = {
31
68
  ...fixture,
32
69
  match: { ...fixture.match },
33
70
  response: normalizeLiveFixture(fixture.response, this.options.live)
34
71
  };
72
+ else if (countIdentity !== void 0 && fixture !== countIdentity) this.fixtureCountOrigins.set(fixture, countIdentity);
73
+ setFixtureMisbehaviorPosition(fixture, position);
74
+ if (misbehaviorEnabled && fixture.misbehavior !== void 0) markFixtureMisbehaviorEnabled(fixture);
35
75
  return fixture;
36
76
  }
37
77
  addFixture(fixture) {
@@ -46,6 +86,11 @@ var LLMock = class LLMock {
46
86
  this.fixtures.unshift(this.normalizeFixture(fixture));
47
87
  return this;
48
88
  }
89
+ /** Mark a fixture this LLMock built, for {@link normalizeFixture}. */
90
+ own(fixture) {
91
+ this.ownFixtures.add(fixture);
92
+ return fixture;
93
+ }
49
94
  getFixtures() {
50
95
  return this.fixtures;
51
96
  }
@@ -71,6 +116,8 @@ var LLMock = class LLMock {
71
116
  * still make `start()` reject.
72
117
  */
73
118
  acceptLoaded(loaded) {
119
+ if (this.misbehaviorEnabled) loaded.fixtures.forEach(enableHeldFixtureMisbehavior);
120
+ if (this.options.responsesTools === "extended") loaded.fixtures.forEach(markFixtureResponsesToolsExtended);
74
121
  if (loaded.mcpFakes.length > 0) if (this.serverInstance) this.addFakesAfterStart(this.serverInstance, loaded.mcpFakes);
75
122
  else {
76
123
  planFileFakes(this.mounts, [...this.mcpFakeBuffer, ...loaded.mcpFakes], new Set(loaded.mcpFakes));
@@ -100,7 +147,8 @@ var LLMock = class LLMock {
100
147
  const target = ensureFakeMount(this.mounts, path, {
101
148
  journal: server.journal,
102
149
  ...registry ? { registry } : {},
103
- logger
150
+ logger,
151
+ replaySpeed: server.defaults.replaySpeed
104
152
  }, logger);
105
153
  if ("conflict" in target) continue;
106
154
  const result = target.handler.addMcpFakes?.(group, { kind: "file" });
@@ -125,7 +173,17 @@ var LLMock = class LLMock {
125
173
  }
126
174
  else entries = input;
127
175
  if (!Array.isArray(entries)) throw new TypeError("addFixturesFromJSON: expected an array of fixture entries; use loadFixtureFile for a {fixtures:[...]} file");
128
- const converted = entries.map((e) => entryToFixture(e, void 0, this.options.live));
176
+ const converted = entries.map((entry) => {
177
+ const position = `code#${this.fixtureAddition++}`;
178
+ const fixture = entryToFixture(entry, void 0, this.options.live, {
179
+ file: position,
180
+ index: 0
181
+ });
182
+ setFixtureMisbehaviorPosition(fixture, position);
183
+ if (this.misbehaviorEnabled) enableHeldFixtureMisbehavior(fixture);
184
+ if (this.options.responsesTools === "extended") markFixtureResponsesToolsExtended(fixture);
185
+ return fixture;
186
+ });
129
187
  const errors = validateFixtures(converted, this.options.live).filter((i) => i.severity === "error");
130
188
  if (errors.length > 0) throw new Error(`Fixture validation failed: ${JSON.stringify(errors)}`);
131
189
  this.fixtures.push(...converted);
@@ -133,15 +191,21 @@ var LLMock = class LLMock {
133
191
  }
134
192
  clearFixtures() {
135
193
  clearFixtureQueue(this.fixtures);
194
+ this.serverInstance?.journal.clearMisbehaviorCounters();
136
195
  this.unloadMcpFakes();
137
196
  return this;
138
197
  }
139
198
  on(match, response, opts) {
140
- return this.addFixture({
199
+ let normalized = response;
200
+ if (typeof response !== "function") {
201
+ normalized = normalizeResponse(response, this.options.live);
202
+ if (this.options.responsesTools === "extended" && !isLiveResponse(normalized)) normalizeResponsesToolsKeys(normalized);
203
+ }
204
+ return this.addFixture(this.own({
141
205
  match,
142
- response: typeof response === "function" ? response : normalizeResponse(response, this.options.live),
206
+ response: normalized,
143
207
  ...opts
144
- });
208
+ }));
145
209
  }
146
210
  onLive(match, transcript, opts) {
147
211
  return this.on({
@@ -175,99 +239,99 @@ var LLMock = class LLMock {
175
239
  }, response, opts);
176
240
  }
177
241
  onImage(prompt, response) {
178
- return this.addFixture({
242
+ return this.addFixture(this.own({
179
243
  match: {
180
244
  userMessage: prompt,
181
245
  endpoint: "image"
182
246
  },
183
247
  response
184
- });
248
+ }));
185
249
  }
186
250
  onSpeech(input, response) {
187
- return this.addFixture({
251
+ return this.addFixture(this.own({
188
252
  match: {
189
253
  userMessage: input,
190
254
  endpoint: "speech"
191
255
  },
192
256
  response
193
- });
257
+ }));
194
258
  }
195
259
  onTranscription(response) {
196
- return this.addFixture({
260
+ return this.addFixture(this.own({
197
261
  match: { endpoint: "transcription" },
198
262
  response
199
- });
263
+ }));
200
264
  }
201
265
  onTranslation(response) {
202
- return this.addFixture({
266
+ return this.addFixture(this.own({
203
267
  match: { endpoint: "translation" },
204
268
  response
205
- });
269
+ }));
206
270
  }
207
271
  onVideo(prompt, response) {
208
- return this.addFixture({
272
+ return this.addFixture(this.own({
209
273
  match: {
210
274
  userMessage: prompt,
211
275
  endpoint: "video"
212
276
  },
213
277
  response
214
- });
278
+ }));
215
279
  }
216
280
  onAudio(input, response) {
217
- return this.addFixture({
281
+ return this.addFixture(this.own({
218
282
  match: { userMessage: input },
219
283
  response
220
- });
284
+ }));
221
285
  }
222
286
  onSoundEffect(text, response) {
223
- return this.addFixture({
287
+ return this.addFixture(this.own({
224
288
  match: {
225
289
  userMessage: text,
226
290
  endpoint: "audio-gen"
227
291
  },
228
292
  response
229
- });
293
+ }));
230
294
  }
231
295
  onMusic(prompt, response) {
232
- return this.addFixture({
296
+ return this.addFixture(this.own({
233
297
  match: {
234
298
  userMessage: prompt,
235
299
  endpoint: "audio-gen"
236
300
  },
237
301
  response
238
- });
302
+ }));
239
303
  }
240
304
  onElevenLabsTTS(text, response) {
241
- return this.addFixture({
305
+ return this.addFixture(this.own({
242
306
  match: {
243
307
  userMessage: text,
244
308
  endpoint: "elevenlabs-tts"
245
309
  },
246
310
  response
247
- });
311
+ }));
248
312
  }
249
313
  onElevenLabsVoiceDesign(description, response) {
250
- return this.addFixture({
314
+ return this.addFixture(this.own({
251
315
  match: {
252
316
  userMessage: description,
253
317
  endpoint: "elevenlabs-voice-design"
254
318
  },
255
319
  response: voiceDesignToJson(response)
256
- });
320
+ }));
257
321
  }
258
322
  onFalAudio(prompt, response, model) {
259
- return this.addFixture({
323
+ return this.addFixture(this.own({
260
324
  match: {
261
325
  userMessage: prompt,
262
326
  endpoint: "fal-audio",
263
327
  ...model ? { model } : {}
264
328
  },
265
329
  response
266
- });
330
+ }));
267
331
  }
268
332
  onFalQueue(modelOrPrompt, response, opts) {
269
333
  const { billableUnits, ...fixtureOpts } = opts ?? {};
270
- return this.addFixture({
334
+ return this.addFixture(this.own({
271
335
  match: {
272
336
  model: modelOrPrompt,
273
337
  endpoint: "fal"
@@ -277,7 +341,7 @@ var LLMock = class LLMock {
277
341
  ...billableUnits != null ? { billableUnits } : {}
278
342
  },
279
343
  ...fixtureOpts
280
- });
344
+ }));
281
345
  }
282
346
  onFalRun(modelOrPrompt, response, opts) {
283
347
  return this.onFalQueue(modelOrPrompt, response, opts);
@@ -344,6 +408,7 @@ var LLMock = class LLMock {
344
408
  if (registry && handler.setRegistry) handler.setRegistry(registry);
345
409
  const logger = this.serverInstance.defaults.logger;
346
410
  if (handler.setLogger) handler.setLogger(logger);
411
+ if (handler instanceof MCPMock) handler.setReplaySpeed(this.serverInstance.defaults.replaySpeed);
347
412
  if (shadowedBy) logger.warn(build(msg`MCP fakes: the mount at ${quote(path)} is shadowed by the MCP mock auto-mounted at ${quote(shadowedBy.path)}; its requests reach the auto-mount`));
348
413
  }
349
414
  return this;
@@ -392,6 +457,32 @@ var LLMock = class LLMock {
392
457
  if (this.serverInstance) this.serverInstance.defaults.chaos = void 0;
393
458
  return this;
394
459
  }
460
+ /** Replace construction/runtime baselines without changing named test overrides. */
461
+ setMisbehavior(config) {
462
+ if (!this.misbehaviorEnabled) throw new TypeError("setMisbehavior: misbehavior is not enabled; construct with enableMisbehavior: true");
463
+ const parsed = parseMisbehavior(config);
464
+ if (!parsed.ok) throw new TypeError(`${parsed.issue.rule}: ${parsed.issue.message}`);
465
+ this.options.misbehavior = parsed.config;
466
+ const defaults = this.serverInstance?.defaults;
467
+ if (defaults?.misbehavior) {
468
+ defaults.misbehavior.baseline = parsed.config.seed === "random" ? {
469
+ ...parsed.config,
470
+ seed: resolveMisbehaviorSeed(parsed.config.seed, defaults.logger)
471
+ } : parsed.config;
472
+ if (defaults.misbehavior.byTestId.has(DEFAULT_TEST_ID)) defaults.misbehavior.byTestId.set(DEFAULT_TEST_ID, defaults.misbehavior.baseline);
473
+ }
474
+ return this;
475
+ }
476
+ /** Clear the baseline and all runtime overrides, before or after start. */
477
+ clearMisbehavior() {
478
+ delete this.options.misbehavior;
479
+ const scope = this.serverInstance?.defaults.misbehavior;
480
+ if (scope) {
481
+ scope.baseline = void 0;
482
+ scope.byTestId.clear();
483
+ }
484
+ return this;
485
+ }
395
486
  enableRecording(config) {
396
487
  this.options.record = config;
397
488
  return this;
@@ -441,6 +532,8 @@ var LLMock = class LLMock {
441
532
  mcpFakes: [...this.mcpFakeBuffer]
442
533
  };
443
534
  this.serverInstance = await (this.resolvedInboundAuth ? createServerWithResolvedAuth(this.fixtures, this.options, this.resolvedInboundAuth, this.mounts, serviceFixtures) : createServer(this.fixtures, this.options, this.mounts, serviceFixtures));
535
+ const countIdentity = (fixture) => this.fixtureCountOrigins.get(fixture) ?? fixture;
536
+ this.serverInstance.journal.configureFixtureCountIdentity(countIdentity);
444
537
  this.mcpFakeBuffer.length = 0;
445
538
  return this.serverInstance.url;
446
539
  }
@@ -1 +1 @@
1
- {"version":3,"file":"llmock.js","names":[],"sources":["../src/llmock.ts"],"sourcesContent":["import type { LiveTranscript } from \"./live-types.js\";\nimport { normalizeLiveFixture, normalizeLiveOptions } from \"./live-fixture.js\";\nimport { isLiveResponse } from \"./helpers.js\";\nimport type {\n AudioResponse,\n ChaosConfig,\n EmbeddingFixtureOpts,\n FalQueueOpts,\n Fixture,\n FixtureFileEntry,\n FixtureFileResponse,\n FixtureMatch,\n FixtureOpts,\n ImageResponse,\n McpFakeSource,\n MockServerOptions,\n Mountable,\n RecordConfig,\n ResponseFactory,\n TranscriptionResponse,\n VideoResponse,\n VoiceDesignResponse,\n} from \"./types.js\";\nimport {\n createServer,\n createServerWithResolvedAuth,\n performFullReset,\n type ServerInstance,\n} from \"./server.js\";\nimport type { ResolvedInboundAuth } from \"./api-key-auth.js\";\nimport {\n isInjectableStatus,\n INJECTED_STATUS_RANGE,\n queueOneShotError,\n clearFixtureQueue,\n entryToFixture,\n normalizeResponse,\n validateFixtures,\n} from \"./fixture-loader.js\";\nimport {\n loadFixtureFileWithServices,\n loadFixturesFromDirWithServices,\n type FixturesWithServices,\n} from \"./fixture-loader-services.js\";\nimport { ensureFakeMount, isAutoMount, planFileFakes } from \"./mcp-fakes-mount.js\";\nimport { build, msg, quote } from \"./message-text.js\";\nimport { Journal } from \"./journal.js\";\nimport type { SearchFixture, SearchResult } from \"./search.js\";\nimport type { RerankFixture, RerankResult } from \"./rerank.js\";\nimport type { ModerationFixture, ModerationResult } from \"./moderation.js\";\nimport { imageResponseToFalJson, videoResponseToFalJson } from \"./fal.js\";\nimport { voiceDesignToJson } from \"./elevenlabs-voice.js\";\n\nexport class LLMock {\n private fixtures: Fixture[] = [];\n private searchFixtures: SearchFixture[] = [];\n private rerankFixtures: RerankFixture[] = [];\n private moderationFixtures: ModerationFixture[] = [];\n private mounts: Array<{ path: string; handler: Mountable }> = [];\n /** `mcpFakes` blocks loaded before `start()`, handed to the server at start (W2). */\n private mcpFakeBuffer: McpFakeSource[] = [];\n /** MCPMocks the server or a load auto-mounted (W3, W6), for the L6 check. */\n private serverInstance: ServerInstance | null = null;\n private options: MockServerOptions;\n private readonly resolvedInboundAuth?: ResolvedInboundAuth;\n\n constructor(options?: MockServerOptions, resolvedInboundAuth?: ResolvedInboundAuth) {\n this.options = options ?? {};\n if (this.options.live !== undefined) normalizeLiveOptions(this.options.live);\n this.resolvedInboundAuth = resolvedInboundAuth;\n }\n\n // ---- Fixture management ----\n\n private normalizeFixture(fixture: Fixture): Fixture {\n if (\n isLiveResponse(fixture.response) ||\n (fixture.match.endpoint === \"openai-live\" && typeof fixture.response !== \"function\")\n ) {\n return {\n ...fixture,\n match: { ...fixture.match },\n response: normalizeLiveFixture(fixture.response, this.options.live),\n };\n }\n return fixture;\n }\n\n addFixture(fixture: Fixture): this {\n this.fixtures.push(this.normalizeFixture(fixture));\n return this;\n }\n\n addFixtures(fixtures: Fixture[]): this {\n this.fixtures.push(...fixtures.map((fixture) => this.normalizeFixture(fixture)));\n return this;\n }\n\n prependFixture(fixture: Fixture): this {\n this.fixtures.unshift(this.normalizeFixture(fixture));\n return this;\n }\n\n getFixtures(): readonly Fixture[] {\n return this.fixtures;\n }\n\n /**\n * Load a fixture file: its LLM fixtures and its `mcpFakes` blocks (F4).\n * Before `start()` the blocks are buffered and handed to the server at\n * start (W2); after it they go to the MCPMock serving each block's `mount`\n * path, auto-mounted when none does (W6). A block that cannot be honored\n * throws a `FixtureLoadError` and nothing from the file is added.\n */\n loadFixtureFile(filePath: string): this {\n return this.acceptLoaded(loadFixtureFileWithServices(filePath, undefined, this.options.live));\n }\n\n /** As {@link loadFixtureFile}, for every fixture file under `dirPath`. */\n loadFixtureDir(dirPath: string): this {\n return this.acceptLoaded(\n loadFixturesFromDirWithServices(dirPath, undefined, this.options.live),\n );\n }\n\n /**\n * Add a load's fakes (all or nothing), then its LLM fixtures. Before start\n * the fakes are checked as the start-time hand-off will check them, with\n * the blocks already buffered and the mounts added so far, so a file whose\n * fakes would be rejected adds nothing. A mount added after the load can\n * still make `start()` reject.\n */\n private acceptLoaded(loaded: FixturesWithServices): this {\n if (loaded.mcpFakes.length > 0) {\n if (this.serverInstance) {\n this.addFakesAfterStart(this.serverInstance, loaded.mcpFakes);\n } else {\n planFileFakes(\n this.mounts,\n [...this.mcpFakeBuffer, ...loaded.mcpFakes],\n new Set(loaded.mcpFakes),\n );\n this.mcpFakeBuffer.push(...loaded.mcpFakes);\n }\n }\n this.fixtures.push(...loaded.fixtures);\n return this;\n }\n\n /**\n * W6 for a load after start. Every group of blocks (by `mount` path) is\n * checked before any mount is touched: a path held by a mount that is not\n * an MCPMock is a mount conflict, and each group is first added to a\n * throwaway store. Groups for an existing MCPMock are then added to it (its\n * own add is all or nothing and finds collisions with its loaded ids);\n * groups for a free path are added last, to a new auto-mounted MCPMock,\n * wired as W4. The add warnings (L8) are logged at `warn`.\n */\n private addFakesAfterStart(server: ServerInstance, sources: McpFakeSource[]): void {\n const { onExisting, onNew } = planFileFakes(this.mounts, sources);\n\n const logger = server.defaults.logger;\n for (const { handler, group } of onExisting) {\n const result = handler.addMcpFakes?.(group, { kind: \"file\" });\n for (const warning of result?.warnings ?? []) logger.warn(warning.message);\n }\n const registry = server.defaults.registry;\n for (const { path, group } of onNew) {\n const target = ensureFakeMount(\n this.mounts,\n path,\n {\n journal: server.journal,\n ...(registry ? { registry } : {}),\n logger,\n },\n logger,\n );\n // The check above found no mount at this path, and nothing ran since.\n if (\"conflict\" in target) continue;\n const result = target.handler.addMcpFakes?.(group, { kind: \"file\" });\n for (const warning of result?.warnings ?? []) logger.warn(warning.message);\n }\n }\n\n /** R4, R5: unload the fakes of every mount and the pre-start buffer. */\n private unloadMcpFakes(): void {\n this.mcpFakeBuffer.length = 0;\n for (const { handler } of this.mounts) handler.clearMcpFakes?.();\n }\n\n /**\n * Add fixtures from a JSON string or pre-parsed array of fixture entries.\n * Validates all fixtures and throws if any have severity \"error\".\n */\n addFixturesFromJSON(input: string | FixtureFileEntry[]): this {\n let entries: FixtureFileEntry[];\n if (typeof input === \"string\") {\n try {\n entries = JSON.parse(input);\n } catch (err) {\n throw new Error(\n `addFixturesFromJSON: invalid JSON — ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n } else {\n entries = input;\n }\n if (!Array.isArray(entries)) {\n throw new TypeError(\n \"addFixturesFromJSON: expected an array of fixture entries; use loadFixtureFile for a {fixtures:[...]} file\",\n );\n }\n const converted = entries.map((e) => entryToFixture(e, undefined, this.options.live));\n const issues = validateFixtures(converted, this.options.live);\n const errors = issues.filter((i) => i.severity === \"error\");\n if (errors.length > 0) {\n throw new Error(`Fixture validation failed: ${JSON.stringify(errors)}`);\n }\n this.fixtures.push(...converted);\n return this;\n }\n\n // Clears in place to preserve array reference identity — the running\n // server reads this same array on every request — and invalidates any\n // one-shot claim parked in flight so it cannot re-arm into the cleared queue.\n clearFixtures(): this {\n clearFixtureQueue(this.fixtures);\n this.unloadMcpFakes();\n return this;\n }\n\n // ---- Convenience ----\n\n on(\n match: FixtureMatch,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.addFixture({\n match,\n response:\n typeof response === \"function\" ? response : normalizeResponse(response, this.options.live),\n ...opts,\n });\n }\n\n onLive(\n match: Omit<FixtureMatch, \"endpoint\">,\n transcript: LiveTranscript,\n opts?: FixtureOpts,\n ): this {\n return this.on({ ...match, endpoint: \"openai-live\" }, { live: transcript }, opts);\n }\n\n onMessage(\n pattern: string | RegExp,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.on({ userMessage: pattern }, response, opts);\n }\n\n onEmbedding(\n pattern: string | RegExp,\n response: FixtureFileResponse | ResponseFactory,\n opts?: EmbeddingFixtureOpts,\n ): this {\n return this.on({ inputText: pattern }, response, opts);\n }\n\n onJsonOutput(pattern: string | RegExp, jsonContent: object | string, opts?: FixtureOpts): this {\n const content = typeof jsonContent === \"string\" ? jsonContent : JSON.stringify(jsonContent);\n return this.on({ userMessage: pattern, responseFormat: \"json_object\" }, { content }, opts);\n }\n\n onToolCall(\n name: string,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.on({ toolName: name }, response, opts);\n }\n\n onToolResult(\n id: string,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.on({ toolCallId: id }, response, opts);\n }\n\n onTurn(\n turn: number,\n pattern: string | RegExp,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.on({ userMessage: pattern, turnIndex: turn }, response, opts);\n }\n\n onImage(prompt: string | RegExp, response: ImageResponse): this {\n return this.addFixture({\n match: { userMessage: prompt, endpoint: \"image\" },\n response,\n });\n }\n\n onSpeech(input: string | RegExp, response: AudioResponse): this {\n return this.addFixture({\n match: { userMessage: input, endpoint: \"speech\" },\n response,\n });\n }\n\n onTranscription(response: TranscriptionResponse): this {\n return this.addFixture({\n match: { endpoint: \"transcription\" },\n response,\n });\n }\n\n onTranslation(response: TranscriptionResponse): this {\n return this.addFixture({\n match: { endpoint: \"translation\" },\n response,\n });\n }\n\n onVideo(prompt: string | RegExp, response: VideoResponse): this {\n return this.addFixture({\n match: { userMessage: prompt, endpoint: \"video\" },\n response,\n });\n }\n\n onAudio(input: string | RegExp, response: AudioResponse): this {\n return this.addFixture({ match: { userMessage: input }, response });\n }\n\n onSoundEffect(text: string | RegExp, response: AudioResponse): this {\n return this.addFixture({\n match: { userMessage: text, endpoint: \"audio-gen\" },\n response,\n });\n }\n\n onMusic(prompt: string | RegExp, response: AudioResponse): this {\n return this.addFixture({\n match: { userMessage: prompt, endpoint: \"audio-gen\" },\n response,\n });\n }\n\n onElevenLabsTTS(text: string | RegExp, response: AudioResponse): this {\n return this.addFixture({\n match: { userMessage: text, endpoint: \"elevenlabs-tts\" },\n response,\n });\n }\n\n onElevenLabsVoiceDesign(description: string | RegExp, response: VoiceDesignResponse): this {\n return this.addFixture({\n match: { userMessage: description, endpoint: \"elevenlabs-voice-design\" },\n response: voiceDesignToJson(response),\n });\n }\n\n onFalAudio(prompt: string | RegExp, response: AudioResponse, model?: string): this {\n return this.addFixture({\n match: { userMessage: prompt, endpoint: \"fal-audio\", ...(model ? { model } : {}) },\n response,\n });\n }\n\n // fal.queue.* is the dominant client API; onFalRun is a sync alias.\n //\n // `opts.billableUnits` rides through to the completed `queue-result`\n // response's `x-fal-billable-units` header (emitted alongside\n // `x-fal-request-id`), letting consumers like `@tanstack/ai-fal` surface a\n // billed-units value on replay. Omit it to preserve the header-less default.\n onFalQueue(modelOrPrompt: string | RegExp, response: unknown, opts?: FalQueueOpts): this {\n const { billableUnits, ...fixtureOpts } = opts ?? {};\n return this.addFixture({\n match: { model: modelOrPrompt, endpoint: \"fal\" },\n response: { json: response, ...(billableUnits != null ? { billableUnits } : {}) },\n ...fixtureOpts,\n });\n }\n\n onFalRun(modelOrPrompt: string | RegExp, response: unknown, opts?: FalQueueOpts): this {\n return this.onFalQueue(modelOrPrompt, response, opts);\n }\n\n /**\n * Register a fal.ai image fixture. Wraps an `ImageResponse` (the shape used\n * by `onImage` and OpenAI/Azure image fixtures) into fal's image envelope\n * before storing it as a `RawJSONResponse`. Defaults `width`/`height` to\n * 1024 when the fixture's `ImageItem` doesn't carry them.\n */\n onFalImage(modelOrPrompt: string | RegExp, response: ImageResponse, opts?: FalQueueOpts): this {\n return this.onFalQueue(modelOrPrompt, imageResponseToFalJson(response), opts);\n }\n\n /**\n * Register a fal.ai video fixture. Wraps a `VideoResponse` into fal's video\n * envelope (`{ video: { url, content_type, file_name, file_size }, seed }`)\n * before storing it as a `RawJSONResponse`.\n */\n onFalVideo(modelOrPrompt: string | RegExp, response: VideoResponse, opts?: FalQueueOpts): this {\n return this.onFalQueue(modelOrPrompt, videoResponseToFalJson(response), opts);\n }\n\n // ---- Service mock convenience methods ----\n\n onSearch(pattern: string | RegExp, results: SearchResult[]): this {\n this.searchFixtures.push({ match: pattern, results });\n return this;\n }\n\n onRerank(pattern: string | RegExp, results: RerankResult[]): this {\n this.rerankFixtures.push({ match: pattern, results });\n return this;\n }\n\n onModerate(pattern: string | RegExp, result: ModerationResult): this {\n this.moderationFixtures.push({ match: pattern, result });\n return this;\n }\n\n /**\n * Queue a one-shot error that will be returned for the next matching\n * request, then automatically removed. Implemented as an internal fixture\n * inserted at the front of the fixture list; see `queueOneShotError` for\n * the endpoint gate and the served-not-evaluated consumption rule.\n */\n nextRequestError(\n status: number,\n errorBody?: { message?: string; type?: string; code?: string },\n ): this {\n // Same gate as `POST /__aimock/error` and fixture validation — this is the\n // form the docs use, and an unchecked status reaches `res.writeHead` on\n // the next matched request: 99/1000 throw there (the injected error is\n // lost and the caller sees a generic 500), and 1xx hangs the request until\n // the client times out. Throwing HERE names the offending call instead.\n if (!isInjectableStatus(status)) {\n throw new RangeError(\n `nextRequestError: invalid status ${String(status)} — must be ${INJECTED_STATUS_RANGE}`,\n );\n }\n queueOneShotError(this.fixtures, status, errorBody);\n return this;\n }\n\n // ---- Mounts ----\n\n mount(path: string, handler: Mountable): this {\n // W4: an auto-mounted MCPMock at `path` takes its requests first (dispatch\n // takes the first match), so the new mount is shadowed; so is one at\n // `path` plus \"/\", whose root request the auto-mount answers as its own.\n // An auto-mount lets any other sub-path fall through: that is no shadow.\n const shadowedBy = this.serverInstance\n ? this.mounts.find(\n (m) => isAutoMount(m.handler) && (path === m.path || path === m.path + \"/\"),\n )\n : undefined;\n this.mounts.push({ path, handler });\n\n // If server is already running, wire up journal, registry, logger and baseUrl\n // immediately so late mounts behave identically to pre-start mounts.\n if (this.serverInstance) {\n if (handler.setJournal) handler.setJournal(this.serverInstance.journal);\n if (handler.setBaseUrl) handler.setBaseUrl(this.serverInstance.url + path);\n const registry = this.serverInstance.defaults.registry;\n if (registry && handler.setRegistry) handler.setRegistry(registry);\n const logger = this.serverInstance.defaults.logger;\n if (handler.setLogger) handler.setLogger(logger);\n if (shadowedBy) {\n logger.warn(\n build(\n msg`MCP fakes: the mount at ${quote(path)} is shadowed by the MCP mock auto-mounted at ${quote(shadowedBy.path)}; its requests reach the auto-mount`,\n ),\n );\n }\n }\n\n return this;\n }\n\n // ---- Journal proxies ----\n\n getRequests(): import(\"./types.js\").JournalEntry[] {\n return this.journal.getAll();\n }\n\n getLastRequest(): import(\"./types.js\").JournalEntry | null {\n return this.journal.getLast();\n }\n\n /**\n * Clear the request journal. Fixture match-counts (sequencing state) are\n * left intact — use `resetMatchCounts()` for those.\n */\n clearRequests(): void {\n this.journal.clearEntries();\n }\n\n /**\n * Reset fixture match counts, and the MCP fakes consumption state of every\n * mount (R6), for `testId` or for all test ids. Fakes stay loaded.\n */\n resetMatchCounts(testId?: string): this {\n if (this.serverInstance) {\n this.serverInstance.journal.clearMatchCounts(testId);\n }\n for (const { handler } of this.mounts) handler.resetScenarioState?.(testId);\n return this;\n }\n\n // ---- Chaos ----\n\n /**\n * Set the server-wide chaos baseline. Writes through to a RUNNING server as\n * well as the construction options: an untagged `POST /__aimock/chaos`\n * installs a baseline override that shadows `options.chaos`, and without this\n * the call would silently do nothing until the next `reset()`. Per-testId\n * overrides still win over the baseline.\n */\n setChaos(config: ChaosConfig): this {\n this.options.chaos = config;\n if (this.serverInstance) this.serverInstance.defaults.chaos = config;\n return this;\n }\n\n /**\n * Turn chaos off everywhere: the construction config, the runtime baseline,\n * and every per-testId override (assigning `undefined` is the full-clear\n * signal the `defaults.chaos` setter defines).\n */\n clearChaos(): this {\n delete this.options.chaos;\n if (this.serverInstance) this.serverInstance.defaults.chaos = undefined;\n return this;\n }\n\n // ---- Recording ----\n\n enableRecording(config: RecordConfig): this {\n this.options.record = config;\n return this;\n }\n\n disableRecording(): this {\n delete this.options.record;\n return this;\n }\n\n // ---- Reset ----\n\n /**\n * Full reset — the in-process equivalent of `POST /__aimock/reset`. Shares\n * one implementation with the control-API route so the two cannot drift.\n *\n * The one deliberate difference: search / rerank / moderation fixtures are\n * also cleared here. Those are registered through this class only — the\n * control API has no route that creates them, so the HTTP reset can neither\n * reach nor observe them.\n *\n * NOT ALL OF THIS IS PER-INSTANCE. `performFullReset` clears module-global\n * state as well: the Gemini interaction and event-id counters\n * (`resetInteractionCounter` / `resetEventIdCounter` in\n * `./gemini-interactions.js`), the fal.ai job/queue maps (`falJobs`,\n * `falQueueStates`), the fine-tuning job store (`clearFineTuningStore`),\n * and the ElevenLabs Voice Design store (`clearElevenLabsVoices`). With two\n * `LLMock` instances live in one process,\n * `a.reset()` rewinds the Gemini id sequence that `b` is mid-way through —\n * `b` then re-emits `aimock-int-0` / `evt_1`, ids it has already handed\n * out — and drops `b`'s in-flight fal jobs. Give each instance its own\n * process (or its own vitest worker) if that matters.\n *\n * The global stores are cleared even before `start()`, when there is no\n * server instance to reset.\n */\n reset(): this {\n this.searchFixtures.length = 0;\n this.rerankFixtures.length = 0;\n this.moderationFixtures.length = 0;\n // `ServerInstance` carries no mounts, so the fakes are unloaded here (R4).\n this.unloadMcpFakes();\n performFullReset(this.fixtures, this.serverInstance);\n return this;\n }\n\n // ---- Server lifecycle ----\n\n async start(): Promise<string> {\n if (this.serverInstance) {\n throw new Error(\"Server already started\");\n }\n const serviceFixtures = {\n search: this.searchFixtures,\n rerank: this.rerankFixtures,\n moderation: this.moderationFixtures,\n mcpFakes: [...this.mcpFakeBuffer],\n };\n this.serverInstance = await (this.resolvedInboundAuth\n ? createServerWithResolvedAuth(\n this.fixtures,\n this.options,\n this.resolvedInboundAuth,\n this.mounts,\n serviceFixtures,\n )\n : createServer(this.fixtures, this.options, this.mounts, serviceFixtures));\n // The server holds the buffered fakes now (W2). A start that rejects took\n // its hand-off back, so the buffer is kept for a retry.\n this.mcpFakeBuffer.length = 0;\n return this.serverInstance.url;\n }\n\n closeLiveSessions(testId?: string): this {\n this.serverInstance?.closeLiveSessions(testId);\n return this;\n }\n\n async stop(): Promise<void> {\n if (!this.serverInstance) {\n throw new Error(\"Server not started\");\n }\n const { server } = this.serverInstance;\n await new Promise<void>((resolve, reject) => {\n server.close((err: Error | undefined) => (err ? reject(err) : resolve()));\n });\n this.serverInstance = null;\n }\n\n // ---- Accessors ----\n\n get journal(): Journal {\n if (!this.serverInstance) {\n throw new Error(\"Server not started\");\n }\n return this.serverInstance.journal;\n }\n\n get url(): string {\n if (!this.serverInstance) {\n throw new Error(\"Server not started\");\n }\n return this.serverInstance.url;\n }\n\n get baseUrl(): string {\n return this.url;\n }\n\n get port(): number {\n const parsed = new URL(this.url); // this.url throws if not started\n if (!parsed.port) {\n throw new Error(`Server URL has no explicit port: ${this.url}`);\n }\n return parseInt(parsed.port, 10);\n }\n\n // ---- Static factory ----\n\n static async create(options?: MockServerOptions): Promise<LLMock> {\n const instance = new LLMock(options);\n await instance.start();\n return instance;\n }\n}\n\n/** @internal Configuration startup preserves a policy resolved from the selected source. */\nexport function createLLMockWithResolvedAuth(\n options: MockServerOptions,\n resolvedAuth: ResolvedInboundAuth,\n): LLMock {\n return new LLMock(options, resolvedAuth);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"llmock.js","names":[],"sources":["../src/llmock.ts"],"sourcesContent":["import type { LiveTranscript } from \"./live-types.js\";\nimport { normalizeLiveFixture, normalizeLiveOptions } from \"./live-fixture.js\";\nimport { isLiveResponse, markServerOwnedFixtures } from \"./helpers.js\";\nimport type {\n AudioResponse,\n ChaosConfig,\n EmbeddingFixtureOpts,\n FalQueueOpts,\n Fixture,\n FixtureFileEntry,\n FixtureFileResponse,\n FixtureMatch,\n FixtureOpts,\n FixtureResponse,\n ImageResponse,\n McpFakeSource,\n MockServerOptions,\n MisbehaviorConfig,\n MisbehaviorFaultId,\n Mountable,\n RecordConfig,\n ResponseFactory,\n TranscriptionResponse,\n VideoResponse,\n VoiceDesignResponse,\n} from \"./types.js\";\nimport {\n createServer,\n createServerWithResolvedAuth,\n performFullReset,\n type ServerInstance,\n} from \"./server.js\";\nimport type { ResolvedInboundAuth } from \"./api-key-auth.js\";\nimport {\n isInjectableStatus,\n INJECTED_STATUS_RANGE,\n queueOneShotError,\n clearFixtureQueue,\n entryToFixture,\n normalizeResponse,\n normalizeResponsesToolsKeys,\n validateFixtures,\n enableHeldFixtureMisbehavior,\n markFixtureResponsesToolsExtended,\n markFixtureMisbehaviorEnabled,\n MisbehaviorConfigError,\n} from \"./fixture-loader.js\";\nimport {\n loadFixtureFileWithServices,\n loadFixturesFromDirWithServices,\n type FixturesWithServices,\n} from \"./fixture-loader-services.js\";\nimport { ensureFakeMount, isAutoMount, planFileFakes } from \"./mcp-fakes-mount.js\";\nimport { MCPMock } from \"./mcp-mock.js\";\nimport { build, msg, quote } from \"./message-text.js\";\nimport { DEFAULT_TEST_ID } from \"./constants.js\";\nimport { Journal } from \"./journal.js\";\nimport type { SearchFixture, SearchResult } from \"./search.js\";\nimport type { RerankFixture, RerankResult } from \"./rerank.js\";\nimport type { ModerationFixture, ModerationResult } from \"./moderation.js\";\nimport { imageResponseToFalJson, videoResponseToFalJson } from \"./fal.js\";\nimport { voiceDesignToJson } from \"./elevenlabs-voice.js\";\n\nimport {\n getFixtureMisbehaviorPosition,\n parseMisbehavior,\n resolveMisbehaviorSeed,\n setFixtureMisbehaviorPosition,\n validateFixtureMisbehavior,\n} from \"./misbehavior.js\";\n\nexport class LLMock {\n private fixtures: Fixture[] = [];\n private fixtureAddition = 0;\n private readonly fixtureCountOrigins = new WeakMap<Fixture, Fixture>();\n /**\n * Fixtures this LLMock built itself (on() and the other helpers), so the\n * caller never held them: the stored object is their identity.\n */\n private readonly ownFixtures = new WeakSet<Fixture>();\n private searchFixtures: SearchFixture[] = [];\n private rerankFixtures: RerankFixture[] = [];\n private moderationFixtures: ModerationFixture[] = [];\n private mounts: Array<{ path: string; handler: Mountable }> = [];\n /** `mcpFakes` blocks loaded before `start()`, handed to the server at start (W2). */\n private mcpFakeBuffer: McpFakeSource[] = [];\n /** MCPMocks the server or a load auto-mounted (W3, W6), for the L6 check. */\n private serverInstance: ServerInstance | null = null;\n private options: MockServerOptions;\n private readonly resolvedInboundAuth?: ResolvedInboundAuth;\n\n constructor(options?: MockServerOptions, resolvedInboundAuth?: ResolvedInboundAuth) {\n // Only this LLMock's server reads its array, so fixtures it makes may be marked in place.\n markServerOwnedFixtures(this.fixtures);\n this.options = options ?? {};\n if (this.options.live !== undefined) normalizeLiveOptions(this.options.live);\n // The `misbehavior` option is read only with `enableMisbehavior: true`;\n // otherwise it is unused data, as in 1.44.0.\n if (this.misbehaviorEnabled && this.options.misbehavior !== undefined) {\n this.setMisbehavior(this.options.misbehavior);\n }\n this.resolvedInboundAuth = resolvedInboundAuth;\n }\n\n /** Misbehavior (fixture keys, header, `misbehavior` option) needs `enableMisbehavior: true`. */\n private get misbehaviorEnabled(): boolean {\n return this.options.enableMisbehavior === true;\n }\n\n // ---- Fixture management ----\n\n private normalizeFixture(fixture: Fixture): Fixture {\n const misbehaviorEnabled = this.misbehaviorEnabled;\n // A fixture loaded from a file holds its `misbehavior` key until enabled.\n if (misbehaviorEnabled) enableHeldFixtureMisbehavior(fixture);\n // A fixture this LLMock built (on() and the helpers) is new to it, so it\n // needs no per-addition copy, and the object stored is its identity.\n const own = this.ownFixtures.delete(fixture);\n const countIdentity = own ? undefined : (this.fixtureCountOrigins.get(fixture) ?? fixture);\n const previousPosition = getFixtureMisbehaviorPosition(fixture);\n const isNewAddition = previousPosition === undefined || /^code#\\d+$/.test(previousPosition);\n const position = isNewAddition ? `code#${this.fixtureAddition++}` : previousPosition;\n // With misbehavior enabled, re-adding a code fixture creates a new fault\n // source without changing the old entry, so each addition is its own copy.\n // The journal counts and records every copy under the caller's object\n // (fixtureCountOrigins), so the identity APIs read as they do without\n // misbehavior: an object added twice is one caller fixture, as in 1.44.0.\n // Without misbehavior the position is never read, and the caller's object\n // is stored as in 1.44.0.\n if (isNewAddition && misbehaviorEnabled && !own) fixture = { ...fixture };\n if (misbehaviorEnabled && fixture.misbehavior !== undefined) {\n const parsed = parseMisbehavior(fixture.misbehavior);\n if (parsed.ok) fixture = { ...fixture, misbehavior: parsed.config };\n const issue = parsed.ok ? validateFixtureMisbehavior(fixture) : parsed.issue;\n if (issue) {\n throw new MisbehaviorConfigError({\n rule: issue.rule,\n file: position,\n detail: issue.message,\n });\n }\n }\n\n if (\n isLiveResponse(fixture.response) ||\n (fixture.match.endpoint === \"openai-live\" && typeof fixture.response !== \"function\")\n ) {\n fixture = {\n ...fixture,\n match: { ...fixture.match },\n response: normalizeLiveFixture(fixture.response, this.options.live),\n };\n } else if (countIdentity !== undefined && fixture !== countIdentity) {\n this.fixtureCountOrigins.set(fixture, countIdentity);\n }\n setFixtureMisbehaviorPosition(fixture, position);\n if (misbehaviorEnabled && fixture.misbehavior !== undefined) {\n markFixtureMisbehaviorEnabled(fixture);\n }\n // A fixture passed in is not marked for responsesTools \"extended\": the\n // server reads it through its extended view, so the caller's object (which\n // another server may share) is left as it was loaded.\n return fixture;\n }\n\n addFixture(fixture: Fixture): this {\n this.fixtures.push(this.normalizeFixture(fixture));\n return this;\n }\n\n addFixtures(fixtures: Fixture[]): this {\n this.fixtures.push(...fixtures.map((fixture) => this.normalizeFixture(fixture)));\n return this;\n }\n\n prependFixture(fixture: Fixture): this {\n this.fixtures.unshift(this.normalizeFixture(fixture));\n return this;\n }\n\n /** Mark a fixture this LLMock built, for {@link normalizeFixture}. */\n private own(fixture: Fixture): Fixture {\n this.ownFixtures.add(fixture);\n return fixture;\n }\n\n getFixtures(): readonly Fixture[] {\n return this.fixtures;\n }\n\n /**\n * Load a fixture file: its LLM fixtures and its `mcpFakes` blocks (F4).\n * Before `start()` the blocks are buffered and handed to the server at\n * start (W2); after it they go to the MCPMock serving each block's `mount`\n * path, auto-mounted when none does (W6). A block that cannot be honored\n * throws a `FixtureLoadError` and nothing from the file is added.\n */\n loadFixtureFile(filePath: string): this {\n return this.acceptLoaded(loadFixtureFileWithServices(filePath, undefined, this.options.live));\n }\n\n /** As {@link loadFixtureFile}, for every fixture file under `dirPath`. */\n loadFixtureDir(dirPath: string): this {\n return this.acceptLoaded(\n loadFixturesFromDirWithServices(dirPath, undefined, this.options.live),\n );\n }\n\n /**\n * Add a load's fakes (all or nothing), then its LLM fixtures. Before start\n * the fakes are checked as the start-time hand-off will check them, with\n * the blocks already buffered and the mounts added so far, so a file whose\n * fakes would be rejected adds nothing. A mount added after the load can\n * still make `start()` reject.\n */\n private acceptLoaded(loaded: FixturesWithServices): this {\n // With misbehavior enabled, a bad `misbehavior` key throws before anything is added.\n if (this.misbehaviorEnabled) loaded.fixtures.forEach(enableHeldFixtureMisbehavior);\n if (this.options.responsesTools === \"extended\") {\n loaded.fixtures.forEach(markFixtureResponsesToolsExtended);\n }\n if (loaded.mcpFakes.length > 0) {\n if (this.serverInstance) {\n this.addFakesAfterStart(this.serverInstance, loaded.mcpFakes);\n } else {\n planFileFakes(\n this.mounts,\n [...this.mcpFakeBuffer, ...loaded.mcpFakes],\n new Set(loaded.mcpFakes),\n );\n this.mcpFakeBuffer.push(...loaded.mcpFakes);\n }\n }\n this.fixtures.push(...loaded.fixtures);\n return this;\n }\n\n /**\n * W6 for a load after start. Every group of blocks (by `mount` path) is\n * checked before any mount is touched: a path held by a mount that is not\n * an MCPMock is a mount conflict, and each group is first added to a\n * throwaway store. Groups for an existing MCPMock are then added to it (its\n * own add is all or nothing and finds collisions with its loaded ids);\n * groups for a free path are added last, to a new auto-mounted MCPMock,\n * wired as W4. The add warnings (L8) are logged at `warn`.\n */\n private addFakesAfterStart(server: ServerInstance, sources: McpFakeSource[]): void {\n const { onExisting, onNew } = planFileFakes(this.mounts, sources);\n\n const logger = server.defaults.logger;\n for (const { handler, group } of onExisting) {\n const result = handler.addMcpFakes?.(group, { kind: \"file\" });\n for (const warning of result?.warnings ?? []) logger.warn(warning.message);\n }\n const registry = server.defaults.registry;\n for (const { path, group } of onNew) {\n const target = ensureFakeMount(\n this.mounts,\n path,\n {\n journal: server.journal,\n ...(registry ? { registry } : {}),\n logger,\n replaySpeed: server.defaults.replaySpeed,\n },\n logger,\n );\n // The check above found no mount at this path, and nothing ran since.\n if (\"conflict\" in target) continue;\n const result = target.handler.addMcpFakes?.(group, { kind: \"file\" });\n for (const warning of result?.warnings ?? []) logger.warn(warning.message);\n }\n }\n\n /** R4, R5: unload the fakes of every mount and the pre-start buffer. */\n private unloadMcpFakes(): void {\n this.mcpFakeBuffer.length = 0;\n for (const { handler } of this.mounts) handler.clearMcpFakes?.();\n }\n\n /**\n * Add fixtures from a JSON string or pre-parsed array of fixture entries.\n * Validates all fixtures and throws if any have severity \"error\".\n */\n addFixturesFromJSON(input: string | FixtureFileEntry[]): this {\n let entries: FixtureFileEntry[];\n if (typeof input === \"string\") {\n try {\n entries = JSON.parse(input);\n } catch (err) {\n throw new Error(\n `addFixturesFromJSON: invalid JSON — ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n } else {\n entries = input;\n }\n if (!Array.isArray(entries)) {\n throw new TypeError(\n \"addFixturesFromJSON: expected an array of fixture entries; use loadFixtureFile for a {fixtures:[...]} file\",\n );\n }\n const converted = entries.map((entry) => {\n const position = `code#${this.fixtureAddition++}`;\n const fixture = entryToFixture(entry, undefined, this.options.live, {\n file: position,\n index: 0,\n });\n setFixtureMisbehaviorPosition(fixture, position);\n if (this.misbehaviorEnabled) enableHeldFixtureMisbehavior(fixture);\n if (this.options.responsesTools === \"extended\") markFixtureResponsesToolsExtended(fixture);\n return fixture;\n });\n\n const issues = validateFixtures(converted, this.options.live);\n const errors = issues.filter((i) => i.severity === \"error\");\n if (errors.length > 0) {\n throw new Error(`Fixture validation failed: ${JSON.stringify(errors)}`);\n }\n this.fixtures.push(...converted);\n return this;\n }\n\n // Clears in place to preserve array reference identity — the running\n // server reads this same array on every request — and invalidates any\n // one-shot claim parked in flight so it cannot re-arm into the cleared queue.\n clearFixtures(): this {\n clearFixtureQueue(this.fixtures);\n this.serverInstance?.journal.clearMisbehaviorCounters();\n this.unloadMcpFakes();\n return this;\n }\n\n // ---- Convenience ----\n\n on(\n match: FixtureMatch,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n let normalized: FixtureResponse | ResponseFactory = response as ResponseFactory;\n if (typeof response !== \"function\") {\n normalized = normalizeResponse(response, this.options.live);\n // `customToolCalls` / `responsesBlocks` are normalized only where they are\n // read; otherwise they stay as written, as in 1.44.0.\n if (this.options.responsesTools === \"extended\" && !isLiveResponse(normalized)) {\n normalizeResponsesToolsKeys(normalized as unknown as Record<string, unknown>);\n }\n }\n return this.addFixture(this.own({ match, response: normalized, ...opts }));\n }\n\n onLive(\n match: Omit<FixtureMatch, \"endpoint\">,\n transcript: LiveTranscript,\n opts?: FixtureOpts,\n ): this {\n return this.on({ ...match, endpoint: \"openai-live\" }, { live: transcript }, opts);\n }\n\n onMessage(\n pattern: string | RegExp,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.on({ userMessage: pattern }, response, opts);\n }\n\n onEmbedding(\n pattern: string | RegExp,\n response: FixtureFileResponse | ResponseFactory,\n opts?: EmbeddingFixtureOpts,\n ): this {\n return this.on({ inputText: pattern }, response, opts);\n }\n\n onJsonOutput(pattern: string | RegExp, jsonContent: object | string, opts?: FixtureOpts): this {\n const content = typeof jsonContent === \"string\" ? jsonContent : JSON.stringify(jsonContent);\n return this.on({ userMessage: pattern, responseFormat: \"json_object\" }, { content }, opts);\n }\n\n onToolCall(\n name: string,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.on({ toolName: name }, response, opts);\n }\n\n onToolResult(\n id: string,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.on({ toolCallId: id }, response, opts);\n }\n\n onTurn(\n turn: number,\n pattern: string | RegExp,\n response: FixtureFileResponse | ResponseFactory,\n opts?: FixtureOpts,\n ): this {\n return this.on({ userMessage: pattern, turnIndex: turn }, response, opts);\n }\n\n onImage(prompt: string | RegExp, response: ImageResponse): this {\n return this.addFixture(\n this.own({\n match: { userMessage: prompt, endpoint: \"image\" },\n response,\n }),\n );\n }\n\n onSpeech(input: string | RegExp, response: AudioResponse): this {\n return this.addFixture(\n this.own({\n match: { userMessage: input, endpoint: \"speech\" },\n response,\n }),\n );\n }\n\n onTranscription(response: TranscriptionResponse): this {\n return this.addFixture(\n this.own({\n match: { endpoint: \"transcription\" },\n response,\n }),\n );\n }\n\n onTranslation(response: TranscriptionResponse): this {\n return this.addFixture(\n this.own({\n match: { endpoint: \"translation\" },\n response,\n }),\n );\n }\n\n onVideo(prompt: string | RegExp, response: VideoResponse): this {\n return this.addFixture(\n this.own({\n match: { userMessage: prompt, endpoint: \"video\" },\n response,\n }),\n );\n }\n\n onAudio(input: string | RegExp, response: AudioResponse): this {\n return this.addFixture(this.own({ match: { userMessage: input }, response }));\n }\n\n onSoundEffect(text: string | RegExp, response: AudioResponse): this {\n return this.addFixture(\n this.own({\n match: { userMessage: text, endpoint: \"audio-gen\" },\n response,\n }),\n );\n }\n\n onMusic(prompt: string | RegExp, response: AudioResponse): this {\n return this.addFixture(\n this.own({\n match: { userMessage: prompt, endpoint: \"audio-gen\" },\n response,\n }),\n );\n }\n\n onElevenLabsTTS(text: string | RegExp, response: AudioResponse): this {\n return this.addFixture(\n this.own({\n match: { userMessage: text, endpoint: \"elevenlabs-tts\" },\n response,\n }),\n );\n }\n\n onElevenLabsVoiceDesign(description: string | RegExp, response: VoiceDesignResponse): this {\n return this.addFixture(\n this.own({\n match: { userMessage: description, endpoint: \"elevenlabs-voice-design\" },\n response: voiceDesignToJson(response),\n }),\n );\n }\n\n onFalAudio(prompt: string | RegExp, response: AudioResponse, model?: string): this {\n return this.addFixture(\n this.own({\n match: { userMessage: prompt, endpoint: \"fal-audio\", ...(model ? { model } : {}) },\n response,\n }),\n );\n }\n\n // fal.queue.* is the dominant client API; onFalRun is a sync alias.\n //\n // `opts.billableUnits` rides through to the completed `queue-result`\n // response's `x-fal-billable-units` header (emitted alongside\n // `x-fal-request-id`), letting consumers like `@tanstack/ai-fal` surface a\n // billed-units value on replay. Omit it to preserve the header-less default.\n onFalQueue(modelOrPrompt: string | RegExp, response: unknown, opts?: FalQueueOpts): this {\n const { billableUnits, ...fixtureOpts } = opts ?? {};\n return this.addFixture(\n this.own({\n match: { model: modelOrPrompt, endpoint: \"fal\" },\n response: { json: response, ...(billableUnits != null ? { billableUnits } : {}) },\n ...fixtureOpts,\n }),\n );\n }\n\n onFalRun(modelOrPrompt: string | RegExp, response: unknown, opts?: FalQueueOpts): this {\n return this.onFalQueue(modelOrPrompt, response, opts);\n }\n\n /**\n * Register a fal.ai image fixture. Wraps an `ImageResponse` (the shape used\n * by `onImage` and OpenAI/Azure image fixtures) into fal's image envelope\n * before storing it as a `RawJSONResponse`. Defaults `width`/`height` to\n * 1024 when the fixture's `ImageItem` doesn't carry them.\n */\n onFalImage(modelOrPrompt: string | RegExp, response: ImageResponse, opts?: FalQueueOpts): this {\n return this.onFalQueue(modelOrPrompt, imageResponseToFalJson(response), opts);\n }\n\n /**\n * Register a fal.ai video fixture. Wraps a `VideoResponse` into fal's video\n * envelope (`{ video: { url, content_type, file_name, file_size }, seed }`)\n * before storing it as a `RawJSONResponse`.\n */\n onFalVideo(modelOrPrompt: string | RegExp, response: VideoResponse, opts?: FalQueueOpts): this {\n return this.onFalQueue(modelOrPrompt, videoResponseToFalJson(response), opts);\n }\n\n // ---- Service mock convenience methods ----\n\n onSearch(pattern: string | RegExp, results: SearchResult[]): this {\n this.searchFixtures.push({ match: pattern, results });\n return this;\n }\n\n onRerank(pattern: string | RegExp, results: RerankResult[]): this {\n this.rerankFixtures.push({ match: pattern, results });\n return this;\n }\n\n onModerate(pattern: string | RegExp, result: ModerationResult): this {\n this.moderationFixtures.push({ match: pattern, result });\n return this;\n }\n\n /**\n * Queue a one-shot error that will be returned for the next matching\n * request, then automatically removed. Implemented as an internal fixture\n * inserted at the front of the fixture list; see `queueOneShotError` for\n * the endpoint gate and the served-not-evaluated consumption rule.\n */\n nextRequestError(\n status: number,\n errorBody?: { message?: string; type?: string; code?: string },\n ): this {\n // Same gate as `POST /__aimock/error` and fixture validation — this is the\n // form the docs use, and an unchecked status reaches `res.writeHead` on\n // the next matched request: 99/1000 throw there (the injected error is\n // lost and the caller sees a generic 500), and 1xx hangs the request until\n // the client times out. Throwing HERE names the offending call instead.\n if (!isInjectableStatus(status)) {\n throw new RangeError(\n `nextRequestError: invalid status ${String(status)} — must be ${INJECTED_STATUS_RANGE}`,\n );\n }\n queueOneShotError(this.fixtures, status, errorBody);\n return this;\n }\n\n // ---- Mounts ----\n\n mount(path: string, handler: Mountable): this {\n // W4: an auto-mounted MCPMock at `path` takes its requests first (dispatch\n // takes the first match), so the new mount is shadowed; so is one at\n // `path` plus \"/\", whose root request the auto-mount answers as its own.\n // An auto-mount lets any other sub-path fall through: that is no shadow.\n const shadowedBy = this.serverInstance\n ? this.mounts.find(\n (m) => isAutoMount(m.handler) && (path === m.path || path === m.path + \"/\"),\n )\n : undefined;\n this.mounts.push({ path, handler });\n\n // If server is already running, wire up journal, registry, logger and baseUrl\n // immediately so late mounts behave identically to pre-start mounts.\n if (this.serverInstance) {\n if (handler.setJournal) handler.setJournal(this.serverInstance.journal);\n if (handler.setBaseUrl) handler.setBaseUrl(this.serverInstance.url + path);\n const registry = this.serverInstance.defaults.registry;\n if (registry && handler.setRegistry) handler.setRegistry(registry);\n const logger = this.serverInstance.defaults.logger;\n if (handler.setLogger) handler.setLogger(logger);\n // B11/R12: only aimock's own MCPMock takes the replay speed; a custom\n // Mountable is never called (1.44.0 had no such hook).\n if (handler instanceof MCPMock) {\n handler.setReplaySpeed(this.serverInstance.defaults.replaySpeed);\n }\n if (shadowedBy) {\n logger.warn(\n build(\n msg`MCP fakes: the mount at ${quote(path)} is shadowed by the MCP mock auto-mounted at ${quote(shadowedBy.path)}; its requests reach the auto-mount`,\n ),\n );\n }\n }\n\n return this;\n }\n\n // ---- Journal proxies ----\n\n getRequests(): import(\"./types.js\").JournalEntry[] {\n return this.journal.getAll();\n }\n\n getLastRequest(): import(\"./types.js\").JournalEntry | null {\n return this.journal.getLast();\n }\n\n /**\n * Clear the request journal. Fixture match-counts (sequencing state) are\n * left intact — use `resetMatchCounts()` for those.\n */\n clearRequests(): void {\n this.journal.clearEntries();\n }\n\n /**\n * Reset fixture match counts, and the MCP fakes consumption state of every\n * mount (R6), for `testId` or for all test ids. Fakes stay loaded.\n */\n resetMatchCounts(testId?: string): this {\n if (this.serverInstance) {\n this.serverInstance.journal.clearMatchCounts(testId);\n }\n for (const { handler } of this.mounts) handler.resetScenarioState?.(testId);\n return this;\n }\n\n // ---- Chaos ----\n\n /**\n * Set the server-wide chaos baseline. Writes through to a RUNNING server as\n * well as the construction options: an untagged `POST /__aimock/chaos`\n * installs a baseline override that shadows `options.chaos`, and without this\n * the call would silently do nothing until the next `reset()`. Per-testId\n * overrides still win over the baseline.\n */\n setChaos(config: ChaosConfig): this {\n this.options.chaos = config;\n if (this.serverInstance) this.serverInstance.defaults.chaos = config;\n return this;\n }\n\n /**\n * Turn chaos off everywhere: the construction config, the runtime baseline,\n * and every per-testId override (assigning `undefined` is the full-clear\n * signal the `defaults.chaos` setter defines).\n */\n clearChaos(): this {\n delete this.options.chaos;\n if (this.serverInstance) this.serverInstance.defaults.chaos = undefined;\n return this;\n }\n\n // ---- Semantic misbehavior ----\n\n /** Replace construction/runtime baselines without changing named test overrides. */\n setMisbehavior(config: MisbehaviorConfig | MisbehaviorFaultId): this {\n if (!this.misbehaviorEnabled) {\n throw new TypeError(\n \"setMisbehavior: misbehavior is not enabled; construct with enableMisbehavior: true\",\n );\n }\n const parsed = parseMisbehavior(config);\n if (!parsed.ok) throw new TypeError(`${parsed.issue.rule}: ${parsed.issue.message}`);\n this.options.misbehavior = parsed.config;\n const defaults = this.serverInstance?.defaults;\n if (defaults?.misbehavior) {\n defaults.misbehavior.baseline =\n parsed.config.seed === \"random\"\n ? { ...parsed.config, seed: resolveMisbehaviorSeed(parsed.config.seed, defaults.logger) }\n : parsed.config;\n if (defaults.misbehavior.byTestId.has(DEFAULT_TEST_ID)) {\n defaults.misbehavior.byTestId.set(DEFAULT_TEST_ID, defaults.misbehavior.baseline);\n }\n }\n return this;\n }\n\n /** Clear the baseline and all runtime overrides, before or after start. */\n clearMisbehavior(): this {\n delete this.options.misbehavior;\n const scope = this.serverInstance?.defaults.misbehavior;\n if (scope) {\n scope.baseline = undefined;\n scope.byTestId.clear();\n }\n return this;\n }\n\n // ---- Recording ----\n\n enableRecording(config: RecordConfig): this {\n this.options.record = config;\n return this;\n }\n\n disableRecording(): this {\n delete this.options.record;\n return this;\n }\n\n // ---- Reset ----\n\n /**\n * Full reset — the in-process equivalent of `POST /__aimock/reset`. Shares\n * one implementation with the control-API route so the two cannot drift.\n *\n * The one deliberate difference: search / rerank / moderation fixtures are\n * also cleared here. Those are registered through this class only — the\n * control API has no route that creates them, so the HTTP reset can neither\n * reach nor observe them.\n *\n * NOT ALL OF THIS IS PER-INSTANCE. `performFullReset` clears module-global\n * state as well: the Gemini interaction and event-id counters\n * (`resetInteractionCounter` / `resetEventIdCounter` in\n * `./gemini-interactions.js`), the fal.ai job/queue maps (`falJobs`,\n * `falQueueStates`), the fine-tuning job store (`clearFineTuningStore`),\n * and the ElevenLabs Voice Design store (`clearElevenLabsVoices`). With two\n * `LLMock` instances live in one process,\n * `a.reset()` rewinds the Gemini id sequence that `b` is mid-way through —\n * `b` then re-emits `aimock-int-0` / `evt_1`, ids it has already handed\n * out — and drops `b`'s in-flight fal jobs. Give each instance its own\n * process (or its own vitest worker) if that matters.\n *\n * The global stores are cleared even before `start()`, when there is no\n * server instance to reset.\n */\n reset(): this {\n this.searchFixtures.length = 0;\n this.rerankFixtures.length = 0;\n this.moderationFixtures.length = 0;\n // `ServerInstance` carries no mounts, so the fakes are unloaded here (R4).\n this.unloadMcpFakes();\n performFullReset(this.fixtures, this.serverInstance);\n return this;\n }\n\n // ---- Server lifecycle ----\n\n async start(): Promise<string> {\n if (this.serverInstance) {\n throw new Error(\"Server already started\");\n }\n const serviceFixtures = {\n search: this.searchFixtures,\n rerank: this.rerankFixtures,\n moderation: this.moderationFixtures,\n mcpFakes: [...this.mcpFakeBuffer],\n };\n this.serverInstance = await (this.resolvedInboundAuth\n ? createServerWithResolvedAuth(\n this.fixtures,\n this.options,\n this.resolvedInboundAuth,\n this.mounts,\n serviceFixtures,\n )\n : createServer(this.fixtures, this.options, this.mounts, serviceFixtures));\n const countIdentity = (fixture: Fixture) => this.fixtureCountOrigins.get(fixture) ?? fixture;\n this.serverInstance.journal.configureFixtureCountIdentity(countIdentity);\n // The server holds the buffered fakes now (W2). A start that rejects took\n // its hand-off back, so the buffer is kept for a retry.\n this.mcpFakeBuffer.length = 0;\n return this.serverInstance.url;\n }\n\n closeLiveSessions(testId?: string): this {\n this.serverInstance?.closeLiveSessions(testId);\n return this;\n }\n\n async stop(): Promise<void> {\n if (!this.serverInstance) {\n throw new Error(\"Server not started\");\n }\n const { server } = this.serverInstance;\n await new Promise<void>((resolve, reject) => {\n server.close((err: Error | undefined) => (err ? reject(err) : resolve()));\n });\n this.serverInstance = null;\n }\n\n // ---- Accessors ----\n\n get journal(): Journal {\n if (!this.serverInstance) {\n throw new Error(\"Server not started\");\n }\n return this.serverInstance.journal;\n }\n\n get url(): string {\n if (!this.serverInstance) {\n throw new Error(\"Server not started\");\n }\n return this.serverInstance.url;\n }\n\n get baseUrl(): string {\n return this.url;\n }\n\n get port(): number {\n const parsed = new URL(this.url); // this.url throws if not started\n if (!parsed.port) {\n throw new Error(`Server URL has no explicit port: ${this.url}`);\n }\n return parseInt(parsed.port, 10);\n }\n\n // ---- Static factory ----\n\n static async create(options?: MockServerOptions): Promise<LLMock> {\n const instance = new LLMock(options);\n await instance.start();\n return instance;\n }\n}\n\n/** @internal Configuration startup preserves a policy resolved from the selected source. */\nexport function createLLMockWithResolvedAuth(\n options: MockServerOptions,\n resolvedAuth: ResolvedInboundAuth,\n): LLMock {\n return new LLMock(options, resolvedAuth);\n}\n"],"mappings":";;;;;;;;;;;;;;AAuEA,IAAa,SAAb,MAAa,OAAO;CAClB,AAAQ,WAAsB,EAAE;CAChC,AAAQ,kBAAkB;CAC1B,AAAiB,sCAAsB,IAAI,SAA2B;;;;;CAKtE,AAAiB,8BAAc,IAAI,SAAkB;CACrD,AAAQ,iBAAkC,EAAE;CAC5C,AAAQ,iBAAkC,EAAE;CAC5C,AAAQ,qBAA0C,EAAE;CACpD,AAAQ,SAAsD,EAAE;;CAEhE,AAAQ,gBAAiC,EAAE;;CAE3C,AAAQ,iBAAwC;CAChD,AAAQ;CACR,AAAiB;CAEjB,YAAY,SAA6B,qBAA2C;AAElF,0BAAwB,KAAK,SAAS;AACtC,OAAK,UAAU,WAAW,EAAE;AAC5B,MAAI,KAAK,QAAQ,SAAS,OAAW,sBAAqB,KAAK,QAAQ,KAAK;AAG5E,MAAI,KAAK,sBAAsB,KAAK,QAAQ,gBAAgB,OAC1D,MAAK,eAAe,KAAK,QAAQ,YAAY;AAE/C,OAAK,sBAAsB;;;CAI7B,IAAY,qBAA8B;AACxC,SAAO,KAAK,QAAQ,sBAAsB;;CAK5C,AAAQ,iBAAiB,SAA2B;EAClD,MAAM,qBAAqB,KAAK;AAEhC,MAAI,mBAAoB,8BAA6B,QAAQ;EAG7D,MAAM,MAAM,KAAK,YAAY,OAAO,QAAQ;EAC5C,MAAM,gBAAgB,MAAM,SAAa,KAAK,oBAAoB,IAAI,QAAQ,IAAI;EAClF,MAAM,mBAAmB,8BAA8B,QAAQ;EAC/D,MAAM,gBAAgB,qBAAqB,UAAa,aAAa,KAAK,iBAAiB;EAC3F,MAAM,WAAW,gBAAgB,QAAQ,KAAK,sBAAsB;AAQpE,MAAI,iBAAiB,sBAAsB,CAAC,IAAK,WAAU,EAAE,GAAG,SAAS;AACzE,MAAI,sBAAsB,QAAQ,gBAAgB,QAAW;GAC3D,MAAM,SAAS,iBAAiB,QAAQ,YAAY;AACpD,OAAI,OAAO,GAAI,WAAU;IAAE,GAAG;IAAS,aAAa,OAAO;IAAQ;GACnE,MAAM,QAAQ,OAAO,KAAK,2BAA2B,QAAQ,GAAG,OAAO;AACvE,OAAI,MACF,OAAM,IAAI,uBAAuB;IAC/B,MAAM,MAAM;IACZ,MAAM;IACN,QAAQ,MAAM;IACf,CAAC;;AAIN,MACE,eAAe,QAAQ,SAAS,IAC/B,QAAQ,MAAM,aAAa,iBAAiB,OAAO,QAAQ,aAAa,WAEzE,WAAU;GACR,GAAG;GACH,OAAO,EAAE,GAAG,QAAQ,OAAO;GAC3B,UAAU,qBAAqB,QAAQ,UAAU,KAAK,QAAQ,KAAK;GACpE;WACQ,kBAAkB,UAAa,YAAY,cACpD,MAAK,oBAAoB,IAAI,SAAS,cAAc;AAEtD,gCAA8B,SAAS,SAAS;AAChD,MAAI,sBAAsB,QAAQ,gBAAgB,OAChD,+BAA8B,QAAQ;AAKxC,SAAO;;CAGT,WAAW,SAAwB;AACjC,OAAK,SAAS,KAAK,KAAK,iBAAiB,QAAQ,CAAC;AAClD,SAAO;;CAGT,YAAY,UAA2B;AACrC,OAAK,SAAS,KAAK,GAAG,SAAS,KAAK,YAAY,KAAK,iBAAiB,QAAQ,CAAC,CAAC;AAChF,SAAO;;CAGT,eAAe,SAAwB;AACrC,OAAK,SAAS,QAAQ,KAAK,iBAAiB,QAAQ,CAAC;AACrD,SAAO;;;CAIT,AAAQ,IAAI,SAA2B;AACrC,OAAK,YAAY,IAAI,QAAQ;AAC7B,SAAO;;CAGT,cAAkC;AAChC,SAAO,KAAK;;;;;;;;;CAUd,gBAAgB,UAAwB;AACtC,SAAO,KAAK,aAAa,4BAA4B,UAAU,QAAW,KAAK,QAAQ,KAAK,CAAC;;;CAI/F,eAAe,SAAuB;AACpC,SAAO,KAAK,aACV,gCAAgC,SAAS,QAAW,KAAK,QAAQ,KAAK,CACvE;;;;;;;;;CAUH,AAAQ,aAAa,QAAoC;AAEvD,MAAI,KAAK,mBAAoB,QAAO,SAAS,QAAQ,6BAA6B;AAClF,MAAI,KAAK,QAAQ,mBAAmB,WAClC,QAAO,SAAS,QAAQ,kCAAkC;AAE5D,MAAI,OAAO,SAAS,SAAS,EAC3B,KAAI,KAAK,eACP,MAAK,mBAAmB,KAAK,gBAAgB,OAAO,SAAS;OACxD;AACL,iBACE,KAAK,QACL,CAAC,GAAG,KAAK,eAAe,GAAG,OAAO,SAAS,EAC3C,IAAI,IAAI,OAAO,SAAS,CACzB;AACD,QAAK,cAAc,KAAK,GAAG,OAAO,SAAS;;AAG/C,OAAK,SAAS,KAAK,GAAG,OAAO,SAAS;AACtC,SAAO;;;;;;;;;;;CAYT,AAAQ,mBAAmB,QAAwB,SAAgC;EACjF,MAAM,EAAE,YAAY,UAAU,cAAc,KAAK,QAAQ,QAAQ;EAEjE,MAAM,SAAS,OAAO,SAAS;AAC/B,OAAK,MAAM,EAAE,SAAS,WAAW,YAAY;GAC3C,MAAM,SAAS,QAAQ,cAAc,OAAO,EAAE,MAAM,QAAQ,CAAC;AAC7D,QAAK,MAAM,WAAW,QAAQ,YAAY,EAAE,CAAE,QAAO,KAAK,QAAQ,QAAQ;;EAE5E,MAAM,WAAW,OAAO,SAAS;AACjC,OAAK,MAAM,EAAE,MAAM,WAAW,OAAO;GACnC,MAAM,SAAS,gBACb,KAAK,QACL,MACA;IACE,SAAS,OAAO;IAChB,GAAI,WAAW,EAAE,UAAU,GAAG,EAAE;IAChC;IACA,aAAa,OAAO,SAAS;IAC9B,EACD,OACD;AAED,OAAI,cAAc,OAAQ;GAC1B,MAAM,SAAS,OAAO,QAAQ,cAAc,OAAO,EAAE,MAAM,QAAQ,CAAC;AACpE,QAAK,MAAM,WAAW,QAAQ,YAAY,EAAE,CAAE,QAAO,KAAK,QAAQ,QAAQ;;;;CAK9E,AAAQ,iBAAuB;AAC7B,OAAK,cAAc,SAAS;AAC5B,OAAK,MAAM,EAAE,aAAa,KAAK,OAAQ,SAAQ,iBAAiB;;;;;;CAOlE,oBAAoB,OAA0C;EAC5D,IAAI;AACJ,MAAI,OAAO,UAAU,SACnB,KAAI;AACF,aAAU,KAAK,MAAM,MAAM;WACpB,KAAK;AACZ,SAAM,IAAI,MACR,uCAAuC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GACxF;;MAGH,WAAU;AAEZ,MAAI,CAAC,MAAM,QAAQ,QAAQ,CACzB,OAAM,IAAI,UACR,6GACD;EAEH,MAAM,YAAY,QAAQ,KAAK,UAAU;GACvC,MAAM,WAAW,QAAQ,KAAK;GAC9B,MAAM,UAAU,eAAe,OAAO,QAAW,KAAK,QAAQ,MAAM;IAClE,MAAM;IACN,OAAO;IACR,CAAC;AACF,iCAA8B,SAAS,SAAS;AAChD,OAAI,KAAK,mBAAoB,8BAA6B,QAAQ;AAClE,OAAI,KAAK,QAAQ,mBAAmB,WAAY,mCAAkC,QAAQ;AAC1F,UAAO;IACP;EAGF,MAAM,SADS,iBAAiB,WAAW,KAAK,QAAQ,KAAK,CACvC,QAAQ,MAAM,EAAE,aAAa,QAAQ;AAC3D,MAAI,OAAO,SAAS,EAClB,OAAM,IAAI,MAAM,8BAA8B,KAAK,UAAU,OAAO,GAAG;AAEzE,OAAK,SAAS,KAAK,GAAG,UAAU;AAChC,SAAO;;CAMT,gBAAsB;AACpB,oBAAkB,KAAK,SAAS;AAChC,OAAK,gBAAgB,QAAQ,0BAA0B;AACvD,OAAK,gBAAgB;AACrB,SAAO;;CAKT,GACE,OACA,UACA,MACM;EACN,IAAI,aAAgD;AACpD,MAAI,OAAO,aAAa,YAAY;AAClC,gBAAa,kBAAkB,UAAU,KAAK,QAAQ,KAAK;AAG3D,OAAI,KAAK,QAAQ,mBAAmB,cAAc,CAAC,eAAe,WAAW,CAC3E,6BAA4B,WAAiD;;AAGjF,SAAO,KAAK,WAAW,KAAK,IAAI;GAAE;GAAO,UAAU;GAAY,GAAG;GAAM,CAAC,CAAC;;CAG5E,OACE,OACA,YACA,MACM;AACN,SAAO,KAAK,GAAG;GAAE,GAAG;GAAO,UAAU;GAAe,EAAE,EAAE,MAAM,YAAY,EAAE,KAAK;;CAGnF,UACE,SACA,UACA,MACM;AACN,SAAO,KAAK,GAAG,EAAE,aAAa,SAAS,EAAE,UAAU,KAAK;;CAG1D,YACE,SACA,UACA,MACM;AACN,SAAO,KAAK,GAAG,EAAE,WAAW,SAAS,EAAE,UAAU,KAAK;;CAGxD,aAAa,SAA0B,aAA8B,MAA0B;EAC7F,MAAM,UAAU,OAAO,gBAAgB,WAAW,cAAc,KAAK,UAAU,YAAY;AAC3F,SAAO,KAAK,GAAG;GAAE,aAAa;GAAS,gBAAgB;GAAe,EAAE,EAAE,SAAS,EAAE,KAAK;;CAG5F,WACE,MACA,UACA,MACM;AACN,SAAO,KAAK,GAAG,EAAE,UAAU,MAAM,EAAE,UAAU,KAAK;;CAGpD,aACE,IACA,UACA,MACM;AACN,SAAO,KAAK,GAAG,EAAE,YAAY,IAAI,EAAE,UAAU,KAAK;;CAGpD,OACE,MACA,SACA,UACA,MACM;AACN,SAAO,KAAK,GAAG;GAAE,aAAa;GAAS,WAAW;GAAM,EAAE,UAAU,KAAK;;CAG3E,QAAQ,QAAyB,UAA+B;AAC9D,SAAO,KAAK,WACV,KAAK,IAAI;GACP,OAAO;IAAE,aAAa;IAAQ,UAAU;IAAS;GACjD;GACD,CAAC,CACH;;CAGH,SAAS,OAAwB,UAA+B;AAC9D,SAAO,KAAK,WACV,KAAK,IAAI;GACP,OAAO;IAAE,aAAa;IAAO,UAAU;IAAU;GACjD;GACD,CAAC,CACH;;CAGH,gBAAgB,UAAuC;AACrD,SAAO,KAAK,WACV,KAAK,IAAI;GACP,OAAO,EAAE,UAAU,iBAAiB;GACpC;GACD,CAAC,CACH;;CAGH,cAAc,UAAuC;AACnD,SAAO,KAAK,WACV,KAAK,IAAI;GACP,OAAO,EAAE,UAAU,eAAe;GAClC;GACD,CAAC,CACH;;CAGH,QAAQ,QAAyB,UAA+B;AAC9D,SAAO,KAAK,WACV,KAAK,IAAI;GACP,OAAO;IAAE,aAAa;IAAQ,UAAU;IAAS;GACjD;GACD,CAAC,CACH;;CAGH,QAAQ,OAAwB,UAA+B;AAC7D,SAAO,KAAK,WAAW,KAAK,IAAI;GAAE,OAAO,EAAE,aAAa,OAAO;GAAE;GAAU,CAAC,CAAC;;CAG/E,cAAc,MAAuB,UAA+B;AAClE,SAAO,KAAK,WACV,KAAK,IAAI;GACP,OAAO;IAAE,aAAa;IAAM,UAAU;IAAa;GACnD;GACD,CAAC,CACH;;CAGH,QAAQ,QAAyB,UAA+B;AAC9D,SAAO,KAAK,WACV,KAAK,IAAI;GACP,OAAO;IAAE,aAAa;IAAQ,UAAU;IAAa;GACrD;GACD,CAAC,CACH;;CAGH,gBAAgB,MAAuB,UAA+B;AACpE,SAAO,KAAK,WACV,KAAK,IAAI;GACP,OAAO;IAAE,aAAa;IAAM,UAAU;IAAkB;GACxD;GACD,CAAC,CACH;;CAGH,wBAAwB,aAA8B,UAAqC;AACzF,SAAO,KAAK,WACV,KAAK,IAAI;GACP,OAAO;IAAE,aAAa;IAAa,UAAU;IAA2B;GACxE,UAAU,kBAAkB,SAAS;GACtC,CAAC,CACH;;CAGH,WAAW,QAAyB,UAAyB,OAAsB;AACjF,SAAO,KAAK,WACV,KAAK,IAAI;GACP,OAAO;IAAE,aAAa;IAAQ,UAAU;IAAa,GAAI,QAAQ,EAAE,OAAO,GAAG,EAAE;IAAG;GAClF;GACD,CAAC,CACH;;CASH,WAAW,eAAgC,UAAmB,MAA2B;EACvF,MAAM,EAAE,eAAe,GAAG,gBAAgB,QAAQ,EAAE;AACpD,SAAO,KAAK,WACV,KAAK,IAAI;GACP,OAAO;IAAE,OAAO;IAAe,UAAU;IAAO;GAChD,UAAU;IAAE,MAAM;IAAU,GAAI,iBAAiB,OAAO,EAAE,eAAe,GAAG,EAAE;IAAG;GACjF,GAAG;GACJ,CAAC,CACH;;CAGH,SAAS,eAAgC,UAAmB,MAA2B;AACrF,SAAO,KAAK,WAAW,eAAe,UAAU,KAAK;;;;;;;;CASvD,WAAW,eAAgC,UAAyB,MAA2B;AAC7F,SAAO,KAAK,WAAW,eAAe,uBAAuB,SAAS,EAAE,KAAK;;;;;;;CAQ/E,WAAW,eAAgC,UAAyB,MAA2B;AAC7F,SAAO,KAAK,WAAW,eAAe,uBAAuB,SAAS,EAAE,KAAK;;CAK/E,SAAS,SAA0B,SAA+B;AAChE,OAAK,eAAe,KAAK;GAAE,OAAO;GAAS;GAAS,CAAC;AACrD,SAAO;;CAGT,SAAS,SAA0B,SAA+B;AAChE,OAAK,eAAe,KAAK;GAAE,OAAO;GAAS;GAAS,CAAC;AACrD,SAAO;;CAGT,WAAW,SAA0B,QAAgC;AACnE,OAAK,mBAAmB,KAAK;GAAE,OAAO;GAAS;GAAQ,CAAC;AACxD,SAAO;;;;;;;;CAST,iBACE,QACA,WACM;AAMN,MAAI,CAAC,mBAAmB,OAAO,CAC7B,OAAM,IAAI,WACR,oCAAoC,OAAO,OAAO,CAAC,aAAa,wBACjE;AAEH,oBAAkB,KAAK,UAAU,QAAQ,UAAU;AACnD,SAAO;;CAKT,MAAM,MAAc,SAA0B;EAK5C,MAAM,aAAa,KAAK,iBACpB,KAAK,OAAO,MACT,MAAM,YAAY,EAAE,QAAQ,KAAK,SAAS,EAAE,QAAQ,SAAS,EAAE,OAAO,KACxE,GACD;AACJ,OAAK,OAAO,KAAK;GAAE;GAAM;GAAS,CAAC;AAInC,MAAI,KAAK,gBAAgB;AACvB,OAAI,QAAQ,WAAY,SAAQ,WAAW,KAAK,eAAe,QAAQ;AACvE,OAAI,QAAQ,WAAY,SAAQ,WAAW,KAAK,eAAe,MAAM,KAAK;GAC1E,MAAM,WAAW,KAAK,eAAe,SAAS;AAC9C,OAAI,YAAY,QAAQ,YAAa,SAAQ,YAAY,SAAS;GAClE,MAAM,SAAS,KAAK,eAAe,SAAS;AAC5C,OAAI,QAAQ,UAAW,SAAQ,UAAU,OAAO;AAGhD,OAAI,mBAAmB,QACrB,SAAQ,eAAe,KAAK,eAAe,SAAS,YAAY;AAElE,OAAI,WACF,QAAO,KACL,MACE,GAAG,2BAA2B,MAAM,KAAK,CAAC,+CAA+C,MAAM,WAAW,KAAK,CAAC,qCACjH,CACF;;AAIL,SAAO;;CAKT,cAAmD;AACjD,SAAO,KAAK,QAAQ,QAAQ;;CAG9B,iBAA2D;AACzD,SAAO,KAAK,QAAQ,SAAS;;;;;;CAO/B,gBAAsB;AACpB,OAAK,QAAQ,cAAc;;;;;;CAO7B,iBAAiB,QAAuB;AACtC,MAAI,KAAK,eACP,MAAK,eAAe,QAAQ,iBAAiB,OAAO;AAEtD,OAAK,MAAM,EAAE,aAAa,KAAK,OAAQ,SAAQ,qBAAqB,OAAO;AAC3E,SAAO;;;;;;;;;CAYT,SAAS,QAA2B;AAClC,OAAK,QAAQ,QAAQ;AACrB,MAAI,KAAK,eAAgB,MAAK,eAAe,SAAS,QAAQ;AAC9D,SAAO;;;;;;;CAQT,aAAmB;AACjB,SAAO,KAAK,QAAQ;AACpB,MAAI,KAAK,eAAgB,MAAK,eAAe,SAAS,QAAQ;AAC9D,SAAO;;;CAMT,eAAe,QAAsD;AACnE,MAAI,CAAC,KAAK,mBACR,OAAM,IAAI,UACR,qFACD;EAEH,MAAM,SAAS,iBAAiB,OAAO;AACvC,MAAI,CAAC,OAAO,GAAI,OAAM,IAAI,UAAU,GAAG,OAAO,MAAM,KAAK,IAAI,OAAO,MAAM,UAAU;AACpF,OAAK,QAAQ,cAAc,OAAO;EAClC,MAAM,WAAW,KAAK,gBAAgB;AACtC,MAAI,UAAU,aAAa;AACzB,YAAS,YAAY,WACnB,OAAO,OAAO,SAAS,WACnB;IAAE,GAAG,OAAO;IAAQ,MAAM,uBAAuB,OAAO,OAAO,MAAM,SAAS,OAAO;IAAE,GACvF,OAAO;AACb,OAAI,SAAS,YAAY,SAAS,IAAI,gBAAgB,CACpD,UAAS,YAAY,SAAS,IAAI,iBAAiB,SAAS,YAAY,SAAS;;AAGrF,SAAO;;;CAIT,mBAAyB;AACvB,SAAO,KAAK,QAAQ;EACpB,MAAM,QAAQ,KAAK,gBAAgB,SAAS;AAC5C,MAAI,OAAO;AACT,SAAM,WAAW;AACjB,SAAM,SAAS,OAAO;;AAExB,SAAO;;CAKT,gBAAgB,QAA4B;AAC1C,OAAK,QAAQ,SAAS;AACtB,SAAO;;CAGT,mBAAyB;AACvB,SAAO,KAAK,QAAQ;AACpB,SAAO;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BT,QAAc;AACZ,OAAK,eAAe,SAAS;AAC7B,OAAK,eAAe,SAAS;AAC7B,OAAK,mBAAmB,SAAS;AAEjC,OAAK,gBAAgB;AACrB,mBAAiB,KAAK,UAAU,KAAK,eAAe;AACpD,SAAO;;CAKT,MAAM,QAAyB;AAC7B,MAAI,KAAK,eACP,OAAM,IAAI,MAAM,yBAAyB;EAE3C,MAAM,kBAAkB;GACtB,QAAQ,KAAK;GACb,QAAQ,KAAK;GACb,YAAY,KAAK;GACjB,UAAU,CAAC,GAAG,KAAK,cAAc;GAClC;AACD,OAAK,iBAAiB,OAAO,KAAK,sBAC9B,6BACE,KAAK,UACL,KAAK,SACL,KAAK,qBACL,KAAK,QACL,gBACD,GACD,aAAa,KAAK,UAAU,KAAK,SAAS,KAAK,QAAQ,gBAAgB;EAC3E,MAAM,iBAAiB,YAAqB,KAAK,oBAAoB,IAAI,QAAQ,IAAI;AACrF,OAAK,eAAe,QAAQ,8BAA8B,cAAc;AAGxE,OAAK,cAAc,SAAS;AAC5B,SAAO,KAAK,eAAe;;CAG7B,kBAAkB,QAAuB;AACvC,OAAK,gBAAgB,kBAAkB,OAAO;AAC9C,SAAO;;CAGT,MAAM,OAAsB;AAC1B,MAAI,CAAC,KAAK,eACR,OAAM,IAAI,MAAM,qBAAqB;EAEvC,MAAM,EAAE,WAAW,KAAK;AACxB,QAAM,IAAI,SAAe,SAAS,WAAW;AAC3C,UAAO,OAAO,QAA4B,MAAM,OAAO,IAAI,GAAG,SAAS,CAAE;IACzE;AACF,OAAK,iBAAiB;;CAKxB,IAAI,UAAmB;AACrB,MAAI,CAAC,KAAK,eACR,OAAM,IAAI,MAAM,qBAAqB;AAEvC,SAAO,KAAK,eAAe;;CAG7B,IAAI,MAAc;AAChB,MAAI,CAAC,KAAK,eACR,OAAM,IAAI,MAAM,qBAAqB;AAEvC,SAAO,KAAK,eAAe;;CAG7B,IAAI,UAAkB;AACpB,SAAO,KAAK;;CAGd,IAAI,OAAe;EACjB,MAAM,SAAS,IAAI,IAAI,KAAK,IAAI;AAChC,MAAI,CAAC,OAAO,KACV,OAAM,IAAI,MAAM,oCAAoC,KAAK,MAAM;AAEjE,SAAO,SAAS,OAAO,MAAM,GAAG;;CAKlC,aAAa,OAAO,SAA8C;EAChE,MAAM,WAAW,IAAI,OAAO,QAAQ;AACpC,QAAM,SAAS,OAAO;AACtB,SAAO;;;;AAKX,SAAgB,6BACd,SACA,cACQ;AACR,QAAO,IAAI,OAAO,SAAS,aAAa"}
@@ -72,6 +72,7 @@ function pushAutoMount(mounts, path, handler, wiring, logger) {
72
72
  if (wiring.journal) handler.setJournal(wiring.journal);
73
73
  if (wiring.registry) handler.setRegistry(wiring.registry);
74
74
  if (wiring.logger) handler.setLogger(wiring.logger);
75
+ handler.setReplaySpeed(wiring.replaySpeed ?? 1);
75
76
  const others = mounts.filter((m) => m.path !== path && isFakeMount(m.handler));
76
77
  mounts.push({
77
78
  path,
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-fakes-mount.cjs","names":["quote","build","MCPMock","MCP_FAKES_DEFAULT_MOUNT","FixtureLoadError","blockIdOf","McpFakeStore","McpFakesAddError"],"sources":["../src/mcp-fakes-mount.ts"],"sourcesContent":["/**\n * Where MCP fakes go on a server (spec 5.2): find the mount that serves a\n * block's `mount` path, auto-mount an `MCPMock` when none does (W3, W6), and\n * the start-time hand-off of `ServiceFixtures.mcpFakes` (F2).\n */\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\nimport type { McpFakeSource, Mountable } from \"./types.js\";\nimport { FixtureLoadError } from \"./fixture-loader.js\";\nimport { MCPMock } from \"./mcp-mock.js\";\nimport {\n MCP_FAKES_DEFAULT_MOUNT,\n McpFakeStore,\n McpFakesAddError,\n blockIdOf,\n type McpFakeIssue,\n} from \"./mcp-fakes.js\";\nimport { build, msg, quote, type Message } from \"./message-text.js\";\n\nexport type MountList = Array<{ path: string; handler: Mountable }>;\n\n/** Dependencies an auto-mount gets when it is created (after start: W4 wiring). */\nexport interface FakeMountWiring {\n journal?: Journal;\n registry?: MetricsRegistry;\n logger?: Logger;\n}\n\n/**\n * The control-API prefix. The server hands every request whose path starts\n * with it to the control API, before any mount, so no mount under it is\n * reachable.\n */\nexport const CONTROL_PREFIX = \"/__aimock\";\n\n/**\n * What serves a block's `mount` path. A conflict is a path no fake there can\n * be served at: a mount that is not an MCPMock serves it first (`not-mcp`), an\n * MCPMock mounted at `mountPath` answers it as its root (`mcp-root`, the path\n * is `mountPath` plus a trailing \"/\"), or it is under the control prefix\n * (`control`).\n */\nexport type FakeMountTarget =\n | { kind: \"mcp\"; handler: Mountable }\n | { kind: \"conflict\"; mountPath: string; reason: \"not-mcp\" | \"mcp-root\" | \"control\" }\n | { kind: \"none\" };\n\n/** The handlers an auto-mount created (W3, W6), whichever path created them. */\nconst autoMounts = new WeakSet<Mountable>();\n\n/** `handler` was auto-mounted for MCP fakes (at start, after start or by the control API). */\nexport function isAutoMount(handler: Mountable): boolean {\n return autoMounts.has(handler);\n}\n\n/** A mount \"is an MCPMock\" for fakes if and only if it implements `addMcpFakes`. */\nexport function isFakeMount(handler: Mountable): boolean {\n return typeof handler.addMcpFakes === \"function\";\n}\n\n/**\n * The mount that serves `path`, in dispatch order: an MCPMock at exactly\n * `path`; a conflict when the path is under the control prefix, when a mount\n * that is not an MCPMock serves it first (it would shadow the fakes), or when\n * an MCPMock at `path` without its trailing \"/\" answers it as its root; none\n * when no mount serves it. An MCPMock mounted at a prefix of `path` lets any\n * other sub-path fall through, so it is skipped. `pending` are the paths an\n * add will auto-mount before this one, in order (they follow `mounts`).\n */\nexport function findFakeMount(\n mounts: MountList,\n path: string,\n pending: readonly string[] = [],\n): FakeMountTarget {\n if (path.startsWith(CONTROL_PREFIX)) {\n return { kind: \"conflict\", mountPath: CONTROL_PREFIX, reason: \"control\" };\n }\n for (const { path: mountPath, handler } of mounts) {\n if (path !== mountPath && !path.startsWith(mountPath + \"/\")) continue;\n if (!isFakeMount(handler)) return { kind: \"conflict\", mountPath, reason: \"not-mcp\" };\n if (mountPath === path) return { kind: \"mcp\", handler };\n if (path === mountPath + \"/\") return { kind: \"conflict\", mountPath, reason: \"mcp-root\" };\n }\n for (const mountPath of pending) {\n if (path === mountPath + \"/\") return { kind: \"conflict\", mountPath, reason: \"mcp-root\" };\n }\n return { kind: \"none\" };\n}\n\n/** Why the fakes for `path` cannot be served there (no location prefix). */\nexport function mountConflictDetail(\n path: string,\n target: Extract<FakeMountTarget, { kind: \"conflict\" }>,\n): Message {\n switch (target.reason) {\n case \"control\":\n return msg`mount ${quote(path)} is under the control API prefix ${quote(target.mountPath)}, which takes its requests first, so its fakes cannot be served`;\n case \"mcp-root\":\n return msg`mount ${quote(path)} is answered by the MCP mock at ${quote(target.mountPath)} as its root, so its fakes cannot be served`;\n case \"not-mcp\":\n return msg`mount ${quote(path)} is served by ${quote(target.mountPath)}, which is not an MCP mock, so its fakes cannot be served`;\n }\n}\n\n/** Wire, push and log (L4, L5) an auto-mounted MCPMock. */\nfunction pushAutoMount(\n mounts: MountList,\n path: string,\n handler: MCPMock,\n wiring: FakeMountWiring,\n logger: Logger,\n): void {\n if (wiring.journal) handler.setJournal(wiring.journal);\n if (wiring.registry) handler.setRegistry(wiring.registry);\n if (wiring.logger) handler.setLogger(wiring.logger);\n const others = mounts.filter((m) => m.path !== path && isFakeMount(m.handler));\n mounts.push({ path, handler });\n autoMounts.add(handler);\n logger.info(build(msg`MCP fakes: auto-mounted an MCP mock at ${quote(path)}`));\n for (const other of others) {\n logger.warn(\n build(\n msg`MCP fakes: auto-mounted an MCP mock at ${quote(path)} while an MCP mock is mounted at ${quote(other.path)}; fakes for one path do not reach the other`,\n ),\n );\n }\n}\n\n/**\n * The MCPMock for `path`: the one already mounted there, or a new one pushed\n * onto `mounts` (auto-mount, logs L4 and L5). A conflict is returned, not\n * thrown, so the caller can name the offending block.\n */\nexport function ensureFakeMount(\n mounts: MountList,\n path: string,\n wiring: FakeMountWiring,\n logger: Logger,\n): { handler: Mountable; created: boolean } | { conflict: string } {\n const target = findFakeMount(mounts, path);\n if (target.kind === \"conflict\") return { conflict: target.mountPath };\n if (target.kind === \"mcp\") return { handler: target.handler, created: false };\n const handler = new MCPMock();\n pushAutoMount(mounts, path, handler, wiring, logger);\n return { handler, created: true };\n}\n\n/** A block's `mount` path, read without running getters; the add validates it. */\nfunction mountOf(raw: unknown): string {\n if (typeof raw !== \"object\" || raw === null) return MCP_FAKES_DEFAULT_MOUNT;\n const value: unknown = Object.getOwnPropertyDescriptor(raw, \"mount\")?.value;\n return typeof value === \"string\" ? value : MCP_FAKES_DEFAULT_MOUNT;\n}\n\n/** The mount-conflict error for one file-origin block. */\nexport function mountConflictError(\n block: McpFakeSource,\n path: string,\n target: Extract<FakeMountTarget, { kind: \"conflict\" }>,\n): FixtureLoadError {\n return new FixtureLoadError({\n rule: \"mcp-fakes/mount-conflict\",\n file: block.source,\n blockId: blockIdOf(block.source, \"\", block.blockIndex),\n entryId: null,\n detail: mountConflictDetail(path, target),\n });\n}\n\n/** Where a checked add of file blocks goes: each group by its `mount` path. */\nexport interface FileFakesPlan {\n /** Groups for a path an MCPMock already serves, with that mount. */\n onExisting: Array<{ handler: Mountable; group: McpFakeSource[] }>;\n /** Groups for a path no mount serves, in order: each gets an auto-mount. */\n onNew: Array<{ path: string; group: McpFakeSource[] }>;\n /** The add warnings (L8), as a throwaway store reports them. */\n warnings: McpFakeIssue[];\n}\n\n/**\n * Check file-origin `blocks` against `mounts` without changing anything:\n * group them by `mount` path (default `/mcp`), find each group's mount (a\n * mount conflict is one error per block), and add each group to a throwaway\n * store (bad blocks and id collisions inside the input). Throws one\n * `McpFakesAddError` holding every error. With `checked`, only the groups\n * that hold a checked block are checked, and only checked blocks get a\n * mount-conflict error (the others were checked before).\n */\nexport function planFileFakes(\n mounts: MountList,\n blocks: readonly McpFakeSource[],\n checked?: ReadonlySet<McpFakeSource>,\n): FileFakesPlan {\n const groups = new Map<string, McpFakeSource[]>();\n for (const block of blocks) {\n const path = mountOf(block.raw);\n const group = groups.get(path);\n if (group) group.push(block);\n else groups.set(path, [block]);\n }\n\n const errors: FixtureLoadError[] = [];\n const warnings: McpFakeIssue[] = [];\n const plan: FileFakesPlan = { onExisting: [], onNew: [], warnings };\n for (const [path, group] of groups) {\n if (checked && !group.some((block) => checked.has(block))) continue;\n const target = findFakeMount(\n mounts,\n path,\n plan.onNew.map((g) => g.path),\n );\n if (target.kind === \"conflict\") {\n for (const block of group) {\n if (!checked || checked.has(block)) errors.push(mountConflictError(block, path, target));\n }\n continue;\n }\n try {\n warnings.push(...new McpFakeStore().add(group, { kind: \"file\" }).warnings);\n } catch (err) {\n if (!(err instanceof McpFakesAddError)) throw err;\n errors.push(...err.errors);\n warnings.push(...err.warnings);\n continue;\n }\n if (target.kind === \"mcp\") plan.onExisting.push({ handler: target.handler, group });\n else plan.onNew.push({ path, group });\n }\n if (errors.length > 0) throw new McpFakesAddError(errors, warnings);\n return plan;\n}\n\n/** A start-time hand-off: the auto-mounts are in place, the rest waits for `commit`. */\nexport interface McpFakesHandOff {\n /** Add the groups for existing MCPMocks (after the server listens). */\n commit(): void;\n /** Take the auto-mounts back off the mounts array (start failed). */\n undo(): void;\n}\n\n/**\n * Start-time hand-off (F2): check every block first (`planFileFakes`; on any\n * error, a bad block, an id collision or a mount conflict, throws one\n * `McpFakesAddError` holding every error and changes nothing). Then the\n * groups for paths no mount serves are added to new MCPMocks, pushed onto\n * `mounts` (auto-mount, L4, L5) so the mount loop wires them. The groups for\n * an MCPMock already mounted are added by `commit`, once the server listens,\n * so a start that fails after the hand-off leaves those mounts untouched;\n * `undo` removes the auto-mounts. The add warnings (L8) are logged at `warn`.\n */\nexport function handOffMcpFakes(\n mounts: MountList,\n blocks: McpFakeSource[],\n logger: Logger,\n): McpFakesHandOff {\n const plan = planFileFakes(mounts, blocks);\n const created: Array<{ path: string; handler: MCPMock }> = [];\n for (const { path, group } of plan.onNew) {\n const handler = new MCPMock();\n for (const warning of handler.addMcpFakes(group, { kind: \"file\" }).warnings) {\n logger.warn(warning.message);\n }\n created.push({ path, handler });\n }\n for (const { path, handler } of created) pushAutoMount(mounts, path, handler, {}, logger);\n return {\n commit(): void {\n for (const { handler, group } of plan.onExisting) {\n const result = handler.addMcpFakes?.(group, { kind: \"file\" });\n for (const warning of result?.warnings ?? []) logger.warn(warning.message);\n }\n },\n undo(): void {\n for (const { handler } of created) {\n const at = mounts.findIndex((m) => m.handler === handler);\n if (at !== -1) mounts.splice(at, 1);\n }\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mcp-fakes-mount.cjs","names":["quote","build","MCPMock","MCP_FAKES_DEFAULT_MOUNT","FixtureLoadError","blockIdOf","McpFakeStore","McpFakesAddError"],"sources":["../src/mcp-fakes-mount.ts"],"sourcesContent":["/**\n * Where MCP fakes go on a server (spec 5.2): find the mount that serves a\n * block's `mount` path, auto-mount an `MCPMock` when none does (W3, W6), and\n * the start-time hand-off of `ServiceFixtures.mcpFakes` (F2).\n */\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\nimport type { McpFakeSource, Mountable } from \"./types.js\";\nimport { FixtureLoadError } from \"./fixture-loader.js\";\nimport { MCPMock } from \"./mcp-mock.js\";\nimport {\n MCP_FAKES_DEFAULT_MOUNT,\n McpFakeStore,\n McpFakesAddError,\n blockIdOf,\n type McpFakeIssue,\n} from \"./mcp-fakes.js\";\nimport { build, msg, quote, type Message } from \"./message-text.js\";\n\nexport type MountList = Array<{ path: string; handler: Mountable }>;\n\n/** Dependencies an auto-mount gets when it is created (after start: W4 wiring). */\nexport interface FakeMountWiring {\n journal?: Journal;\n registry?: MetricsRegistry;\n logger?: Logger;\n /** T1: the server's replay speed for recorded fake timing (default 1). */\n replaySpeed?: number;\n}\n\n/**\n * The control-API prefix. The server hands every request whose path starts\n * with it to the control API, before any mount, so no mount under it is\n * reachable.\n */\nexport const CONTROL_PREFIX = \"/__aimock\";\n\n/**\n * What serves a block's `mount` path. A conflict is a path no fake there can\n * be served at: a mount that is not an MCPMock serves it first (`not-mcp`), an\n * MCPMock mounted at `mountPath` answers it as its root (`mcp-root`, the path\n * is `mountPath` plus a trailing \"/\"), or it is under the control prefix\n * (`control`).\n */\nexport type FakeMountTarget =\n | { kind: \"mcp\"; handler: Mountable }\n | { kind: \"conflict\"; mountPath: string; reason: \"not-mcp\" | \"mcp-root\" | \"control\" }\n | { kind: \"none\" };\n\n/** The handlers an auto-mount created (W3, W6), whichever path created them. */\nconst autoMounts = new WeakSet<Mountable>();\n\n/** `handler` was auto-mounted for MCP fakes (at start, after start or by the control API). */\nexport function isAutoMount(handler: Mountable): boolean {\n return autoMounts.has(handler);\n}\n\n/** A mount \"is an MCPMock\" for fakes if and only if it implements `addMcpFakes`. */\nexport function isFakeMount(handler: Mountable): boolean {\n return typeof handler.addMcpFakes === \"function\";\n}\n\n/**\n * The mount that serves `path`, in dispatch order: an MCPMock at exactly\n * `path`; a conflict when the path is under the control prefix, when a mount\n * that is not an MCPMock serves it first (it would shadow the fakes), or when\n * an MCPMock at `path` without its trailing \"/\" answers it as its root; none\n * when no mount serves it. An MCPMock mounted at a prefix of `path` lets any\n * other sub-path fall through, so it is skipped. `pending` are the paths an\n * add will auto-mount before this one, in order (they follow `mounts`).\n */\nexport function findFakeMount(\n mounts: MountList,\n path: string,\n pending: readonly string[] = [],\n): FakeMountTarget {\n if (path.startsWith(CONTROL_PREFIX)) {\n return { kind: \"conflict\", mountPath: CONTROL_PREFIX, reason: \"control\" };\n }\n for (const { path: mountPath, handler } of mounts) {\n if (path !== mountPath && !path.startsWith(mountPath + \"/\")) continue;\n if (!isFakeMount(handler)) return { kind: \"conflict\", mountPath, reason: \"not-mcp\" };\n if (mountPath === path) return { kind: \"mcp\", handler };\n if (path === mountPath + \"/\") return { kind: \"conflict\", mountPath, reason: \"mcp-root\" };\n }\n for (const mountPath of pending) {\n if (path === mountPath + \"/\") return { kind: \"conflict\", mountPath, reason: \"mcp-root\" };\n }\n return { kind: \"none\" };\n}\n\n/** Why the fakes for `path` cannot be served there (no location prefix). */\nexport function mountConflictDetail(\n path: string,\n target: Extract<FakeMountTarget, { kind: \"conflict\" }>,\n): Message {\n switch (target.reason) {\n case \"control\":\n return msg`mount ${quote(path)} is under the control API prefix ${quote(target.mountPath)}, which takes its requests first, so its fakes cannot be served`;\n case \"mcp-root\":\n return msg`mount ${quote(path)} is answered by the MCP mock at ${quote(target.mountPath)} as its root, so its fakes cannot be served`;\n case \"not-mcp\":\n return msg`mount ${quote(path)} is served by ${quote(target.mountPath)}, which is not an MCP mock, so its fakes cannot be served`;\n }\n}\n\n/** Wire, push and log (L4, L5) an auto-mounted MCPMock. */\nfunction pushAutoMount(\n mounts: MountList,\n path: string,\n handler: MCPMock,\n wiring: FakeMountWiring,\n logger: Logger,\n): void {\n if (wiring.journal) handler.setJournal(wiring.journal);\n if (wiring.registry) handler.setRegistry(wiring.registry);\n if (wiring.logger) handler.setLogger(wiring.logger);\n handler.setReplaySpeed(wiring.replaySpeed ?? 1);\n const others = mounts.filter((m) => m.path !== path && isFakeMount(m.handler));\n mounts.push({ path, handler });\n autoMounts.add(handler);\n logger.info(build(msg`MCP fakes: auto-mounted an MCP mock at ${quote(path)}`));\n for (const other of others) {\n logger.warn(\n build(\n msg`MCP fakes: auto-mounted an MCP mock at ${quote(path)} while an MCP mock is mounted at ${quote(other.path)}; fakes for one path do not reach the other`,\n ),\n );\n }\n}\n\n/**\n * The MCPMock for `path`: the one already mounted there, or a new one pushed\n * onto `mounts` (auto-mount, logs L4 and L5). A conflict is returned, not\n * thrown, so the caller can name the offending block.\n */\nexport function ensureFakeMount(\n mounts: MountList,\n path: string,\n wiring: FakeMountWiring,\n logger: Logger,\n): { handler: Mountable; created: boolean } | { conflict: string } {\n const target = findFakeMount(mounts, path);\n if (target.kind === \"conflict\") return { conflict: target.mountPath };\n if (target.kind === \"mcp\") return { handler: target.handler, created: false };\n const handler = new MCPMock();\n pushAutoMount(mounts, path, handler, wiring, logger);\n return { handler, created: true };\n}\n\n/** A block's `mount` path, read without running getters; the add validates it. */\nfunction mountOf(raw: unknown): string {\n if (typeof raw !== \"object\" || raw === null) return MCP_FAKES_DEFAULT_MOUNT;\n const value: unknown = Object.getOwnPropertyDescriptor(raw, \"mount\")?.value;\n return typeof value === \"string\" ? value : MCP_FAKES_DEFAULT_MOUNT;\n}\n\n/** The mount-conflict error for one file-origin block. */\nexport function mountConflictError(\n block: McpFakeSource,\n path: string,\n target: Extract<FakeMountTarget, { kind: \"conflict\" }>,\n): FixtureLoadError {\n return new FixtureLoadError({\n rule: \"mcp-fakes/mount-conflict\",\n file: block.source,\n blockId: blockIdOf(block.source, \"\", block.blockIndex),\n entryId: null,\n detail: mountConflictDetail(path, target),\n });\n}\n\n/** Where a checked add of file blocks goes: each group by its `mount` path. */\nexport interface FileFakesPlan {\n /** Groups for a path an MCPMock already serves, with that mount. */\n onExisting: Array<{ handler: Mountable; group: McpFakeSource[] }>;\n /** Groups for a path no mount serves, in order: each gets an auto-mount. */\n onNew: Array<{ path: string; group: McpFakeSource[] }>;\n /** The add warnings (L8), as a throwaway store reports them. */\n warnings: McpFakeIssue[];\n}\n\n/**\n * Check file-origin `blocks` against `mounts` without changing anything:\n * group them by `mount` path (default `/mcp`), find each group's mount (a\n * mount conflict is one error per block), and add each group to a throwaway\n * store (bad blocks and id collisions inside the input). Throws one\n * `McpFakesAddError` holding every error. With `checked`, only the groups\n * that hold a checked block are checked, and only checked blocks get a\n * mount-conflict error (the others were checked before).\n */\nexport function planFileFakes(\n mounts: MountList,\n blocks: readonly McpFakeSource[],\n checked?: ReadonlySet<McpFakeSource>,\n): FileFakesPlan {\n const groups = new Map<string, McpFakeSource[]>();\n for (const block of blocks) {\n const path = mountOf(block.raw);\n const group = groups.get(path);\n if (group) group.push(block);\n else groups.set(path, [block]);\n }\n\n const errors: FixtureLoadError[] = [];\n const warnings: McpFakeIssue[] = [];\n const plan: FileFakesPlan = { onExisting: [], onNew: [], warnings };\n for (const [path, group] of groups) {\n if (checked && !group.some((block) => checked.has(block))) continue;\n const target = findFakeMount(\n mounts,\n path,\n plan.onNew.map((g) => g.path),\n );\n if (target.kind === \"conflict\") {\n for (const block of group) {\n if (!checked || checked.has(block)) errors.push(mountConflictError(block, path, target));\n }\n continue;\n }\n try {\n warnings.push(...new McpFakeStore().add(group, { kind: \"file\" }).warnings);\n } catch (err) {\n if (!(err instanceof McpFakesAddError)) throw err;\n errors.push(...err.errors);\n warnings.push(...err.warnings);\n continue;\n }\n if (target.kind === \"mcp\") plan.onExisting.push({ handler: target.handler, group });\n else plan.onNew.push({ path, group });\n }\n if (errors.length > 0) throw new McpFakesAddError(errors, warnings);\n return plan;\n}\n\n/** A start-time hand-off: the auto-mounts are in place, the rest waits for `commit`. */\nexport interface McpFakesHandOff {\n /** Add the groups for existing MCPMocks (after the server listens). */\n commit(): void;\n /** Take the auto-mounts back off the mounts array (start failed). */\n undo(): void;\n}\n\n/**\n * Start-time hand-off (F2): check every block first (`planFileFakes`; on any\n * error, a bad block, an id collision or a mount conflict, throws one\n * `McpFakesAddError` holding every error and changes nothing). Then the\n * groups for paths no mount serves are added to new MCPMocks, pushed onto\n * `mounts` (auto-mount, L4, L5) so the mount loop wires them. The groups for\n * an MCPMock already mounted are added by `commit`, once the server listens,\n * so a start that fails after the hand-off leaves those mounts untouched;\n * `undo` removes the auto-mounts. The add warnings (L8) are logged at `warn`.\n */\nexport function handOffMcpFakes(\n mounts: MountList,\n blocks: McpFakeSource[],\n logger: Logger,\n): McpFakesHandOff {\n const plan = planFileFakes(mounts, blocks);\n const created: Array<{ path: string; handler: MCPMock }> = [];\n for (const { path, group } of plan.onNew) {\n const handler = new MCPMock();\n for (const warning of handler.addMcpFakes(group, { kind: \"file\" }).warnings) {\n logger.warn(warning.message);\n }\n created.push({ path, handler });\n }\n for (const { path, handler } of created) pushAutoMount(mounts, path, handler, {}, logger);\n return {\n commit(): void {\n for (const { handler, group } of plan.onExisting) {\n const result = handler.addMcpFakes?.(group, { kind: \"file\" });\n for (const warning of result?.warnings ?? []) logger.warn(warning.message);\n }\n },\n undo(): void {\n for (const { handler } of created) {\n const at = mounts.findIndex((m) => m.handler === handler);\n if (at !== -1) mounts.splice(at, 1);\n }\n },\n };\n}\n"],"mappings":"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"}
@@ -72,6 +72,7 @@ function pushAutoMount(mounts, path, handler, wiring, logger) {
72
72
  if (wiring.journal) handler.setJournal(wiring.journal);
73
73
  if (wiring.registry) handler.setRegistry(wiring.registry);
74
74
  if (wiring.logger) handler.setLogger(wiring.logger);
75
+ handler.setReplaySpeed(wiring.replaySpeed ?? 1);
75
76
  const others = mounts.filter((m) => m.path !== path && isFakeMount(m.handler));
76
77
  mounts.push({
77
78
  path,