@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
package/dist/responses.js CHANGED
@@ -1,10 +1,12 @@
1
1
  import { isReasoningModel } from "./model-utils.js";
2
- import { extractOverrides, flattenHeaders, generateId, generateToolCallId, getContext, getTestId, isContentWithToolCallsResponse, isErrorResponse, isJsonObject, isTextResponse, isToolCallResponse, resolveFixtureBlocks, resolveReasoningForModel, resolveResponse, resolveStrictMode, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField } from "./helpers.js";
2
+ import { assertCustomToolCalls, assertResponsesToolCalls, extractOverrides, flattenHeaders, generateId, generateToolCallId, getContext, getTestId, isCombinedFixtureResponse, isErrorResponse, isJsonObject, isTextResponse, isToolCallResponse, prepareOpenAIChatMisbehavior, resolveFixtureBlocks, resolveOpenAIChatMisbehaviorUsage, resolveReasoningForModel, resolveResponse, resolveServedBlockOutcome, resolveStrictMode, serializeErrorResponse, servedToolCalls, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField, toolCallFixtureBlock, withoutLegacyNamespaces } from "./helpers.js";
3
3
  import { calculateDelay, delay, writeErrorResponse } from "./sse-writer.js";
4
4
  import { applyChaosAsync } from "./chaos.js";
5
- import { matchFixtureDiagnostic, recordMatchOptions } from "./router.js";
5
+ import { planMisbehavior, recordMisbehaviorOutcome } from "./misbehavior.js";
6
+ import { matchFixtureDiagnostic, recordMatchOptions, setResponsesOfferedTools } from "./router.js";
6
7
  import { proxyAndRecord } from "./recorder.js";
7
8
  import { createInterruptionSignal } from "./interruption.js";
9
+ import { randomBytes } from "node:crypto";
8
10
 
9
11
  //#region src/responses.ts
