@copilotkit/aimock 1.44.0 → 1.45.1

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 (278) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +42 -0
  4. package/README.md +6 -0
  5. package/dist/agui-types.d.cts.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 +216 -17
  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 +214 -18
  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/openrouter-video.cjs +30 -2
  181. package/dist/openrouter-video.cjs.map +1 -1
  182. package/dist/openrouter-video.d.cts.map +1 -1
  183. package/dist/openrouter-video.d.ts.map +1 -1
  184. package/dist/openrouter-video.js +30 -2
  185. package/dist/openrouter-video.js.map +1 -1
  186. package/dist/record-sanitize.cjs +231 -0
  187. package/dist/record-sanitize.cjs.map +1 -0
  188. package/dist/record-sanitize.js +224 -0
  189. package/dist/record-sanitize.js.map +1 -0
  190. package/dist/recorder.cjs +106 -5
  191. package/dist/recorder.cjs.map +1 -1
  192. package/dist/recorder.d.cts +2 -1
  193. package/dist/recorder.d.cts.map +1 -1
  194. package/dist/recorder.d.ts +2 -1
  195. package/dist/recorder.d.ts.map +1 -1
  196. package/dist/recorder.js +107 -7
  197. package/dist/recorder.js.map +1 -1
  198. package/dist/responses.cjs +667 -171
  199. package/dist/responses.cjs.map +1 -1
  200. package/dist/responses.d.cts +5 -13
  201. package/dist/responses.d.cts.map +1 -1
  202. package/dist/responses.d.ts +5 -13
  203. package/dist/responses.d.ts.map +1 -1
  204. package/dist/responses.js +665 -174
  205. package/dist/responses.js.map +1 -1
  206. package/dist/router.cjs +24 -2
  207. package/dist/router.cjs.map +1 -1
  208. package/dist/router.d.cts.map +1 -1
  209. package/dist/router.d.ts.map +1 -1
  210. package/dist/router.js +25 -4
  211. package/dist/router.js.map +1 -1
  212. package/dist/server.cjs +572 -76
  213. package/dist/server.cjs.map +1 -1
  214. package/dist/server.d.cts.map +1 -1
  215. package/dist/server.d.ts.map +1 -1
  216. package/dist/server.js +574 -78
  217. package/dist/server.js.map +1 -1
  218. package/dist/stream-collapse.cjs +236 -34
  219. package/dist/stream-collapse.cjs.map +1 -1
  220. package/dist/stream-collapse.d.cts +39 -4
  221. package/dist/stream-collapse.d.cts.map +1 -1
  222. package/dist/stream-collapse.d.ts +39 -4
  223. package/dist/stream-collapse.d.ts.map +1 -1
  224. package/dist/stream-collapse.js +235 -35
  225. package/dist/stream-collapse.js.map +1 -1
  226. package/dist/types.cjs.map +1 -1
  227. package/dist/types.d.cts +315 -4
  228. package/dist/types.d.cts.map +1 -1
  229. package/dist/types.d.ts +315 -4
  230. package/dist/types.d.ts.map +1 -1
  231. package/dist/types.js.map +1 -1
  232. package/dist/validate-cli.cjs +36 -4
  233. package/dist/validate-cli.cjs.map +1 -1
  234. package/dist/validate-cli.js +37 -5
  235. package/dist/validate-cli.js.map +1 -1
  236. package/dist/vector-types.d.ts.map +1 -1
  237. package/dist/version.cjs +45 -0
  238. package/dist/version.cjs.map +1 -0
  239. package/dist/version.js +44 -0
  240. package/dist/version.js.map +1 -0
  241. package/dist/vitest.cjs +66 -10
  242. package/dist/vitest.cjs.map +1 -1
  243. package/dist/vitest.d.cts +35 -4
  244. package/dist/vitest.d.cts.map +1 -1
  245. package/dist/vitest.d.ts +35 -4
  246. package/dist/vitest.d.ts.map +1 -1
  247. package/dist/vitest.js +67 -11
  248. package/dist/vitest.js.map +1 -1
  249. package/dist/watcher.cjs +6 -2
  250. package/dist/watcher.cjs.map +1 -1
  251. package/dist/watcher.js +6 -2
  252. package/dist/watcher.js.map +1 -1
  253. package/dist/ws-gemini-live.cjs +210 -101
  254. package/dist/ws-gemini-live.cjs.map +1 -1
  255. package/dist/ws-gemini-live.d.cts +9 -3
  256. package/dist/ws-gemini-live.d.cts.map +1 -1
  257. package/dist/ws-gemini-live.d.ts +9 -3
  258. package/dist/ws-gemini-live.d.ts.map +1 -1
  259. package/dist/ws-gemini-live.js +211 -102
  260. package/dist/ws-gemini-live.js.map +1 -1
  261. package/dist/ws-realtime.cjs +175 -62
  262. package/dist/ws-realtime.cjs.map +1 -1
  263. package/dist/ws-realtime.d.cts +8 -3
  264. package/dist/ws-realtime.d.cts.map +1 -1
  265. package/dist/ws-realtime.d.ts +8 -3
  266. package/dist/ws-realtime.d.ts.map +1 -1
  267. package/dist/ws-realtime.js +176 -63
  268. package/dist/ws-realtime.js.map +1 -1
  269. package/dist/ws-responses.cjs +109 -16
  270. package/dist/ws-responses.cjs.map +1 -1
  271. package/dist/ws-responses.d.cts +8 -12
  272. package/dist/ws-responses.d.cts.map +1 -1
  273. package/dist/ws-responses.d.ts +8 -12
  274. package/dist/ws-responses.d.ts.map +1 -1
  275. package/dist/ws-responses.js +111 -18
  276. package/dist/ws-responses.js.map +1 -1
  277. package/package.json +10 -2
  278. package/skills/write-fixtures/SKILL.md +179 -21
