@copilotkit/aimock 1.43.0 → 1.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (396) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +46 -0
  4. package/README.md +14 -2
  5. package/dist/agui-handler.d.ts.map +1 -1
  6. package/dist/aimock-cli.cjs +31 -5
  7. package/dist/aimock-cli.cjs.map +1 -1
  8. package/dist/aimock-cli.d.cts.map +1 -1
  9. package/dist/aimock-cli.d.ts.map +1 -1
  10. package/dist/aimock-cli.js +31 -5
  11. package/dist/aimock-cli.js.map +1 -1
  12. package/dist/bedrock-converse.cjs +344 -43
  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 +345 -44
  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 +9 -5
  25. package/dist/chaos.cjs.map +1 -1
  26. package/dist/chaos.d.cts.map +1 -1
  27. package/dist/chaos.d.ts.map +1 -1
  28. package/dist/chaos.js +10 -7
  29. package/dist/chaos.js.map +1 -1
  30. package/dist/cli.cjs +238 -9
  31. package/dist/cli.cjs.map +1 -1
  32. package/dist/cli.js +242 -13
  33. package/dist/cli.js.map +1 -1
  34. package/dist/cohere.cjs +159 -72
  35. package/dist/cohere.cjs.map +1 -1
  36. package/dist/cohere.d.cts.map +1 -1
  37. package/dist/cohere.d.ts.map +1 -1
  38. package/dist/cohere.js +160 -73
  39. package/dist/cohere.js.map +1 -1
  40. package/dist/config-loader.cjs +63 -5
  41. package/dist/config-loader.cjs.map +1 -1
  42. package/dist/config-loader.d.cts +37 -4
  43. package/dist/config-loader.d.cts.map +1 -1
  44. package/dist/config-loader.d.ts +37 -4
  45. package/dist/config-loader.d.ts.map +1 -1
  46. package/dist/config-loader.js +63 -5
  47. package/dist/config-loader.js.map +1 -1
  48. package/dist/constants.cjs +8 -0
  49. package/dist/constants.cjs.map +1 -1
  50. package/dist/constants.d.cts +6 -2
  51. package/dist/constants.d.ts +6 -2
  52. package/dist/constants.js +8 -1
  53. package/dist/constants.js.map +1 -1
  54. package/dist/echo-text.cjs +65 -0
  55. package/dist/echo-text.cjs.map +1 -0
  56. package/dist/echo-text.d.cts +22 -0
  57. package/dist/echo-text.d.cts.map +1 -0
  58. package/dist/echo-text.d.ts +22 -0
  59. package/dist/echo-text.d.ts.map +1 -0
  60. package/dist/echo-text.js +65 -0
  61. package/dist/echo-text.js.map +1 -0
  62. package/dist/embeddings.cjs +18 -1
  63. package/dist/embeddings.cjs.map +1 -1
  64. package/dist/embeddings.d.cts +1 -1
  65. package/dist/embeddings.d.cts.map +1 -1
  66. package/dist/embeddings.d.ts +1 -1
  67. package/dist/embeddings.d.ts.map +1 -1
  68. package/dist/embeddings.js +18 -1
  69. package/dist/embeddings.js.map +1 -1
  70. package/dist/fakes-plugin.cjs +131 -0
  71. package/dist/fakes-plugin.cjs.map +1 -0
  72. package/dist/fakes-plugin.d.cts +23 -0
  73. package/dist/fakes-plugin.d.cts.map +1 -0
  74. package/dist/fakes-plugin.d.ts +23 -0
  75. package/dist/fakes-plugin.d.ts.map +1 -0
  76. package/dist/fakes-plugin.js +127 -0
  77. package/dist/fakes-plugin.js.map +1 -0
  78. package/dist/files.cjs +9 -11
  79. package/dist/files.cjs.map +1 -1
  80. package/dist/files.d.cts.map +1 -1
  81. package/dist/files.d.ts.map +1 -1
  82. package/dist/files.js +10 -13
  83. package/dist/files.js.map +1 -1
  84. package/dist/fine-tuning.cjs +1 -19
  85. package/dist/fine-tuning.cjs.map +1 -1
  86. package/dist/fine-tuning.d.cts.map +1 -1
  87. package/dist/fine-tuning.d.ts.map +1 -1
  88. package/dist/fine-tuning.js +2 -20
  89. package/dist/fine-tuning.js.map +1 -1
  90. package/dist/fixture-loader-services.cjs +116 -0
  91. package/dist/fixture-loader-services.cjs.map +1 -0
  92. package/dist/fixture-loader-services.d.cts +48 -0
  93. package/dist/fixture-loader-services.d.cts.map +1 -0
  94. package/dist/fixture-loader-services.d.ts +48 -0
  95. package/dist/fixture-loader-services.d.ts.map +1 -0
  96. package/dist/fixture-loader-services.js +114 -0
  97. package/dist/fixture-loader-services.js.map +1 -0
  98. package/dist/fixture-loader.cjs +552 -29
  99. package/dist/fixture-loader.cjs.map +1 -1
  100. package/dist/fixture-loader.d.cts +140 -2
  101. package/dist/fixture-loader.d.cts.map +1 -1
  102. package/dist/fixture-loader.d.ts +140 -2
  103. package/dist/fixture-loader.d.ts.map +1 -1
  104. package/dist/fixture-loader.js +542 -31
  105. package/dist/fixture-loader.js.map +1 -1
  106. package/dist/gemini-embeddings.cjs +42 -2
  107. package/dist/gemini-embeddings.cjs.map +1 -1
  108. package/dist/gemini-embeddings.js +43 -3
  109. package/dist/gemini-embeddings.js.map +1 -1
  110. package/dist/gemini-interactions.cjs +288 -52
  111. package/dist/gemini-interactions.cjs.map +1 -1
  112. package/dist/gemini-interactions.d.cts.map +1 -1
  113. package/dist/gemini-interactions.d.ts.map +1 -1
  114. package/dist/gemini-interactions.js +289 -53
  115. package/dist/gemini-interactions.js.map +1 -1
  116. package/dist/gemini.cjs +234 -38
  117. package/dist/gemini.cjs.map +1 -1
  118. package/dist/gemini.d.cts.map +1 -1
  119. package/dist/gemini.d.ts.map +1 -1
  120. package/dist/gemini.js +235 -39
  121. package/dist/gemini.js.map +1 -1
  122. package/dist/helpers.cjs +1157 -12
  123. package/dist/helpers.cjs.map +1 -1
  124. package/dist/helpers.d.cts +85 -4
  125. package/dist/helpers.d.cts.map +1 -1
  126. package/dist/helpers.d.ts +85 -4
  127. package/dist/helpers.d.ts.map +1 -1
  128. package/dist/helpers.js +1113 -14
  129. package/dist/helpers.js.map +1 -1
  130. package/dist/images.cjs +3 -2
  131. package/dist/images.cjs.map +1 -1
  132. package/dist/images.js +3 -2
  133. package/dist/images.js.map +1 -1
  134. package/dist/index.cjs +39 -1
  135. package/dist/index.d.cts +11 -4
  136. package/dist/index.d.ts +11 -4
  137. package/dist/index.js +11 -4
  138. package/dist/jest.cjs +43 -14
  139. package/dist/jest.cjs.map +1 -1
  140. package/dist/jest.d.cts +47 -5
  141. package/dist/jest.d.cts.map +1 -1
  142. package/dist/jest.d.ts +47 -5
  143. package/dist/jest.d.ts.map +1 -1
  144. package/dist/jest.js +45 -16
  145. package/dist/jest.js.map +1 -1
  146. package/dist/journal.cjs +155 -13
  147. package/dist/journal.cjs.map +1 -1
  148. package/dist/journal.d.cts +54 -4
  149. package/dist/journal.d.cts.map +1 -1
  150. package/dist/journal.d.ts +54 -4
  151. package/dist/journal.d.ts.map +1 -1
  152. package/dist/journal.js +155 -13
  153. package/dist/journal.js.map +1 -1
  154. package/dist/live-sanitize.cjs +4 -32
  155. package/dist/live-sanitize.cjs.map +1 -1
  156. package/dist/live-sanitize.js +4 -32
  157. package/dist/live-sanitize.js.map +1 -1
  158. package/dist/llmock.cjs +204 -39
  159. package/dist/llmock.cjs.map +1 -1
  160. package/dist/llmock.d.cts +51 -1
  161. package/dist/llmock.d.cts.map +1 -1
  162. package/dist/llmock.d.ts +51 -1
  163. package/dist/llmock.d.ts.map +1 -1
  164. package/dist/llmock.js +206 -41
  165. package/dist/llmock.js.map +1 -1
  166. package/dist/mcp-fakes-mount.cjs +217 -0
  167. package/dist/mcp-fakes-mount.cjs.map +1 -0
  168. package/dist/mcp-fakes-mount.js +211 -0
  169. package/dist/mcp-fakes-mount.js.map +1 -0
  170. package/dist/mcp-fakes-report.cjs +111 -0
  171. package/dist/mcp-fakes-report.cjs.map +1 -0
  172. package/dist/mcp-fakes-report.d.cts +40 -0
  173. package/dist/mcp-fakes-report.d.cts.map +1 -0
  174. package/dist/mcp-fakes-report.d.ts +40 -0
  175. package/dist/mcp-fakes-report.d.ts.map +1 -0
  176. package/dist/mcp-fakes-report.js +107 -0
  177. package/dist/mcp-fakes-report.js.map +1 -0
  178. package/dist/mcp-fakes.cjs +1942 -0
  179. package/dist/mcp-fakes.cjs.map +1 -0
  180. package/dist/mcp-fakes.d.cts +504 -0
  181. package/dist/mcp-fakes.d.cts.map +1 -0
  182. package/dist/mcp-fakes.d.ts +504 -0
  183. package/dist/mcp-fakes.d.ts.map +1 -0
  184. package/dist/mcp-fakes.js +1937 -0
  185. package/dist/mcp-fakes.js.map +1 -0
  186. package/dist/mcp-handler.cjs +534 -27
  187. package/dist/mcp-handler.cjs.map +1 -1
  188. package/dist/mcp-handler.d.cts +49 -0
  189. package/dist/mcp-handler.d.cts.map +1 -0
  190. package/dist/mcp-handler.d.ts +49 -0
  191. package/dist/mcp-handler.d.ts.map +1 -0
  192. package/dist/mcp-handler.js +530 -28
  193. package/dist/mcp-handler.js.map +1 -1
  194. package/dist/mcp-mock.cjs +326 -47
  195. package/dist/mcp-mock.cjs.map +1 -1
  196. package/dist/mcp-mock.d.cts +97 -1
  197. package/dist/mcp-mock.d.cts.map +1 -1
  198. package/dist/mcp-mock.d.ts +97 -1
  199. package/dist/mcp-mock.d.ts.map +1 -1
  200. package/dist/mcp-mock.js +327 -48
  201. package/dist/mcp-mock.js.map +1 -1
  202. package/dist/mcp-recorder.cjs +1224 -0
  203. package/dist/mcp-recorder.cjs.map +1 -0
  204. package/dist/mcp-recorder.d.cts +108 -0
  205. package/dist/mcp-recorder.d.cts.map +1 -0
  206. package/dist/mcp-recorder.d.ts +108 -0
  207. package/dist/mcp-recorder.d.ts.map +1 -0
  208. package/dist/mcp-recorder.js +1215 -0
  209. package/dist/mcp-recorder.js.map +1 -0
  210. package/dist/mcp-stub.cjs +5 -1
  211. package/dist/mcp-stub.d.cts +3 -1
  212. package/dist/mcp-stub.d.ts +3 -1
  213. package/dist/mcp-stub.js +3 -1
  214. package/dist/mcp-types.d.cts +6 -0
  215. package/dist/mcp-types.d.ts +6 -0
  216. package/dist/message-text.cjs +192 -0
  217. package/dist/message-text.cjs.map +1 -0
  218. package/dist/message-text.d.cts +31 -0
  219. package/dist/message-text.d.cts.map +1 -0
  220. package/dist/message-text.d.ts +31 -0
  221. package/dist/message-text.d.ts.map +1 -0
  222. package/dist/message-text.js +184 -0
  223. package/dist/message-text.js.map +1 -0
  224. package/dist/messages.cjs +400 -56
  225. package/dist/messages.cjs.map +1 -1
  226. package/dist/messages.d.cts.map +1 -1
  227. package/dist/messages.d.ts.map +1 -1
  228. package/dist/messages.js +401 -58
  229. package/dist/messages.js.map +1 -1
  230. package/dist/metrics.cjs +46 -66
  231. package/dist/metrics.cjs.map +1 -1
  232. package/dist/metrics.d.cts +14 -6
  233. package/dist/metrics.d.cts.map +1 -1
  234. package/dist/metrics.d.ts +14 -6
  235. package/dist/metrics.d.ts.map +1 -1
  236. package/dist/metrics.js +32 -40
  237. package/dist/metrics.js.map +1 -1
  238. package/dist/misbehavior.cjs +1523 -0
  239. package/dist/misbehavior.cjs.map +1 -0
  240. package/dist/misbehavior.d.cts +43 -0
  241. package/dist/misbehavior.d.cts.map +1 -0
  242. package/dist/misbehavior.d.ts +43 -0
  243. package/dist/misbehavior.d.ts.map +1 -0
  244. package/dist/misbehavior.js +1511 -0
  245. package/dist/misbehavior.js.map +1 -0
  246. package/dist/ollama.cjs +315 -113
  247. package/dist/ollama.cjs.map +1 -1
  248. package/dist/ollama.d.cts.map +1 -1
  249. package/dist/ollama.d.ts.map +1 -1
  250. package/dist/ollama.js +316 -114
  251. package/dist/ollama.js.map +1 -1
  252. package/dist/openapi.cjs +3405 -0
  253. package/dist/openapi.cjs.map +1 -0
  254. package/dist/openapi.d.cts +30 -0
  255. package/dist/openapi.d.cts.map +1 -0
  256. package/dist/openapi.d.ts +30 -0
  257. package/dist/openapi.d.ts.map +1 -0
  258. package/dist/openapi.js +3404 -0
  259. package/dist/openapi.js.map +1 -0
  260. package/dist/openrouter-video.cjs +4 -4
  261. package/dist/openrouter-video.cjs.map +1 -1
  262. package/dist/openrouter-video.d.cts.map +1 -1
  263. package/dist/openrouter-video.d.ts.map +1 -1
  264. package/dist/openrouter-video.js +4 -4
  265. package/dist/openrouter-video.js.map +1 -1
  266. package/dist/record-sanitize.cjs +231 -0
  267. package/dist/record-sanitize.cjs.map +1 -0
  268. package/dist/record-sanitize.js +224 -0
  269. package/dist/record-sanitize.js.map +1 -0
  270. package/dist/recorder.cjs +115 -6
  271. package/dist/recorder.cjs.map +1 -1
  272. package/dist/recorder.d.cts +2 -1
  273. package/dist/recorder.d.cts.map +1 -1
  274. package/dist/recorder.d.ts +2 -1
  275. package/dist/recorder.d.ts.map +1 -1
  276. package/dist/recorder.js +116 -8
  277. package/dist/recorder.js.map +1 -1
  278. package/dist/responses.cjs +701 -166
  279. package/dist/responses.cjs.map +1 -1
  280. package/dist/responses.d.cts +5 -13
  281. package/dist/responses.d.cts.map +1 -1
  282. package/dist/responses.d.ts +5 -13
  283. package/dist/responses.d.ts.map +1 -1
  284. package/dist/responses.js +699 -169
  285. package/dist/responses.js.map +1 -1
  286. package/dist/route-registry.cjs +951 -0
  287. package/dist/route-registry.cjs.map +1 -0
  288. package/dist/route-registry.d.cts +33 -0
  289. package/dist/route-registry.d.cts.map +1 -0
  290. package/dist/route-registry.d.ts +33 -0
  291. package/dist/route-registry.d.ts.map +1 -0
  292. package/dist/route-registry.js +866 -0
  293. package/dist/route-registry.js.map +1 -0
  294. package/dist/router.cjs +24 -2
  295. package/dist/router.cjs.map +1 -1
  296. package/dist/router.d.cts.map +1 -1
  297. package/dist/router.d.ts.map +1 -1
  298. package/dist/router.js +25 -4
  299. package/dist/router.js.map +1 -1
  300. package/dist/server.cjs +1159 -264
  301. package/dist/server.cjs.map +1 -1
  302. package/dist/server.d.cts +4 -1
  303. package/dist/server.d.cts.map +1 -1
  304. package/dist/server.d.ts +4 -1
  305. package/dist/server.d.ts.map +1 -1
  306. package/dist/server.js +1067 -172
  307. package/dist/server.js.map +1 -1
  308. package/dist/speech.cjs +4 -2
  309. package/dist/speech.cjs.map +1 -1
  310. package/dist/speech.js +4 -2
  311. package/dist/speech.js.map +1 -1
  312. package/dist/stream-collapse.cjs +236 -34
  313. package/dist/stream-collapse.cjs.map +1 -1
  314. package/dist/stream-collapse.d.cts +39 -4
  315. package/dist/stream-collapse.d.cts.map +1 -1
  316. package/dist/stream-collapse.d.ts +39 -4
  317. package/dist/stream-collapse.d.ts.map +1 -1
  318. package/dist/stream-collapse.js +235 -35
  319. package/dist/stream-collapse.js.map +1 -1
  320. package/dist/suite.cjs +1 -1
  321. package/dist/suite.js +1 -1
  322. package/dist/transcription.cjs +2 -2
  323. package/dist/transcription.cjs.map +1 -1
  324. package/dist/transcription.d.cts.map +1 -1
  325. package/dist/transcription.d.ts.map +1 -1
  326. package/dist/transcription.js +2 -2
  327. package/dist/transcription.js.map +1 -1
  328. package/dist/types.cjs +18 -0
  329. package/dist/types.cjs.map +1 -0
  330. package/dist/types.d.cts +516 -5
  331. package/dist/types.d.cts.map +1 -1
  332. package/dist/types.d.ts +516 -5
  333. package/dist/types.d.ts.map +1 -1
  334. package/dist/types.js +17 -0
  335. package/dist/types.js.map +1 -0
  336. package/dist/validate-cli.cjs +123 -33
  337. package/dist/validate-cli.cjs.map +1 -1
  338. package/dist/validate-cli.js +125 -35
  339. package/dist/validate-cli.js.map +1 -1
  340. package/dist/vector-stores.cjs +1180 -0
  341. package/dist/vector-stores.cjs.map +1 -0
  342. package/dist/vector-stores.d.cts +106 -0
  343. package/dist/vector-stores.d.cts.map +1 -0
  344. package/dist/vector-stores.d.ts +106 -0
  345. package/dist/vector-stores.d.ts.map +1 -0
  346. package/dist/vector-stores.js +1164 -0
  347. package/dist/vector-stores.js.map +1 -0
  348. package/dist/vector-types.d.cts.map +1 -1
  349. package/dist/version.cjs +45 -0
  350. package/dist/version.cjs.map +1 -0
  351. package/dist/version.js +44 -0
  352. package/dist/version.js.map +1 -0
  353. package/dist/video.cjs +19 -0
  354. package/dist/video.cjs.map +1 -1
  355. package/dist/video.d.cts.map +1 -1
  356. package/dist/video.d.ts.map +1 -1
  357. package/dist/video.js +19 -0
  358. package/dist/video.js.map +1 -1
  359. package/dist/vitest.cjs +77 -16
  360. package/dist/vitest.cjs.map +1 -1
  361. package/dist/vitest.d.cts +38 -5
  362. package/dist/vitest.d.cts.map +1 -1
  363. package/dist/vitest.d.ts +38 -5
  364. package/dist/vitest.d.ts.map +1 -1
  365. package/dist/vitest.js +78 -17
  366. package/dist/vitest.js.map +1 -1
  367. package/dist/watcher.cjs +6 -2
  368. package/dist/watcher.cjs.map +1 -1
  369. package/dist/watcher.js +6 -2
  370. package/dist/watcher.js.map +1 -1
  371. package/dist/ws-gemini-live.cjs +210 -101
  372. package/dist/ws-gemini-live.cjs.map +1 -1
  373. package/dist/ws-gemini-live.d.cts +9 -3
  374. package/dist/ws-gemini-live.d.cts.map +1 -1
  375. package/dist/ws-gemini-live.d.ts +9 -3
  376. package/dist/ws-gemini-live.d.ts.map +1 -1
  377. package/dist/ws-gemini-live.js +211 -102
  378. package/dist/ws-gemini-live.js.map +1 -1
  379. package/dist/ws-realtime.cjs +175 -62
  380. package/dist/ws-realtime.cjs.map +1 -1
  381. package/dist/ws-realtime.d.cts +8 -3
  382. package/dist/ws-realtime.d.cts.map +1 -1
  383. package/dist/ws-realtime.d.ts +8 -3
  384. package/dist/ws-realtime.d.ts.map +1 -1
  385. package/dist/ws-realtime.js +176 -63
  386. package/dist/ws-realtime.js.map +1 -1
  387. package/dist/ws-responses.cjs +121 -16
  388. package/dist/ws-responses.cjs.map +1 -1
  389. package/dist/ws-responses.d.cts +8 -12
  390. package/dist/ws-responses.d.cts.map +1 -1
  391. package/dist/ws-responses.d.ts +8 -12
  392. package/dist/ws-responses.d.ts.map +1 -1
  393. package/dist/ws-responses.js +123 -18
  394. package/dist/ws-responses.js.map +1 -1
  395. package/package.json +13 -2
  396. package/skills/write-fixtures/SKILL.md +179 -21
