@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
package/dist/helpers.cjs CHANGED
@@ -4,9 +4,256 @@ const require_echo_text = require('./echo-text.cjs');
4
4
  const require_message_text = require('./message-text.cjs');
5
5
  const require_model_utils = require('./model-utils.cjs');
6
6
  const require_api_key_auth = require('./api-key-auth.cjs');
7
+ let node_async_hooks = require("node:async_hooks");
7
8
  let node_crypto = require("node:crypto");
8
9
 
9
10
  //#region src/helpers.ts
11
+ /** Preserve authored text for string wires without silently repairing invalid JSON. */
12
+ function toolArgsForWire(tc) {
13
+ const raw = tc.arguments || "{}";
14
+ try {
15
+ const value = JSON.parse(raw);
16
+ return {
17
+ kind: "parsed",
18
+ value,
19
+ text: JSON.stringify(value),
20
+ raw
21
+ };
22
+ } catch {
23
+ return {
24
+ kind: "verbatim",
25
+ raw
26
+ };
27
+ }
28
+ }
29
+ /** Object wires reject the verbatim branch; string wires never need this error. */
30
+ var InvalidToolArgumentsError = class extends Error {
31
+ toolName;
32
+ parseDiagnostic;
33
+ constructor(tc) {
34
+ let parseDiagnostic = "";
35
+ try {
36
+ JSON.parse(tc.arguments || "{}");
37
+ } catch (error) {
38
+ parseDiagnostic = error instanceof Error ? error.message : String(error);
39
+ }
40
+ super(`aimock: fixture tool call "${tc.name}" has invalid JSON arguments; this wire carries arguments as an object. Use a wire that carries tool arguments as a string to test malformed JSON. (${parseDiagnostic})`);
41
+ this.name = "InvalidToolArgumentsError";
42
+ this.toolName = tc.name;
43
+ this.parseDiagnostic = parseDiagnostic;
44
+ }
45
+ };
46
+ const toolArgumentsScope = new node_async_hooks.AsyncLocalStorage();
47
+ /** Run `fn` with the `strictToolArguments` setting and logger of one request. */
48
+ function runWithToolArgumentsScope(scope, fn) {
49
+ return toolArgumentsScope.run(scope, fn);
50
+ }
51
+ /** Whether `strictToolArguments` is on for the request being served (default off). */
52
+ function strictToolArgumentsEnabled() {
53
+ return toolArgumentsScope.getStore()?.strict === true;
54
+ }
55
+ /**
56
+ * Serve the rest of this request's tool arguments as authored (the
57
+ * `strictToolArguments` behavior). An applied misbehavior fault calls this so
58
+ * that the malformed arguments it injects are not replaced with `{}`; the
59
+ * planner only applies a fault to a fixture whose own arguments are valid
60
+ * wherever the default would replace them.
61
+ */
62
+ function keepAuthoredToolArguments() {
63
+ const scope = toolArgumentsScope.getStore();
64
+ if (scope) scope.strict = true;
65
+ }
66
+ /**
67
+ * The fixture tool-call arguments a wire serves on its normal (no misbehavior)
68
+ * path. Valid JSON gives the parsed value, its re-serialized text, and the
69
+ * authored text. Invalid JSON logs one warning and, by default, is served as
70
+ * `{}` / `"{}"`, as in 1.44.0. With `strictToolArguments`, an object wire
71
+ * (`"object"`) throws {@link InvalidToolArgumentsError} and a string wire
72
+ * (`"string"`) serves the authored text unchanged.
73
+ */
74
+ function servedToolArgs(tc, wire, logger) {
75
+ const args = toolArgsForWire(tc);
76
+ if (args.kind === "parsed") return args;
77
+ const scope = toolArgumentsScope.getStore();
78
+ (logger ?? scope?.logger)?.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
79
+ if (scope?.strict !== true) return {
80
+ value: {},
81
+ text: "{}",
82
+ raw: "{}"
83
+ };
84
+ if (wire === "object") throw new InvalidToolArgumentsError(tc);
85
+ return {
86
+ value: void 0,
87
+ text: args.raw,
88
+ raw: args.raw
89
+ };
90
+ }
91
+ /**
92
+ * A custom (freeform) tool call reached a wire that has no faithful projection
93
+ * for it. Only the OpenAI Responses API (HTTP and WebSocket) emits custom tool
94
+ * calls, from a fixture's `customToolCalls` or a `customToolCall` block in its
95
+ * `responsesBlocks`. The handlers of the other wires raise this through
96
+ * {@link rejectResponsesOnlyToolCalls} (usually via
97
+ * {@link requireEmittedFunctionToolCalls}) before they write any content.
98
+ * Ollama `/api/generate` is the exception: it rejects every tool-call fixture
99
+ * with a 400 before this check.
100
+ */
101
+ var UnsupportedToolCallError = class extends Error {
102
+ toolName;
103
+ wire;
104
+ constructor(toolName, wire) {
105
+ super(`aimock: fixture tool call "${toolName}" is a custom tool call, which only the OpenAI Responses API supports; this request arrived on ${wire}`);
106
+ this.name = "UnsupportedToolCallError";
107
+ this.toolName = toolName;
108
+ this.wire = wire;
109
+ }
110
+ };
111
+ /**
112
+ * A fixture tool call is malformed: a `toolCalls` entry that is not an object,
113
+ * a malformed `customToolCalls` entry, or a malformed tool block in
114
+ * `responsesBlocks`. Programmatic and factory fixtures skip load validation,
115
+ * so this request-time guard is their validation for the rules
116
+ * {@link assertResponsesToolCalls} and {@link resolveResponsesBlocks} check.
117
+ * An HTTP request then fails with a 500 that carries this message and `code`;
118
+ * a WebSocket turn gets the transport's error message with the code. A
119
+ * malformed legacy `blocks` entry keeps the plain Error of
120
+ * {@link resolveFixtureBlocks}.
121
+ */
122
+ var InvalidFixtureToolCallError = class extends Error {
123
+ code = "aimock_invalid_fixture_tool_call";
124
+ constructor(index, problem, carrier = "toolCalls") {
125
+ super(carrier === "blocks" ? `Invalid fixture block at index ${index}: ${problem}` : `Invalid fixture tool call: ${problem} (${carrier}[${index}])`);
126
+ this.name = "InvalidFixtureToolCallError";
127
+ }
128
+ };
129
+ /**
130
+ * Request-time guard for one custom tool call (a `customToolCalls` entry).
131
+ * Programmatic and factory fixtures skip load validation, so this is their
132
+ * validation: an object with a non-empty string `name`, a string `input`, no
133
+ * `arguments`, a string `id` when present and a non-empty string `namespace`
134
+ * when present.
135
+ */
136
+ function assertCustomToolCall(call, index) {
137
+ const fail = (problem) => {
138
+ throw new InvalidFixtureToolCallError(index, problem, "customToolCalls");
139
+ };
140
+ if (!isPlainObject(call)) fail("expected an object");
141
+ const tc = call;
142
+ if (tc.namespace !== void 0 && (typeof tc.namespace !== "string" || tc.namespace === "")) fail("\"namespace\" must be a non-empty string when present");
143
+ if (typeof tc.name !== "string" || tc.name === "") fail("custom tool call requires a non-empty string \"name\" field");
144
+ if (typeof tc.input !== "string") fail("\"input\" must be a string for a custom tool call");
145
+ if (tc.arguments !== void 0) fail("custom tool call takes \"input\", not \"arguments\"");
146
+ if (tc.id !== void 0 && typeof tc.id !== "string") fail("custom tool call \"id\" must be a string when present");
147
+ }
148
+ /**
149
+ * Validate a response's `customToolCalls` (OpenAI Responses only) before it is
150
+ * served. Throws {@link InvalidFixtureToolCallError} on the first malformed
151
+ * entry. The key is new, so no fixture of an earlier release reaches this.
152
+ */
153
+ function assertCustomToolCalls(response) {
154
+ const list = response.customToolCalls;
155
+ if (list === void 0) return;
156
+ if (!Array.isArray(list)) throw new InvalidFixtureToolCallError(0, "customToolCalls must be an array", "customToolCalls");
157
+ list.forEach((call, i) => assertCustomToolCall(call, i));
158
+ }
159
+ /**
160
+ * Validate the custom calls among the tool calls the OpenAI Responses API is
161
+ * about to serve (see {@link servedToolCalls}). Function calls are not
162
+ * checked here: they keep the handling of earlier releases (a non-string
163
+ * `arguments` fails with the uncoded error of the Responses builders).
164
+ */
165
+ function assertResponsesToolCalls(calls) {
166
+ let customIndex = 0;
167
+ for (const tc of calls) if (isPlainObject(tc) && tc.type === "custom") assertCustomToolCall(tc, customIndex++);
168
+ }
169
+ /**
170
+ * A fixture's `toolCalls` for a wire that cannot carry custom tool calls.
171
+ * Every entry is a function call and passes through unchanged, exactly as in
172
+ * earlier releases (the builders read its `name`, `arguments` and `id`; its
173
+ * Responses-only `namespace` is not emitted). Custom calls live in
174
+ * `customToolCalls`, which {@link rejectResponsesOnlyToolCalls} rejects.
175
+ */
176
+ function requireFunctionToolCalls(calls) {
177
+ return calls;
178
+ }
179
+ /**
180
+ * Reject the Responses-only custom tool calls on a wire that cannot carry
181
+ * them: a non-empty `customToolCalls`, or a `customToolCall` block in
182
+ * `responsesBlocks`. Throws {@link UnsupportedToolCallError} naming `wire`.
183
+ * Both keys are new, so no fixture of an earlier release reaches this error.
184
+ */
185
+ function rejectResponsesOnlyToolCalls(response, wire) {
186
+ const r = response;
187
+ if (Array.isArray(r.customToolCalls) && r.customToolCalls.length > 0) {
188
+ const first = r.customToolCalls[0];
189
+ throw new UnsupportedToolCallError(isPlainObject(first) && typeof first.name === "string" ? first.name : "", wire);
190
+ }
191
+ if (Array.isArray(r.responsesBlocks)) {
192
+ for (const block of r.responsesBlocks) if (isPlainObject(block) && block.type === "customToolCall") throw new UnsupportedToolCallError(typeof block.name === "string" ? block.name : "", wire);
193
+ }
194
+ }
195
+ /**
196
+ * {@link rejectResponsesOnlyToolCalls}, then {@link requireFunctionToolCalls}
197
+ * on the response's `toolCalls` (ignoring any `blocks`).
198
+ */
199
+ function requireServedFunctionToolCalls(response, wire) {
200
+ rejectResponsesOnlyToolCalls(response, wire);
201
+ return requireFunctionToolCalls(response.toolCalls ?? []);
202
+ }
203
+ /**
204
+ * The function calls a non-Responses wire emits for this response, after
205
+ * {@link rejectResponsesOnlyToolCalls}. Non-empty ordered `blocks` are
206
+ * authoritative: the builders stream them (and validate them through
207
+ * {@link resolveFixtureBlocks}) and never emit the legacy `toolCalls`, so the
208
+ * result is empty. Without blocks, `toolCalls` is checked by
209
+ * {@link requireFunctionToolCalls}.
210
+ */
211
+ function requireEmittedFunctionToolCalls(response, wire) {
212
+ rejectResponsesOnlyToolCalls(response, wire);
213
+ if (Array.isArray(response.blocks) && response.blocks.length > 0) return [];
214
+ return requireFunctionToolCalls(response.toolCalls ?? []);
215
+ }
216
+ /** True for the fixture tool-call errors a handler surfaces as a coded 500. */
217
+ function isFixtureToolCallError(error) {
218
+ return error instanceof InvalidToolArgumentsError || error instanceof UnsupportedToolCallError || error instanceof InvalidFixtureToolCallError;
219
+ }
220
+ /** The aimock error code carried in every provider envelope for a fixture tool-call error. */
221
+ function fixtureToolCallErrorCode(error) {
222
+ if (error instanceof UnsupportedToolCallError) return "aimock_unsupported_tool_call";
223
+ if (error instanceof InvalidFixtureToolCallError) return error.code;
224
+ return "aimock_invalid_tool_arguments";
225
+ }
226
+ /**
227
+ * Google RPC error `details` carrying an aimock fixture tool-call code, for the
228
+ * Gemini, Vertex AI and Gemini Live envelopes (`google.rpc.ErrorInfo`: an
229
+ * UPPER_SNAKE `reason`, a `domain`, and string `metadata`).
230
+ */
231
+ function googleFixtureToolCallErrorDetails(code) {
232
+ return [{
233
+ "@type": "type.googleapis.com/google.rpc.ErrorInfo",
234
+ reason: code.toUpperCase(),
235
+ domain: "aimock",
236
+ metadata: { code }
237
+ }];
238
+ }
239
+ /**
240
+ * Run a WebSocket preflight `check` that may raise a fixture tool-call error
241
+ * ({@link InvalidToolArgumentsError}, {@link UnsupportedToolCallError} or
242
+ * {@link InvalidFixtureToolCallError}).
243
+ * Such an error marks the already-journaled entry as a 500 carrying the
244
+ * message, then propagates to the transport's error handler.
245
+ */
246
+ function journalFixtureToolCallError(journalEntry, check) {
247
+ try {
248
+ return check();
249
+ } catch (error) {
250
+ if (isFixtureToolCallError(error)) {
251
+ journalEntry.response.status = 500;
252
+ journalEntry.response.error = error.message;
253
+ }
254
+ throw error;
255
+ }
256
+ }
10
257
  /**
11
258
  * Resolve effective strict mode from per-request header and server default.
12
259
  * Header values override the server default — same precedence pattern as chaos
@@ -183,14 +430,192 @@ function isLiveResponse(value) {
183
430
  function isResponseFactory(r) {
184
431
  return typeof r === "function";
185
432
  }
433
+ const serverFixtureLists = /* @__PURE__ */ new WeakMap();
434
+ /**
435
+ * Array methods that read elements and write them back. On a server list
436
+ * they run on the caller's array, so the extended view of an element is
437
+ * never written into it.
438
+ */
439
+ const ELEMENT_MOVING_METHODS = new Set([
440
+ "copyWithin",
441
+ "fill",
442
+ "pop",
443
+ "push",
444
+ "reverse",
445
+ "shift",
446
+ "sort",
447
+ "splice",
448
+ "unshift"
449
+ ]);
450
+ /**
451
+ * The fixture list one server reads and writes: a live view of the caller's
452
+ * `fixtures` array, with its own identity. The matcher reads the server's
453
+ * `responsesTools` mode from the list it is given, so every handler on every
454
+ * wire sees that server's mode without extra parameters, and two servers that
455
+ * share one array each keep their own mode. With `"extended"`, reading an
456
+ * element gives `extendedView(element)` (the fixture with its held keys
457
+ * applied); the caller's array and its fixtures are never changed for it.
458
+ * Writes (push, splice, `length = 0`, ...) reach the caller's array.
459
+ */
460
+ function createServerFixtureList(fixtures, mode, extendedView) {
461
+ const boundMethods = /* @__PURE__ */ new Map();
462
+ const list = new Proxy(fixtures, { get(target, key) {
463
+ const value = Reflect.get(target, key);
464
+ if (typeof value === "function") {
465
+ if (!ELEMENT_MOVING_METHODS.has(key)) return value;
466
+ let bound = boundMethods.get(key);
467
+ if (bound === void 0) {
468
+ bound = value.bind(target);
469
+ boundMethods.set(key, bound);
470
+ }
471
+ return bound;
472
+ }
473
+ if (typeof key !== "string" || mode() !== "extended") return value;
474
+ const first = key.charCodeAt(0);
475
+ if (first < 48 || first > 57 || value === null || typeof value !== "object") return value;
476
+ return extendedView(value);
477
+ } });
478
+ serverFixtureLists.set(list, {
479
+ target: fixtures,
480
+ mode
481
+ });
482
+ return list;
483
+ }
484
+ /** Fixture arrays that only their own server reads: see {@link markServerOwnedFixtures}. */
485
+ const serverOwnedFixtureArrays = /* @__PURE__ */ new WeakSet();
486
+ /**
487
+ * @internal Mark `fixtures` as an array no other server reads (LLMock's own
488
+ * array). A server may change the fixtures it makes itself (the control API)
489
+ * in place only in such an array; in a caller's array, which another server
490
+ * may share, they stay as made and an extended server reads them through its
491
+ * view.
492
+ */
493
+ function markServerOwnedFixtures(fixtures) {
494
+ serverOwnedFixtureArrays.add(fixtures);
495
+ }
496
+ /** Whether the array behind `fixtures` (a server list or an array) is server-owned. */
497
+ function isServerOwnedFixtures(fixtures) {
498
+ return serverOwnedFixtureArrays.has(fixtureListTarget(fixtures));
499
+ }
500
+ /** Whether `fixtures` is the list of a server running with `responsesTools: "extended"`. */
501
+ function isExtendedResponsesToolsList(fixtures) {
502
+ return serverFixtureLists.get(fixtures)?.mode() === "extended";
503
+ }
504
+ /** The caller's array behind a server list; any other array is itself. */
505
+ function fixtureListTarget(fixtures) {
506
+ return serverFixtureLists.get(fixtures)?.target ?? fixtures;
507
+ }
508
+ /**
509
+ * Requests matched against an extended fixture list. {@link resolveResponse}
510
+ * reads it, so the response a handler serves follows the same mode as the
511
+ * match that chose its fixture.
512
+ */
513
+ const extendedResponsesToolsRequests = /* @__PURE__ */ new WeakSet();
514
+ /** Mark `request` as matched with `responsesTools: "extended"`. */
515
+ function markExtendedResponsesToolsRequest(request) {
516
+ extendedResponsesToolsRequests.add(request);
517
+ }
518
+ /**
519
+ * Drop `customToolCalls` and `responsesBlocks`, for responses served without
520
+ * `responsesTools: "extended"`. Earlier releases ignored both keys, so a
521
+ * fixture that carries them serves exactly as before. Returns the same object
522
+ * when neither key is set.
523
+ */
524
+ function withoutResponsesToolKeys(response) {
525
+ if (!isPlainObject(response)) return response;
526
+ if (!("customToolCalls" in response) && !("responsesBlocks" in response)) return response;
527
+ const copy = { ...response };
528
+ delete copy.customToolCalls;
529
+ delete copy.responsesBlocks;
530
+ return copy;
531
+ }
186
532
  async function resolveResponse(fixture, request) {
187
- if (typeof fixture.response === "function") try {
188
- return normalizeFactoryResponse(await fixture.response(request));
189
- } catch (err) {
190
- const msg = err instanceof Error ? err.message : String(err);
191
- throw new Error(`Response factory threw: ${msg}`, { cause: err });
533
+ const served = (response) => sanitizeFixtureResponse(extendedResponsesToolsRequests.has(request) ? response : withoutResponsesToolKeys(response));
534
+ if (typeof fixture.response === "function") {
535
+ let normalized;
536
+ try {
537
+ normalized = normalizeFactoryResponse(await fixture.response(request));
538
+ } catch (err) {
539
+ const msg = err instanceof Error ? err.message : String(err);
540
+ throw new Error(`Response factory threw: ${msg}`, { cause: err });
541
+ }
542
+ return served(normalized);
192
543
  }
193
- return fixture.response;
544
+ return served(fixture.response);
545
+ }
546
+ function isPlainObject(value) {
547
+ return value !== null && typeof value === "object" && !Array.isArray(value);
548
+ }
549
+ /** A non-empty string, the only valid value of a tool call's `namespace`. */
550
+ function isValidNamespace(value) {
551
+ return typeof value === "string" && value !== "";
552
+ }
553
+ /**
554
+ * Drop the keys a served `toolCalls` entry or `toolCall` block ignores, so
555
+ * no handler reads them: on a `toolCalls` entry, `type` and `input` (a
556
+ * `toolCalls` entry is always a function call; custom calls live in
557
+ * `customToolCalls`) and an invalid `namespace` (not a non-empty string); on a
558
+ * `toolCall` block, `input` and an invalid `namespace`. Earlier releases
559
+ * ignored all of these, so fixtures that carry them serve exactly as before.
560
+ * Returns the same object when nothing needs dropping. `customToolCalls` and
561
+ * `responsesBlocks` are new keys and are validated, not sanitized.
562
+ */
563
+ function sanitizeFixtureResponse(response) {
564
+ if (!isPlainObject(response)) return response;
565
+ const r = response;
566
+ const dropKeys = (entry, keys) => {
567
+ if (!isPlainObject(entry)) return entry;
568
+ const drop = keys.filter((key) => key in entry && (key !== "namespace" || !isValidNamespace(entry.namespace)));
569
+ if (drop.length === 0) return entry;
570
+ const copy = { ...entry };
571
+ for (const key of drop) delete copy[key];
572
+ return copy;
573
+ };
574
+ const sanitizeList = (list, pick) => {
575
+ if (!Array.isArray(list)) return list;
576
+ const next = list.map(pick);
577
+ return next.some((entry, k) => entry !== list[k]) ? next : list;
578
+ };
579
+ const toolCalls = sanitizeList(r.toolCalls, (entry) => dropKeys(entry, [
580
+ "type",
581
+ "input",
582
+ "namespace"
583
+ ]));
584
+ let blocks = sanitizeList(r.blocks, (entry) => isPlainObject(entry) && entry.type === "toolCall" ? dropKeys(entry, ["input", "namespace"]) : entry);
585
+ if (!(Array.isArray(r.blocks) && r.blocks.length > 0) && Array.isArray(r.responsesBlocks) && r.responsesBlocks.length > 0 && !r.responsesBlocks.some((entry) => isPlainObject(entry) && entry.type === "customToolCall")) blocks = r.responsesBlocks;
586
+ if (toolCalls === r.toolCalls && blocks === r.blocks) return response;
587
+ const copy = { ...r };
588
+ if (toolCalls !== r.toolCalls) copy.toolCalls = toolCalls;
589
+ if (blocks !== r.blocks) copy.blocks = blocks;
590
+ return copy;
591
+ }
592
+ /**
593
+ * Drop `namespace` from every `toolCalls` entry and `toolCall` block, for
594
+ * OpenAI Responses requests served without `responsesTools: "extended"`:
595
+ * earlier releases never emitted it. (Without that mode, resolveResponse has
596
+ * already dropped `customToolCalls` and `responsesBlocks`.) Returns the same
597
+ * object when no namespace is set.
598
+ */
599
+ function withoutLegacyNamespaces(response) {
600
+ if (!isPlainObject(response)) return response;
601
+ const r = response;
602
+ const strip = (list, isTarget) => {
603
+ if (!Array.isArray(list)) return list;
604
+ if (!list.some((entry) => isPlainObject(entry) && isTarget(entry) && "namespace" in entry)) return list;
605
+ return list.map((entry) => {
606
+ if (!isPlainObject(entry) || !isTarget(entry) || !("namespace" in entry)) return entry;
607
+ const copy = { ...entry };
608
+ delete copy.namespace;
609
+ return copy;
610
+ });
611
+ };
612
+ const toolCalls = strip(r.toolCalls, () => true);
613
+ const blocks = strip(r.blocks, (entry) => entry.type === "toolCall");
614
+ if (toolCalls === r.toolCalls && blocks === r.blocks) return response;
615
+ const copy = { ...r };
616
+ if (toolCalls !== r.toolCalls) copy.toolCalls = toolCalls;
617
+ if (blocks !== r.blocks) copy.blocks = blocks;
618
+ return copy;
194
619
  }
195
620
  function normalizeFactoryResponse(raw) {
196
621
  const r = { ...raw };
@@ -209,6 +634,10 @@ function normalizeFactoryResponse(raw) {
209
634
  };
210
635
  return { ...block };
211
636
  });
