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