@@ -4,6 +4,7 @@ const require_message_text = require('./message-text.cjs');
4
4
  const require_helpers = require('./helpers.cjs');
5
5
  const require_live_fixture = require('./live-fixture.cjs');
6
6
  const require_chaos = require('./chaos.cjs');
7
+ const require_misbehavior = require('./misbehavior.cjs');
7
8
  let node_fs = require("node:fs");
8
9
  let node_path = require("node:path");
9
10
 
@@ -35,8 +36,32 @@ function normalizeResponse(raw, liveOptions) {
35
36
  });
36
37
  return response;
37
38
  }
39
+ /**
40
+ * @internal Normalize `responsesBlocks` and `customToolCalls` in place, for a
41
+ * server with `responsesTools: "extended"`: stringify object `arguments` on
42
+ * `toolCall` blocks, and give a `customToolCalls` entry with no `type` the
43
+ * type `"custom"`. A custom call's free-text `input` is never stringified.
44
+ * {@link normalizeResponse} leaves both keys as written, as 1.44.0 did.
45
+ */
46
+ function normalizeResponsesToolsKeys(response) {
47
+ if (Array.isArray(response.responsesBlocks)) response.responsesBlocks = response.responsesBlocks.map((block) => {
48
+ const b = block;
49
+ if (b !== null && typeof b === "object" && b.type === "toolCall" && typeof b.arguments === "object" && b.arguments !== null) return {
50
+ ...b,
51
+ arguments: JSON.stringify(b.arguments)
52
+ };
53
+ return block;
54
+ });
55
+ if (Array.isArray(response.customToolCalls)) response.customToolCalls = response.customToolCalls.map((tc) => {
56
+ const entry = tc;
57
+ return entry !== null && typeof entry === "object" && entry.type === void 0 ? {
58
+ ...entry,
59
+ type: "custom"
60
+ } : tc;
61
+ });
62
+ }
38
63
  var InvalidFixtureMatchError = class extends TypeError {};