637
+ if (Array.isArray(r.responsesBlocks)) r.responsesBlocks = r.responsesBlocks.map((block) => isPlainObject(block) && block.type === "toolCall" && typeof block.arguments === "object" && block.arguments !== null ? {
638
+ ...block,
639
+ arguments: JSON.stringify(block.arguments)
640
+ } : block);
212
641
  return r;
213
642
  }
214
643
  function generateId(prefix = "chatcmpl") {
@@ -280,6 +709,12 @@ function isTextResponse(r) {
280
709
  function isToolCallResponse(r) {
281
710
  return "toolCalls" in r && Array.isArray(r.toolCalls) && !("content" in r && typeof r.content === "string");
282
711
  }
712
+ /**
713
+ * Whether `r` is a combined content + tool calls response: a string `content`
714
+ * with a `toolCalls` array, or a non-empty `blocks` array. Public, with the
715
+ * 1.44.0 result for every input; `responsesBlocks` does not count. aimock's
716
+ * own handlers use {@link isCombinedFixtureResponse}, which also counts it.
717
+ */
283
718
  function isContentWithToolCallsResponse(r) {
284
719
  const o = r;
285
720
  const hasContentAndToolCalls = "content" in r && typeof o.content === "string" && "toolCalls" in r && Array.isArray(o.toolCalls);
@@ -287,6 +722,19 @@ function isContentWithToolCallsResponse(r) {
287
722
  return hasContentAndToolCalls || hasNonEmptyBlocks;
288
723
  }
289
724
  /**
725
+ * @internal The combined-response guard aimock's handlers, validation and
726
+ * misbehavior use: {@link isContentWithToolCallsResponse} plus a non-empty
727
+ * `responsesBlocks`. In legacy mode those keys are dropped before serving, so
728
+ * only `responsesTools: "extended"` sees the extra clause.
729
+ */
730
+ function isCombinedFixtureResponse(r) {
731
+ const o = r;
732
+ const hasContentAndToolCalls = "content" in r && typeof o.content === "string" && "toolCalls" in r && Array.isArray(o.toolCalls);
733
+ const hasNonEmptyBlocks = Array.isArray(o.blocks) && o.blocks.length > 0;
734
+ const hasNonEmptyResponsesBlocks = Array.isArray(o.responsesBlocks) && o.responsesBlocks.length > 0;
735
+ return hasContentAndToolCalls || hasNonEmptyBlocks || hasNonEmptyResponsesBlocks;
736
+ }
737
+ /**
290
738
  * Validate and pass through the ordered `blocks` field of a combined
291
739
  * content+toolCalls fixture. Used ONLY on the new block-iteration path (when a
292
740
  * fixture explicitly sets `blocks`); it is NOT a legacy-order reconstructor —
@@ -313,7 +761,9 @@ function isContentWithToolCallsResponse(r) {
313
761
  * optional string `id`. The return type guarantees `arguments: string` — every
314
762
  * caller relies on that. Throws on a malformed array or entry — same fail-fast
315
763
  * idiom as the other fixture validators in this module (see e.g. the factory
316
- * guard at {@link resolveResponse}).
764
+ * guard at {@link resolveResponse}). A `customToolCall` block is not a
765
+ * `blocks` entry (it belongs in `responsesBlocks`, see
766
+ * {@link resolveResponsesBlocks}) and fails as an unknown type.
317
767
  */
318
768
  function resolveFixtureBlocks(blocks) {
319
769
  if (!Array.isArray(blocks)) throw new Error(`Invalid fixture blocks: expected an array, got ${typeof blocks}`);
@@ -343,6 +793,130 @@ function resolveFixtureBlocks(blocks) {
343
793
  } else throw new Error(`Invalid fixture block at index ${i}: unknown type ${JSON.stringify(b.type)} (expected "text" or "toolCall")`);
344
794
  });
345
795
  }
796
+ /**
797
+ * Validate and normalize a fixture's `responsesBlocks` (OpenAI Responses
798
+ * only). Same rules and normalized copy as {@link resolveFixtureBlocks}, plus:
799
+ * a `customToolCall` block with a non-empty string `name`, a string `input`
800
+ * (never parsed or stringified), no `arguments` and an optional string `id`;
801
+ * on either tool block, `namespace` must be a non-empty string when present,
802
+ * and a `toolCall` block carries no `input`. The key is new, so a malformed
803
+ * tool block throws the coded {@link InvalidFixtureToolCallError}; a malformed
804
+ * text block or an unknown type throws a plain Error.
805
+ */
806
+ function resolveResponsesBlocks(blocks) {
807
+ if (!Array.isArray(blocks)) throw new Error(`Invalid fixture responsesBlocks: expected an array, got ${typeof blocks}`);
808
+ return blocks.map((block, i) => {
809
+ if (!isPlainObject(block)) throw new Error(`Invalid fixture block at index ${i}: expected an object`);
810
+ const b = block;
811
+ const failToolCall = (problem) => {
812
+ throw new InvalidFixtureToolCallError(i, problem, "blocks");
813
+ };
814
+ if ((b.type === "toolCall" || b.type === "customToolCall") && b.namespace !== void 0 && (typeof b.namespace !== "string" || b.namespace === "")) failToolCall("\"namespace\" must be a non-empty string when present");
815
+ const namespace = typeof b.namespace === "string" ? { namespace: b.namespace } : {};
816
+ const id = typeof b.id === "string" ? { id: b.id } : {};
817
+ if (b.type === "text") {
818
+ if (typeof b.text !== "string") throw new Error(`Invalid fixture block at index ${i}: "text" block requires a string "text" field`);
819
+ return {
820
+ type: "text",
821
+ text: b.text
822
+ };
823
+ }
824
+ if (b.type === "toolCall") {
825
+ if (b.input !== void 0) failToolCall("\"input\" is only valid on a \"customToolCall\" block");
826
+ if (typeof b.name !== "string") failToolCall("\"toolCall\" block requires a string \"name\" field");
827
+ if (b.id !== void 0 && typeof b.id !== "string") failToolCall("\"toolCall\" block \"id\" must be a string when present");
828
+ const args = typeof b.arguments === "object" && b.arguments !== null ? JSON.stringify(b.arguments) : b.arguments;
829
+ if (typeof args !== "string") failToolCall("\"toolCall\" block requires a string or object \"arguments\" field");
830
+ return {
831
+ type: "toolCall",
832
+ name: b.name,
833
+ arguments: args,
834
+ ...id,
835
+ ...namespace
836
+ };
837
+ }
838
+ if (b.type === "customToolCall") {
839
+ if (typeof b.name !== "string" || b.name === "") failToolCall("\"customToolCall\" block requires a non-empty string \"name\" field");
840
+ if (typeof b.input !== "string") failToolCall("\"customToolCall\" block requires a string \"input\" field");
841
+ if (b.arguments !== void 0) failToolCall("\"customToolCall\" block takes \"input\", not \"arguments\"");
842
+ if (b.id !== void 0 && typeof b.id !== "string") failToolCall("\"customToolCall\" block \"id\" must be a string when present");
843
+ return {
844
+ type: "customToolCall",
845
+ name: b.name,
846
+ input: b.input,
847
+ ...id,
848
+ ...namespace
849
+ };
850
+ }
851
+ throw new Error(`Invalid fixture block at index ${i}: unknown type ${JSON.stringify(b.type)} (expected "text", "toolCall" or "customToolCall")`);
852
+ });
853
+ }
854
+ /** Allocate the exact OpenAI call identities once for both wire output and observations. */
855
+ function prepareOpenAIChatMisbehavior(plan) {
856
+ const response = plan.response;
857
+ const combined = isCombinedFixtureResponse(response);
858
+ const outcome = combined ? resolveServedBlockOutcome(response) : void 0;
859
+ const calls = outcome?.toolCalls ?? (combined || isToolCallResponse(response) ? servedToolCalls(response) : []);
860
+ const duplicate = plan.duplicateId;
861
+ const toolCalls = calls.map((call, index) => ({
862
+ ...call,
863
+ ...index === duplicate?.destinationIndex ? {} : { id: call.id || generateToolCallId() }
864
+ }));
865
+ if (duplicate) toolCalls[duplicate.destinationIndex].id = toolCalls[duplicate.sourceIndex].id;
866
+ let preparedResponse = { ...response };
867
+ if ("usage" in preparedResponse) delete preparedResponse.usage;
868
+ if (plan.stop === "length") preparedResponse = {
869
+ ...preparedResponse,
870
+ finishReason: "length"
871
+ };
872
+ if (outcome) preparedResponse = withServedToolCalls({
873
+ ...preparedResponse,
874
+ content: outcome.content
875
+ }, toolCalls, rebuildOrderedBlocks(outcome.ordered, toolCalls));
876
+ else if (combined || isToolCallResponse(response)) preparedResponse = withServedToolCalls(preparedResponse, toolCalls);
877
+ return {
878
+ ...plan,
879
+ response: preparedResponse,
880
+ summary: {
881
+ ...plan.summary,
882
+ servedToolCalls: toolCalls.map(servedMisbehaviorToolCall)
883
+ }
884
+ };
885
+ }
886
+ /**
887
+ * The journaled form of a served misbehavior tool call: `{ name, arguments,
888
+ * id }`. A Responses custom tool call adds `type: "custom"` and carries its
889
+ * free-text `input` in `arguments`; `namespace` is recorded only when set.
890
+ */
891
+ function servedMisbehaviorToolCall(call) {
892
+ const namespace = call.namespace !== void 0 ? { namespace: call.namespace } : {};
893
+ return call.type === "custom" ? {
894
+ type: "custom",
895
+ name: call.name,
896
+ arguments: call.input,
897
+ id: call.id,
898
+ ...namespace
899
+ } : {
900
+ name: call.name,
901
+ arguments: call.arguments,
902
+ id: call.id,
903
+ ...namespace
904
+ };
905
+ }
906
+ /** The text a tool call contributes to an estimated completion: name plus arguments or input. */
907
+ function toolCallUsageText(call) {
908
+ return call.name + (call.type === "custom" ? call.input : call.arguments);
909
+ }
910
+ /** Estimate once from the full applied output, before any delivery interruption. */
911
+ function resolveOpenAIChatMisbehaviorUsage(plan, request, exposeReasoning = true) {
912
+ const response = plan.response;
913
+ const combined = isCombinedFixtureResponse(response);
914
+ const outcome = combined ? resolveServedBlockOutcome(response) : void 0;
915
+ const calls = outcome?.toolCalls ?? (combined || isToolCallResponse(response) ? servedToolCalls(response) : []);
916
+ const content = outcome?.content ?? ("content" in response && typeof response.content === "string" ? response.content : "");
917
+ const reasoning = plan.reasoning ?? ("reasoning" in response && typeof response.reasoning === "string" ? response.reasoning : "");
918
+ return resolveUsage(void 0, request.messages.map((message) => typeof message.content === "string" ? message.content : Array.isArray(message.content) ? message.content.map((part) => part.text ?? "").join("") : "").join(""), content + calls.map(toolCallUsageText).join("") + (plan.refusal ?? "") + (exposeReasoning ? reasoning : ""));
919
+ }
346
920
  /** Project normalized ordered blocks without changing the fixture or generating ids. */
347
921
  function resolveFixtureBlockOutcome(blocks) {
348
922
  const ordered = resolveFixtureBlocks(blocks);
@@ -361,6 +935,118 @@ function resolveFixtureBlockOutcome(blocks) {
361
935
  hasToolCalls: toolCalls.length > 0
362
936
  };
363
937
  }
938
+ /**
939
+ * Every tool call a response serves, in order: the function calls of
940
+ * `toolCalls`, then the custom calls of `customToolCalls` (each tagged
941
+ * `type: "custom"`). Only the OpenAI Responses API may serve the custom ones;
942
+ * the other wires reject a non-empty `customToolCalls` first
943
+ * ({@link rejectResponsesOnlyToolCalls}).
944
+ */
945
+ function servedToolCalls(response) {
946
+ const calls = [...response.toolCalls ?? []];
947
+ for (const call of Array.isArray(response.customToolCalls) ? response.customToolCalls : []) calls.push(isPlainObject(call) ? {
948
+ ...call,
949
+ type: "custom"
950
+ } : call);
951
+ return calls;
952
+ }
953
+ /**
954
+ * Resolve the ordered blocks a response serves, with each tool block projected
955
+ * to its {@link FixtureToolCall}: a non-empty `responsesBlocks` (OpenAI
956
+ * Responses only, through {@link resolveResponsesBlocks}), else a non-empty
957
+ * legacy `blocks` (through {@link resolveFixtureBlocks}, which rejects a
958
+ * `customToolCall` block with the error of earlier releases). Undefined when
959
+ * the response has neither.
960
+ */
961
+ function resolveServedBlockOutcome(response) {
962
+ const r = response;
963
+ let ordered;
964
+ let source;
965
+ if (Array.isArray(r.responsesBlocks) && r.responsesBlocks.length > 0) {
966
+ ordered = resolveResponsesBlocks(r.responsesBlocks);
967
+ source = "responsesBlocks";
968
+ } else if (Array.isArray(r.blocks) && r.blocks.length > 0) {
969
+ ordered = resolveFixtureBlocks(r.blocks);
970
+ source = "blocks";
971
+ } else return void 0;
972
+ let content = "";
973
+ const toolCalls = [];
974
+ for (const block of ordered) if (block.type === "text") content += block.text;
975
+ else toolCalls.push(fixtureBlockToolCall(block));
976
+ return {
977
+ ordered,
978
+ content,
979
+ toolCalls,
980
+ hasToolCalls: toolCalls.length > 0,
981
+ source
982
+ };
983
+ }
984
+ /**
985
+ * Write rewritten served tool calls (and, with `ordered`, rebuilt ordered
986
+ * blocks) back into a copy of `response`, split by carrier: function calls go
987
+ * to `toolCalls`, custom calls to `customToolCalls`, and the blocks to
988
+ * `responsesBlocks` when the response served those, else to `blocks`.
989
+ */
990
+ function withServedToolCalls(response, toolCalls, ordered) {
991
+ const functionCalls = [];
992
+ const customCalls = [];
993
+ for (const call of toolCalls) if (call.type === "custom") customCalls.push(call);
994
+ else functionCalls.push(call);
995
+ const out = {
996
+ ...response,
997
+ toolCalls: functionCalls
998
+ };
999
+ if (customCalls.length > 0 || "customToolCalls" in response) out.customToolCalls = customCalls;
1000
+ if (ordered) {
1001
+ const r = response;
1002
+ if (Array.isArray(r.responsesBlocks) && r.responsesBlocks.length > 0) out.responsesBlocks = ordered;
1003
+ else out.blocks = ordered;
1004
+ }
1005
+ return out;
1006
+ }
1007
+ /** Project one normalized tool block to the matching served tool call. */
1008
+ function fixtureBlockToolCall(block) {
1009
+ const id = block.id !== void 0 ? { id: block.id } : {};
1010
+ const namespace = block.namespace !== void 0 ? { namespace: block.namespace } : {};
1011
+ return block.type === "customToolCall" ? {
1012
+ type: "custom",
1013
+ name: block.name,
1014
+ input: block.input,
1015
+ ...id,
1016
+ ...namespace
1017
+ } : {
1018
+ name: block.name,
1019
+ arguments: block.arguments,
1020
+ ...id,
1021
+ ...namespace
1022
+ };
1023
+ }
1024
+ /** Project one served tool call back to its ordered tool block. */
1025
+ function toolCallFixtureBlock(call) {
1026
+ const id = call.id !== void 0 ? { id: call.id } : {};
1027
+ const namespace = call.namespace !== void 0 ? { namespace: call.namespace } : {};
1028
+ return call.type === "custom" ? {
1029
+ type: "customToolCall",
1030
+ name: call.name,
1031
+ input: call.input,
1032
+ ...id,
1033
+ ...namespace
1034
+ } : {
1035
+ type: "toolCall",
1036
+ name: call.name,
1037
+ arguments: call.arguments,
1038
+ ...id,
1039
+ ...namespace
1040
+ };
1041
+ }
1042
+ /**
1043
+ * Rebuild ordered blocks after their tool calls were rewritten one-for-one:
1044
+ * text blocks are copied and the Nth tool block becomes `toolCalls[N]`.
1045
+ */
1046
+ function rebuildOrderedBlocks(ordered, toolCalls) {
1047
+ let callIndex = 0;
1048
+ return ordered.map((block) => block.type === "text" ? { ...block } : toolCallFixtureBlock(toolCalls[callIndex++]));
1049
+ }
364
1050
  function isErrorResponse(r) {
365
1051
  return "error" in r && r.error !== null && typeof r.error === "object" && "message" in r.error && typeof r.error.message === "string";
366
1052
  }
@@ -438,6 +1124,17 @@ function estimateTokens(text) {
438
1124
  return Math.max(1, Math.ceil(text.length / 4));
439
1125
  }
440
1126
  /**
1127
+ * Estimate prompt tokens from a request's messages array.
1128
+ */
1129
+ function estimatePromptTokens(messages) {
1130
+ let totalChars = 0;
1131
+ for (const msg of messages) if (typeof msg.content === "string") totalChars += msg.content.length;
1132
+ else if (Array.isArray(msg.content)) {
1133
+ for (const part of msg.content) if (part.text) totalChars += part.text.length;
1134
+ }
1135
+ return Math.max(1, Math.ceil(totalChars / 4));
1136
+ }
1137
+ /**
441
1138
  * Build usage object: use explicit overrides if provided, otherwise estimate.
442
1139
  *
443
1140
  * Shared by BOTH the non-streaming completion builders (below) and the
@@ -552,6 +1249,140 @@ function buildTextChunks(content, model, chunkSize, reasoning, overrides) {
552
1249
  });
553
1250
  return chunks;
554
1251
  }
1252
+ /** K9 exposure is selected by the provider path, never the echoed response model. */
1253
+ function buildOpenAIReasoningChunks(reasoning, model, chunkSize, exposeReasoning, overrides) {
1254
+ return buildTextChunks(exposeReasoning ? reasoning : "", model, chunkSize, void 0, {
1255
+ ...overrides,
1256
+ role: "assistant",
1257
+ finishReason: "length"
1258
+ }).map((chunk) => ({
1259
+ ...chunk,
1260
+ choices: chunk.choices.map((choice) => ({
1261
+ ...choice,
1262
+ delta: choice.delta.role !== void 0 ? {
1263
+ role: "assistant",
1264
+ content: null
1265
+ } : typeof choice.delta.content === "string" ? {
1266
+ role: "assistant",
1267
+ content: "",
1268
+ reasoning: choice.delta.content,
1269
+ reasoning_details: [{
1270
+ type: "reasoning.text",
1271
+ text: choice.delta.content,
1272
+ format: "unknown",
1273
+ index: 0
1274
+ }]
1275
+ } : {}
1276
+ }))
1277
+ }));
1278
+ }
1279
+ /** Render exposed reasoning or native OpenAI's empty length completion. */
1280
+ function buildOpenAIReasoningCompletion(reasoning, model, exposeReasoning, overrides, requestMessages) {
1281
+ const visible = exposeReasoning ? reasoning : "";
1282
+ const completion = buildTextCompletion(visible, model, void 0, {
1283
+ ...overrides,
1284
+ role: "assistant",
1285
+ finishReason: "length",
1286
+ usage: void 0
1287
+ }, requestMessages);
1288
+ return {
1289
+ ...completion,
1290
+ choices: completion.choices.map((choice) => ({
1291
+ ...choice,
1292
+ message: {
1293
+ role: "assistant",
1294
+ content: null,
1295
+ refusal: null,
1296
+ ...exposeReasoning ? {
1297
+ reasoning: visible,
1298
+ reasoning_details: [{
1299
+ type: "reasoning.text",
1300
+ text: visible,
1301
+ format: "unknown",
1302
+ index: 0
1303
+ }]
1304
+ } : {}
1305
+ }
1306
+ }))
1307
+ };
1308
+ }
1309
+ /** Render withheld output with only the role and native content-filter terminal. */
1310
+ function buildOpenAIContentFilterChunks(model, overrides) {
1311
+ return buildTextChunks("", model, 1, void 0, {
1312
+ ...overrides,
1313
+ role: "assistant",
1314
+ finishReason: "content_filter"
1315
+ }).map((chunk) => ({
1316
+ ...chunk,
1317
+ choices: chunk.choices.map((choice) => ({
1318
+ ...choice,
1319
+ delta: choice.delta.role !== void 0 ? {
1320
+ role: "assistant",
1321
+ content: null
1322
+ } : {}
1323
+ }))
1324
+ }));
1325
+ }
1326
+ /** Non-streaming filtered output contains no answer, refusal text, reasoning, or calls. */
1327
+ function buildOpenAIContentFilterCompletion(model, overrides, requestMessages) {
1328
+ const completion = buildTextCompletion("", model, void 0, {
1329
+ ...overrides,
1330
+ role: "assistant",
1331
+ finishReason: "content_filter",
1332
+ usage: void 0
1333
+ }, requestMessages);
1334
+ return {
1335
+ ...completion,
1336
+ choices: completion.choices.map((choice) => ({
1337
+ ...choice,
1338
+ message: {
1339
+ role: "assistant",
1340
+ content: null,
1341
+ refusal: null
1342
+ }
1343
+ }))
1344
+ };
1345
+ }
1346
+ /** Render an applied refusal on its own channel without ordinary content or reasoning. */
1347
+ function buildOpenAIRefusalChunks(refusal, model, chunkSize, overrides) {
1348
+ return buildTextChunks(refusal, model, chunkSize, void 0, {
1349
+ ...overrides,
1350
+ role: "assistant",
1351
+ finishReason: "stop"
1352
+ }).map((chunk) => ({
1353
+ ...chunk,
1354
+ choices: chunk.choices.map((choice) => {
1355
+ const delta = choice.delta.role !== void 0 ? {
1356
+ role: "assistant",
1357
+ content: null
1358
+ } : typeof choice.delta.content === "string" ? { refusal: choice.delta.content } : {};
1359
+ return {
1360
+ ...choice,
1361
+ delta
1362
+ };
1363
+ })
1364
+ }));
1365
+ }
1366
+ /** Refusal-only counterpart of the ordinary text completion builder. */
1367
+ function buildOpenAIRefusalCompletion(refusal, model, overrides, requestMessages) {
1368
+ const completion = buildTextCompletion(refusal, model, void 0, {
1369
+ ...overrides,
1370
+ role: "assistant",
1371
+ finishReason: "stop",
1372
+ usage: void 0
1373
+ }, requestMessages);
1374
+ return {
1375
+ ...completion,
1376
+ choices: completion.choices.map((choice) => ({
1377
+ ...choice,
1378
+ message: {
1379
+ role: "assistant",
1380
+ content: null,
1381
+ refusal
1382
+ }
1383
+ }))
1384
+ };
1385
+ }
555
1386
  function buildToolCallChunks(toolCalls, model, chunkSize, reasoning, overrides) {
556
1387
  const id = overrides?.id ?? generateId();
557
1388
  const created = overrides?.created ?? Math.floor(Date.now() / 1e3);
@@ -1600,19 +2431,35 @@ function parseMcpUndeclared(raw) {
1600
2431
 
1601
2432
  //#endregion
1602
2433
  exports.FORMAT_TO_CONTENT_TYPE = FORMAT_TO_CONTENT_TYPE;
2434
+ exports.InvalidFixtureToolCallError = InvalidFixtureToolCallError;
2435
+ exports.InvalidToolArgumentsError = InvalidToolArgumentsError;
1603
2436
  exports.MAX_EMBEDDING_DIMENSIONS = MAX_EMBEDDING_DIMENSIONS;
1604
2437
  exports.RequestBodyTooLargeError = RequestBodyTooLargeError;
2438
+ exports.UnsupportedToolCallError = UnsupportedToolCallError;
2439
+ exports.assertCustomToolCalls = assertCustomToolCalls;
2440
+ exports.assertResponsesToolCalls = assertResponsesToolCalls;
1605
2441
  exports.buildContentWithToolCallsChunks = buildContentWithToolCallsChunks;
1606
2442
  exports.buildContentWithToolCallsCompletion = buildContentWithToolCallsCompletion;
1607
2443
  exports.buildEmbeddingResponse = buildEmbeddingResponse;
2444
+ exports.buildOpenAIContentFilterChunks = buildOpenAIContentFilterChunks;
2445
+ exports.buildOpenAIContentFilterCompletion = buildOpenAIContentFilterCompletion;
2446
+ exports.buildOpenAIReasoningChunks = buildOpenAIReasoningChunks;
2447
+ exports.buildOpenAIReasoningCompletion = buildOpenAIReasoningCompletion;
2448
+ exports.buildOpenAIRefusalChunks = buildOpenAIRefusalChunks;
2449
+ exports.buildOpenAIRefusalCompletion = buildOpenAIRefusalCompletion;
1608
2450
  exports.buildTextChunks = buildTextChunks;
1609
2451
  exports.buildTextCompletion = buildTextCompletion;
1610
2452
  exports.buildToolCallChunks = buildToolCallChunks;
1611
2453
  exports.buildToolCallCompletion = buildToolCallCompletion;
1612
2454
  exports.buildUsageChunk = buildUsageChunk;
2455
+ exports.createServerFixtureList = createServerFixtureList;
1613
2456
  exports.decodeMcpHeaderValue = decodeMcpHeaderValue;
1614
2457
  exports.describeMatch = describeMatch;
2458
+ exports.estimatePromptTokens = estimatePromptTokens;
2459
+ exports.estimateTokens = estimateTokens;
1615
2460
  exports.extractOverrides = extractOverrides;
2461
+ exports.fixtureListTarget = fixtureListTarget;
2462
+ exports.fixtureToolCallErrorCode = fixtureToolCallErrorCode;
1616
2463
  exports.flattenHeaders = flattenHeaders;
1617
2464
  exports.formatToMime = formatToMime;
1618
2465
  exports.generateDeterministicEmbedding = generateDeterministicEmbedding;
@@ -1622,45 +2469,72 @@ exports.generateToolCallId = generateToolCallId;
1622
2469
  exports.generateToolUseId = generateToolUseId;
1623
2470
  exports.getContext = getContext;
1624
2471
  exports.getTestId = getTestId;
2472
+ exports.googleFixtureToolCallErrorDetails = googleFixtureToolCallErrorDetails;
1625
2473
  exports.hasMintedRequestId = hasMintedRequestId;
1626
2474
  exports.isAudioResponse = isAudioResponse;
2475
+ exports.isCombinedFixtureResponse = isCombinedFixtureResponse;
1627
2476
  exports.isContentWithToolCallsResponse = isContentWithToolCallsResponse;
1628
2477
  exports.isEmbeddingResponse = isEmbeddingResponse;
1629
2478
  exports.isErrorResponse = isErrorResponse;
2479
+ exports.isExtendedResponsesToolsList = isExtendedResponsesToolsList;
2480
+ exports.isFixtureToolCallError = isFixtureToolCallError;
1630
2481
  exports.isImageResponse = isImageResponse;
1631
2482
  exports.isJSONResponse = isJSONResponse;
1632
2483
  exports.isJsonObject = isJsonObject;
1633
2484
  exports.isLiveResponse = isLiveResponse;
1634
2485
  exports.isResponseFactory = isResponseFactory;
2486
+ exports.isServerOwnedFixtures = isServerOwnedFixtures;
1635
2487
  exports.isTextResponse = isTextResponse;
1636
2488
  exports.isToolCallResponse = isToolCallResponse;
1637
2489
  exports.isTranscriptionResponse = isTranscriptionResponse;
1638
2490
  exports.isVideoResponse = isVideoResponse;
2491
+ exports.journalFixtureToolCallError = journalFixtureToolCallError;
2492
+ exports.keepAuthoredToolArguments = keepAuthoredToolArguments;
2493
+ exports.markExtendedResponsesToolsRequest = markExtendedResponsesToolsRequest;
1639
2494
  exports.markMintedRequestId = markMintedRequestId;
2495
+ exports.markServerOwnedFixtures = markServerOwnedFixtures;
1640
2496
  exports.matchesPattern = matchesPattern;
1641
2497
  exports.normalizeEmbeddingInput = normalizeEmbeddingInput;
1642
2498
  exports.normalizeTextInput = normalizeTextInput;
1643
2499
  exports.parseStrictIntegerText = parseStrictIntegerText;
2500
+ exports.prepareOpenAIChatMisbehavior = prepareOpenAIChatMisbehavior;
1644
2501
  exports.readBody = readBody;
1645
2502
  exports.readBodyBufferBounded = readBodyBufferBounded;
2503
+ exports.rebuildOrderedBlocks = rebuildOrderedBlocks;
2504
+ exports.rejectResponsesOnlyToolCalls = rejectResponsesOnlyToolCalls;
2505
+ exports.requireEmittedFunctionToolCalls = requireEmittedFunctionToolCalls;
2506
+ exports.requireFunctionToolCalls = requireFunctionToolCalls;
2507
+ exports.requireServedFunctionToolCalls = requireServedFunctionToolCalls;
1646
2508
  exports.resolveFixtureBlockOutcome = resolveFixtureBlockOutcome;
1647
2509
  exports.resolveFixtureBlocks = resolveFixtureBlocks;
1648
2510
  exports.resolveMcpIdentity = resolveMcpIdentity;
2511
+ exports.resolveOpenAIChatMisbehaviorUsage = resolveOpenAIChatMisbehaviorUsage;
1649
2512
  exports.resolveReasoningArtifactsForModel = resolveReasoningArtifactsForModel;
1650
2513
  exports.resolveReasoningForModel = resolveReasoningForModel;
1651
2514
  exports.resolveRequestId = resolveRequestId;
1652
2515
  exports.resolveResponse = resolveResponse;
2516
+ exports.resolveResponsesBlocks = resolveResponsesBlocks;
2517
+ exports.resolveServedBlockOutcome = resolveServedBlockOutcome;
1653
2518
  exports.resolveStrictMode = resolveStrictMode;
1654
2519
  exports.resolveTestId = resolveTestId;
1655
2520
  exports.resolveUsage = resolveUsage;
2521
+ exports.runWithToolArgumentsScope = runWithToolArgumentsScope;
1656
2522
  exports.serializeErrorResponse = serializeErrorResponse;
2523
+ exports.servedToolArgs = servedToolArgs;
2524
+ exports.servedToolCalls = servedToolCalls;
1657
2525
  exports.slugifyContext = slugifyContext;
1658
2526
  exports.slugifyTestId = slugifyTestId;
1659
2527
  exports.strictNoMatchLogLine = strictNoMatchLogLine;
1660
2528
  exports.strictNoMatchMessage = strictNoMatchMessage;
1661
2529
  exports.strictOverrideField = strictOverrideField;
2530
+ exports.strictToolArgumentsEnabled = strictToolArgumentsEnabled;
2531
+ exports.toolArgsForWire = toolArgsForWire;
2532
+ exports.toolCallFixtureBlock = toolCallFixtureBlock;
1662
2533
  exports.validateChatMessages = validateChatMessages;
1663
2534
  exports.validateEmbeddingDimensions = validateEmbeddingDimensions;
1664
2535
  exports.validateToolsField = validateToolsField;
2536
+ exports.withServedToolCalls = withServedToolCalls;
2537
+ exports.withoutLegacyNamespaces = withoutLegacyNamespaces;
2538
+ exports.withoutResponsesToolKeys = withoutResponsesToolKeys;
1665
2539
  exports.wouldProxyMiss = wouldProxyMiss;
1666
2540
  //# sourceMappingURL=helpers.cjs.map