@@ -1,6 +1,7 @@
1
- import { extractOverrides, flattenHeaders, generateToolCallId, getContext, getTestId, isContentWithToolCallsResponse, isErrorResponse, isJsonObject, isTextResponse, isToolCallResponse, resolveFixtureBlockOutcome, resolveResponse, resolveStrictMode, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField } from "./helpers.js";
1
+ import { extractOverrides, flattenHeaders, generateToolCallId, getContext, getTestId, isCombinedFixtureResponse, isErrorResponse, isJsonObject, isTextResponse, isToolCallResponse, prepareOpenAIChatMisbehavior, requireEmittedFunctionToolCalls, resolveFixtureBlockOutcome, resolveOpenAIChatMisbehaviorUsage, resolveResponse, resolveStrictMode, servedToolArgs, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField } from "./helpers.js";
2
2
  import { calculateDelay, delay, writeErrorResponse } from "./sse-writer.js";
3
3
  import { applyChaosAsync } from "./chaos.js";
4
+ import { planMisbehavior, recordMisbehaviorOutcome } from "./misbehavior.js";
4
5
  import { matchFixtureDiagnostic } from "./router.js";
5
6
  import { proxyAndRecord } from "./recorder.js";
6
7
  import { createInterruptionSignal } from "./interruption.js";
