@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/ollama.js CHANGED
@@ -1,6 +1,7 @@
1
- import { flattenHeaders, generateDeterministicEmbedding, getContext, getTestId, isContentWithToolCallsResponse, isEmbeddingResponse, isErrorResponse, isJsonObject, isTextResponse, isToolCallResponse, normalizeEmbeddingInput, resolveFixtureBlocks, resolveReasoningForModel, resolveResponse, resolveStrictMode, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField, validateChatMessages, validateToolsField } from "./helpers.js";
1
+ import { InvalidToolArgumentsError, estimatePromptTokens, estimateTokens, flattenHeaders, generateDeterministicEmbedding, getContext, getTestId, isCombinedFixtureResponse, isEmbeddingResponse, isErrorResponse, isJsonObject, isTextResponse, isToolCallResponse, normalizeEmbeddingInput, requireEmittedFunctionToolCalls, resolveFixtureBlockOutcome, resolveFixtureBlocks, resolveReasoningForModel, resolveResponse, resolveStrictMode, serializeErrorResponse, servedToolArgs, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField, toolArgsForWire, validateChatMessages, validateToolsField } from "./helpers.js";
2
2
  import { writeErrorResponse } from "./sse-writer.js";
3
3
  import { applyChaosAsync } from "./chaos.js";
4
+ import { planMisbehavior, recordMisbehaviorOutcome } from "./misbehavior.js";
4
5
  import { matchFixtureDiagnostic, recordMatchOptions } from "./router.js";
5
6
  import { proxyAndRecord } from "./recorder.js";
6
7
  import { createInterruptionSignal } from "./interruption.js";
@@ -122,20 +123,8 @@ function buildOllamaChatTextResponse(content, model, reasoning) {
122
123
  ...DURATION_FIELDS
123
124
  };
124
125
  }