10
12
  function extractTextContent(content) {
@@ -12,7 +14,36 @@ function extractTextContent(content) {
12
14
  if (typeof content === "string") return content;
13
15
  return content.filter((p) => p.type === "input_text" || p.type === "output_text").map((p) => p.text ?? "").join("");
14
16
  }
15
- function responsesInputToMessages(req) {
17
+ /**
18
+ * A `custom_tool_call_output.output` is a string or a list of content parts
19
+ * (`input_text` / `input_image` / `input_file`). Flatten a list to its
20
+ * concatenated `input_text` text so text matchers such as
21
+ * `toolResultContains` see it.
22
+ *
23
+ * The value comes straight from the request body, so it is read as `unknown`.
24
+ * Like `function_call_output`, a malformed output never fails the request:
25
+ * any other value (null, an object, a number) yields empty text, and list
26
+ * entries that are not `input_text` parts with string text are skipped.
27
+ */
28
+ function customToolOutputText(output) {
29
+ if (typeof output === "string") return output;
30
+ if (!Array.isArray(output)) return "";
31
+ return output.map((p) => {
32
+ if (p === null || typeof p !== "object") return "";
33
+ const part = p;
34
+ return part.type === "input_text" && typeof part.text === "string" ? part.text : "";
35
+ }).join("");
36
+ }
37
+ /**
38
+ * Convert Responses input items into chat messages for fixture matching. The
39
+ * default is the conversion of earlier releases: `custom_tool_call` and
40
+ * `custom_tool_call_output` items are skipped, and history namespaces are not
41
+ * kept. With `extended`, a custom tool call becomes an assistant message with
42
+ * `custom_tool_calls` (so turn counting sees it), its output becomes a `tool`
43
+ * message (so `hasToolResult` and `toolCallId` see it), and a `function_call`
44
+ * keeps its `namespace`.
45
+ */
46
+ function responsesInputToMessages(req, extended = false) {
16
47
  const messages = [];
17
48
  const itemReferencePlaceholders = /* @__PURE__ */ new WeakSet();
18
49
  if (req.instructions) messages.push({
@@ -26,6 +57,25 @@ function responsesInputToMessages(req) {
26
57
  });
27
58
  return messages;
28
59
  }
60
+ /** A synthesized call for an output item that has no matching call. */
61
+ const attachSynthesizedCall = (target, item) => {
62
+ const id = item.call_id ?? generateToolCallId();
63
+ if (item.type === "custom_tool_call_output") (target.custom_tool_calls ??= []).push({
64
+ id,
65
+ type: "custom",
66
+ name: "",
67
+ input: ""
68
+ });
69
+ else (target.tool_calls ??= []).push({
70
+ id,
71
+ type: "function",
72
+ function: {
73
+ name: "",
74
+ arguments: ""
75
+ }
76
+ });
77
+ };
78
+ const hasCalls = (m) => m.tool_calls !== void 0 || m.custom_tool_calls !== void 0;
29
79
  for (const item of req.input) if (item.role === "system" || item.role === "developer") messages.push({
30
80
  role: "system",
31
81
  content: extractTextContent(item.content)
@@ -44,61 +94,55 @@ function responsesInputToMessages(req) {
44
94
  tool_calls: [{
45
95
  id: item.call_id ?? generateToolCallId(),
46
96
  type: "function",
97
+ ...extended && typeof item.namespace === "string" ? { namespace: item.namespace } : {},
47
98
  function: {
48
99
  name: item.name ?? "",
49
100
  arguments: item.arguments ?? ""
50
101
  }
51
102
  }]
52
103
  });
53
- else if (item.type === "function_call_output") {
54
- if (!messages.some((m) => m.role === "assistant" && m.tool_calls?.some((tc) => tc.id === item.call_id))) {
104
+ else if (extended && item.type === "custom_tool_call") messages.push({
105
+ role: "assistant",
106
+ content: null,
107
+ custom_tool_calls: [{
108
+ id: item.call_id ?? generateToolCallId(),
109
+ type: "custom",
110
+ name: item.name ?? "",
111
+ input: typeof item.input === "string" ? item.input : "",
112
+ ...typeof item.namespace === "string" ? { namespace: item.namespace } : {}
113
+ }]
114
+ });
115
+ else if (item.type === "function_call_output" || extended && item.type === "custom_tool_call_output") {
116
+ if (!messages.some((m) => m.role === "assistant" && (m.tool_calls?.some((tc) => tc.id === item.call_id) || m.custom_tool_calls?.some((tc) => tc.id === item.call_id)))) {
55
117
  const lastMsg = messages[messages.length - 1];
56
- if (lastMsg && lastMsg.role === "assistant" && itemReferencePlaceholders.has(lastMsg) && !lastMsg.tool_calls) {
118
+ if (lastMsg && lastMsg.role === "assistant" && itemReferencePlaceholders.has(lastMsg) && !hasCalls(lastMsg)) {
57
119
  lastMsg.content = null;
58
- lastMsg.tool_calls = [{
59
- id: item.call_id ?? generateToolCallId(),
60
- type: "function",
61
- function: {
62
- name: "",
63
- arguments: ""
64
- }
65
- }];
120
+ attachSynthesizedCall(lastMsg, item);
66
121
  itemReferencePlaceholders.delete(lastMsg);
67
122
  } else {
68
123
  let appended = false;
69
124
  for (let k = messages.length - 1; k >= 0; k--) {
70
125
  const m = messages[k];
71
- if (m.role === "assistant" && m.tool_calls) {
72
- m.tool_calls.push({
73
- id: item.call_id ?? generateToolCallId(),
74
- type: "function",
75
- function: {
76
- name: "",
77
- arguments: ""
78
- }
79
- });
126
+ if (m.role === "assistant" && hasCalls(m)) {
127
+ attachSynthesizedCall(m, item);
80
128
  appended = true;
81
129
  break;
82
130
  }
83
131
  if (m.role === "user") break;
84
132
  }
85
- if (!appended) messages.push({
86
- role: "assistant",
87
- content: null,
88
- tool_calls: [{
89
- id: item.call_id ?? generateToolCallId(),
90
- type: "function",
91
- function: {
92
- name: "",
93
- arguments: ""
94
- }
95
- }]
96
- });
133
+ if (!appended) {
134
+ const synthesized = {
135
+ role: "assistant",
136
+ content: null
137
+ };
138
+ attachSynthesizedCall(synthesized, item);
139
+ messages.push(synthesized);
140
+ }
97
141
  }
98
142
  }
99
143
  messages.push({
100
144
  role: "tool",
101
- content: item.output ?? "",
145
+ content: item.type === "custom_tool_call_output" ? customToolOutputText(item.output) : item.output ?? "",
102
146
  tool_call_id: item.call_id
103
147
  });
104
148
  } else if (item.type === "item_reference") {
@@ -111,6 +155,7 @@ function responsesInputToMessages(req) {
111
155
  }
112
156
  return messages;
113
157
  }
158
+ /** The request tools of earlier releases: top-level `function` tools only. */
114
159
  function responsesToolsToCompletionsTools(tools) {
115
160
  if (!tools || tools.length === 0) return void 0;
116
161
  return tools.filter((t) => t.type === "function").map((t) => ({
@@ -122,17 +167,146 @@ function responsesToolsToCompletionsTools(tools) {
122
167
  }
123
168
  }));
124
169
  }
125
- function responsesToCompletionRequest(req) {
170
+ /** One Responses `function` or `custom` tool in aimock's normalized form. */
171
+ function normalizeResponsesTool(tool, namespace) {
172
+ if (tool === null || typeof tool !== "object") return void 0;
173
+ const t = tool;
174
+ const ns = namespace !== void 0 ? { namespace } : {};
175
+ if (t.type === "function") {
176
+ const fn = t;
177
+ return {
178
+ type: "function",
179
+ ...ns,
180
+ function: {
181
+ name: fn.name,
182
+ description: fn.description,
183
+ parameters: fn.parameters
184
+ }
185
+ };
186
+ }
187
+ if (t.type === "custom") {
188
+ const custom = t;
189
+ return {
190
+ type: "custom",
191
+ name: custom.name,
192
+ ...custom.description !== void 0 ? { description: custom.description } : {},
193
+ ...ns,
194
+ ...custom.format !== void 0 ? { format: custom.format } : {}
195
+ };
196
+ }
197
+ }
198
+ /**
199
+ * Input items whose `tools` add to the request's tools:
200
+ * `additional_tools` (Codex responses-lite) and `tool_search_output` (tools
201
+ * loaded by tool search, e.g. Codex's deferred MCP namespaces). Both carry the
202
+ * same function / custom / namespace tool shapes as `req.tools`.
203
+ */
204
+ function isToolCarryingItemType(type) {
205
+ return type === "additional_tools" || type === "tool_search_output";
206
+ }
207
+ /**
208
+ * Why a `namespace` tool cannot be flattened, or undefined when it can: its
209
+ * name is not a non-empty string (OpenAI's `NamespaceToolParam` requires
210
+ * `name` with `minLength: 1`), or its `tools` is not an array.
211
+ */
212
+ function namespaceToolProblem(tool) {
213
+ if (typeof tool.name !== "string" || tool.name === "") return "name is not a non-empty string";
214
+ if (!Array.isArray(tool.tools)) return "tools is not an array";
215
+ }
216
+ /**
217
+ * Flatten every Responses tool a request offers, for extended matching and for
218
+ * `toolNamespace`. Sources, in order: `req.tools`, then the `tools` of every
219
+ * `additional_tools` or `tool_search_output` input item, in input order.
220
+ * `namespace` tools contribute one entry per inner function/custom tool, each
221
+ * carrying the namespace; other tool types are ignored.
222
+ *
223
+ * Malformed items never fail the request (earlier releases ignored them): a
224
+ * `namespace` tool without a non-empty string name or with a non-array
225
+ * `tools`, a `null` entry, and a tool-carrying item whose `tools` is not an
226
+ * array are dropped, with a warning when `warn` is given.
227
+ */
228
+ function flattenResponsesTools(req, warn) {
229
+ const tools = [];
230
+ const customTools = [];
231
+ const add = (normalized) => {
232
+ if (!normalized) return;
233
+ if (normalized.type === "custom") customTools.push(normalized);
234
+ else tools.push(normalized);
235
+ };
236
+ const sources = [];
237
+ if (Array.isArray(req.tools)) req.tools.forEach((tool, i) => sources.push({
238
+ tool,
239
+ path: `tools[${i}]`
240
+ }));
241
+ if (Array.isArray(req.input)) req.input.forEach((item, i) => {
242
+ if (!isToolCarryingItemType(item?.type) || item?.tools === void 0) return;
243
+ if (!Array.isArray(item.tools)) {
244
+ warn?.(`Ignoring input[${i}].tools: not an array`);
245
+ return;
246
+ }
247
+ item.tools.forEach((tool, j) => sources.push({
248
+ tool,
249
+ path: `input[${i}].tools[${j}]`
250
+ }));
251
+ });
252
+ for (const { tool, path } of sources) {
253
+ const t = tool;
254
+ if (t === null) {
255
+ warn?.(`Ignoring ${path}: null tool`);
256
+ continue;
257
+ }
258
+ if (t?.type === "namespace") {
259
+ const problem = namespaceToolProblem(t);
260
+ if (problem) {
261
+ warn?.(`Ignoring namespace tool ${path}: ${problem}`);
262
+ continue;
263
+ }
264
+ t.tools.forEach((inner, k) => {
265
+ if (inner === null) warn?.(`Ignoring ${path}.tools[${k}]: null tool`);
266
+ else add(normalizeResponsesTool(inner, t.name));
267
+ });
268
+ continue;
269
+ }
270
+ add(normalizeResponsesTool(tool));
271
+ }
126
272
  return {
273
+ tools,
274
+ customTools,
275
+ sourceCount: sources.length
276
+ };
277
+ }
278
+ function responsesToCompletionRequest(req, options = {}) {
279
+ const extended = options.extended === true;
280
+ const all = flattenResponsesTools(req, extended && options.logger ? (message) => options.logger?.warn(`Responses request: ${message}`) : void 0);
281
+ const completionReq = {
127
282
  model: req.model,
128
- messages: responsesInputToMessages(req),
283
+ messages: responsesInputToMessages(req, extended),
129
284
  stream: req.stream,
130
285
  temperature: req.temperature,
131
286
  max_tokens: req.max_output_tokens,
132
- tools: responsesToolsToCompletionsTools(req.tools),
287
+ tools: extended ? all.sourceCount > 0 ? all.tools : void 0 : responsesToolsToCompletionsTools(req.tools),
288
+ ...extended && all.customTools.length > 0 ? { customTools: all.customTools } : {},
133
289
  tool_choice: req.tool_choice,
134
290
  response_format: req.response_format
135
291
  };
292
+ setResponsesOfferedTools(completionReq, {
293
+ tools: all.tools,
294
+ customTools: all.customTools
295
+ });
296
+ return completionReq;
297
+ }
298
+ /**
299
+ * The HTTP request check of earlier releases for `tools`: keep the
300
+ * converter's empty/falsy bypass, and reject only a collection the legacy
301
+ * converter would throw on (not an array, or a `null` entry). Malformed
302
+ * nested tools (inside a `namespace` tool or a tool-carrying input item) are
303
+ * dropped by the conversion instead.
304
+ */
305
+ function validateResponsesTools(tools) {
306
+ if (tools && tools.length !== 0) {
307
+ if (!Array.isArray(tools)) return "tools must be an array";
308
+ if (tools.some((tool) => tool === null)) return "tools entries must not be null";
309
+ }
136
310
  }
137
311
  function responsesStatus(finishReason, defaultStatus) {
138
312
  if (!finishReason) return defaultStatus;
@@ -180,56 +354,105 @@ function buildTextStreamEvents(content, model, chunkSize, reasoning, webSearches
180
354
  });
181
355
  return events;
182
356
  }
357
+ function requireFixtureToolArguments(value) {
358
+ if (typeof value !== "string") throw new Error("Invalid fixture tool call: \"arguments\" must be a string after normalization");
359
+ return value;
360
+ }
361
+ function requireFixtureToolInput(value) {
362
+ if (typeof value !== "string") throw new Error("Invalid fixture tool call: \"input\" must be a string for a custom tool call");
363
+ return value;
364
+ }
365
+ function isCustomEmittable(call) {
366
+ if (call === null || typeof call !== "object") return false;
367
+ return call.type === "custom" || call.type === "customToolCall";
368
+ }
369
+ /**
370
+ * A Responses `function_call` item. `namespace` is added only when the fixture
371
+ * sets it, so a call without one keeps the keys and key order it had before
372
+ * namespaces existed (existing fixtures stay byte-identical on the wire).
373
+ */
374
+ function functionCallItem(call, fcId, callId, args, status) {
375
+ return {
376
+ type: "function_call",
377
+ id: fcId,
378
+ call_id: callId,
379
+ ...call.namespace !== void 0 ? { namespace: call.namespace } : {},
380
+ name: call.name,
381
+ arguments: args,
382
+ status
383
+ };
384
+ }
385
+ /**
386
+ * `ctc_…` item id, the prefix the OpenAI guide and Codex's item-id map use for
387
+ * custom tool calls. The underscore separator is deliberate: it matches the
388
+ * real id format, so this does not use `generateId` (which would give
389
+ * `ctc-…`, like aimock's own `fc-…` ids).
390
+ */
391
+ function customToolCallItemId() {
392
+ return `ctc_${randomBytes(12).toString("base64url")}`;
393
+ }
394
+ /** A Responses `custom_tool_call` item; `namespace` is added only when set. */
395
+ function customToolCallItem(call, ctcId, callId, input, status) {
396
+ return {
397
+ type: "custom_tool_call",
398
+ id: ctcId,
399
+ call_id: callId,
400
+ ...call.namespace !== void 0 ? { namespace: call.namespace } : {},
401
+ name: call.name,
402
+ input,
403
+ status
404
+ };
405
+ }
406
+ /**
407
+ * A `toolCalls` entry as earlier releases served it: a function call whose
408
+ * `type`, `input` and `namespace` are ignored. The exported builders take
409
+ * fixtures in that form; the handler passes `namespace` (extended mode) and
410
+ * custom calls to the internal builders instead.
411
+ */
412
+ function legacyFunctionCall(call) {
413
+ if (call === null || typeof call !== "object") return call;
414
+ const { name, arguments: args, id } = call;
415
+ return id !== void 0 ? {
416
+ name,
417
+ arguments: args,
418
+ id
419
+ } : {
420
+ name,
421
+ arguments: args
422
+ };
423
+ }
424
+ /**
425
+ * The exported builders' `toolCalls` as legacy function calls. It walks
426
+ * `toolCalls` by index up to `toolCalls.length`, as earlier releases did, so an
427
+ * untyped caller's non-array value gives the same calls: none for an object
428
+ * without `length`, and entries by index for a string or array-like.
429
+ */
430
+ function legacyFunctionCalls(toolCalls) {
431
+ const calls = [];
432
+ for (let idx = 0; idx < toolCalls.length; idx++) calls.push(legacyFunctionCall(toolCalls[idx]));
433
+ return calls;
434
+ }
435
+ /** Resolve exported-builder `blocks` as earlier releases did (no namespace emitted). */
436
+ function legacyOrderedBlocks(blocks) {
437
+ if (!blocks || blocks.length === 0) return void 0;
438
+ return resolveFixtureBlocks(blocks).map((block) => {
439
+ if (block.type === "text" || block.namespace === void 0) return block;
440
+ const copy = { ...block };
441
+ delete copy.namespace;
442
+ return copy;
443
+ });
444
+ }
183
445
  function buildToolCallStreamEvents(toolCalls, model, chunkSize, reasoning, webSearches, overrides, emitEncryptedReasoning = false, synthesizeSummarylessReasoning = false) {
446
+ return toolCallStreamEvents(legacyFunctionCalls(toolCalls), model, chunkSize, reasoning, webSearches, overrides, emitEncryptedReasoning, synthesizeSummarylessReasoning);
447
+ }
448
+ function toolCallStreamEvents(toolCalls, model, chunkSize, reasoning, webSearches, overrides, emitEncryptedReasoning = false, synthesizeSummarylessReasoning = false) {
449
+ assertResponsesToolCalls(toolCalls);
184
450
  const { respId, created, events, prefixOutputItems, nextOutputIndex } = buildResponsePreamble(model, chunkSize, reasoning, webSearches, overrides, emitEncryptedReasoning, synthesizeSummarylessReasoning);
185
451
  const fcOutputItems = [];
186
452
  for (let idx = 0; idx < toolCalls.length; idx++) {
187
- const tc = toolCalls[idx];
188
- const callId = tc.id || generateToolCallId();
189
- const fcId = generateId("fc");
190
- const outputIndex = nextOutputIndex + idx;
191
- events.push({
192
- type: "response.output_item.added",
193
- output_index: outputIndex,
194
- item: {
195
- type: "function_call",
196
- id: fcId,
197
- call_id: callId,
198
- name: tc.name,
199
- arguments: "",
200
- status: "in_progress"
201
- }
202
- });
203
- const args = tc.arguments;
204
- for (let i = 0; i < args.length; i += chunkSize) {
205
- const slice = args.slice(i, i + chunkSize);
206
- events.push({
207
- type: "response.function_call_arguments.delta",
208
- item_id: fcId,
209
- output_index: outputIndex,
210
- delta: slice
211
- });
212
- }
213
- events.push({
214
- type: "response.function_call_arguments.done",
215
- item_id: fcId,
216
- output_index: outputIndex,
217
- arguments: args
218
- });
219
- const doneItem = {
220
- type: "function_call",
221
- id: fcId,
222
- call_id: callId,
223
- name: tc.name,
224
- arguments: args,
225
- status: "completed"
226
- };
227
- events.push({
228
- type: "response.output_item.done",
229
- output_index: outputIndex,
230
- item: doneItem
231
- });
232
- fcOutputItems.push(doneItem);
453
+ const { events: callEvents, item } = buildToolCallOutputEvents(toolCalls[idx], chunkSize, nextOutputIndex + idx);
454
+ events.push(...callEvents);
455
+ fcOutputItems.push(item);
233
456
  }
234
457
  events.push({
235
458
  type: "response.completed",
@@ -460,7 +683,7 @@ function buildWebSearchStreamEvents(queries, startOutputIndex) {
460
683
  }
461
684
  return events;
462
685
  }
463
- function buildResponsePreamble(model, chunkSize, reasoning, webSearches, overrides, emitEncryptedReasoning = false, synthesizeSummarylessReasoning = false) {
686
+ function buildResponsePreamble(model, chunkSize, reasoning, webSearches, overrides, emitEncryptedReasoning = false, synthesizeSummarylessReasoning = false, forceReasoning = false) {
464
687
  const respId = overrides?.id ?? responseId();
465
688
  const created = overrides?.created ?? Math.floor(Date.now() / 1e3);
466
689
  const effectiveModel = overrides?.model ?? model;
@@ -489,7 +712,7 @@ function buildResponsePreamble(model, chunkSize, reasoning, webSearches, overrid
489
712
  output: []
490
713
  }
491
714
  });
492
- if (reasoning || shouldSynthesizeBlobOnlyReasoning(reasoning, model, synthesizeSummarylessReasoning)) {
715
+ if (reasoning || forceReasoning || shouldSynthesizeBlobOnlyReasoning(reasoning, model, synthesizeSummarylessReasoning)) {
493
716
  const reasoningEvents = buildReasoningStreamEvents(reasoning, chunkSize, emitEncryptedReasoning);
494
717
  events.push(...reasoningEvents);
495
718
  const doneEvent = reasoningEvents.find((e) => e.type === "response.output_item.done" && e.item?.type === "reasoning");
@@ -585,27 +808,20 @@ function buildMessageOutputEvents(content, chunkSize, outputIndex) {
585
808
  /**
586
809
  * Emit the output_item.added → arguments deltas → arguments.done →
587
810
  * output_item.done events for a single function_call at `outputIndex`,
588
- * returning the completed item for the final `output` array. Behavior is
589
- * identical to the inline per-tool-call loop in the legacy path; both the
590
- * legacy branch and the ordered-blocks branch share this so wire output stays
591
- * byte-identical for a given (tool, outputIndex).
811
+ * returning the completed item for the final `output` array. Every Responses
812
+ * streaming builder (tool-only, legacy content+toolCalls and ordered blocks)
813
+ * reaches this through {@link buildToolCallOutputEvents}, so a given
814
+ * (tool, outputIndex) has the same wire output on every path.
592
815
  */
593
816
  function buildFunctionCallOutputEvents(toolCall, chunkSize, outputIndex) {
594
817
  const callId = toolCall.id || generateToolCallId();
595
818
  const fcId = generateId("fc");
596
- const args = toolCall.arguments;
819
+ const args = requireFixtureToolArguments(toolCall.arguments);
597
820
  const events = [];
598
821
  events.push({
599
822
  type: "response.output_item.added",
600
823
  output_index: outputIndex,
601
- item: {
602
- type: "function_call",
603
- id: fcId,
604
- call_id: callId,
605
- name: toolCall.name,
606
- arguments: "",
607
- status: "in_progress"
608
- }
824
+ item: functionCallItem(toolCall, fcId, callId, "", "in_progress")
609
825
  });
610
826
  for (let i = 0; i < args.length; i += chunkSize) events.push({
611
827
  type: "response.function_call_arguments.delta",
@@ -619,24 +835,78 @@ function buildFunctionCallOutputEvents(toolCall, chunkSize, outputIndex) {
619
835
  output_index: outputIndex,
620
836
  arguments: args
621
837
  });
622
- const fcItem = {
623
- type: "function_call",
624
- id: fcId,
625
- call_id: callId,
626
- name: toolCall.name,
627
- arguments: args,
628
- status: "completed"
838
+ const item = functionCallItem(toolCall, fcId, callId, args, "completed");
839
+ events.push({
840
+ type: "response.output_item.done",
841
+ output_index: outputIndex,
842
+ item
843
+ });
844
+ return {
845
+ events,
846
+ item
629
847
  };
848
+ }
849
+ /**
850
+ * `text` cut into slices of at most `chunkSize` UTF-16 units, never between
851
+ * the two halves of a surrogate pair: a cut that would land inside a pair
852
+ * moves back before it, or, when that would leave the slice empty
853
+ * (`chunkSize` 1), forward past it. The slices rejoin to `text` exactly.
854
+ */
855
+ function surrogateSafeChunks(text, chunkSize) {
856
+ const chunks = [];
857
+ for (let i = 0; i < text.length;) {
858
+ let end = Math.min(i + chunkSize, text.length);
859
+ const high = text.charCodeAt(end - 1);
860
+ const low = text.charCodeAt(end);
861
+ if (high >= 55296 && high <= 56319 && low >= 56320 && low <= 57343) end = end - 1 > i ? end - 1 : end + 1;
862
+ chunks.push(text.slice(i, end));
863
+ i = end;
864
+ }
865
+ return chunks;
866
+ }
867
+ /**
868
+ * Emit output_item.added → custom_tool_call_input.delta×N →
869
+ * custom_tool_call_input.done → output_item.done for one `custom_tool_call` at
870
+ * `outputIndex`, returning the completed item. An empty `input`
871
+ * yields no delta event.
872
+ */
873
+ function buildCustomToolCallOutputEvents(call, chunkSize, outputIndex) {
874
+ const callId = call.id || generateToolCallId();
875
+ const ctcId = customToolCallItemId();
876
+ const input = requireFixtureToolInput(call.input);
877
+ const events = [];
878
+ events.push({
879
+ type: "response.output_item.added",
880
+ output_index: outputIndex,
881
+ item: customToolCallItem(call, ctcId, callId, "", "in_progress")
882
+ });
883
+ for (const delta of surrogateSafeChunks(input, chunkSize)) events.push({
884
+ type: "response.custom_tool_call_input.delta",
885
+ item_id: ctcId,
886
+ output_index: outputIndex,
887
+ delta
888
+ });
889
+ events.push({
890
+ type: "response.custom_tool_call_input.done",
891
+ item_id: ctcId,
892
+ output_index: outputIndex,
893
+ input
894
+ });
895
+ const item = customToolCallItem(call, ctcId, callId, input, "completed");
630
896
  events.push({
631
897
  type: "response.output_item.done",
632
898
  output_index: outputIndex,
633
- item: fcItem
899
+ item
634
900
  });
635
901
  return {
636
902
  events,
637
- fcItem
903
+ item
638
904
  };
639
905
  }
906
+ /** Streaming dispatcher: one function or custom tool call at `outputIndex`. */
907
+ function buildToolCallOutputEvents(call, chunkSize, outputIndex) {
908
+ return isCustomEmittable(call) ? buildCustomToolCallOutputEvents(call, chunkSize, outputIndex) : buildFunctionCallOutputEvents(call, chunkSize, outputIndex);
909
+ }
640
910
  function buildOutputPrefix(content, model, reasoning, webSearches, emitEncryptedReasoning = false, synthesizeSummarylessReasoning = false) {
641
911
  const output = [];
642
912
  if (reasoning || shouldSynthesizeBlobOnlyReasoning(reasoning, model, synthesizeSummarylessReasoning)) output.push(buildReasoningOutputItem(generateId("rs"), {
@@ -680,6 +950,7 @@ function buildTextResponse(content, model, reasoning, webSearches, overrides, em
680
950
  return buildResponseEnvelope(model, buildOutputPrefix(content, model, reasoning, webSearches, emitEncryptedReasoning, synthesizeSummarylessReasoning), overrides);
681
951
  }
682
952
  function buildToolCallResponse(toolCalls, model, reasoning, webSearches, overrides, emitEncryptedReasoning = false, synthesizeSummarylessReasoning = false) {
953
+ assertResponsesToolCalls(toolCalls);
683
954
  const output = [];
684
955
  if (reasoning || shouldSynthesizeBlobOnlyReasoning(reasoning, model, synthesizeSummarylessReasoning)) output.push(buildReasoningOutputItem(generateId("rs"), {
685
956
  summaryText: reasoning,
@@ -694,21 +965,22 @@ function buildToolCallResponse(toolCalls, model, reasoning, webSearches, overrid
694
965
  query
695
966
  }
696
967
  });
697
- for (const tc of toolCalls) output.push({
698
- type: "function_call",
699
- id: generateId("fc"),
700
- call_id: tc.id || generateToolCallId(),
701
- name: tc.name,
702
- arguments: tc.arguments,
703
- status: "completed"
704
- });
968
+ for (const tc of toolCalls) output.push(buildToolCallOutputItem(tc));
705
969
  return buildResponseEnvelope(model, output, overrides);
706
970
  }
707
971
  function buildContentWithToolCallsStreamEvents(content, toolCalls, model, chunkSize, reasoning, webSearches, overrides, blocks, emitEncryptedReasoning = false, synthesizeSummarylessReasoning = false) {
972
+ const ordered = legacyOrderedBlocks(blocks);
973
+ return contentWithToolCallsStreamEvents(content, ordered && ordered.length > 0 ? [] : legacyFunctionCalls(toolCalls), model, chunkSize, reasoning, webSearches, overrides, ordered, emitEncryptedReasoning, synthesizeSummarylessReasoning);
974
+ }
975
+ /**
976
+ * Internal content+tool-calls builder. `ordered` is already resolved (see
977
+ * {@link resolveServedBlockOutcome}); when non-empty it is streamed in array
978
+ * order, otherwise the legacy message-first path runs over `toolCalls`.
979
+ */
980
+ function contentWithToolCallsStreamEvents(content, toolCalls, model, chunkSize, reasoning, webSearches, overrides, ordered, emitEncryptedReasoning = false, synthesizeSummarylessReasoning = false) {
708
981
  const { respId, created, events, prefixOutputItems, nextOutputIndex } = buildResponsePreamble(model, chunkSize, reasoning, webSearches, overrides, emitEncryptedReasoning, synthesizeSummarylessReasoning);
709
982
  const orderedOutputItems = [];
710
- if (blocks && blocks.length > 0) {
711
- const ordered = resolveFixtureBlocks(blocks);
983
+ if (ordered && ordered.length > 0) {
712
984
  let outputIndex = nextOutputIndex;
713
985
  for (const block of ordered) {
714
986
  if (block.type === "text") {
@@ -716,25 +988,21 @@ function buildContentWithToolCallsStreamEvents(content, toolCalls, model, chunkS
716
988
  events.push(...msgEvents);
717
989
  orderedOutputItems.push(msgItem);
718
990
  } else {
719
- const { events: fcEvents, fcItem } = buildFunctionCallOutputEvents({
720
- name: block.name,
721
- arguments: block.arguments,
722
- id: block.id
723
- }, chunkSize, outputIndex);
724
- events.push(...fcEvents);
725
- orderedOutputItems.push(fcItem);
991
+ const { events: callEvents, item } = buildToolCallOutputEvents(block, chunkSize, outputIndex);
992
+ events.push(...callEvents);
993
+ orderedOutputItems.push(item);
726
994
  }
727
995
  outputIndex += 1;
728
996
  }
729
997
  } else {
998
+ assertResponsesToolCalls(toolCalls);
730
999
  const { events: msgEvents, msgItem } = buildMessageOutputEvents(content, chunkSize, nextOutputIndex);
731
1000
  events.push(...msgEvents);
732
1001
  orderedOutputItems.push(msgItem);
733
1002
  for (let idx = 0; idx < toolCalls.length; idx++) {
734
- const fcOutputIndex = nextOutputIndex + 1 + idx;
735
- const { events: fcEvents, fcItem } = buildFunctionCallOutputEvents(toolCalls[idx], chunkSize, fcOutputIndex);
736
- events.push(...fcEvents);
737
- orderedOutputItems.push(fcItem);
1003
+ const { events: callEvents, item } = buildToolCallOutputEvents(toolCalls[idx], chunkSize, nextOutputIndex + 1 + idx);
1004
+ events.push(...callEvents);
1005
+ orderedOutputItems.push(item);
738
1006
  }
739
1007
  }
740
1008
  events.push({
@@ -751,15 +1019,10 @@ function buildContentWithToolCallsStreamEvents(content, toolCalls, model, chunkS
751
1019
  });
752
1020
  return events;
753
1021
  }
754
- function buildFunctionCallOutputItem(tc) {
755
- return {
756
- type: "function_call",
757
- id: generateId("fc"),
758
- call_id: tc.id || generateToolCallId(),
759
- name: tc.name,
760
- arguments: tc.arguments,
761
- status: "completed"
762
- };
1022
+ /** Non-streaming dispatcher: the completed function or custom tool call item. */
1023
+ function buildToolCallOutputItem(tc) {
1024
+ if (isCustomEmittable(tc)) return customToolCallItem(tc, customToolCallItemId(), tc.id || generateToolCallId(), requireFixtureToolInput(tc.input), "completed");
1025
+ return functionCallItem(tc, generateId("fc"), tc.id || generateToolCallId(), requireFixtureToolArguments(tc.arguments), "completed");
763
1026
  }
764
1027
  function buildMessageOutputItem(content) {
765
1028
  return {
@@ -774,9 +1037,8 @@ function buildMessageOutputItem(content) {
774
1037
  }]
775
1038
  };
776
1039
  }
777
- function buildContentWithToolCallsResponse(content, toolCalls, model, reasoning, webSearches, overrides, blocks, emitEncryptedReasoning = false, synthesizeSummarylessReasoning = false) {
778
- if (blocks && blocks.length > 0) {
779
- const ordered = resolveFixtureBlocks(blocks);
1040
+ function buildContentWithToolCallsResponse(content, toolCalls, model, reasoning, webSearches, overrides, ordered, emitEncryptedReasoning = false, synthesizeSummarylessReasoning = false) {
1041
+ if (ordered && ordered.length > 0) {
780
1042
  const output = [];
781
1043
  if (reasoning || shouldSynthesizeBlobOnlyReasoning(reasoning, model, synthesizeSummarylessReasoning)) output.push(buildReasoningOutputItem(generateId("rs"), {
782
1044
  summaryText: reasoning,
@@ -792,15 +1054,12 @@ function buildContentWithToolCallsResponse(content, toolCalls, model, reasoning,
792
1054
  }
793
1055
  });
794
1056
  for (const block of ordered) if (block.type === "text") output.push(buildMessageOutputItem(block.text));
795
- else output.push(buildFunctionCallOutputItem({
796
- name: block.name,
797
- arguments: block.arguments,
798
- id: block.id
799
- }));
1057
+ else output.push(buildToolCallOutputItem(block));
800
1058
  return buildResponseEnvelope(model, output, overrides);
801
1059
  }
1060
+ assertResponsesToolCalls(toolCalls);
802
1061
  const output = buildOutputPrefix(content, model, reasoning, webSearches, emitEncryptedReasoning, synthesizeSummarylessReasoning);
803
- for (const tc of toolCalls) output.push(buildFunctionCallOutputItem(tc));
1062
+ for (const tc of toolCalls) output.push(buildToolCallOutputItem(tc));
804
1063
  return buildResponseEnvelope(model, output, overrides);
805
1064
  }
806
1065
  async function writeResponsesSSEStream(res, events, optionsOrLatency) {
@@ -869,7 +1128,41 @@ async function handleResponses(req, res, raw, fixtures, journal, defaults, setCo
869
1128
  } }));
870
1129
  return;
871
1130
  }
872
- const completionReq = responsesToCompletionRequest(responsesReq);
1131
+ let inputError;
1132
+ if (typeof responsesReq.input !== "string" && !Array.isArray(responsesReq.input)) inputError = "input must be a string or an array";
1133
+ else if (Array.isArray(responsesReq.input)) for (const [index, item] of responsesReq.input.entries()) {
1134
+ if (item === null) {
1135
+ inputError = `input[${index}] must not be null`;
1136
+ break;
1137
+ }
1138
+ if ((item.role === "system" || item.role === "developer" || item.role === "user" || item.role === "assistant") && item.content && typeof item.content !== "string" && !Array.isArray(item.content)) {
1139
+ inputError = `input[${index}].content must be a string or an array`;
1140
+ break;
1141
+ }
1142
+ }
1143
+ const validationError = inputError ?? validateResponsesTools(responsesReq.tools);
1144
+ if (validationError) {
1145
+ journal.add({
1146
+ method: req.method ?? "POST",
1147
+ path: req.url ?? "/v1/responses",
1148
+ headers: flattenHeaders(req.headers),
1149
+ body: responsesReq,
1150
+ response: {
1151
+ status: 400,
1152
+ fixture: null
1153
+ }
1154
+ });
1155
+ writeErrorResponse(res, 400, JSON.stringify({ error: {
1156
+ message: validationError,
1157
+ type: "invalid_request_error"
1158
+ } }));
1159
+ return;
1160
+ }
1161
+ const extendedTools = defaults.responsesTools === "extended";
1162
+ const completionReq = responsesToCompletionRequest(responsesReq, {
1163
+ extended: extendedTools,
1164
+ logger: defaults.logger
1165
+ });
873
1166
  completionReq._endpointType = "chat";
874
1167
  completionReq._context = getContext(req);
875
1168
  const emitEncryptedReasoning = requestWantsEncryptedReasoning(responsesReq);
@@ -945,27 +1238,116 @@ async function handleResponses(req, res, raw, fixtures, journal, defaults, setCo
945
1238
  } }));
946
1239
  return;
947
1240
  }
948
- const response = await resolveResponse(fixture, completionReq);
1241
+ const resolvedResponse = await resolveResponse(fixture, completionReq);
1242
+ const response = extendedTools ? resolvedResponse : withoutLegacyNamespaces(resolvedResponse);
1243
+ const misbehavior = planMisbehavior({
1244
+ wire: "openai-responses",
1245
+ fixture,
1246
+ response,
1247
+ request: completionReq,
1248
+ stream: responsesReq.stream === true,
1249
+ emitsToolCallIds: true,
1250
+ defaults,
1251
+ rawHeaders: req.headers,
1252
+ url: req.url
1253
+ });
1254
+ if (misbehavior.kind === "error") {
1255
+ recordMisbehaviorOutcome({
1256
+ entry: journal.add({
1257
+ method: req.method ?? "POST",
1258
+ path: req.url ?? "/v1/responses",
1259
+ headers: flattenHeaders(req.headers),
1260
+ body: completionReq,
1261
+ response: {
1262
+ status: misbehavior.status,
1263
+ fixture
1264
+ }
1265
+ }),
1266
+ summary: misbehavior.summary,
1267
+ defaults,
1268
+ testId
1269
+ });
1270
+ if (!misbehavior.summary?.evaluations.some((evaluation) => evaluation.outcome === "error")) defaults.logger.error(misbehavior.message);
1271
+ writeErrorResponse(res, misbehavior.status, JSON.stringify({ error: {
1272
+ message: misbehavior.message,
1273
+ type: "invalid_request_error",
1274
+ code: misbehavior.code
1275
+ } }));
1276
+ return;
1277
+ }
949
1278
  const latency = fixture.latency ?? defaults.latency;
950
1279
  const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
951
1280
  const fixtureTimings = fixture.recordedTimings;
952
1281
  const effectiveReplaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;
953
- if (isErrorResponse(response)) {
954
- const status = response.status ?? 500;
955
- journal.add({
1282
+ if (misbehavior.kind === "applied") {
1283
+ const prepared = prepareResponsesMisbehavior(misbehavior);
1284
+ const output = buildResponsesMisbehavior(prepared, completionReq, chunkSize, emitEncryptedReasoning);
1285
+ const entry = journal.add({
956
1286
  method: req.method ?? "POST",
957
1287
  path: req.url ?? "/v1/responses",
958
1288
  headers: flattenHeaders(req.headers),
959
1289
  body: completionReq,
960
1290
  response: {
961
- status,
1291
+ status: 200,
962
1292
  fixture
963
1293
  }
964
1294
  });
1295
+ recordMisbehaviorOutcome({
1296
+ entry,
1297
+ summary: prepared.summary,
1298
+ defaults,
1299
+ testId
1300
+ });
1301
+ if (responsesReq.stream !== true) {
1302
+ res.writeHead(200, { "Content-Type": "application/json" });
1303
+ res.end(JSON.stringify(output.response));
1304
+ } else {
1305
+ const appliedTimings = prepared.duplicateId && fixtureTimings ? {
1306
+ ...fixtureTimings,
1307
+ interChunkDelaysMs: [...fixtureTimings.interChunkDelaysMs]
1308
+ } : fixtureTimings;
1309
+ if (prepared.duplicateId && appliedTimings) {
1310
+ const lastGap = appliedTimings.interChunkDelaysMs.at(-1) ?? 0;
1311
+ while (appliedTimings.interChunkDelaysMs.length < output.events.length - 1) appliedTimings.interChunkDelaysMs.push(lastGap);
1312
+ }
1313
+ const interruption = createInterruptionSignal(fixture);
1314
+ if (!await writeResponsesSSEStream(res, output.events, {
1315
+ latency,
1316
+ streamingProfile: fixture.streamingProfile,
1317
+ recordedTimings: appliedTimings,
1318
+ replaySpeed: effectiveReplaySpeed,
1319
+ signal: interruption?.signal,
1320
+ onChunkSent: interruption?.tick
1321
+ })) {
1322
+ if (!res.writableEnded) res.destroy();
1323
+ entry.response.interrupted = true;
1324
+ entry.response.interruptReason = interruption?.reason();
1325
+ }
1326
+ interruption?.cleanup();
1327
+ }
1328
+ return;
1329
+ }
1330
+ if (isErrorResponse(response)) {
1331
+ const status = response.status ?? 500;
1332
+ recordMisbehaviorOutcome({
1333
+ entry: journal.add({
1334
+ method: req.method ?? "POST",
1335
+ path: req.url ?? "/v1/responses",
1336
+ headers: flattenHeaders(req.headers),
1337
+ body: completionReq,
1338
+ response: {
1339
+ status,
1340
+ fixture
1341
+ }
1342
+ }),
1343
+ summary: misbehavior.summary,
1344
+ defaults,
1345
+ testId
1346
+ });
965
1347
  writeErrorResponse(res, status, serializeErrorResponse(response), { retryAfter: response.retryAfter });
966
1348
  return;
967
1349
  }
968
- if (isContentWithToolCallsResponse(response)) {
1350
+ if (isCombinedFixtureResponse(response)) {
969
1351
  const overrides = extractOverrides(response);
970
1352
  const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
971
1353
  const effReasoning = resolveReasoningForModel(response.reasoning, completionReq.model, effectiveStrict, defaults.logger);
@@ -979,12 +1361,21 @@ async function handleResponses(req, res, raw, fixtures, journal, defaults, setCo
979
1361
  fixture
980
1362
  }
981
1363
  });
1364
+ recordMisbehaviorOutcome({
1365
+ entry: journalEntry,
1366
+ summary: misbehavior.summary,
1367
+ defaults,
1368
+ testId
1369
+ });
1370
+ const ordered = resolveServedBlockOutcome(response)?.ordered;
1371
+ if (!ordered) assertCustomToolCalls(response);
1372
+ const toolCalls = servedToolCalls(response);
982
1373
  if (responsesReq.stream !== true) {
983
- const body = buildContentWithToolCallsResponse(response.content ?? "", response.toolCalls ?? [], completionReq.model, effReasoning, response.webSearches, overrides, response.blocks, emitEncryptedReasoning, synthesizeSummarylessReasoning);
1374
+ const body = buildContentWithToolCallsResponse(response.content ?? "", toolCalls, completionReq.model, effReasoning, response.webSearches, overrides, ordered, emitEncryptedReasoning, synthesizeSummarylessReasoning);
984
1375
  res.writeHead(200, { "Content-Type": "application/json" });
985
1376
  res.end(JSON.stringify(body));
986
1377
  } else {
987
- const events = buildContentWithToolCallsStreamEvents(response.content ?? "", response.toolCalls ?? [], completionReq.model, chunkSize, effReasoning, response.webSearches, overrides, response.blocks, emitEncryptedReasoning, synthesizeSummarylessReasoning);
1378
+ const events = contentWithToolCallsStreamEvents(response.content ?? "", toolCalls, completionReq.model, chunkSize, effReasoning, response.webSearches, overrides, ordered, emitEncryptedReasoning, synthesizeSummarylessReasoning);
988
1379
  const interruption = createInterruptionSignal(fixture);
989
1380
  if (!await writeResponsesSSEStream(res, events, {
990
1381
  latency,
@@ -1016,6 +1407,12 @@ async function handleResponses(req, res, raw, fixtures, journal, defaults, setCo
1016
1407
  fixture
1017
1408
  }
1018
1409
  });
1410
+ recordMisbehaviorOutcome({
1411
+ entry: journalEntry,
1412
+ summary: misbehavior.summary,
1413
+ defaults,
1414
+ testId
1415
+ });
1019
1416
  if (responsesReq.stream !== true) {
1020
1417
  const body = buildTextResponse(response.content, completionReq.model, effReasoning, response.webSearches, overrides, emitEncryptedReasoning, synthesizeSummarylessReasoning);
1021
1418
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -1053,12 +1450,19 @@ async function handleResponses(req, res, raw, fixtures, journal, defaults, setCo
1053
1450
  fixture
1054
1451
  }
1055
1452
  });
1453
+ recordMisbehaviorOutcome({
1454
+ entry: journalEntry,
1455
+ summary: misbehavior.summary,
1456
+ defaults,
1457
+ testId
1458
+ });
1459
+ assertCustomToolCalls(response);
1056
1460
  if (responsesReq.stream !== true) {
1057
- const body = buildToolCallResponse(response.toolCalls, completionReq.model, effReasoning, response.webSearches, overrides, emitEncryptedReasoning, synthesizeSummarylessReasoning);
1461
+ const body = buildToolCallResponse(servedToolCalls(response), completionReq.model, effReasoning, response.webSearches, overrides, emitEncryptedReasoning, synthesizeSummarylessReasoning);
1058
1462
  res.writeHead(200, { "Content-Type": "application/json" });
1059
1463
  res.end(JSON.stringify(body));
1060
1464
  } else {
1061
- const events = buildToolCallStreamEvents(response.toolCalls, completionReq.model, chunkSize, effReasoning, response.webSearches, overrides, emitEncryptedReasoning, synthesizeSummarylessReasoning);
1465
+ const events = toolCallStreamEvents(servedToolCalls(response), completionReq.model, chunkSize, effReasoning, response.webSearches, overrides, emitEncryptedReasoning, synthesizeSummarylessReasoning);
1062
1466
  const interruption = createInterruptionSignal(fixture);
1063
1467
  if (!await writeResponsesSSEStream(res, events, {
1064
1468
  latency,
@@ -1076,22 +1480,148 @@ async function handleResponses(req, res, raw, fixtures, journal, defaults, setCo
1076
1480
  }
1077
1481
  return;
1078
1482
  }
1079
- journal.add({
1080
- method: req.method ?? "POST",
1081
- path: req.url ?? "/v1/responses",
1082
- headers: flattenHeaders(req.headers),
1083
- body: completionReq,
1084
- response: {
1085
- status: 500,
1086
- fixture
1087
- }
1483
+ recordMisbehaviorOutcome({
1484
+ entry: journal.add({
1485
+ method: req.method ?? "POST",
1486
+ path: req.url ?? "/v1/responses",
1487
+ headers: flattenHeaders(req.headers),
1488
+ body: completionReq,
1489
+ response: {
1490
+ status: 500,
1491
+ fixture
1492
+ }
1493
+ }),
1494
+ summary: misbehavior.summary,
1495
+ defaults,
1496
+ testId
1088
1497
  });
1089
1498
  writeErrorResponse(res, 500, JSON.stringify({ error: {
1090
1499
  message: "Fixture response did not match any known type",
1091
1500
  type: "server_error"
1092
1501
  } }));
1093
1502
  }
1503
+ /** Prepare call IDs once, retaining the engine's effective blocks and exact served-call metadata. */
1504
+ function prepareResponsesMisbehavior(plan) {
1505
+ return prepareOpenAIChatMisbehavior(plan);
1506
+ }
1507
+ /** Build one faulted payload/event sequence for both HTTP and Responses WebSocket consumers. */
1508
+ function buildResponsesMisbehavior(plan, request, chunkSize, emitEncryptedReasoning = false) {
1509
+ const response = plan.response;
1510
+ if (!(isCombinedFixtureResponse(response) || isTextResponse(response) || isToolCallResponse(response))) throw new Error("Responses misbehavior requires a prepared chat response");
1511
+ const overrides = extractOverrides(response);
1512
+ const reasoning = plan.reasoning ?? ("reasoning" in response ? response.reasoning : void 0);
1513
+ const webSearches = "webSearches" in response ? response.webSearches : void 0;
1514
+ const { respId, created, events, prefixOutputItems, nextOutputIndex } = buildResponsePreamble(request.model, chunkSize, reasoning, webSearches, overrides, emitEncryptedReasoning, false, plan.reasoning !== void 0);
1515
+ const output = [...prefixOutputItems];
1516
+ let outputIndex = nextOutputIndex;
1517
+ const incomplete = plan.stop === "length" || plan.stop === "content_filter";
1518
+ if (plan.refusal !== void 0) {
1519
+ const id = itemId();
1520
+ const part = {
1521
+ type: "refusal",
1522
+ refusal: plan.refusal
1523
+ };
1524
+ const item = {
1525
+ id,
1526
+ type: "message",
1527
+ role: "assistant",
1528
+ status: "completed",
1529
+ content: [part]
1530
+ };
1531
+ const position = {
1532
+ item_id: id,
1533
+ output_index: outputIndex,
1534
+ content_index: 0
1535
+ };
1536
+ events.push({
1537
+ type: "response.output_item.added",
1538
+ output_index: outputIndex,
1539
+ item: {
1540
+ ...item,
1541
+ status: "in_progress",
1542
+ content: []
1543
+ }
1544
+ }, {
1545
+ type: "response.content_part.added",
1546
+ ...position,
1547
+ part: {
1548
+ type: "refusal",
1549
+ refusal: ""
1550
+ }
1551
+ });
1552
+ for (let i = 0; i < plan.refusal.length; i += chunkSize) events.push({
1553
+ type: "response.refusal.delta",
1554
+ ...position,
1555
+ delta: plan.refusal.slice(i, i + chunkSize)
1556
+ });
1557
+ events.push({
1558
+ type: "response.refusal.done",
1559
+ ...position,
1560
+ refusal: plan.refusal
1561
+ }, {
1562
+ type: "response.content_part.done",
1563
+ ...position,
1564
+ part
1565
+ }, {
1566
+ type: "response.output_item.done",
1567
+ output_index: outputIndex,
1568
+ item
1569
+ });
1570
+ output.push(item);
1571
+ } else if (plan.summary.fault !== "empty-response" && plan.stop !== "content_filter" && plan.reasoning === void 0) {
1572
+ const combined = isCombinedFixtureResponse(response);
1573
+ const blocks = (combined ? resolveServedBlockOutcome(response) : void 0)?.ordered ?? [..."content" in response && typeof response.content === "string" ? [{
1574
+ type: "text",
1575
+ text: response.content
1576
+ }] : [], ...combined || isToolCallResponse(response) ? servedToolCalls(response).map(toolCallFixtureBlock) : []];
1577
+ let callIndex = 0;
1578
+ for (const block of blocks) {
1579
+ if (block.type === "text") {
1580
+ const message = buildMessageOutputEvents(block.text, chunkSize, outputIndex);
1581
+ events.push(...message.events);
1582
+ output.push(message.msgItem);
1583
+ } else {
1584
+ const call = block.type === "customToolCall" ? buildCustomToolCallOutputEvents(block, chunkSize, outputIndex) : buildFunctionCallOutputEvents(block, chunkSize, outputIndex);
1585
+ const cut = plan.summary.fault === "stop-length-mid-tool" && callIndex === plan.target?.index;
1586
+ const item = cut ? {
1587
+ ...call.item,
1588
+ status: "incomplete"
1589
+ } : call.item;
1590
+ events.push(...call.events.map((event) => cut && event.type === "response.output_item.done" ? {
1591
+ ...event,
1592
+ item
1593
+ } : event));
1594
+ output.push(item);
1595
+ callIndex++;
1596
+ }
1597
+ outputIndex++;
1598
+ }
1599
+ }
1600
+ const usage = resolveOpenAIChatMisbehaviorUsage(plan, request);
1601
+ const body = {
1602
+ id: respId,
1603
+ object: "response",
1604
+ created_at: created,
1605
+ model: overrides?.model ?? request.model,
1606
+ status: incomplete ? "incomplete" : responsesStatus(overrides?.finishReason, "completed"),
1607
+ ...incomplete ? { incomplete_details: { reason: plan.stop === "content_filter" ? "content_filter" : "max_output_tokens" } } : {},
1608
+ output,
1609
+ usage: {
1610
+ input_tokens: usage.prompt_tokens,
1611
+ output_tokens: usage.completion_tokens,
1612
+ total_tokens: usage.total_tokens
1613
+ }
1614
+ };
1615
+ events.push({
1616
+ type: incomplete ? "response.incomplete" : "response.completed",
1617
+ response: body
1618
+ });
1619
+ return {
1620
+ response: body,
1621
+ events
1622
+ };
1623
+ }
1094
1624
 
1095
1625
  //#endregion
1096
- export { buildContentWithToolCallsStreamEvents, buildTextResponse, buildTextStreamEvents, buildToolCallStreamEvents, handleResponses, requestIncludesEncryptedReasoning, requestWantsEncryptedReasoning, responsesToCompletionRequest };
1626
+ export { buildContentWithToolCallsStreamEvents, buildResponsesMisbehavior, buildTextResponse, buildTextStreamEvents, buildToolCallStreamEvents, contentWithToolCallsStreamEvents, handleResponses, prepareResponsesMisbehavior, requestIncludesEncryptedReasoning, requestWantsEncryptedReasoning, responsesToCompletionRequest, toolCallStreamEvents };
1097
1627
  //# sourceMappingURL=responses.js.map