@@ -22,6 +23,29 @@ const STEP_TYPES = new Set([
22
23
  "model_output",
23
24
  ...STEP_RESULT_TYPES
24
25
  ]);
26
+ function validateInteractionsInput(input) {
27
+ if (typeof input === "number") return "input must not be a number";
28
+ if (!Array.isArray(input)) return null;
29
+ const first = input[0];
30
+ if (isJsonObject(first) && "role" in first) for (const [index, turn] of input.entries()) {
31
+ if (!isJsonObject(turn)) continue;
32
+ const blocks = turn.content ?? turn.parts;
33
+ if (typeof blocks === "string" && blocks.length > 0) return `input[${index}].${turn.content != null ? "content" : "parts"} must be an array`;
34
+ }
35
+ else if (!(isJsonObject(first) && typeof first.type === "string" && STEP_TYPES.has(first.type))) {
36
+ const nullIndex = input.findIndex((part) => part === null);
37
+ if (nullIndex !== -1) return `input[${nullIndex}] must be an object`;
38
+ }
39
+ return null;
40
+ }
41
+ function validateInteractionsTools(tools) {
42
+ if (typeof tools === "string" && tools.length > 0) return "tools must be an array";
43
+ if (Array.isArray(tools)) {
44
+ const nullIndex = tools.findIndex((tool) => tool === null);
45
+ if (nullIndex !== -1) return `tools[${nullIndex}] must be an object`;
46
+ }
47
+ return null;
48
+ }
25
49
  function geminiInteractionsToCompletionRequest(req) {
26
50
  const messages = [];
27
51
  const model = req.model ?? "gemini-2.5-flash";
@@ -192,18 +216,12 @@ function buildInteractionsTextResponse(content, model, interactionId, overrides)
192
216
  };