125
- function buildOllamaChatToolCallChunks(toolCalls, model, chunkSize, logger, reasoning) {
126
- const ollamaToolCalls = toolCalls.map((tc) => {
127
- let argsObj;
128
- try {
129
- argsObj = JSON.parse(tc.arguments || "{}");
130
- } catch {
131
- logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
132
- argsObj = {};
133
- }
134
- return { function: {
135
- name: tc.name,
136
- arguments: argsObj
137
- } };
138
- });
126
+ function buildOllamaChatToolCallChunks(toolCalls, model, chunkSize, reasoning) {
127
+ const ollamaToolCalls = toolCalls.map(toOllamaToolCall);
139
128
  const chunks = [];
140
129
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
141
130
  if (reasoning) for (let i = 0; i < reasoning.length; i += chunkSize) {
@@ -173,20 +162,8 @@ function buildOllamaChatToolCallChunks(toolCalls, model, chunkSize, logger, reas
173
162
  });
174
163
  return chunks;
175
164
  }
176
- function buildOllamaChatToolCallResponse(toolCalls, model, logger, reasoning) {
177
- const ollamaToolCalls = toolCalls.map((tc) => {
178
- let argsObj;
179
- try {
180
- argsObj = JSON.parse(tc.arguments || "{}");
181
- } catch {
182
- logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
183
- argsObj = {};
184
- }
185
- return { function: {
186
- name: tc.name,
187
- arguments: argsObj
188
- } };
189
- });
165
+ function buildOllamaChatToolCallResponse(toolCalls, model, reasoning) {
166
+ const ollamaToolCalls = toolCalls.map(toOllamaToolCall);
190
167
  return {
191
168
  model,
192
169
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -200,20 +177,13 @@ function buildOllamaChatToolCallResponse(toolCalls, model, logger, reasoning) {
200
177
  ...DURATION_FIELDS
201
178
  };
202
179
  }
203
- function toOllamaToolCall(tc, logger) {
204
- let argsObj;
205
- try {
206
- argsObj = JSON.parse(tc.arguments || "{}");
207
- } catch {
208
- logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
209
- argsObj = {};
210
- }
180
+ function toOllamaToolCall(tc) {
211
181
  return { function: {
212
182
  name: tc.name,
213
- arguments: argsObj
183
+ arguments: servedToolArgs(tc, "object").value
214
184
  } };
215
185
  }
216
- function buildOllamaChatContentWithToolCallsChunks(content, toolCalls, model, chunkSize, logger, reasoning, blocks) {
186
+ function buildOllamaChatContentWithToolCallsChunks(content, toolCalls, model, chunkSize, reasoning, blocks) {
217
187
  const chunks = [];
218
188
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
219
189
  if (blocks && blocks.length > 0) {
@@ -249,7 +219,7 @@ function buildOllamaChatContentWithToolCallsChunks(content, toolCalls, model, ch
249
219
  message: {
250
220
  role: "assistant",
251
221
  content: "",
252
- tool_calls: [toOllamaToolCall(block, logger)]
222
+ tool_calls: [toOllamaToolCall(block)]
253
223
  },
254
224
  done: false
255
225
  });
@@ -290,19 +260,7 @@ function buildOllamaChatContentWithToolCallsChunks(content, toolCalls, model, ch
290
260
  done: false
291
261
  });
292
262
  }
293
- const ollamaToolCalls = toolCalls.map((tc) => {
294
- let argsObj;
295
- try {
296
- argsObj = JSON.parse(tc.arguments || "{}");
297
- } catch {
298
- logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
299
- argsObj = {};
300
- }
301
- return { function: {
302
- name: tc.name,
303
- arguments: argsObj
304
- } };
305
- });
263
+ const ollamaToolCalls = toolCalls.map(toOllamaToolCall);
306
264
  chunks.push({
307
265
  model,
308
266
  created_at: createdAt,
@@ -325,7 +283,7 @@ function buildOllamaChatContentWithToolCallsChunks(content, toolCalls, model, ch
325
283
  });
326
284
  return chunks;
327
285
  }
328
- function buildOllamaChatContentWithToolCallsResponse(content, toolCalls, model, logger, reasoning, blocks) {
286
+ function buildOllamaChatContentWithToolCallsResponse(content, toolCalls, model, reasoning, blocks) {
329
287
  if (blocks && blocks.length > 0) {
330
288
  const ordered = resolveFixtureBlocks(blocks);
331
289
  content = ordered.filter((b) => b.type === "text").map((b) => b.text).join("");
@@ -334,19 +292,7 @@ function buildOllamaChatContentWithToolCallsResponse(content, toolCalls, model,
334
292
  arguments: b.arguments
335
293
  }));
336
294
  }
337
- const ollamaToolCalls = toolCalls.map((tc) => {
338
- let argsObj;
339
- try {
340
- argsObj = JSON.parse(tc.arguments || "{}");
341
- } catch {
342
- logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
343
- argsObj = {};
344
- }
345
- return { function: {
346
- name: tc.name,
347
- arguments: argsObj
348
- } };
349
- });
295
+ const ollamaToolCalls = toolCalls.map(toOllamaToolCall);
350
296
  return {
351
297
  model,
352
298
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -360,6 +306,105 @@ function buildOllamaChatContentWithToolCallsResponse(content, toolCalls, model,
360
306
  ...DURATION_FIELDS
361
307
  };
362
308
  }
309
+ /** Prepare fault output and its observations together before sending any bytes. */
310
+ function prepareOllamaMisbehavior(plan, request, stream, chunkSize, effectiveStrict, logger) {
311
+ const response = plan.response;
312
+ const combined = isCombinedFixtureResponse(response);
313
+ const outcome = combined && response.blocks?.length ? resolveFixtureBlockOutcome(response.blocks) : void 0;
314
+ const calls = outcome?.toolCalls ?? (combined || isToolCallResponse(response) ? response.toolCalls ?? [] : []);
315
+ const content = outcome?.content ?? ("content" in response ? response.content ?? "" : "");
316
+ const model = request.model;
317
+ if (plan.summary.fault === "tool-args-invalid-json") {
318
+ const target = calls[plan.target?.index ?? 0];
319
+ if (!target || toolArgsForWire(target).kind !== "verbatim") throw new Error("Ollama invalid-JSON fault requires an invalid prepared target");
320
+ const diagnostic = new InvalidToolArgumentsError(target).parseDiagnostic;
321
+ const error = `error parsing tool call: raw='${target.arguments}', err=${diagnostic}`;
322
+ let priorContent = content;
323
+ if (outcome) {
324
+ priorContent = "";
325
+ let index = 0;
326
+ for (const block of outcome.ordered) if (block.type === "text") priorContent += block.text;
327
+ else if (index++ === plan.target?.index) break;
328
+ }
329
+ return {
330
+ chunks: stream ? [...buildOllamaChatTextChunks(priorContent, model, chunkSize).slice(0, -1), { error }] : [{ error }],
331
+ status: stream ? 200 : 500,
332
+ summary: {
333
+ ...plan.summary,
334
+ servedToolCalls: []
335
+ }
336
+ };
337
+ }
338
+ const servedToolCalls = calls.map((call) => {
339
+ const parsed = toolArgsForWire(call);
340
+ if (parsed.kind === "verbatim") throw new InvalidToolArgumentsError(call);
341
+ return {
342
+ name: call.name,
343
+ arguments: parsed.text
344
+ };
345
+ });
346
+ let chunks;
347
+ let reasoning = "";
348
+ if (plan.summary.fault === "reasoning-only") {
349
+ reasoning = plan.reasoning ?? "";
350
+ const base = {
351
+ model,
352
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
353
+ };
354
+ if (stream) {
355
+ chunks = [];
356
+ for (let i = 0; i < reasoning.length; i += chunkSize) chunks.push({
357
+ ...base,
358
+ message: {
359
+ role: "assistant",
360
+ content: "",
361
+ thinking: reasoning.slice(i, i + chunkSize)
362
+ },
363
+ done: false
364
+ });
365
+ chunks.push({
366
+ ...base,
367
+ message: {
368
+ role: "assistant",
369
+ content: ""
370
+ },
371
+ done: true,
372
+ ...DURATION_FIELDS,
373
+ done_reason: "length"
374
+ });
375
+ } else chunks = [{
376
+ ...base,
377
+ message: {
378
+ role: "assistant",
379
+ content: "",
380
+ thinking: reasoning
381
+ },
382
+ done: true,
383
+ ...DURATION_FIELDS,
384
+ done_reason: "length"
385
+ }];
386
+ } else {
387
+ reasoning = resolveReasoningForModel("reasoning" in response ? response.reasoning : void 0, model, effectiveStrict, logger) ?? "";
388
+ if (combined) chunks = stream ? buildOllamaChatContentWithToolCallsChunks(content, calls, model, chunkSize, reasoning, response.blocks) : [buildOllamaChatContentWithToolCallsResponse(content, calls, model, reasoning, response.blocks)];
389
+ else if (isToolCallResponse(response)) chunks = stream ? buildOllamaChatToolCallChunks(calls, model, chunkSize, reasoning) : [buildOllamaChatToolCallResponse(calls, model, reasoning)];
390
+ else chunks = stream ? buildOllamaChatTextChunks(content, model, chunkSize, reasoning) : [buildOllamaChatTextResponse(content, model, reasoning)];
391
+ }
392
+ const completion = content + reasoning + servedToolCalls.map((call) => call.name + call.arguments).join("");
393
+ const terminal = chunks.length - 1;
394
+ chunks[terminal] = {
395
+ ...chunks[terminal],
396
+ prompt_eval_count: estimatePromptTokens(request.messages),
397
+ eval_count: estimateTokens(completion)
398
+ };
399
+ return {
400
+ chunks,
401
+ status: 200,
402
+ summary: {
403
+ ...plan.summary,
404
+ servedToolCalls
405
+ }
406
+ };
407
+ }
363
408
  function buildOllamaGenerateTextChunks(content, model, chunkSize, reasoning) {
364
409
  const chunks = [];
365
410
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -540,25 +585,100 @@ async function handleOllama(req, res, raw, fixtures, journal, defaults, setCorsH
540
585
  return;
541
586
  }
542
587
  const response = await resolveResponse(fixture, completionReq);
588
+ const misbehavior = planMisbehavior({
589
+ wire: "ollama",
590
+ fixture,
591
+ response,
592
+ request: completionReq,
593
+ stream: ollamaReq.stream !== false,
594
+ defaults,
595
+ rawHeaders: req.headers,
596
+ url: req.url
597
+ });
598
+ if (misbehavior.kind === "error") {
599
+ recordMisbehaviorOutcome({
600
+ entry: journal.add({
601
+ method: req.method ?? "POST",
602
+ path: urlPath,
603
+ headers: flattenHeaders(req.headers),
604
+ body: completionReq,
605
+ response: {
606
+ status: misbehavior.status,
607
+ fixture
608
+ }
609
+ }),
610
+ summary: misbehavior.summary,
611
+ defaults,
612
+ testId
613
+ });
614
+ const message = `${misbehavior.code}: ${misbehavior.message}`;
615
+ if (!misbehavior.summary?.evaluations.length) defaults.logger.error(message);
616
+ writeErrorResponse(res, misbehavior.status, JSON.stringify({ error: message }));
617
+ return;
618
+ }
543
619
  const latency = fixture.latency ?? defaults.latency;
544
620
  const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
545
621
  const streaming = ollamaReq.stream !== false;
546
- if (isErrorResponse(response)) {
547
- const status = response.status ?? 500;
548
- journal.add({
622
+ if (misbehavior.kind === "applied") {
623
+ const prepared = prepareOllamaMisbehavior(misbehavior, completionReq, streaming, chunkSize, resolveStrictMode(defaults.strict, req.headers), logger);
624
+ const journalEntry = journal.add({
549
625
  method: req.method ?? "POST",
550
626
  path: urlPath,
551
627
  headers: flattenHeaders(req.headers),
552
628
  body: completionReq,
553
629
  response: {
554
- status,
630
+ status: prepared.status,
555
631
  fixture
556
632
  }
557
633
  });
634
+ recordMisbehaviorOutcome({
635
+ entry: journalEntry,
636
+ summary: prepared.summary,
637
+ defaults,
638
+ testId
639
+ });
640
+ if (!streaming) {
641
+ res.writeHead(prepared.status, { "Content-Type": "application/json" });
642
+ res.end(JSON.stringify(prepared.chunks[0]));
643
+ } else {
644
+ const interruption = createInterruptionSignal(fixture);
645
+ if (!await writeNDJSONStream(res, prepared.chunks, {
646
+ latency,
647
+ streamingProfile: fixture.streamingProfile,
648
+ recordedTimings: fixture.recordedTimings,
649
+ replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
650
+ signal: interruption?.signal,
651
+ onChunkSent: interruption?.tick
652
+ })) {
653
+ if (!res.writableEnded) res.destroy();
654
+ journalEntry.response.interrupted = true;
655
+ journalEntry.response.interruptReason = interruption?.reason();
656
+ }
657
+ interruption?.cleanup();
658
+ }
659
+ return;
660
+ }
661
+ if (isErrorResponse(response)) {
662
+ const status = response.status ?? 500;
663
+ recordMisbehaviorOutcome({
664
+ entry: journal.add({
665
+ method: req.method ?? "POST",
666
+ path: urlPath,
667
+ headers: flattenHeaders(req.headers),
668
+ body: completionReq,
669
+ response: {
670
+ status,
671
+ fixture
672
+ }
673
+ }),
674
+ summary: misbehavior.summary,
675
+ defaults,
676
+ testId
677
+ });
558
678
  writeErrorResponse(res, status, serializeErrorResponse(response), { retryAfter: response.retryAfter });
559
679
  return;
560
680
  }
561
- if (isContentWithToolCallsResponse(response)) {
681
+ if (isCombinedFixtureResponse(response)) {
562
682
  if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Ollama API -- ignoring");
563
683
  const journalEntry = journal.add({
564
684
  method: req.method ?? "POST",
@@ -570,14 +690,21 @@ async function handleOllama(req, res, raw, fixtures, journal, defaults, setCorsH
570
690
  fixture
571
691
  }
572
692
  });
693
+ recordMisbehaviorOutcome({
694
+ entry: journalEntry,
695
+ summary: misbehavior.summary,
696
+ defaults,
697
+ testId
698
+ });
699
+ const functionToolCalls = requireEmittedFunctionToolCalls(response, "Ollama");
573
700
  const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
574
701
  const effReasoning = resolveReasoningForModel(response.reasoning, completionReq.model, effectiveStrict, logger);
575
702
  if (!streaming) {
576
- const body = buildOllamaChatContentWithToolCallsResponse(response.content ?? "", response.toolCalls ?? [], completionReq.model, logger, effReasoning, response.blocks);
703
+ const body = buildOllamaChatContentWithToolCallsResponse(response.content ?? "", functionToolCalls, completionReq.model, effReasoning, response.blocks);
577
704
  res.writeHead(200, { "Content-Type": "application/json" });
578
705
  res.end(JSON.stringify(body));
579
706
  } else {
580
- const chunks = buildOllamaChatContentWithToolCallsChunks(response.content ?? "", response.toolCalls ?? [], completionReq.model, chunkSize, logger, effReasoning, response.blocks);
707
+ const chunks = buildOllamaChatContentWithToolCallsChunks(response.content ?? "", functionToolCalls, completionReq.model, chunkSize, effReasoning, response.blocks);
581
708
  const interruption = createInterruptionSignal(fixture);
582
709
  if (!await writeNDJSONStream(res, chunks, {
583
710
  latency,
@@ -607,6 +734,12 @@ async function handleOllama(req, res, raw, fixtures, journal, defaults, setCorsH
607
734
  fixture
608
735
  }
609
736
  });
737
+ recordMisbehaviorOutcome({
738
+ entry: journalEntry,
739
+ summary: misbehavior.summary,
740
+ defaults,
741
+ testId
742
+ });
610
743
  const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
611
744
  const effReasoning = resolveReasoningForModel(response.reasoning, completionReq.model, effectiveStrict, logger);
612
745
  if (!streaming) {
@@ -644,14 +777,21 @@ async function handleOllama(req, res, raw, fixtures, journal, defaults, setCorsH
644
777
  fixture
645
778
  }
646
779
  });
780
+ recordMisbehaviorOutcome({
781
+ entry: journalEntry,
782
+ summary: misbehavior.summary,
783
+ defaults,
784
+ testId
785
+ });
786
+ const functionToolCalls = requireEmittedFunctionToolCalls(response, "Ollama");
647
787
  const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
648
788
  const effReasoning = resolveReasoningForModel(response.reasoning, completionReq.model, effectiveStrict, logger);
649
789
  if (!streaming) {
650
- const body = buildOllamaChatToolCallResponse(response.toolCalls, completionReq.model, logger, effReasoning);
790
+ const body = buildOllamaChatToolCallResponse(functionToolCalls, completionReq.model, effReasoning);
651
791
  res.writeHead(200, { "Content-Type": "application/json" });
652
792
  res.end(JSON.stringify(body));
653
793
  } else {
654
- const chunks = buildOllamaChatToolCallChunks(response.toolCalls, completionReq.model, chunkSize, logger, effReasoning);
794
+ const chunks = buildOllamaChatToolCallChunks(functionToolCalls, completionReq.model, chunkSize, effReasoning);
655
795
  const interruption = createInterruptionSignal(fixture);
656
796
  if (!await writeNDJSONStream(res, chunks, {
657
797
  latency,
@@ -669,15 +809,20 @@ async function handleOllama(req, res, raw, fixtures, journal, defaults, setCorsH
669
809
  }
670
810
  return;
671
811
  }
672
- journal.add({
673
- method: req.method ?? "POST",
674
- path: urlPath,
675
- headers: flattenHeaders(req.headers),
676
- body: completionReq,
677
- response: {
678
- status: 500,
679
- fixture
680
- }
812
+ recordMisbehaviorOutcome({
813
+ entry: journal.add({
814
+ method: req.method ?? "POST",
815
+ path: urlPath,
816
+ headers: flattenHeaders(req.headers),
817
+ body: completionReq,
818
+ response: {
819
+ status: 500,
820
+ fixture
821
+ }
822
+ }),
823
+ summary: misbehavior.summary,
824
+ defaults,
825
+ testId
681
826
  });
682
827
  writeErrorResponse(res, 500, JSON.stringify({ error: {
683
828
  message: "Fixture response did not match any known type",
@@ -815,20 +960,57 @@ async function handleOllamaGenerate(req, res, raw, fixtures, journal, defaults,
815
960
  return;
816
961
  }
817
962
  const response = await resolveResponse(fixture, completionReq);
963
+ const misbehavior = planMisbehavior({
964
+ wire: "ollama",
965
+ faultRenderingAvailable: false,
966
+ fixture,
967
+ response,
968
+ request: completionReq,
969
+ stream: generateReq.stream !== false,
970
+ defaults,
971
+ rawHeaders: req.headers,
972
+ url: req.url
973
+ });
974
+ if (misbehavior.kind === "error") {
975
+ recordMisbehaviorOutcome({
976
+ entry: journal.add({
977
+ method: req.method ?? "POST",
978
+ path: urlPath,
979
+ headers: flattenHeaders(req.headers),
980
+ body: completionReq,
981
+ response: {
982
+ status: misbehavior.status,
983
+ fixture
984
+ }
985
+ }),
986
+ summary: misbehavior.summary,
987
+ defaults,
988
+ testId
989
+ });
990
+ const message = `${misbehavior.code}: ${misbehavior.message}`;
991
+ if (!misbehavior.summary?.evaluations.length) defaults.logger.error(message);
992
+ writeErrorResponse(res, misbehavior.status, JSON.stringify({ error: message }));
993
+ return;
994
+ }
818
995
  const latency = fixture.latency ?? defaults.latency;
819
996
  const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
820
997
  const streaming = generateReq.stream !== false;
821
998
  if (isErrorResponse(response)) {
822
999
  const status = response.status ?? 500;
823
- journal.add({
824
- method: req.method ?? "POST",
825
- path: urlPath,
826
- headers: flattenHeaders(req.headers),
827
- body: completionReq,
828
- response: {
829
- status,
830
- fixture
831
- }
1000
+ recordMisbehaviorOutcome({
1001
+ entry: journal.add({
1002
+ method: req.method ?? "POST",
1003
+ path: urlPath,
1004
+ headers: flattenHeaders(req.headers),
1005
+ body: completionReq,
1006
+ response: {
1007
+ status,
1008
+ fixture
1009
+ }
1010
+ }),
1011
+ summary: misbehavior.summary,
1012
+ defaults,
1013
+ testId
832
1014
  });
833
1015
  writeErrorResponse(res, status, serializeErrorResponse(response), { retryAfter: response.retryAfter });
834
1016
  return;
@@ -844,6 +1026,12 @@ async function handleOllamaGenerate(req, res, raw, fixtures, journal, defaults,
844
1026
  fixture
845
1027
  }
846
1028
  });
1029
+ recordMisbehaviorOutcome({
1030
+ entry: journalEntry,
1031
+ summary: misbehavior.summary,
1032
+ defaults,
1033
+ testId
1034
+ });
847
1035
  const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
848
1036
  const effReasoning = resolveReasoningForModel(response.reasoning, completionReq.model, effectiveStrict, defaults.logger);
849
1037
  if (!streaming) {
@@ -869,16 +1057,21 @@ async function handleOllamaGenerate(req, res, raw, fixtures, journal, defaults,
869
1057
  }
870
1058
  return;
871
1059
  }
872
- if (isToolCallResponse(response) || isContentWithToolCallsResponse(response)) {
873
- journal.add({
874
- method: req.method ?? "POST",
875
- path: urlPath,
876
- headers: flattenHeaders(req.headers),
877
- body: completionReq,
878
- response: {
879
- status: 400,
880
- fixture
881
- }
1060
+ if (isToolCallResponse(response) || isCombinedFixtureResponse(response)) {
1061
+ recordMisbehaviorOutcome({
1062
+ entry: journal.add({
1063
+ method: req.method ?? "POST",
1064
+ path: urlPath,
1065
+ headers: flattenHeaders(req.headers),
1066
+ body: completionReq,
1067
+ response: {
1068
+ status: 400,
1069
+ fixture
1070
+ }
1071
+ }),
1072
+ summary: misbehavior.summary,
1073
+ defaults,
1074
+ testId
882
1075
  });
883
1076
  writeErrorResponse(res, 400, JSON.stringify({ error: {
884
1077
  message: "Tool call fixtures are not supported on /api/generate — use /api/chat instead",
@@ -886,15 +1079,20 @@ async function handleOllamaGenerate(req, res, raw, fixtures, journal, defaults,
886
1079
  } }));
887
1080
  return;
888
1081
  }
889
- journal.add({
890
- method: req.method ?? "POST",
891
- path: urlPath,
892
- headers: flattenHeaders(req.headers),
893
- body: completionReq,
894
- response: {
895
- status: 500,
896
- fixture
897
- }
1082
+ recordMisbehaviorOutcome({
1083
+ entry: journal.add({
1084
+ method: req.method ?? "POST",
1085
+ path: urlPath,
1086
+ headers: flattenHeaders(req.headers),
1087
+ body: completionReq,
1088
+ response: {
1089
+ status: 500,
1090
+ fixture
1091
+ }
1092
+ }),
1093
+ summary: misbehavior.summary,
1094
+ defaults,
1095
+ testId
898
1096
  });
899
1097
  writeErrorResponse(res, 500, JSON.stringify({ error: {
900
1098
  message: "Fixture response did not match any known type",