39
- function entryToFixture(entry, logger, liveOptions) {
64
+ function entryToFixture(entry, logger, liveOptions, source) {
40
65
  if (entry === null || typeof entry !== "object" || Array.isArray(entry)) throw new TypeError("Fixture entry must be an object");
41
66
  if (entry.match == null) throw new TypeError("Fixture match must be an object");
42
67
  if (typeof entry.match !== "object" || Array.isArray(entry.match)) throw new InvalidFixtureMatchError("Fixture match must be an object");
@@ -68,6 +93,19 @@ function entryToFixture(entry, logger, liveOptions) {
68
93
  ...entry.openRouterProcessing !== void 0 && { openRouterProcessing: entry.openRouterProcessing },
69
94
  ...entry.metadata !== void 0 && { metadata: entry.metadata }
70
95
  };
96
+ if (source) require_misbehavior.setFixtureMisbehaviorPosition(fixture, `${source.file}#${source.index}`);
97
+ const heldToolNamespace = entry.match.toolNamespace !== void 0;
98
+ const response = fixture.response;
99
+ const heldResponseKeys = entry.match.endpoint !== "openai-live" && !require_helpers.isLiveResponse(entry.response) && typeof response === "object" && response !== null && ("customToolCalls" in response || "responsesBlocks" in response);
100
+ if (heldToolNamespace || heldResponseKeys) heldResponsesTools.set(fixture, {
101
+ ...heldToolNamespace && { toolNamespace: entry.match.toolNamespace },
102
+ normalize: heldResponseKeys
103
+ });
104
+ if (entry.misbehavior !== void 0) heldFixtureMisbehavior.set(fixture, {
105
+ value: entry.misbehavior,
106
+ path: source ? `fixtures[${source.index}].misbehavior` : "misbehavior",
107
+ file: source?.file ?? null
108
+ });
71
109
  if (fixture.recordedTimings) {
72
110
  const rt = fixture.recordedTimings;
73
111
  if (!Number.isFinite(rt.ttftMs) || rt.ttftMs < 0) rt.ttftMs = 0;
@@ -134,7 +172,7 @@ function hasMcpFakesKey(parsed) {
134
172
  *
135
173
  * @internal Shared with fixture-loader-services.ts.
136
174
  */
137
- function fixturesOf(parsed, filePath, logger, liveOptions, fixturesOptional) {
175
+ function fixturesOf(parsed, filePath, logger, liveOptions, fixturesOptional, source = filePath) {
138
176
  if (fixturesOptional && typeof parsed === "object" && parsed !== null && !Object.prototype.hasOwnProperty.call(parsed, "fixtures")) return [];
139
177
  if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.fixtures)) {
140
178
  warn(logger, `Missing or invalid "fixtures" array in ${filePath}`);
@@ -142,7 +180,10 @@ function fixturesOf(parsed, filePath, logger, liveOptions, fixturesOptional) {
142
180
  }
143
181
  const fixtures = [];
144
182
  for (const [index, entry] of parsed.fixtures.entries()) try {
145
- fixtures.push(entryToFixture(entry, logger, liveOptions));
183
+ fixtures.push(entryToFixture(entry, logger, liveOptions, {
184
+ file: source,
185
+ index
186
+ }));
146
187
  } catch (error) {
147
188
  if (!(error instanceof InvalidFixtureMatchError)) throw error;
148
189
  warn(logger, `Skipping fixture at index ${index} in ${filePath}: ${error.message}`);
@@ -168,10 +209,13 @@ function cannotCarryFakes(filePath) {
168
209
  * `mcpFakes` key; use `loadFixtureFileWithServices` (fixture-loader-services.ts) for such files.
169
210
  */
170
211
  function loadFixtureFile(filePath, logger, liveOptions) {
212
+ return loadLlmFixtureFile(filePath, filePath, logger, liveOptions);
213
+ }
214
+ function loadLlmFixtureFile(filePath, source, logger, liveOptions) {
171
215
  const read = readFixtureJson(filePath, logger);
172
216
  if (read === null) return [];
173
217
  if (hasMcpFakesKey(read.value)) throw cannotCarryFakes(filePath);
174
- return fixturesOf(read.value, filePath, logger, liveOptions, false);
218
+ return fixturesOf(read.value, filePath, logger, liveOptions, false, source);
175
219
  }
176
220
  /**
177
221
  * The `.json` files (sorted) and the subdirectories (sorted) of `dirPath`,
@@ -217,16 +261,20 @@ function listFixtureDir(dirPath, logger) {
217
261
  * directories.
218
262
  */
219
263
  function loadFixturesFromDir(dirPath, logger, liveOptions) {
264
+ return loadLlmFixtureDir(dirPath, "", logger, liveOptions);
265
+ }
266
+ function loadLlmFixtureDir(dirPath, relDir, logger, liveOptions) {
220
267
  const listing = listFixtureDir(dirPath, logger);
221
268
  if (listing === null) return [];
222
269
  const fixtures = [];
223
270
  for (const name of listing.jsonFiles) {
224
271
  const filePath = (0, node_path.join)(dirPath, name);
225
- fixtures.push(...loadFixtureFile(filePath, logger, liveOptions));
272
+ fixtures.push(...loadLlmFixtureFile(filePath, relDir + name, logger, liveOptions));
226
273
  }
227
- for (const sub of listing.subdirs) fixtures.push(...loadFixturesFromDir((0, node_path.join)(dirPath, sub), logger, liveOptions));
274
+ for (const sub of listing.subdirs) fixtures.push(...loadLlmFixtureDir((0, node_path.join)(dirPath, sub), `${relDir}${sub}/`, logger, liveOptions));
228
275
  return fixtures;
229
276
  }
277
+ const INVALID_ARGUMENTS_HINT = "to send invalid JSON on purpose, use `misbehavior: tool-args-invalid-json`";
230
278
  /**
231
279
  * Render a ref-carrying finding's message with each named index passed through
232
280
  * `renderIndex`. With `String` it reproduces the message `validateFixtures`
@@ -277,6 +325,98 @@ function validateWebSearches(response, fixtureIndex, results) {
277
325
  });
278
326
  }
279
327
  }
328
+ /**
329
+ * Path (`customToolCalls[j]` or `responsesBlocks[j]`) of the first custom
330
+ * tool call a static fixture response carries, or undefined. Factory
331
+ * responses are skipped.
332
+ */
333
+ function firstCustomToolCallPath(response) {
334
+ if (response === null || typeof response !== "object") return void 0;
335
+ const r = response;
336
+ if (Array.isArray(r.customToolCalls) && r.customToolCalls.length > 0) return "customToolCalls[0]";
337
+ if (Array.isArray(r.responsesBlocks)) {
338
+ const j = r.responsesBlocks.findIndex((b) => b !== null && typeof b === "object" && b.type === "customToolCall");
339
+ if (j >= 0) return `responsesBlocks[${j}]`;
340
+ }
341
+ }
342
+ /**
343
+ * Checks for the OpenAI Responses keys `customToolCalls` and
344
+ * `responsesBlocks`. Both are new, so their findings never touch a fixture an
345
+ * earlier release accepted. A custom tool call takes a non-empty string
346
+ * `name`, a string `input` (empty: warning), no `arguments`, a string `id`
347
+ * when present and a non-empty string `namespace` when present.
348
+ * `responsesBlocks` follows the `blocks` rules plus `customToolCall` blocks,
349
+ * and may not be combined with `blocks`.
350
+ */
351
+ function validateResponsesToolKeys(response, fixtureIndex, results) {
352
+ if (response === null || typeof response !== "object") return;
353
+ const r = response;
354
+ const error = (message) => results.push({
355
+ severity: "error",
356
+ fixtureIndex,
357
+ message
358
+ });
359
+ const warning = (message) => results.push({
360
+ severity: "warning",
361
+ fixtureIndex,
362
+ message
363
+ });
364
+ const checkCustom = (tc, path) => {
365
+ if (typeof tc.name !== "string" || tc.name === "") error(`${path}.name must be a non-empty string`);
366
+ if (typeof tc.input !== "string") error(`${path}.input must be a string for a custom tool call`);
367
+ else if (tc.input === "") warning(`${path}.input is empty`);
368
+ if (tc.arguments !== void 0) error(`${path}.arguments is not valid on a custom tool call; use input`);
369
+ if (tc.id !== void 0 && typeof tc.id !== "string") error(`${path}.id must be a string, got ${typeof tc.id}`);
370
+ if (tc.namespace !== void 0 && (typeof tc.namespace !== "string" || tc.namespace === "")) error(`${path}.namespace must be a non-empty string`);
371
+ };
372
+ if (r.customToolCalls !== void 0) if (!Array.isArray(r.customToolCalls)) error(`customToolCalls must be an array, got ${typeof r.customToolCalls}`);
373
+ else r.customToolCalls.forEach((entry, j) => {
374
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
375
+ error(`customToolCalls[${j}] must be an object`);
376
+ return;
377
+ }
378
+ const tc = entry;
379
+ if (tc.type !== void 0 && tc.type !== "custom") error(`customToolCalls[${j}].type must be "custom" when present`);
380
+ checkCustom(tc, `customToolCalls[${j}]`);
381
+ });
382
+ if (r.responsesBlocks === void 0) return;
383
+ if (!Array.isArray(r.responsesBlocks)) {
384
+ error(`responsesBlocks must be an array, got ${typeof r.responsesBlocks}`);
385
+ return;
386
+ }
387
+ if (Array.isArray(r.blocks) && r.blocks.length > 0 && r.responsesBlocks.length > 0) error("blocks and responsesBlocks cannot both be set; use responsesBlocks alone");
388
+ r.responsesBlocks.forEach((entry, j) => {
389
+ const path = `responsesBlocks[${j}]`;
390
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
391
+ error(`${path} must be an object`);
392
+ return;
393
+ }
394
+ const block = entry;
395
+ if (block.type === "text") {
396
+ if (typeof block.text !== "string") error(`${path}.text must be a string`);
397
+ return;
398
+ }
399
+ if (block.type === "customToolCall") {
400
+ checkCustom(block, path);
401
+ return;
402
+ }
403
+ if (block.type !== "toolCall") {
404
+ error(`${path}.type must be "text", "toolCall" or "customToolCall", got ${JSON.stringify(block.type)}`);
405
+ return;
406
+ }
407
+ if (typeof block.name !== "string" || block.name === "") error(`${path}.name must be a non-empty string`);
408
+ if (block.id !== void 0 && typeof block.id !== "string") error(`${path}.id must be a string, got ${typeof block.id}`);
409
+ if (block.input !== void 0) error(`${path}.input is only valid on a "customToolCall" block`);
410
+ if (block.namespace !== void 0 && (typeof block.namespace !== "string" || block.namespace === "")) error(`${path}.namespace must be a non-empty string`);
411
+ const args = block.arguments;
412
+ if (typeof args === "string") try {
413
+ JSON.parse(args);
414
+ } catch {
415
+ error(`${path}.arguments is not valid JSON: ${args}; ${INVALID_ARGUMENTS_HINT}`);
416
+ }
417
+ else if (args === null || typeof args !== "object") error(`${path}.arguments must be a string or an object`);
418
+ });
419
+ }
280
420
  function validateBlocks(response, fixtureIndex, results) {
281
421
  if (response.blocks === void 0) return;
282
422
  if (!Array.isArray(response.blocks)) {
@@ -328,7 +468,7 @@ function validateBlocks(response, fixtureIndex, results) {
328
468
  results.push({
329
469
  severity: "error",
330
470
  fixtureIndex,
331
- message: `blocks[${j}].arguments is not valid JSON: ${block.arguments}`
471
+ message: `blocks[${j}].arguments is not valid JSON: ${block.arguments}; ${INVALID_ARGUMENTS_HINT}`
332
472
  });
333
473
  }
334
474
  else if (typeof block.arguments !== "object" || block.arguments === null) results.push({
@@ -448,13 +588,14 @@ function claimOneShotError(fixtures, fixture) {
448
588
  * generation means the queue was cleared or reset while the claim was parked
449
589
  * (in the chaos-latency await) and the claim is stale — re-arming it would
450
590
  * plant the previous test's injection in the next test's freshly reset queue.
451
- * Keyed by array identity: every clear path preserves the array reference
452
- * (`length = 0`), so the counter follows the live queue.
591
+ * Keyed by the caller's array (a server's own list resolves to it): every
592
+ * clear path preserves the array reference (`length = 0`), so the counter
593
+ * follows the live queue.
453
594
  */
454
595
  const fixtureQueueGeneration = /* @__PURE__ */ new WeakMap();
455
596
  const oneShotClaimGeneration = /* @__PURE__ */ new WeakMap();
456
597
  function queueGeneration(fixtures) {
457
- return fixtureQueueGeneration.get(fixtures) ?? 0;
598
+ return fixtureQueueGeneration.get(require_helpers.fixtureListTarget(fixtures)) ?? 0;
458
599
  }
459
600
  /**
460
601
  * Empty a fixture queue in place (array identity preserved — the running
@@ -466,7 +607,7 @@ function queueGeneration(fixtures) {
466
607
  */
467
608
  function clearFixtureQueue(fixtures) {
468
609
  fixtures.length = 0;
469
- fixtureQueueGeneration.set(fixtures, queueGeneration(fixtures) + 1);
610
+ fixtureQueueGeneration.set(require_helpers.fixtureListTarget(fixtures), queueGeneration(fixtures) + 1);
470
611
  }
471
612
  /**
472
613
  * Re-arm a one-shot error that was claimed for a request which then ended
@@ -510,8 +651,25 @@ function validateFixtures(fixtures, liveOptions) {
510
651
  const results = [];
511
652
  const seenUserMessages = /* @__PURE__ */ new Map();
512
653
  for (let i = 0; i < fixtures.length; i++) {
513
- const f = fixtures[i];
654
+ const original = fixtures[i];
655
+ const f = responsesToolsExtendedFixtures.has(original) ? original : withoutResponsesToolsKeys(original);
514
656
  const response = f.response;
657
+ const misbehaviorIssue = misbehaviorEnabledFixtures.has(original) ? require_misbehavior.validateFixtureMisbehavior(original, `fixtures[${i}].misbehavior`) : void 0;
658
+ if (misbehaviorIssue) results.push({
659
+ severity: "error",
660
+ fixtureIndex: i,
661
+ message: misbehaviorIssue.message
662
+ });
663
+ else if (misbehaviorEnabledFixtures.has(original) && f.misbehavior !== void 0) {
664
+ const parsed = require_misbehavior.parseMisbehavior(f.misbehavior);
665
+ if (parsed.ok && parsed.config.seed === void 0) {
666
+ for (const fault of parsed.config.faults) if (fault.rate !== void 0 && fault.rate < 1 && fault.times === void 0) results.push({
667
+ severity: "warning",
668
+ fixtureIndex: i,
669
+ message: "probabilistic fault without a seed is reproducible only per test id order; set seed to pin it"
670
+ });
671
+ }
672
+ }
515
673
  if (typeof response !== "function" && (require_helpers.isLiveResponse(response) || f.match.endpoint === "openai-live")) try {
516
674
  require_live_fixture.normalizeLiveFixture(response, liveOptions);
517
675
  } catch (error) {
@@ -522,12 +680,13 @@ function validateFixtures(fixtures, liveOptions) {
522
680
  });
523
681
  }
524
682
  if (typeof response === "function") {} else {
525
- if (!require_helpers.isContentWithToolCallsResponse(response) && !require_helpers.isTextResponse(response) && !require_helpers.isToolCallResponse(response) && !require_helpers.isErrorResponse(response) && !require_helpers.isEmbeddingResponse(response) && !require_helpers.isImageResponse(response) && !require_helpers.isAudioResponse(response) && !require_helpers.isTranscriptionResponse(response) && !require_helpers.isVideoResponse(response) && !require_helpers.isJSONResponse(response) && !require_helpers.isLiveResponse(response)) results.push({
683
+ if (!require_helpers.isCombinedFixtureResponse(response) && !require_helpers.isTextResponse(response) && !require_helpers.isToolCallResponse(response) && !require_helpers.isErrorResponse(response) && !require_helpers.isEmbeddingResponse(response) && !require_helpers.isImageResponse(response) && !require_helpers.isAudioResponse(response) && !require_helpers.isTranscriptionResponse(response) && !require_helpers.isVideoResponse(response) && !require_helpers.isJSONResponse(response) && !require_helpers.isLiveResponse(response)) results.push({
526
684
  severity: "error",
527
685
  fixtureIndex: i,
528
686
  message: "response is not a recognized type (must have content, toolCalls, error, embedding, image, audio, transcription, video, json, or live)"
529
687
  });
530
- const hasNonEmptyBlocks = Array.isArray(response.blocks) && response.blocks.length > 0;
688
+ const hasNonEmptyBlocks = Array.isArray(response.blocks) && response.blocks.length > 0 || Array.isArray(response.responsesBlocks) && response.responsesBlocks.length > 0;
689
+ const hasCustomToolCalls = Array.isArray(response.customToolCalls) && response.customToolCalls.length > 0;
531
690
  if (require_helpers.isTextResponse(response)) {
532
691
  if (response.content === "" && !hasNonEmptyBlocks) results.push({
533
692
  severity: "error",
@@ -537,7 +696,7 @@ function validateFixtures(fixtures, liveOptions) {
537
696
  validateReasoning(response, i, results);
538
697
  validateWebSearches(response, i, results);
539
698
  }
540
- if (require_helpers.isContentWithToolCallsResponse(response)) {
699
+ if (require_helpers.isCombinedFixtureResponse(response)) {
541
700
  if (typeof response.content === "string") {
542
701
  if (response.content === "" && !hasNonEmptyBlocks) results.push({
543
702
  severity: "error",
@@ -546,7 +705,7 @@ function validateFixtures(fixtures, liveOptions) {
546
705
  });
547
706
  }
548
707
  if (Array.isArray(response.toolCalls)) {
549
- if (response.toolCalls.length === 0) results.push({
708
+ if (response.toolCalls.length === 0 && !hasCustomToolCalls) results.push({
550
709
  severity: "warning",
551
710
  fixtureIndex: i,
552
711
  message: "toolCalls array is empty — fixture will never produce tool calls"
@@ -564,7 +723,7 @@ function validateFixtures(fixtures, liveOptions) {
564
723
  results.push({
565
724
  severity: "error",
566
725
  fixtureIndex: i,
567
- message: `toolCalls[${j}].arguments is not valid JSON: ${tc.arguments}`
726
+ message: `toolCalls[${j}].arguments is not valid JSON: ${tc.arguments}; ${INVALID_ARGUMENTS_HINT}`
568
727
  });
569
728
  }
570
729
  }
@@ -573,8 +732,9 @@ function validateFixtures(fixtures, liveOptions) {
573
732
  validateWebSearches(response, i, results);
574
733
  }
575
734
  validateBlocks(response, i, results);
735
+ validateResponsesToolKeys(response, i, results);
576
736
  if (require_helpers.isToolCallResponse(response)) {
577
- if (response.toolCalls.length === 0) results.push({
737
+ if (response.toolCalls.length === 0 && !hasCustomToolCalls) results.push({
578
738
  severity: "warning",
579
739
  fixtureIndex: i,
580
740
  message: "toolCalls array is empty — fixture will never produce tool calls"
@@ -592,7 +752,7 @@ function validateFixtures(fixtures, liveOptions) {
592
752
  results.push({
593
753
  severity: "error",
594
754
  fixtureIndex: i,
595
- message: `toolCalls[${j}].arguments is not valid JSON: ${tc.arguments}`
755
+ message: `toolCalls[${j}].arguments is not valid JSON: ${tc.arguments}; ${INVALID_ARGUMENTS_HINT}`
596
756
  });
597
757
  }
598
758
  }
@@ -643,7 +803,7 @@ function validateFixtures(fixtures, liveOptions) {
643
803
  message: `audio.contentType must be a string, got ${typeof audioObj.contentType}`
644
804
  });
645
805
  }
646
- if (require_helpers.isTextResponse(response) || require_helpers.isToolCallResponse(response) || require_helpers.isContentWithToolCallsResponse(response)) {
806
+ if (require_helpers.isTextResponse(response) || require_helpers.isToolCallResponse(response) || require_helpers.isCombinedFixtureResponse(response)) {
647
807
  const r = response;
648
808
  if (r.id !== void 0 && typeof r.id !== "string") results.push({
649
809
  severity: "error",
@@ -813,6 +973,29 @@ function validateFixtures(fixtures, liveOptions) {
813
973
  message: "match.toolResultContains must be a non-empty string"
814
974
  });
815
975
  }
976
+ if (f.match.toolNamespace !== void 0) {
977
+ if (typeof f.match.toolNamespace !== "string") results.push({
978
+ severity: "error",
979
+ fixtureIndex: i,
980
+ message: `match.toolNamespace must be a string, got ${typeof f.match.toolNamespace}`
981
+ });
982
+ else if (f.match.toolNamespace.length === 0) results.push({
983
+ severity: "error",
984
+ fixtureIndex: i,
985
+ message: "match.toolNamespace must be a non-empty string"
986
+ });
987
+ }
988
+ if (typeof f.match.toolNamespace === "string" && f.match.toolNamespace !== "" && f.match.endpoint !== void 0 && f.match.endpoint !== "chat") results.push({
989
+ severity: "warning",
990
+ fixtureIndex: i,
991
+ message: `match.toolNamespace never matches with endpoint ${JSON.stringify(f.match.endpoint)} — only OpenAI Responses requests (endpoint "chat") carry tool namespaces`
992
+ });
993
+ const customCallPath = f.match.endpoint !== void 0 && f.match.endpoint !== "chat" ? firstCustomToolCallPath(f.response) : void 0;
994
+ if (customCallPath !== void 0) results.push({
995
+ severity: "warning",
996
+ fixtureIndex: i,
997
+ message: `${customCallPath} is a custom tool call, which only OpenAI Responses requests (endpoint "chat") can carry — endpoint ${JSON.stringify(f.match.endpoint)} never serves it, so a request that matches this fixture gets an error instead`
998
+ });
816
999
  if (f.match.systemMessage !== void 0) {
817
1000
  const sm = f.match.systemMessage;
818
1001
  if (typeof sm === "string") {} else if (Array.isArray(sm)) {
@@ -853,7 +1036,8 @@ function validateFixtures(fixtures, liveOptions) {
853
1036
  m.context,
854
1037
  m.sequenceIndex,
855
1038
  m.turnIndex,
856
- m.hasToolResult
1039
+ m.hasToolResult,
1040
+ ...m.toolNamespace !== void 0 ? [`toolNamespace=${m.toolNamespace}`] : []
857
1041
  ].map((v) => v === void 0 ? "" : String(v)).join("|");
858
1042
  const prev = seenUserMessages.get(dedupKey);
859
1043
  if (prev !== void 0) {
@@ -871,7 +1055,7 @@ function validateFixtures(fixtures, liveOptions) {
871
1055
  } else seenUserMessages.set(dedupKey, i);
872
1056
  }
873
1057
  const match = f.match;
874
- if (!(match.endpoint !== void 0 || match.context !== void 0 || match.userMessage !== void 0 || match.systemMessage !== void 0 || match.inputText !== void 0 || match.responseFormat !== void 0 || match.toolCallId !== void 0 || match.toolResultContains !== void 0 || match.toolName !== void 0 || match.model !== void 0 || match.predicate !== void 0 || match.turnIndex !== void 0 || match.sequenceIndex !== void 0 || match.hasToolResult !== void 0) && i < fixtures.length - 1) {
1058
+ if (!(match.endpoint !== void 0 || match.context !== void 0 || match.userMessage !== void 0 || match.systemMessage !== void 0 || match.inputText !== void 0 || match.responseFormat !== void 0 || match.toolCallId !== void 0 || match.toolResultContains !== void 0 || match.toolName !== void 0 || match.toolNamespace !== void 0 || match.model !== void 0 || match.predicate !== void 0 || match.turnIndex !== void 0 || match.sequenceIndex !== void 0 || match.hasToolResult !== void 0) && i < fixtures.length - 1) {
875
1059
  const ref = {
876
1060
  rule: "catch-all-not-last",
877
1061
  shadowsFrom: i + 1,
@@ -918,8 +1102,16 @@ function namePart(text) {
918
1102
  return require_message_text.msg`${require_message_text.quote(text)}`;
919
1103
  }
920
1104
  /**
1105
+ * The message of a `FixtureLoadError` or `MisbehaviorConfigError`: the
1106
+ * `file`, every non-null `blockId` and `entryId`, then `[rule] detail`.
1107
+ */
1108
+ function loadErrorMessage(init) {
1109
+ return oneLine(require_message_text.build(require_message_text.msg`${init.file === null ? require_message_text.msg`<no file>` : namePart(init.file)}${init.blockId == null ? require_message_text.msg`` : require_message_text.msg`, block ${namePart(init.blockId)}`}${init.entryId == null ? require_message_text.msg`` : require_message_text.msg`, entry ${namePart(init.entryId)}`}: [${require_message_text.fixed(init.rule)}] ${capPart(init.detail)}`));
1110
+ }
1111
+ /**
921
1112
  * Thrown for a fixture-load case aimock cannot honor (fail-loud rule). Only
922
- * the MCP fakes code throws it; other fixture-loader paths log warnings.
1113
+ * MCP fakes use it (misbehavior throws its own `MisbehaviorConfigError`);
1114
+ * other fixture-loader paths log warnings.
923
1115
  * `blockId` and `entryId` are own properties only when supplied, so
924
1116
  * `toJSON()` (the control API's HTTP 400 `details`) omits them otherwise.
925
1117
  * The message names `file` and every non-null `blockId` and `entryId`, an
@@ -933,10 +1125,7 @@ var FixtureLoadError = class extends Error {
933
1125
  rule;
934
1126
  file;
935
1127
  constructor(init, options) {
936
- const file = init.file === null ? require_message_text.msg`<no file>` : namePart(init.file);
937
- const block = init.blockId == null ? require_message_text.msg`` : require_message_text.msg`, block ${namePart(init.blockId)}`;
938
- const entry = init.entryId == null ? require_message_text.msg`` : require_message_text.msg`, entry ${namePart(init.entryId)}`;
939
- super(oneLine(require_message_text.build(require_message_text.msg`${file}${block}${entry}: [${require_message_text.fixed(init.rule)}] ${capPart(init.detail)}`)), options);
1128
+ super(loadErrorMessage(init), options);
940
1129
  this.rule = init.rule;
941
1130
  this.file = init.file;
942
1131
  if (init.blockId !== void 0) Object.defineProperty(this, "blockId", {
@@ -962,12 +1151,180 @@ var FixtureLoadError = class extends Error {
962
1151
  return json;
963
1152
  }
964
1153
  };
1154
+ /**
1155
+ * Thrown for an invalid fixture `misbehavior` key, or one that cannot apply
1156
+ * to its fixture, on a server with misbehavior enabled (`enableMisbehavior:
1157
+ * true`, CLI `--misbehavior`). Without that opt-in the key is unused data
1158
+ * and nothing throws. The message has the `FixtureLoadError` layout:
1159
+ * `"<file>": [<rule>] <detail>`.
1160
+ */
1161
+ var MisbehaviorConfigError = class extends Error {
1162
+ name = "MisbehaviorConfigError";
1163
+ rule;
1164
+ file;
1165
+ constructor(init) {
1166
+ super(loadErrorMessage(init));
1167
+ this.rule = init.rule;
1168
+ this.file = init.file;
1169
+ }
1170
+ toJSON() {
1171
+ return {
1172
+ name: this.name,
1173
+ rule: this.rule,
1174
+ file: this.file,
1175
+ message: this.message
1176
+ };
1177
+ }
1178
+ };
1179
+ const heldFixtureMisbehavior = /* @__PURE__ */ new WeakMap();
1180
+ const responsesToolsExtendedFixtures = /* @__PURE__ */ new WeakSet();
1181
+ const heldResponsesTools = /* @__PURE__ */ new WeakMap();
1182
+ /**
1183
+ * @internal Mark a fixture for a server with `responsesTools: "extended"`,
1184
+ * where that server made the fixture itself (its own load, `--validate`, the
1185
+ * control API): the held `match.toolNamespace` is applied and
1186
+ * `customToolCalls` / `responsesBlocks` are normalized in place, and
1187
+ * `validateFixtures` checks those keys. Idempotent. A fixture the caller
1188
+ * passed in is never marked: an extended server reads it through
1189
+ * {@link responsesToolsExtendedView}, so another server sharing it still sees
1190
+ * it as 1.44.0 loaded it.
1191
+ */
1192
+ function markFixtureResponsesToolsExtended(fixture) {
1193
+ responsesToolsExtendedFixtures.add(fixture);
1194
+ const held = heldResponsesTools.get(fixture);
1195
+ if (held === void 0) return;
1196
+ heldResponsesTools.delete(fixture);
1197
+ if (held.toolNamespace !== void 0) fixture.match = withHeldToolNamespace(fixture.match, held.toolNamespace);
1198
+ if (held.normalize) normalizeResponsesToolsKeys(fixture.response);
1199
+ }
1200
+ /**
1201
+ * `match` with `toolNamespace` after `toolName`, in the same key order the
1202
+ * listing and journal have always shown in extended mode.
1203
+ */
1204
+ function withHeldToolNamespace(match, toolNamespace) {
1205
+ const rebuilt = {};
1206
+ for (const [key, value] of Object.entries(match)) {
1207
+ rebuilt[key] = value;
1208
+ if (key === "toolName") rebuilt.toolNamespace = toolNamespace;
1209
+ }
1210
+ if (!("toolNamespace" in rebuilt)) rebuilt.toolNamespace = toolNamespace;
1211
+ return rebuilt;
1212
+ }
1213
+ const extendedViews = /* @__PURE__ */ new WeakMap();
1214
+ /** Each extended view's fixture, so identity bookkeeping can name the caller's object. */
1215
+ const extendedViewSources = /* @__PURE__ */ new WeakMap();
1216
+ /**
1217
+ * @internal The fixture an extended view was made from
1218
+ * ({@link responsesToolsExtendedView}); any other fixture is itself. An
1219
+ * extended server counts and journals the caller's fixture, not its view, so
1220
+ * the identity APIs (`getFixtureMatchCount`, `findByFixture`, the count-map
1221
+ * keys, `JournalEntry.response.fixture`) take the object the caller passed in.
1222
+ */
1223
+ function responsesToolsViewSource(fixture) {
1224
+ return extendedViewSources.get(fixture) ?? fixture;
1225
+ }
1226
+ /**
1227
+ * @internal The fixture as a server with `responsesTools: "extended"` reads
1228
+ * it. A fixture with held keys gets a separate object, one per fixture, with
1229
+ * `match.toolNamespace` applied and `customToolCalls` / `responsesBlocks`
1230
+ * normalized, as {@link markFixtureResponsesToolsExtended} would leave it.
1231
+ * The fixture itself is not changed, so a legacy server that shares it is
1232
+ * unaffected. Every other property is read from the fixture on each call.
1233
+ * A fixture with nothing held is returned as it is.
1234
+ */
1235
+ function responsesToolsExtendedView(fixture) {
1236
+ const held = heldResponsesTools.get(fixture);
1237
+ if (held === void 0) return fixture;
1238
+ let cached = extendedViews.get(fixture);
1239
+ if (cached === void 0) {
1240
+ cached = { view: {} };
1241
+ responsesToolsExtendedFixtures.add(cached.view);
1242
+ extendedViewSources.set(cached.view, fixture);
1243
+ extendedViews.set(fixture, cached);
1244
+ }
1245
+ if (cached.viewMatch === void 0 || cached.match !== fixture.match) {
1246
+ cached.match = fixture.match;
1247
+ cached.viewMatch = held.toolNamespace !== void 0 ? withHeldToolNamespace(fixture.match, held.toolNamespace) : fixture.match;
1248
+ }
1249
+ if (!("viewResponse" in cached) || cached.response !== fixture.response) {
1250
+ cached.response = fixture.response;
1251
+ let viewResponse = fixture.response;
1252
+ if (held.normalize && typeof viewResponse === "object" && viewResponse !== null) {
1253
+ const copy = { ...viewResponse };
1254
+ normalizeResponsesToolsKeys(copy);
1255
+ viewResponse = copy;
1256
+ }
1257
+ cached.viewResponse = viewResponse;
1258
+ }
1259
+ const view = cached.view;
1260
+ const source = fixture;
1261
+ for (const key of Object.keys(view)) if (!(key in source)) delete view[key];
1262
+ for (const key of Object.keys(source)) view[key] = key === "match" ? cached.viewMatch : key === "response" ? cached.viewResponse : source[key];
1263
+ require_misbehavior.copyFixtureMisbehaviorPosition(fixture, cached.view);
1264
+ return cached.view;
1265
+ }
1266
+ /**
1267
+ * The fixture as 1.44.0 saw it: without `match.toolNamespace`,
1268
+ * `customToolCalls` and `responsesBlocks`. Returns the same object when none
1269
+ * is set; factory responses are left as they are.
1270
+ */
1271
+ function withoutResponsesToolsKeys(fixture) {
1272
+ let match = fixture.match;
1273
+ if ("toolNamespace" in match) {
1274
+ const copy = { ...match };
1275
+ delete copy.toolNamespace;
1276
+ match = copy;
1277
+ }
1278
+ let response = fixture.response;
1279
+ if (typeof response !== "function" && response !== null && typeof response === "object" && ("customToolCalls" in response || "responsesBlocks" in response)) {
1280
+ const copy = { ...response };
1281
+ delete copy.customToolCalls;
1282
+ delete copy.responsesBlocks;
1283
+ response = copy;
1284
+ }
1285
+ if (match === fixture.match && response === fixture.response) return fixture;
1286
+ return {
1287
+ ...fixture,
1288
+ match,
1289
+ response
1290
+ };
1291
+ }
1292
+ const misbehaviorEnabledFixtures = /* @__PURE__ */ new WeakSet();
1293
+ /**
1294
+ * @internal Recognize the `misbehavior` key a loaded fixture entry carried,
1295
+ * for a server with misbehavior enabled: parse it onto the fixture (in place;
1296
+ * the loader made the object) and check it applies, or throw
1297
+ * `MisbehaviorConfigError`. A fixture with no held key is left unchanged.
1298
+ */
1299
+ function enableHeldFixtureMisbehavior(fixture) {
1300
+ const held = heldFixtureMisbehavior.get(fixture);
1301
+ if (held === void 0) return;
1302
+ const parsed = require_misbehavior.parseMisbehavior(held.value, held.path);
1303
+ if (parsed.ok) fixture.misbehavior = parsed.config;
1304
+ const issue = parsed.ok ? require_misbehavior.validateFixtureMisbehavior(fixture, held.path) : parsed.issue;
1305
+ if (issue) throw new MisbehaviorConfigError({
1306
+ rule: issue.rule,
1307
+ file: held.file,
1308
+ detail: issue.message
1309
+ });
1310
+ heldFixtureMisbehavior.delete(fixture);
1311
+ misbehaviorEnabledFixtures.add(fixture);
1312
+ }
1313
+ /**
1314
+ * @internal Mark a fixture whose `misbehavior` a server with misbehavior
1315
+ * enabled has recognized, so `validateFixtures` checks it.
1316
+ */
1317
+ function markFixtureMisbehaviorEnabled(fixture) {
1318
+ misbehaviorEnabledFixtures.add(fixture);
1319
+ }
965
1320
 
966
1321
  //#endregion
967
1322
  exports.FixtureLoadError = FixtureLoadError;
968
1323
  exports.INJECTED_STATUS_RANGE = INJECTED_STATUS_RANGE;
1324
+ exports.MisbehaviorConfigError = MisbehaviorConfigError;
969
1325
  exports.claimOneShotError = claimOneShotError;
970
1326
  exports.clearFixtureQueue = clearFixtureQueue;
1327
+ exports.enableHeldFixtureMisbehavior = enableHeldFixtureMisbehavior;
971
1328
  exports.entryToFixture = entryToFixture;
972
1329
  exports.fixturesOf = fixturesOf;
973
1330
  exports.hasMcpFakesKey = hasMcpFakesKey;
@@ -976,10 +1333,15 @@ exports.isOneShotError = isOneShotError;
976
1333
  exports.listFixtureDir = listFixtureDir;
977
1334
  exports.loadFixtureFile = loadFixtureFile;
978
1335
  exports.loadFixturesFromDir = loadFixturesFromDir;
1336
+ exports.markFixtureMisbehaviorEnabled = markFixtureMisbehaviorEnabled;
1337
+ exports.markFixtureResponsesToolsExtended = markFixtureResponsesToolsExtended;
979
1338
  exports.normalizeResponse = normalizeResponse;
1339
+ exports.normalizeResponsesToolsKeys = normalizeResponsesToolsKeys;
980
1340
  exports.queueOneShotError = queueOneShotError;
981
1341
  exports.readFixtureJson = readFixtureJson;
982
1342
  exports.releaseOneShotError = releaseOneShotError;
983
1343
  exports.renderValidationRef = renderValidationRef;
1344
+ exports.responsesToolsExtendedView = responsesToolsExtendedView;
1345
+ exports.responsesToolsViewSource = responsesToolsViewSource;
984
1346
  exports.validateFixtures = validateFixtures;
985
1347
  //# sourceMappingURL=fixture-loader.cjs.map