193
217
  }
194
218
  function buildFunctionCallStep(tc, logger) {
195
- let argsObj;
196
- try {
197
- argsObj = JSON.parse(tc.arguments || "{}");
198
- } catch {
199
- logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
200
- argsObj = {};
201
- }
219
+ const args = servedToolArgs(tc, "object", logger);
202
220
  return {
203
221
  type: "function_call",
204
222
  id: tc.id || generateToolCallId(),
205
223
  name: tc.name,
206
- arguments: argsObj
224
+ arguments: args.value
207
225
  };
208
226
  }
209
227
  function buildInteractionsToolCallResponse(toolCalls, model, interactionId, logger, overrides) {
@@ -337,13 +355,7 @@ function buildInteractionsToolCallSSEEvents(toolCalls, interactionId, logger, ov
337
355
  });
338
356
  for (let idx = 0; idx < toolCalls.length; idx++) {
339
357
  const tc = toolCalls[idx];
340
- let argsObj;
341
- try {
342
- argsObj = JSON.parse(tc.arguments || "{}");
343
- } catch {
344
- logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
345
- argsObj = {};
346
- }
358
+ const args = servedToolArgs(tc, "string", logger);
347
359
  events.push({
348
360
  event_type: "step.start",
349
361
  index: idx,
@@ -360,7 +372,7 @@ function buildInteractionsToolCallSSEEvents(toolCalls, interactionId, logger, ov
360
372
  index: idx,
361
373
  delta: {
362
374
  type: "arguments_delta",
363
- arguments: JSON.stringify(argsObj)
375
+ arguments: args.text
364
376
  },
365
377
  event_id: nextEventId()
366
378
  });
@@ -416,13 +428,7 @@ function pushTextStepEvents(events, index, content, chunkSize) {
416
428
  });
417
429
  }
418
430
  function pushFunctionCallStepEvents(events, index, tc, logger) {
419
- let argsObj;
420
- try {
421
- argsObj = JSON.parse(tc.arguments || "{}");
422
- } catch {
423
- logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
424
- argsObj = {};
425
- }
431
+ const args = servedToolArgs(tc, "string", logger);
426
432
  events.push({
427
433
  event_type: "step.start",
428
434
  index,
@@ -439,7 +445,7 @@ function pushFunctionCallStepEvents(events, index, tc, logger) {
439
445
  index,
440
446
  delta: {
441
447
  type: "arguments_delta",
442
- arguments: JSON.stringify(argsObj)
448
+ arguments: args.text
443
449
  },
444
450
  event_id: nextEventId()
445
451
  });
@@ -516,6 +522,35 @@ async function writeGeminiInteractionsSSEStream(res, events, optionsOrLatency) {
516
522
  if (!res.writableEnded) res.end();
517
523
  return true;
518
524
  }
525
+ function prepareInteractionsMisbehavior(plan, streaming) {
526
+ if (!streaming && plan.summary.fault === "stop-length-mid-tool") {
527
+ const response = plan.response;
528
+ const combined = isCombinedFixtureResponse(response);
529
+ const outcome = combined && response.blocks?.length ? resolveFixtureBlockOutcome(response.blocks) : void 0;
530
+ const toolCalls = (outcome?.toolCalls ?? (combined || isToolCallResponse(response) ? response.toolCalls ?? [] : [])).filter((_, index) => index !== plan.target?.index);
531
+ let callIndex = 0;
532
+ const blocks = outcome?.ordered.filter((block) => block.type === "text" || callIndex++ !== plan.target?.index);
533
+ plan = {
534
+ ...plan,
535
+ response: {
536
+ ...response,
537
+ toolCalls,
538
+ ...blocks ? { blocks } : {}
539
+ }
540
+ };
541
+ }
542
+ return prepareOpenAIChatMisbehavior(plan);
543
+ }
544
+ function finalizeInteractionsMisbehavior(events, plan) {
545
+ if (plan.kind !== "applied" || plan.summary.fault !== "stop-length-mid-tool") return events;
546
+ return events.map((event) => event.event_type === "interaction.completed" && isJsonObject(event.interaction) ? {
547
+ ...event,
548
+ interaction: {
549
+ ...event.interaction,
550
+ status: "incomplete"
551
+ }
552
+ } : event);
553
+ }
519
554
  async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaults, setCorsHeaders) {
520
555
  const { logger } = defaults;
521
556
  setCorsHeaders(res);
@@ -552,6 +587,21 @@ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaul
552
587
  writeErrorResponse(res, 400, JSON.stringify(buildInteractionsErrorResponse("Request body must be a JSON object", "INVALID_ARGUMENT")));
553
588
  return;
554
589
  }
590
+ const validationError = validateInteractionsInput(interactionsReq.input) ?? validateInteractionsTools(interactionsReq.tools);
591
+ if (validationError) {
592
+ journal.add({
593
+ method: req.method ?? "POST",
594
+ path: urlPath,
595
+ headers: flattenHeaders(req.headers),
596
+ body: null,
597
+ response: {
598
+ status: 400,
599
+ fixture: null
600
+ }
601
+ });
602
+ writeErrorResponse(res, 400, JSON.stringify(buildInteractionsErrorResponse(validationError, "INVALID_ARGUMENT")));
603
+ return;
604
+ }
555
605
  const completionReq = geminiInteractionsToCompletionRequest(interactionsReq);
556
606
  completionReq._endpointType = "chat";
557
607
  completionReq._context = getContext(req);
@@ -617,29 +667,172 @@ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaul
617
667
  writeErrorResponse(res, 404, JSON.stringify(buildInteractionsErrorResponse("No fixture matched", "NOT_FOUND")));
618
668
  return;
619
669
  }
620
- const response = await resolveResponse(fixture, completionReq);
670
+ let response = await resolveResponse(fixture, completionReq);
671
+ let misbehavior = planMisbehavior({
672
+ wire: "gemini-interactions",
673
+ fixture,
674
+ response,
675
+ request: completionReq,
676
+ stream: streaming,
677
+ emitsToolCallIds: true,
678
+ defaults,
679
+ rawHeaders: req.headers,
680
+ url: req.url
681
+ });
682
+ if (misbehavior.kind === "error") {
683
+ recordMisbehaviorOutcome({
684
+ entry: journal.add({
685
+ method: req.method ?? "POST",
686
+ path: urlPath,
687
+ headers: flattenHeaders(req.headers),
688
+ body: completionReq,
689
+ response: {
690
+ status: misbehavior.status,
691
+ fixture
692
+ }
693
+ }),
694
+ summary: misbehavior.summary,
695
+ defaults,
696
+ testId
697
+ });
698
+ if (!misbehavior.summary?.evaluations.some((evaluation) => evaluation.outcome === "error")) logger.error(`${req.method ?? "POST"} ${urlPath}: ${misbehavior.message}`);
699
+ writeErrorResponse(res, misbehavior.status, JSON.stringify(buildInteractionsErrorResponse(misbehavior.message, misbehavior.code)));
700
+ return;
701
+ }
702
+ if (misbehavior.kind === "applied") {
703
+ misbehavior = prepareInteractionsMisbehavior(misbehavior, streaming);
704
+ response = misbehavior.response;
705
+ }
706
+ const appliedUsage = misbehavior.kind === "applied" ? resolveOpenAIChatMisbehaviorUsage(misbehavior, completionReq) : void 0;
621
707
  const latency = fixture.latency ?? defaults.latency;
622
708
  const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
623
709
  const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;
624
710
  if (isErrorResponse(response)) {
625
711
  const status = response.status ?? 500;
626
- journal.add({
712
+ recordMisbehaviorOutcome({
713
+ entry: journal.add({
714
+ method: req.method ?? "POST",
715
+ path: urlPath,
716
+ headers: flattenHeaders(req.headers),
717
+ body: completionReq,
718
+ response: {
719
+ status,
720
+ fixture
721
+ }
722
+ }),
723
+ summary: misbehavior.summary,
724
+ defaults,
725
+ testId
726
+ });
727
+ writeErrorResponse(res, status, JSON.stringify(buildInteractionsErrorResponse(response.error.message, response.error.type ?? "ERROR")), { retryAfter: response.retryAfter });
728
+ return;
729
+ }
730
+ if (misbehavior.kind === "applied" && misbehavior.summary.fault === "reasoning-only") {
731
+ const journalEntry = journal.add({
627
732
  method: req.method ?? "POST",
628
733
  path: urlPath,
629
734
  headers: flattenHeaders(req.headers),
630
735
  body: completionReq,
631
736
  response: {
632
- status,
737
+ status: 200,
633
738
  fixture
634
739
  }
635
740
  });
636
- writeErrorResponse(res, status, JSON.stringify(buildInteractionsErrorResponse(response.error.message, response.error.type ?? "ERROR")), { retryAfter: response.retryAfter });
741
+ recordMisbehaviorOutcome({
742
+ entry: journalEntry,
743
+ summary: misbehavior.summary,
744
+ defaults,
745
+ testId
746
+ });
747
+ const interactionId = nextInteractionId();
748
+ const usage = {
749
+ ...interactionsUsage({ usage: appliedUsage }),
750
+ total_output_tokens: 0,
751
+ total_thought_tokens: appliedUsage?.completion_tokens ?? 0
752
+ };
753
+ if (!streaming) {
754
+ const overrides = isTextResponse(response) || isToolCallResponse(response) || isCombinedFixtureResponse(response) ? extractOverrides(response) : void 0;
755
+ res.writeHead(200, { "Content-Type": "application/json" });
756
+ res.end(JSON.stringify({
757
+ id: interactionId,
758
+ model: overrides?.model ?? model,
759
+ status: "incomplete",
760
+ steps: [{
761
+ type: "thought",
762
+ signature: "",
763
+ summary: [{
764
+ type: "text",
765
+ text: misbehavior.reasoning ?? ""
766
+ }]
767
+ }],
768
+ usage
769
+ }));
770
+ return;
771
+ }
772
+ const events = [
773
+ {
774
+ event_type: "interaction.created",
775
+ interaction: {
776
+ id: interactionId,
777
+ status: "in_progress"
778
+ },
779
+ event_id: nextEventId()
780
+ },
781
+ {
782
+ event_type: "step.start",
783
+ index: 0,
784
+ step: { type: "thought" },
785
+ event_id: nextEventId()
786
+ },
787
+ {
788
+ event_type: "step.delta",
789
+ index: 0,
790
+ delta: {
791
+ type: "thought_summary",
792
+ content: {
793
+ type: "text",
794
+ text: misbehavior.reasoning ?? ""
795
+ }
796
+ },
797
+ event_id: nextEventId()
798
+ },
799
+ {
800
+ event_type: "step.stop",
801
+ index: 0,
802
+ event_id: nextEventId()
803
+ },
804
+ {
805
+ event_type: "interaction.completed",
806
+ interaction: {
807
+ id: interactionId,
808
+ status: "incomplete",
809
+ usage
810
+ },
811
+ event_id: nextEventId()
812
+ }
813
+ ];
814
+ const interruption = createInterruptionSignal(fixture);
815
+ if (!await writeGeminiInteractionsSSEStream(res, events, {
816
+ latency,
817
+ streamingProfile: fixture.streamingProfile,
818
+ recordedTimings: fixture.recordedTimings,
819
+ replaySpeed,
820
+ signal: interruption?.signal,
821
+ onChunkSent: interruption?.tick
822
+ })) {
823
+ if (!res.writableEnded) res.destroy();
824
+ journalEntry.response.interrupted = true;
825
+ journalEntry.response.interruptReason = interruption?.reason();
826
+ }
827
+ interruption?.cleanup();
637
828
  return;
638
829
  }
639
- const interactionId = nextInteractionId();
640
- if (isContentWithToolCallsResponse(response)) {
830
+ if (isCombinedFixtureResponse(response)) {
641
831
  if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Gemini Interactions API — ignoring");
642
- const overrides = extractOverrides(response);
832
+ const overrides = {
833
+ ...extractOverrides(response),
834
+ ...appliedUsage ? { usage: appliedUsage } : {}
835
+ };
643
836
  const journalEntry = journal.add({
644
837
  method: req.method ?? "POST",
645
838
  path: urlPath,
@@ -650,14 +843,25 @@ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaul
650
843
  fixture
651
844
  }
652
845
  });
846
+ recordMisbehaviorOutcome({
847
+ entry: journalEntry,
848
+ summary: misbehavior.summary,
849
+ defaults,
850
+ testId
851
+ });
852
+ const functionToolCalls = requireEmittedFunctionToolCalls(response, "Gemini Interactions");
853
+ const interactionId = nextInteractionId();
653
854
  if (!streaming) {
654
- const body = buildInteractionsContentWithToolCallsResponse(response.content ?? "", response.toolCalls ?? [], model, interactionId, logger, overrides, response.blocks);
855
+ const body = buildInteractionsContentWithToolCallsResponse(response.content ?? "", functionToolCalls, model, interactionId, logger, overrides, response.blocks);
655
856
  res.writeHead(200, { "Content-Type": "application/json" });
656
- res.end(JSON.stringify(body));
857
+ res.end(JSON.stringify(misbehavior.kind === "applied" && misbehavior.summary.fault === "stop-length-mid-tool" ? {
858
+ ...body,
859
+ status: "incomplete"
860
+ } : body));
657
861
  } else {
658
- const events = buildInteractionsContentWithToolCallsSSEEvents(response.content ?? "", response.toolCalls ?? [], interactionId, chunkSize, logger, overrides, response.blocks);
862
+ const events = buildInteractionsContentWithToolCallsSSEEvents(response.content ?? "", functionToolCalls, interactionId, chunkSize, logger, overrides, response.blocks);
659
863
  const interruption = createInterruptionSignal(fixture);
660
- if (!await writeGeminiInteractionsSSEStream(res, events, {
864
+ if (!await writeGeminiInteractionsSSEStream(res, finalizeInteractionsMisbehavior(events, misbehavior), {
661
865
  latency,
662
866
  streamingProfile: fixture.streamingProfile,
663
867
  recordedTimings: fixture.recordedTimings,
@@ -675,7 +879,10 @@ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaul
675
879
  }
676
880
  if (isTextResponse(response)) {
677
881
  if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Gemini Interactions API — ignoring");
678
- const overrides = extractOverrides(response);
882
+ const overrides = {
883
+ ...extractOverrides(response),
884
+ ...appliedUsage ? { usage: appliedUsage } : {}
885
+ };
679
886
  const journalEntry = journal.add({
680
887
  method: req.method ?? "POST",
681
888
  path: urlPath,
@@ -686,14 +893,24 @@ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaul
686
893
  fixture
687
894
  }
688
895
  });
896
+ recordMisbehaviorOutcome({
897
+ entry: journalEntry,
898
+ summary: misbehavior.summary,
899
+ defaults,
900
+ testId
901
+ });
902
+ const interactionId = nextInteractionId();
689
903
  if (!streaming) {
690
904
  const body = buildInteractionsTextResponse(response.content, model, interactionId, overrides);
691
905
  res.writeHead(200, { "Content-Type": "application/json" });
692
- res.end(JSON.stringify(body));
906
+ res.end(JSON.stringify(misbehavior.kind === "applied" && misbehavior.summary.fault === "stop-length-mid-tool" ? {
907
+ ...body,
908
+ status: "incomplete"
909
+ } : body));
693
910
  } else {
694
911
  const events = buildInteractionsTextSSEEvents(response.content, interactionId, chunkSize, overrides);
695
912
  const interruption = createInterruptionSignal(fixture);
696
- if (!await writeGeminiInteractionsSSEStream(res, events, {
913
+ if (!await writeGeminiInteractionsSSEStream(res, finalizeInteractionsMisbehavior(events, misbehavior), {
697
914
  latency,
698
915
  streamingProfile: fixture.streamingProfile,
699
916
  recordedTimings: fixture.recordedTimings,
@@ -711,7 +928,10 @@ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaul
711
928
  }
712
929
  if (isToolCallResponse(response)) {
713
930
  if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Gemini Interactions API — ignoring");
714
- const overrides = extractOverrides(response);
931
+ const overrides = {
932
+ ...extractOverrides(response),
933
+ ...appliedUsage ? { usage: appliedUsage } : {}
934
+ };
715
935
  const journalEntry = journal.add({
716
936
  method: req.method ?? "POST",
717
937
  path: urlPath,
@@ -722,14 +942,25 @@ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaul
722
942
  fixture
723
943
  }
724
944
  });
945
+ recordMisbehaviorOutcome({
946
+ entry: journalEntry,
947
+ summary: misbehavior.summary,
948
+ defaults,
949
+ testId
950
+ });
951
+ const functionToolCalls = requireEmittedFunctionToolCalls(response, "Gemini Interactions");
952
+ const interactionId = nextInteractionId();
725
953
  if (!streaming) {
726
- const body = buildInteractionsToolCallResponse(response.toolCalls, model, interactionId, logger, overrides);
954
+ const body = buildInteractionsToolCallResponse(functionToolCalls, model, interactionId, logger, overrides);
727
955
  res.writeHead(200, { "Content-Type": "application/json" });
728
- res.end(JSON.stringify(body));
956
+ res.end(JSON.stringify(misbehavior.kind === "applied" && misbehavior.summary.fault === "stop-length-mid-tool" ? {
957
+ ...body,
958
+ status: "incomplete"
959
+ } : body));
729
960
  } else {
730
- const events = buildInteractionsToolCallSSEEvents(response.toolCalls, interactionId, logger, overrides);
961
+ const events = buildInteractionsToolCallSSEEvents(functionToolCalls, interactionId, logger, overrides);
731
962
  const interruption = createInterruptionSignal(fixture);
732
- if (!await writeGeminiInteractionsSSEStream(res, events, {
963
+ if (!await writeGeminiInteractionsSSEStream(res, finalizeInteractionsMisbehavior(events, misbehavior), {
733
964
  latency,
734
965
  streamingProfile: fixture.streamingProfile,
735
966
  recordedTimings: fixture.recordedTimings,
@@ -745,15 +976,20 @@ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaul
745
976
  }
746
977
  return;
747
978
  }
748
- journal.add({
749
- method: req.method ?? "POST",
750
- path: urlPath,
751
- headers: flattenHeaders(req.headers),
752
- body: completionReq,
753
- response: {
754
- status: 500,
755
- fixture
756
- }
979
+ recordMisbehaviorOutcome({
980
+ entry: journal.add({
981
+ method: req.method ?? "POST",
982
+ path: urlPath,
983
+ headers: flattenHeaders(req.headers),
984
+ body: completionReq,
985
+ response: {
986
+ status: 500,
987
+ fixture
988
+ }
989
+ }),
990
+ summary: misbehavior.summary,
991
+ defaults,
992
+ testId
757
993
  });
758
994
  writeErrorResponse(res, 500, JSON.stringify(buildInteractionsErrorResponse("Fixture response did not match any known type", "INTERNAL")));
759
995
  }