zidane 7.2.4 → 8.0.4

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 (376) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +34 -6
  3. package/dist/{acp-D-tVyUjw.js → acp-DQDhi2dj.js} +230 -77
  4. package/dist/acp-DQDhi2dj.js.map +1 -0
  5. package/dist/acp-cli.d.ts +1 -1
  6. package/dist/acp-cli.js +9 -66
  7. package/dist/acp-cli.js.map +1 -1
  8. package/dist/acp.d.ts +10 -590
  9. package/dist/acp.d.ts.map +1 -1
  10. package/dist/acp.js +3 -3
  11. package/dist/{agent-D0zZnGbt.js → agent-DSLNmBp0.js} +14401 -13214
  12. package/dist/agent-DSLNmBp0.js.map +1 -0
  13. package/dist/agent.d.ts +1 -1
  14. package/dist/agent.js +3 -2
  15. package/dist/aliasing-lVFbm81n.js +217 -0
  16. package/dist/aliasing-lVFbm81n.js.map +1 -0
  17. package/dist/{anthropic-C2aole0G.js → anthropic-CD00LhUP.js} +1442 -1264
  18. package/dist/anthropic-CD00LhUP.js.map +1 -0
  19. package/dist/artifacts-CV2hUKnL.js.map +1 -1
  20. package/dist/{atomic-write-B7zr1ddF.js → atomic-write-Ijgf2Mhc.js} +13 -3
  21. package/dist/atomic-write-Ijgf2Mhc.js.map +1 -0
  22. package/dist/call-tool-dHlbGOIY.js +70 -0
  23. package/dist/call-tool-dHlbGOIY.js.map +1 -0
  24. package/dist/chat/pure.d.ts +372 -6
  25. package/dist/chat/pure.d.ts.map +1 -1
  26. package/dist/chat/pure.js +35 -244
  27. package/dist/chat/pure.js.map +1 -1
  28. package/dist/{approvals-DKaJZYlm.js → chat-JPJJ8uu5.js} +15879 -14631
  29. package/dist/chat-JPJJ8uu5.js.map +1 -0
  30. package/dist/chat.d.ts +4 -6
  31. package/dist/chat.js +23 -12
  32. package/dist/cli-shared-C-YCaglL.js +159 -0
  33. package/dist/cli-shared-C-YCaglL.js.map +1 -0
  34. package/dist/client-core-BQSyDALT.d.ts +2115 -0
  35. package/dist/client-core-BQSyDALT.d.ts.map +1 -0
  36. package/dist/codemode-worker.d.ts +1 -0
  37. package/dist/codemode-worker.js +155 -0
  38. package/dist/codemode-worker.js.map +1 -0
  39. package/dist/codemode.d.ts +352 -0
  40. package/dist/codemode.d.ts.map +1 -0
  41. package/dist/codemode.js +5 -0
  42. package/dist/connection-Dr-Js4yZ.d.ts +28 -0
  43. package/dist/connection-Dr-Js4yZ.d.ts.map +1 -0
  44. package/dist/connection-loop-gdh22LTI.js +112 -0
  45. package/dist/connection-loop-gdh22LTI.js.map +1 -0
  46. package/dist/context-segments-BHKlagL6.js +1016 -0
  47. package/dist/context-segments-BHKlagL6.js.map +1 -0
  48. package/dist/{context-transport-T_6AYIvy.js → context-transport-DSMTUsK1.js} +3 -3
  49. package/dist/{context-transport-T_6AYIvy.js.map → context-transport-DSMTUsK1.js.map} +1 -1
  50. package/dist/contexts/daytona.d.ts +19 -8
  51. package/dist/contexts/daytona.d.ts.map +1 -1
  52. package/dist/contexts/daytona.js +1 -440
  53. package/dist/contexts/docker.d.ts +36 -7
  54. package/dist/contexts/docker.d.ts.map +1 -1
  55. package/dist/contexts/docker.js +1 -456
  56. package/dist/contexts/e2b.d.ts +68 -13
  57. package/dist/contexts/e2b.d.ts.map +1 -1
  58. package/dist/contexts/e2b.js +1 -382
  59. package/dist/contexts/sandbox.d.ts +1 -1
  60. package/dist/contexts/sandbox.js +5 -0
  61. package/dist/contexts/sandbox.js.map +1 -1
  62. package/dist/{contexts-CRE9pw07.js → contexts-d7UolYot.js} +1059 -524
  63. package/dist/contexts-d7UolYot.js.map +1 -0
  64. package/dist/contexts.d.ts +3 -3
  65. package/dist/contexts.js +1 -1
  66. package/dist/cred-keys-GpBg71cP.js +105 -0
  67. package/dist/cred-keys-GpBg71cP.js.map +1 -0
  68. package/dist/{auth-BG2rVKSt.js → credentials-JttEuRON.js} +55 -206
  69. package/dist/credentials-JttEuRON.js.map +1 -0
  70. package/dist/{cursor-iojRXHF9.js → cursor-CdjaQTBW.js} +3 -3
  71. package/dist/{cursor-iojRXHF9.js.map → cursor-CdjaQTBW.js.map} +1 -1
  72. package/dist/daemon-cli.d.ts +1 -1
  73. package/dist/daemon-cli.js +3 -2
  74. package/dist/daemon-cli.js.map +1 -1
  75. package/dist/daytona-DG3omipZ.js +513 -0
  76. package/dist/daytona-DG3omipZ.js.map +1 -0
  77. package/dist/defaults-Cx3zvDOV.js +23 -0
  78. package/dist/defaults-Cx3zvDOV.js.map +1 -0
  79. package/dist/dispatcher-BMlu2BmZ.js +4941 -0
  80. package/dist/dispatcher-BMlu2BmZ.js.map +1 -0
  81. package/dist/docker-BZhMMJUp.js +727 -0
  82. package/dist/docker-BZhMMJUp.js.map +1 -0
  83. package/dist/e2b-CEVOdRib.js +480 -0
  84. package/dist/e2b-CEVOdRib.js.map +1 -0
  85. package/dist/edit-utils-EGosADZq.js +234 -0
  86. package/dist/edit-utils-EGosADZq.js.map +1 -0
  87. package/dist/environment-8wsKE2Ln.js +214 -0
  88. package/dist/environment-8wsKE2Ln.js.map +1 -0
  89. package/dist/eval-BNcvle_J.js +1595 -0
  90. package/dist/eval-BNcvle_J.js.map +1 -0
  91. package/dist/eval.d.ts +2 -2
  92. package/dist/eval.js +1 -1489
  93. package/dist/executor-Cx5-8yqi.js +621 -0
  94. package/dist/executor-Cx5-8yqi.js.map +1 -0
  95. package/dist/extension-authoring-DyGGmq9V.js +385 -0
  96. package/dist/extension-authoring-DyGGmq9V.js.map +1 -0
  97. package/dist/extensions.d.ts +2 -2
  98. package/dist/extensions.js +3 -3
  99. package/dist/{fetch-url-DX75gpL0.js → fetch-url-Ct2TjDWt.js} +4 -4
  100. package/dist/{fetch-url-DX75gpL0.js.map → fetch-url-Ct2TjDWt.js.map} +1 -1
  101. package/dist/{format-BF745pLL.js → format-C2NQEVUH.js} +39 -2
  102. package/dist/format-C2NQEVUH.js.map +1 -0
  103. package/dist/format-bytes-C7tfc3Zm.js +55 -0
  104. package/dist/format-bytes-C7tfc3Zm.js.map +1 -0
  105. package/dist/headless-C_rNUuYC.js +960 -0
  106. package/dist/headless-C_rNUuYC.js.map +1 -0
  107. package/dist/headless.d.ts +1 -1
  108. package/dist/headless.js +1 -413
  109. package/dist/{history-search-C4bk1Wz_.js → history-search-CcYzF8-0.js} +7 -3
  110. package/dist/history-search-CcYzF8-0.js.map +1 -0
  111. package/dist/history-search-worker.d.ts +1 -1
  112. package/dist/history-search-worker.js +1 -1
  113. package/dist/host-D7NrI03V.js +378 -0
  114. package/dist/host-D7NrI03V.js.map +1 -0
  115. package/dist/host-signals-BcP5QKUD.js +118 -0
  116. package/dist/host-signals-BcP5QKUD.js.map +1 -0
  117. package/dist/identity-CwfO8K-t.js +26 -0
  118. package/dist/identity-CwfO8K-t.js.map +1 -0
  119. package/dist/{index-BgN1T3lk.d.ts → index-BGe1fQT7.d.ts} +10987 -9320
  120. package/dist/index-BGe1fQT7.d.ts.map +1 -0
  121. package/dist/index-BVQsIKGn.d.ts +448 -0
  122. package/dist/index-BVQsIKGn.d.ts.map +1 -0
  123. package/dist/{index-C6NEd5y_.d.ts → index-CHABJd-L.d.ts} +3386 -502
  124. package/dist/index-CHABJd-L.d.ts.map +1 -0
  125. package/dist/{index-C37_ppWv.d.ts → index-D6a7qXpg.d.ts} +2 -3
  126. package/dist/index-D6a7qXpg.d.ts.map +1 -0
  127. package/dist/index.d.ts +6 -6
  128. package/dist/index.js +27 -23
  129. package/dist/{completion-core-BRo86R_L.js → inline-ghost-BVRQeGoL.js} +50 -2
  130. package/dist/inline-ghost-BVRQeGoL.js.map +1 -0
  131. package/dist/ipc/remote.d.ts +313 -0
  132. package/dist/ipc/remote.d.ts.map +1 -0
  133. package/dist/ipc/remote.js +4 -0
  134. package/dist/ipc.d.ts +6 -964
  135. package/dist/ipc.js +403 -4
  136. package/dist/ipc.js.map +1 -0
  137. package/dist/{isolation-Kfoof0MM.d.ts → isolation-D9L650W5.d.ts} +100 -15
  138. package/dist/isolation-D9L650W5.d.ts.map +1 -0
  139. package/dist/json-rpc--HXQti1E.js +350 -0
  140. package/dist/json-rpc--HXQti1E.js.map +1 -0
  141. package/dist/links-j8rED1sR.js +603 -0
  142. package/dist/links-j8rED1sR.js.map +1 -0
  143. package/dist/local-terminal-D8QThwo_.js +252 -0
  144. package/dist/local-terminal-D8QThwo_.js.map +1 -0
  145. package/dist/{logger--C1VQjwJ.js → logger-Gtqlayx8.js} +199 -173
  146. package/dist/logger-Gtqlayx8.js.map +1 -0
  147. package/dist/logs-view-CWG8aUAG.js.map +1 -1
  148. package/dist/{mcp-CgNd-f3F.js → mcp-_wecH9Qe.js} +608 -562
  149. package/dist/mcp-_wecH9Qe.js.map +1 -0
  150. package/dist/mcp.d.ts +2 -2
  151. package/dist/mcp.js +2 -2
  152. package/dist/media-sniff-UKijEaiQ.js.map +1 -1
  153. package/dist/{messages-2jcOA_5R.js → messages-BqiwcD2Q.js} +1017 -1014
  154. package/dist/messages-BqiwcD2Q.js.map +1 -0
  155. package/dist/{oauth-DxHO17JE.js → oauth-Px_pBB9W.js} +5 -5
  156. package/dist/{oauth-DxHO17JE.js.map → oauth-Px_pBB9W.js.map} +1 -1
  157. package/dist/{openai-compat-Dy56weK8.js → openai-compat-CFrpH7uq.js} +432 -589
  158. package/dist/openai-compat-CFrpH7uq.js.map +1 -0
  159. package/dist/orchestration-BCGmB8Ps.js +511 -0
  160. package/dist/orchestration-BCGmB8Ps.js.map +1 -0
  161. package/dist/output/stream-json.d.ts +1 -2
  162. package/dist/output/stream-json.d.ts.map +1 -1
  163. package/dist/output/stream-json.js +1 -1
  164. package/dist/output/terminal.d.ts +1 -2
  165. package/dist/output/terminal.d.ts.map +1 -1
  166. package/dist/output/terminal.js +13 -7
  167. package/dist/output/terminal.js.map +1 -1
  168. package/dist/{plugins-gbI7SBA6.js → plugins-DS7mahzZ.js} +20 -101
  169. package/dist/plugins-DS7mahzZ.js.map +1 -0
  170. package/dist/plugins.d.ts +1 -1
  171. package/dist/plugins.js +1 -2
  172. package/dist/prelude-Cxo4t74B.js +517 -0
  173. package/dist/prelude-Cxo4t74B.js.map +1 -0
  174. package/dist/presets.d.ts +1 -1
  175. package/dist/presets.js +1 -2
  176. package/dist/project-root-B8zADr4o.js +167 -0
  177. package/dist/project-root-B8zADr4o.js.map +1 -0
  178. package/dist/project-user-paths-BSgtrRxl.js +58 -0
  179. package/dist/project-user-paths-BSgtrRxl.js.map +1 -0
  180. package/dist/{prompts-Pqc3JdW_.d.ts → prompts-DYcvqShf.d.ts} +14 -11
  181. package/dist/prompts-DYcvqShf.d.ts.map +1 -0
  182. package/dist/protocol-DnhqqSmE.js +13 -0
  183. package/dist/protocol-DnhqqSmE.js.map +1 -0
  184. package/dist/providers/anthropic.d.ts +2 -2
  185. package/dist/providers/anthropic.js +1 -2
  186. package/dist/providers/arcee.d.ts +1 -1
  187. package/dist/providers/arcee.js +1 -1
  188. package/dist/providers/baseten.d.ts +1 -1
  189. package/dist/providers/baseten.js +1 -1
  190. package/dist/providers/cerebras.d.ts +1 -1
  191. package/dist/providers/cerebras.js +3 -2
  192. package/dist/providers/cerebras.js.map +1 -1
  193. package/dist/providers/local.d.ts +1 -1
  194. package/dist/providers/local.js +1 -1
  195. package/dist/providers/openai-compat.d.ts +1 -1
  196. package/dist/providers/openai-compat.js +1 -2
  197. package/dist/providers/openai.d.ts +1 -1
  198. package/dist/providers/openai.js +15 -12
  199. package/dist/providers/openai.js.map +1 -1
  200. package/dist/providers/openrouter.d.ts +1 -1
  201. package/dist/providers/openrouter.js +3 -2
  202. package/dist/providers/openrouter.js.map +1 -1
  203. package/dist/providers/xai.d.ts +1 -1
  204. package/dist/providers/xai.js +3 -3
  205. package/dist/{providers-BVhnFyiY.js → providers-XUR7F4Hi.js} +4 -4
  206. package/dist/{providers-BVhnFyiY.js.map → providers-XUR7F4Hi.js.map} +1 -1
  207. package/dist/providers.d.ts +1 -1
  208. package/dist/providers.js +4 -4
  209. package/dist/registry-D6ShBWJU.js +5234 -0
  210. package/dist/registry-D6ShBWJU.js.map +1 -0
  211. package/dist/remote-hosts-BtA_9ZGp.js +97 -0
  212. package/dist/remote-hosts-BtA_9ZGp.js.map +1 -0
  213. package/dist/restate.d.ts +2 -3
  214. package/dist/restate.d.ts.map +1 -1
  215. package/dist/restate.js +631 -572
  216. package/dist/restate.js.map +1 -1
  217. package/dist/run-summary-CklLiZ4y.js +288 -0
  218. package/dist/run-summary-CklLiZ4y.js.map +1 -0
  219. package/dist/{sandbox-BH0WQkXn.d.ts → sandbox-C5dR5DwQ.d.ts} +6 -3
  220. package/dist/sandbox-C5dR5DwQ.d.ts.map +1 -0
  221. package/dist/session/sqlite.d.ts +1 -2
  222. package/dist/session/sqlite.d.ts.map +1 -1
  223. package/dist/session/sqlite.js +1 -616
  224. package/dist/session-factory-BUGY5P40.js +1576 -0
  225. package/dist/session-factory-BUGY5P40.js.map +1 -0
  226. package/dist/{session-BqRP1hS7.js → session-xh9JXu6i.js} +94 -52
  227. package/dist/session-xh9JXu6i.js.map +1 -0
  228. package/dist/session.d.ts +1 -1
  229. package/dist/session.js +2 -2
  230. package/dist/settings-keys-Ckf29NeM.js +677 -0
  231. package/dist/settings-keys-Ckf29NeM.js.map +1 -0
  232. package/dist/shell-path-Dqaoow2J.js +412 -0
  233. package/dist/shell-path-Dqaoow2J.js.map +1 -0
  234. package/dist/{skills-BC3dodWp.js → skills-DKqZJm4X.js} +430 -470
  235. package/dist/skills-DKqZJm4X.js.map +1 -0
  236. package/dist/skills.d.ts +2 -2
  237. package/dist/skills.js +1 -2
  238. package/dist/source-mPExaUm8.js +381 -0
  239. package/dist/source-mPExaUm8.js.map +1 -0
  240. package/dist/sqlite-DV1DQzN2.js +698 -0
  241. package/dist/sqlite-DV1DQzN2.js.map +1 -0
  242. package/dist/{src-CkNQKFKr.js → src-BRMYRJib.js} +903 -1121
  243. package/dist/src-BRMYRJib.js.map +1 -0
  244. package/dist/{stdio-DPpBDPx5.js → stdio-C6YyoW3I.js} +4 -4
  245. package/dist/{stdio-DPpBDPx5.js.map → stdio-C6YyoW3I.js.map} +1 -1
  246. package/dist/{stdio-loader-Bbx19dMZ.js → stdio-loader-C830Vg-Z.js} +4 -4
  247. package/dist/{stdio-loader-Bbx19dMZ.js.map → stdio-loader-C830Vg-Z.js.map} +1 -1
  248. package/dist/system-prompt-MNVHatSD.d.ts.map +1 -1
  249. package/dist/timeout-DEaqslLO.d.ts.map +1 -1
  250. package/dist/todo-edits-Dn251YUP.js +1790 -0
  251. package/dist/todo-edits-Dn251YUP.js.map +1 -0
  252. package/dist/{turn-operations-DrbJlpq9.js → tool-formatters-IQFDrdrs.js} +1456 -2120
  253. package/dist/tool-formatters-IQFDrdrs.js.map +1 -0
  254. package/dist/tool-state-Dku6YqAB.js +989 -0
  255. package/dist/tool-state-Dku6YqAB.js.map +1 -0
  256. package/dist/tools/fetch-url.d.ts +1 -2
  257. package/dist/tools/fetch-url.d.ts.map +1 -1
  258. package/dist/tools/fetch-url.js +1 -2
  259. package/dist/tools/prompts.d.ts +1 -1
  260. package/dist/tools/prompts.js +7 -7
  261. package/dist/tools/prompts.js.map +1 -1
  262. package/dist/tools/web-search.d.ts +1 -2
  263. package/dist/tools/web-search.d.ts.map +1 -1
  264. package/dist/tools/web-search.js +2 -2
  265. package/dist/tools/web-search.js.map +1 -1
  266. package/dist/tools-_DnT3AGM.js +578 -0
  267. package/dist/tools-_DnT3AGM.js.map +1 -0
  268. package/dist/tools.d.ts +3 -3
  269. package/dist/tools.js +4 -2
  270. package/dist/tui.d.ts +534 -517
  271. package/dist/tui.d.ts.map +1 -1
  272. package/dist/tui.js +34933 -27242
  273. package/dist/tui.js.map +1 -1
  274. package/dist/turn-operations-B8sokwi5.js +333 -0
  275. package/dist/turn-operations-B8sokwi5.js.map +1 -0
  276. package/dist/turn-operations-DQX5ZZni.d.ts +3089 -0
  277. package/dist/turn-operations-DQX5ZZni.d.ts.map +1 -0
  278. package/dist/types-C7ZFOztE.d.ts +132 -0
  279. package/dist/types-C7ZFOztE.d.ts.map +1 -0
  280. package/dist/types-CH5XpIHw.js +114 -0
  281. package/dist/types-CH5XpIHw.js.map +1 -0
  282. package/dist/{types-CWgENVLY.js → types-Cu0ZGDu9.js} +10 -6
  283. package/dist/types-Cu0ZGDu9.js.map +1 -0
  284. package/dist/types-IqegbJEE.d.ts +624 -0
  285. package/dist/types-IqegbJEE.d.ts.map +1 -0
  286. package/dist/types.d.ts +4 -4
  287. package/dist/types.js +1 -1
  288. package/dist/url-D8HCl65b.js +1259 -0
  289. package/dist/url-D8HCl65b.js.map +1 -0
  290. package/dist/wasm-BVT-j5cl.js +33 -0
  291. package/dist/wasm-BVT-j5cl.js.map +1 -0
  292. package/dist/workspace-exec-stream-SV2R9d5S.js +355 -0
  293. package/dist/workspace-exec-stream-SV2R9d5S.js.map +1 -0
  294. package/dist/ws-CIHMvlSh.js +952 -0
  295. package/dist/ws-CIHMvlSh.js.map +1 -0
  296. package/dist/ws-client-N7NxzvQp.js +230 -0
  297. package/dist/ws-client-N7NxzvQp.js.map +1 -0
  298. package/dist/{xai-CazjStOK.js → xai-CvJTeLrI.js} +165 -5
  299. package/dist/xai-CvJTeLrI.js.map +1 -0
  300. package/dist/xdg-zweed0-t.js +117 -0
  301. package/dist/xdg-zweed0-t.js.map +1 -0
  302. package/docs/ACP.md +52 -0
  303. package/docs/ARCHITECTURE.md +103 -6
  304. package/docs/ARTIFACTS.md +8 -4
  305. package/docs/CHAT.md +165 -14
  306. package/docs/EXECUTION_CONTEXT.md +8 -0
  307. package/docs/EXTENSIONS.md +123 -2
  308. package/docs/HEADLESS.md +2 -0
  309. package/docs/IPC.md +775 -64
  310. package/docs/MACHINES.md +456 -0
  311. package/docs/PANELS.md +3 -0
  312. package/docs/README.md +4 -0
  313. package/docs/RESTATE.md +3 -2
  314. package/docs/RUN_IN_BACKGROUND.md +10 -2
  315. package/docs/SERVER.md +1085 -0
  316. package/docs/SKILL.md +6 -1
  317. package/docs/TERMINAL.md +75 -0
  318. package/docs/TUI.md +65 -14
  319. package/package.json +58 -26
  320. package/scripts/eval.ts +4 -0
  321. package/dist/acp-D-tVyUjw.js.map +0 -1
  322. package/dist/agent-D0zZnGbt.js.map +0 -1
  323. package/dist/anthropic-C2aole0G.js.map +0 -1
  324. package/dist/approvals-DKaJZYlm.js.map +0 -1
  325. package/dist/atomic-write-B7zr1ddF.js.map +0 -1
  326. package/dist/auth-BG2rVKSt.js.map +0 -1
  327. package/dist/completion-core-BRo86R_L.js.map +0 -1
  328. package/dist/contexts/daytona.js.map +0 -1
  329. package/dist/contexts/docker.js.map +0 -1
  330. package/dist/contexts/e2b.js.map +0 -1
  331. package/dist/contexts-CRE9pw07.js.map +0 -1
  332. package/dist/eval.js.map +0 -1
  333. package/dist/format-BF745pLL.js.map +0 -1
  334. package/dist/glob-DluQFSw1.js +0 -270
  335. package/dist/glob-DluQFSw1.js.map +0 -1
  336. package/dist/headless.js.map +0 -1
  337. package/dist/history-search-C4bk1Wz_.js.map +0 -1
  338. package/dist/index-BgN1T3lk.d.ts.map +0 -1
  339. package/dist/index-C37_ppWv.d.ts.map +0 -1
  340. package/dist/index-C6NEd5y_.d.ts.map +0 -1
  341. package/dist/ipc-zVSyo6K1.js +0 -331
  342. package/dist/ipc-zVSyo6K1.js.map +0 -1
  343. package/dist/ipc.d.ts.map +0 -1
  344. package/dist/isolation-Kfoof0MM.d.ts.map +0 -1
  345. package/dist/json-rpc-rSWMUh9b.js +0 -192
  346. package/dist/json-rpc-rSWMUh9b.js.map +0 -1
  347. package/dist/logger--C1VQjwJ.js.map +0 -1
  348. package/dist/mcp-CgNd-f3F.js.map +0 -1
  349. package/dist/messages-2jcOA_5R.js.map +0 -1
  350. package/dist/openai-compat-Dy56weK8.js.map +0 -1
  351. package/dist/output-cap-C9zukcCj.js +0 -100
  352. package/dist/output-cap-C9zukcCj.js.map +0 -1
  353. package/dist/panels-B6gRk4bf.js +0 -789
  354. package/dist/panels-B6gRk4bf.js.map +0 -1
  355. package/dist/plugins-gbI7SBA6.js.map +0 -1
  356. package/dist/prompts-Pqc3JdW_.d.ts.map +0 -1
  357. package/dist/run-summary-BU_NgwUT.js +0 -269
  358. package/dist/run-summary-BU_NgwUT.js.map +0 -1
  359. package/dist/sandbox-BH0WQkXn.d.ts.map +0 -1
  360. package/dist/session/sqlite.js.map +0 -1
  361. package/dist/session-BqRP1hS7.js.map +0 -1
  362. package/dist/shell-path-h5czzllR.js +0 -1549
  363. package/dist/shell-path-h5czzllR.js.map +0 -1
  364. package/dist/skills-BC3dodWp.js.map +0 -1
  365. package/dist/src-CkNQKFKr.js.map +0 -1
  366. package/dist/todo-edits-UOwgxGKu.js +0 -528
  367. package/dist/todo-edits-UOwgxGKu.js.map +0 -1
  368. package/dist/tool-formatters-DoRLnfuD.d.ts +0 -759
  369. package/dist/tool-formatters-DoRLnfuD.d.ts.map +0 -1
  370. package/dist/transcript-anchors-BeudMbXx.d.ts +0 -1762
  371. package/dist/transcript-anchors-BeudMbXx.d.ts.map +0 -1
  372. package/dist/turn-operations-C8bMBDl_.d.ts +0 -1258
  373. package/dist/turn-operations-C8bMBDl_.d.ts.map +0 -1
  374. package/dist/turn-operations-DrbJlpq9.js.map +0 -1
  375. package/dist/types-CWgENVLY.js.map +0 -1
  376. package/dist/xai-CazjStOK.js.map +0 -1
@@ -1,1119 +1,1122 @@
1
- import { U as fnv1aBase36, i as TOOL_RESULTS_TAG, t as ASSISTANT_TOOL_CALLS_TAG } from "./openai-compat-Dy56weK8.js";
2
- import { a as documentBlockMarker, o as isWireTurn, r as assertResolvedMediaBlock } from "./types-CWgENVLY.js";
1
+ import { a as documentBlockMarker, r as assertResolvedMediaBlock, s as isWireTurn } from "./types-Cu0ZGDu9.js";
3
2
  import { n as reconcileImageMediaType, t as audioFormatFromMediaType } from "./media-sniff-UKijEaiQ.js";
4
- //#region src/session/messages.ts
3
+ import { c as fnv1aBase36 } from "./aliasing-lVFbm81n.js";
4
+ import { r as TOOL_RESULTS_TAG, t as ASSISTANT_TOOL_CALLS_TAG } from "./openai-compat-CFrpH7uq.js";
5
+ //#region src/session/messages-pairing.ts
5
6
  /**
6
- * Decode an Anthropic `tool_result.content` wire value back to zidane's canonical
7
- * `string | ToolResultContent[]` shape.
8
- *
9
- * Anthropic accepts three shapes in the wire: `string`, `Array<{type:'text',text}>`,
10
- * or `Array<{type:'text'|'image', ...}>`. We collapse to `string` only when every
11
- * block is text to keep simple cases readable.
7
+ * Placeholder content inserted into a synthetic `tool_result` when the harness
8
+ * has to repair an orphan `tool_use`. Exported so downstream consumers
9
+ * (training-data collectors, HFI submission) can reject any payload containing
10
+ * it — the marker satisfies the wire-level pairing contract structurally but
11
+ * the content itself is fake and would poison fine-tuning data.
12
12
  */
13
- function decodeAnthropicToolResultContent(content) {
14
- if (typeof content === "string") return content;
15
- if (!Array.isArray(content)) return JSON.stringify(content);
16
- const blocks = [];
17
- for (const raw of content) {
18
- if (!raw || typeof raw !== "object") continue;
19
- const b = raw;
20
- if (b.type === "text" && typeof b.text === "string") {
21
- blocks.push({
22
- type: "text",
23
- text: b.text
24
- });
25
- continue;
26
- }
27
- if (b.type === "image" && b.source && typeof b.source === "object") {
28
- const src = b.source;
29
- if (src.type === "base64" && typeof src.data === "string" && typeof src.media_type === "string") {
30
- blocks.push({
31
- type: "image",
32
- mediaType: src.media_type,
33
- data: src.data
34
- });
35
- continue;
36
- }
37
- }
38
- blocks.push({
39
- type: "text",
40
- text: JSON.stringify(raw)
41
- });
42
- }
43
- if (blocks.length === 0) return "";
44
- if (!blocks.some((b) => b.type !== "text")) return blocks.map((b) => b.text).join("\n");
45
- return blocks;
46
- }
13
+ const SYNTHETIC_TOOL_RESULT_PLACEHOLDER = "[Tool result missing due to internal error]";
47
14
  /**
48
- * Build an Anthropic `image.source` block, reconciling the declared media
49
- * type against the actual magic bytes.
50
- *
51
- * Anthropic's API rejects the entire request when `media_type` disagrees
52
- * with the payload (`messages.N.content.K.image.source.base64: The image
53
- * was specified using the X media type, but the image appears to be a
54
- * Y image`). The upstream sources of `{type:'image'}` blocks each sniff
55
- * before producing the block (read_file / MCP / TUI paste), but this
56
- * wire-boundary reconciliation is a final safety net for any future code
57
- * path or host-supplied prompt part that slips through without sniffing.
58
- *
59
- * The reconciler is conservative: it overrides only when the magic bytes
60
- * positively identify a *different* known format. Unknown / too-short
61
- * payloads pass `mediaType` through unchanged.
15
+ * Replacement text for an assistant message whose every content block was
16
+ * stripped during pairing repair (e.g. the only blocks were orphan
17
+ * `tool_call`s). Providers reject empty `content` arrays — the marker keeps
18
+ * the turn shape valid while signaling "this assistant turn lost its
19
+ * outputs" to the model so it can recover.
62
20
  */
63
- function anthropicImageSource(mediaType, data) {
64
- return {
65
- type: "base64",
66
- media_type: reconcileImageMediaType(mediaType, data),
67
- data
68
- };
69
- }
70
- function anthropicDocumentBlocks(doc) {
71
- if (doc.encoding === "base64" && doc.mediaType === "application/pdf") return [{
72
- type: "document",
73
- source: {
74
- type: "base64",
75
- media_type: "application/pdf",
76
- data: doc.data
77
- },
78
- ...doc.name ? { title: doc.name } : {}
79
- }];
80
- return [{
81
- type: "text",
82
- text: `${doc.name ? `<attachment name="${doc.name}" media_type="${doc.mediaType}" encoding="${doc.encoding}">` : `<attachment media_type="${doc.mediaType}" encoding="${doc.encoding}">`}\n${doc.data}\n</attachment>`
83
- }];
84
- }
85
- function documentMarker(doc) {
86
- return documentBlockMarker(doc, "document attached");
87
- }
88
- function isContentRefBlock(block) {
89
- return (block.type === "image" || block.type === "document") && typeof block.ref === "string";
90
- }
91
- function unresolvedContentRefError(block) {
92
- return /* @__PURE__ */ new Error(`Cannot project content ref "${block.ref}" to provider wire messages without resolveContentRef.`);
93
- }
21
+ const TOOL_USE_INTERRUPTED_MARKER = "[Tool use interrupted]";
94
22
  /**
95
- * Coerce a tool-call id to Anthropic's wire constraint `^[a-zA-Z0-9_-]+$`.
23
+ * Replacement text for a user message whose every content block was a
24
+ * `tool_result` with no matching upstream `tool_call` (e.g. the assistant
25
+ * pair was stripped by an earlier compaction). Same role as
26
+ * {@link TOOL_USE_INTERRUPTED_MARKER} on the user side.
27
+ */
28
+ const ORPHANED_TOOL_RESULT_MARKER = "[Orphaned tool result removed due to conversation resume]";
29
+ /**
30
+ * Defensive repair pass that rewrites a message list so it satisfies the
31
+ * wire-level `tool_use` ↔ `tool_result` adjacency contract every modern
32
+ * provider enforces.
96
33
  *
97
- * Sessions started under another provider (OpenAI-compat / OpenRouter /
98
- * Cerebras) or carrying MCP-namespaced calls can hold ids with `.`, `:`,
99
- * `/` or other characters that 400 the request the moment the user switches
100
- * to an Anthropic model (`messages.N.content.M.tool_use.id: String should
101
- * match pattern '^[a-zA-Z0-9_-]+$'`).
34
+ * Anthropic 400s on orphans with `'tool_use' ids were found without
35
+ * 'tool_result' blocks immediately after` (and its inverse, `tool_result
36
+ * must be preceded by a tool_call with the same toolCallId`). OpenAI's
37
+ * Chat Completions API rejects most mismatches with a 400 too.
102
38
  *
103
- * The mapping is deterministic — every disallowed char becomes `_` — so a
104
- * `tool_use` and its paired `tool_result` (sanitized identically below) keep
105
- * matching ids and the pairing survives. When sanitization actually changed
106
- * the id, a short hash of the ORIGINAL id is appended so distinct inputs
107
- * that would collapse to the same replacement (`call.1` vs `call_1` vs
108
- * `call:1`) stay distinct on the wire. Empty ids get a stable placeholder
109
- * so the block stays well-formed.
39
+ * Six repair modes — modeled after Anthropic's Claude Code defenses:
40
+ *
41
+ * | # | Corruption | Repair |
42
+ * |---|------------|--------|
43
+ * | 1 | assistant `tool_use` whose next user msg lacks a matching `tool_result` | **Prepend** a synthetic `tool_result` block carrying {@link SYNTHETIC_TOOL_RESULT_PLACEHOLDER} with `isError: true` |
44
+ * | 2 | assistant `tool_use` followed by nothing (or by a non-user msg) | **Insert** a new synthetic user message with the same placeholder |
45
+ * | 3 | user `tool_result` with no preceding assistant `tool_call` | **Strip** the orphan block; if it empties the msg, replace with {@link ORPHANED_TOOL_RESULT_MARKER} text block |
46
+ * | 4 | duplicate `tool_call.id` across assistant messages (CC-1212) | **Strip** later instances + their matching tool_results |
47
+ * | 5 | duplicate `tool_result.callId` within a single user message | **Dedupe** by `callId`, keep first |
48
+ * | 6 | assistant message emptied by mode-4 stripping | **Replace** content with {@link TOOL_USE_INTERRUPTED_MARKER} text block |
49
+ *
50
+ * **Repair, not drop.** Earlier versions of this pass simply dropped orphan
51
+ * blocks, which (a) silently rewrote history the model had reasoned over and
52
+ * (b) cascaded — dropping an assistant tool_call would orphan its
53
+ * tool_result, which would then get dropped too, removing any trace of
54
+ * "what the model tried to do" from the transcript. The repair-based pass
55
+ * preserves the model's tool_use shape and patches the dangling result with
56
+ * an `is_error` placeholder so the model sees "I tried X, the result was
57
+ * lost" and can retry intelligently.
58
+ *
59
+ * Adjacency contract: `tool_result` blocks must live in the user message
60
+ * IMMEDIATELY following the assistant message that emitted the matching
61
+ * `tool_use`. The pass enforces strict adjacency — a tool_result two
62
+ * messages downstream of its tool_call is still an orphan.
63
+ *
64
+ * Idempotent: returns the input reference unchanged when no repairs were
65
+ * necessary. Re-running on already-repaired output is a no-op.
66
+ *
67
+ * Pure: does not mutate input messages or their content arrays — every
68
+ * repair allocates a fresh array / object.
110
69
  */
111
- const ANTHROPIC_TOOL_ID_INVALID = /[^\w-]/g;
112
- function sanitizeAnthropicToolId(id) {
113
- if (!id) return "tool_0";
114
- const sanitized = id.replace(ANTHROPIC_TOOL_ID_INVALID, "_");
115
- if (sanitized === id) return id;
116
- return `${sanitized}_${fnv1aBase36(id)}`;
70
+ function ensureToolResultPairing(messages, options = {}) {
71
+ if (messages.length === 0) return messages;
72
+ const fireRepair = options.onRepair ?? (() => {});
73
+ const firstSeenAt = /* @__PURE__ */ new Map();
74
+ for (let i = 0; i < messages.length; i++) {
75
+ const msg = messages[i];
76
+ if (msg.role !== "assistant") continue;
77
+ for (const block of msg.content) if (block.type === "tool_call" && !firstSeenAt.has(block.id)) firstSeenAt.set(block.id, i);
78
+ }
79
+ const pendingOrphansByUserIndex = /* @__PURE__ */ new Map();
80
+ let changed = false;
81
+ const markChanged = () => {
82
+ changed = true;
83
+ };
84
+ const out = [];
85
+ for (let i = 0; i < messages.length; i++) {
86
+ const msg = messages[i];
87
+ if (msg.role === "assistant") processAssistantMessage({
88
+ index: i,
89
+ msg,
90
+ messages,
91
+ firstSeenAt,
92
+ pendingOrphansByUserIndex,
93
+ out,
94
+ fireRepair,
95
+ markChanged
96
+ });
97
+ else processUserMessage({
98
+ index: i,
99
+ msg,
100
+ pendingOrphansByUserIndex,
101
+ out,
102
+ fireRepair,
103
+ markChanged
104
+ });
105
+ }
106
+ return changed ? out : messages;
117
107
  }
118
108
  /**
119
- * Encode zidane's canonical `string | ToolResultContent[]` shape to Anthropic's
120
- * wire format for `tool_result.content`.
109
+ * Process one assistant message: strip mode-4 duplicates, queue mode-1 / 2
110
+ * orphan-tool-use repairs for the next user message (or insert a synthetic
111
+ * one), and emit a mode-6 marker when dedup empties the message.
121
112
  */
122
- function encodeAnthropicToolResultContent(output) {
123
- if (typeof output === "string") return output;
124
- return output.map((b) => {
125
- if (b.type === "text") return {
126
- type: "text",
127
- text: b.text
128
- };
129
- if (b.type === "document") {
130
- assertResolvedMediaBlock(b, "Anthropic wire messages");
131
- return {
132
- type: "text",
133
- text: documentMarker(b)
134
- };
113
+ function processAssistantMessage(args) {
114
+ const { index, msg, messages, firstSeenAt, pendingOrphansByUserIndex, out, fireRepair, markChanged } = args;
115
+ const dedupedContent = [];
116
+ const seenHere = /* @__PURE__ */ new Set();
117
+ let dedupedChanged = false;
118
+ for (const block of msg.content) {
119
+ if (block.type === "tool_call") {
120
+ const isCrossMsgDup = firstSeenAt.get(block.id) !== index;
121
+ const isIntraMsgDup = seenHere.has(block.id);
122
+ if (isCrossMsgDup || isIntraMsgDup) {
123
+ dedupedChanged = true;
124
+ fireRepair({
125
+ mode: "duplicate-tool-use-strip",
126
+ callId: block.id,
127
+ messageIndex: index
128
+ });
129
+ continue;
130
+ }
131
+ seenHere.add(block.id);
135
132
  }
136
- if (b.type === "audio") return {
137
- type: "text",
138
- text: `[audio omitted: ${b.mediaType}]`
139
- };
140
- if (b.type === "video") return {
141
- type: "text",
142
- text: `[video omitted: ${b.mediaType}]`
143
- };
144
- assertResolvedMediaBlock(b, "Anthropic wire messages");
145
- return {
146
- type: "image",
147
- source: anthropicImageSource(b.mediaType, b.data)
148
- };
149
- });
150
- }
151
- function fromAnthropic(msg) {
152
- const role = msg.role;
153
- const content = [];
154
- if (typeof msg.content === "string") {
155
- content.push({
156
- type: "text",
157
- text: msg.content
158
- });
159
- return {
160
- role,
161
- content
162
- };
133
+ dedupedContent.push(block);
163
134
  }
164
- if (Array.isArray(msg.content)) for (const block of msg.content) {
165
- if (!block || typeof block !== "object") continue;
166
- const b = block;
167
- if (b.type === "text") content.push({
168
- type: "text",
169
- text: b.text
170
- });
171
- else if (b.type === "image") {
172
- const source = b.source;
173
- if (source?.type === "base64") content.push({
174
- type: "image",
175
- mediaType: source.media_type,
176
- data: source.data
177
- });
178
- } else if (b.type === "document") {
179
- const source = b.source;
180
- if (source?.type === "base64" && typeof source.media_type === "string" && typeof source.data === "string") content.push({
181
- type: "document",
182
- mediaType: source.media_type,
183
- data: source.data,
184
- encoding: "base64",
185
- ...typeof b.title === "string" ? { name: b.title } : {}
186
- });
187
- else if (source?.type === "text" && typeof source.media_type === "string" && typeof source.data === "string") content.push({
188
- type: "document",
189
- mediaType: source.media_type,
190
- data: source.data,
191
- encoding: "text",
192
- ...typeof b.title === "string" ? { name: b.title } : {}
193
- });
194
- } else if (b.type === "tool_use") content.push({
195
- type: "tool_call",
196
- id: b.id,
197
- name: b.name,
198
- input: b.input
199
- });
200
- else if (b.type === "tool_result") {
201
- const output = decodeAnthropicToolResultContent(b.content);
202
- const block = {
203
- type: "tool_result",
204
- callId: b.tool_use_id,
205
- output
206
- };
207
- if (b.is_error === true) block.isError = true;
208
- content.push(block);
209
- } else if (b.type === "thinking") {
210
- const block = {
211
- type: "thinking",
212
- text: b.thinking ?? ""
213
- };
214
- if (typeof b.signature === "string") {
215
- block.signature = b.signature;
216
- block.signatureProducer = "anthropic";
217
- }
218
- content.push(block);
219
- } else if (b.type === "redacted_thinking") content.push({
220
- type: "redacted_thinking",
221
- data: b.data ?? ""
222
- });
223
- else if (b.type === "server_tool_use") content.push({
224
- type: "server_tool_use",
225
- producer: "anthropic",
226
- id: b.id ?? "",
227
- name: b.name ?? "",
228
- input: b.input ?? {}
229
- });
230
- else if (b.type === "web_search_tool_result") content.push({
231
- type: "server_tool_result",
232
- producer: "anthropic",
233
- toolUseId: b.tool_use_id ?? "",
234
- toolName: "web_search",
235
- content: b.content
135
+ const surviving = collectIds(dedupedContent, "tool_call", (b) => b.id);
136
+ const next = messages[index + 1];
137
+ const nextIsUser = next && next.role === "user";
138
+ const nextResultIds = nextIsUser ? collectResultIds(next.content) : /* @__PURE__ */ new Set();
139
+ const orphans = [];
140
+ for (const id of surviving) if (!nextResultIds.has(id)) orphans.push(id);
141
+ emitAssistantAfterDedup({
142
+ index,
143
+ msg,
144
+ dedupedContent,
145
+ dedupedChanged,
146
+ out,
147
+ fireRepair,
148
+ markChanged
149
+ });
150
+ if (orphans.length === 0) return;
151
+ if (nextIsUser) {
152
+ markChanged();
153
+ pendingOrphansByUserIndex.set(index + 1, orphans);
154
+ for (const callId of orphans) fireRepair({
155
+ mode: "orphan-tool-use-prepend",
156
+ callId,
157
+ messageIndex: index
236
158
  });
159
+ return;
237
160
  }
238
- return {
239
- role,
240
- content
241
- };
161
+ markChanged();
162
+ out.push({
163
+ role: "user",
164
+ content: orphans.map((callId) => syntheticResultBlock(callId))
165
+ });
166
+ for (const callId of orphans) fireRepair({
167
+ mode: "orphan-tool-use-append",
168
+ callId,
169
+ messageIndex: index
170
+ });
242
171
  }
243
- function fromOpenAI(msg) {
244
- const role = msg.role;
245
- const content = [];
246
- const c = msg.content;
247
- if (c == null) return {
248
- role,
249
- content
250
- };
251
- if (typeof c === "string") {
252
- content.push({
253
- type: "text",
254
- text: c
255
- });
256
- return {
257
- role,
258
- content
259
- };
172
+ function emitAssistantAfterDedup(args) {
173
+ const { index, msg, dedupedContent, dedupedChanged, out, fireRepair, markChanged } = args;
174
+ if (!dedupedChanged) {
175
+ out.push(msg);
176
+ return;
260
177
  }
261
- if (typeof c === "object" && !Array.isArray(c) && c._tag === "__zidane_assistant_tc__") {
262
- const tagged = c;
263
- if (typeof tagged.text === "string" && tagged.text) content.push({
264
- type: "text",
265
- text: tagged.text
178
+ markChanged();
179
+ if (dedupedContent.length === 0) {
180
+ out.push({
181
+ ...msg,
182
+ content: [{
183
+ type: "text",
184
+ text: TOOL_USE_INTERRUPTED_MARKER
185
+ }]
266
186
  });
267
- if (Array.isArray(tagged.tool_calls)) for (const raw of tagged.tool_calls) {
268
- if (!raw || typeof raw !== "object") continue;
269
- const tc = raw;
270
- const rawArgs = tc.function?.arguments;
271
- let input = {};
272
- if (typeof rawArgs === "string") try {
273
- input = JSON.parse(rawArgs);
274
- } catch {
275
- input = {};
276
- }
277
- else if (rawArgs && typeof rawArgs === "object") input = rawArgs;
278
- content.push({
279
- type: "tool_call",
280
- id: tc.id ?? "",
281
- name: tc.function?.name ?? "",
282
- input
187
+ fireRepair({
188
+ mode: "empty-assistant-marker",
189
+ messageIndex: index
190
+ });
191
+ return;
192
+ }
193
+ out.push({
194
+ ...msg,
195
+ content: dedupedContent
196
+ });
197
+ }
198
+ /**
199
+ * Process one user message: dedup mode-5 duplicate tool_results, strip
200
+ * mode-3 orphans whose tool_call is missing from the previously-emitted
201
+ * assistant message, and prepend any synthetic results queued by the prior
202
+ * assistant's mode-1 repair.
203
+ */
204
+ function processUserMessage(args) {
205
+ const { index, msg, pendingOrphansByUserIndex, out, fireRepair, markChanged } = args;
206
+ const queuedOrphans = pendingOrphansByUserIndex.get(index);
207
+ pendingOrphansByUserIndex.delete(index);
208
+ const prev = out.length > 0 ? out[out.length - 1] : null;
209
+ const validIds = prev && prev.role === "assistant" ? collectIds(prev.content, "tool_call", (b) => b.id) : /* @__PURE__ */ new Set();
210
+ const seenCallIds = /* @__PURE__ */ new Set();
211
+ let modifiedHere = false;
212
+ const afterDedup = [];
213
+ for (const block of msg.content) {
214
+ if (block.type !== "tool_result") {
215
+ afterDedup.push(block);
216
+ continue;
217
+ }
218
+ if (seenCallIds.has(block.callId)) {
219
+ modifiedHere = true;
220
+ fireRepair({
221
+ mode: "duplicate-tool-result-strip",
222
+ callId: block.callId,
223
+ messageIndex: index
283
224
  });
225
+ continue;
284
226
  }
285
- return {
286
- role,
287
- content
288
- };
227
+ seenCallIds.add(block.callId);
228
+ afterDedup.push(block);
289
229
  }
290
- if (typeof c === "object" && !Array.isArray(c) && c._tag === "__zidane_tool_results__") {
291
- const tagged = c;
292
- if (Array.isArray(tagged.results)) for (const raw of tagged.results) {
293
- if (!raw || typeof raw !== "object") continue;
294
- const r = raw;
295
- content.push({
296
- type: "tool_result",
297
- callId: r.tool_call_id ?? "",
298
- output: r.content ?? ""
230
+ const afterStrip = [];
231
+ for (const block of afterDedup) {
232
+ if (block.type === "tool_result" && !validIds.has(block.callId)) {
233
+ modifiedHere = true;
234
+ fireRepair({
235
+ mode: "orphan-tool-result-strip",
236
+ callId: block.callId,
237
+ messageIndex: index
299
238
  });
239
+ continue;
300
240
  }
301
- if (typeof tagged.text === "string" && tagged.text) content.push({
302
- type: "text",
303
- text: tagged.text
304
- });
305
- return {
306
- role,
307
- content
308
- };
241
+ afterStrip.push(block);
309
242
  }
310
- if (Array.isArray(c)) {
311
- for (const block of c) {
312
- if (!block || typeof block !== "object") continue;
313
- const b = block;
314
- if (b.type === "text") content.push({
243
+ const finalContent = queuedOrphans ? [...queuedOrphans.map(syntheticResultBlock), ...afterStrip] : afterStrip;
244
+ if (!modifiedHere && queuedOrphans === void 0) {
245
+ out.push(msg);
246
+ return;
247
+ }
248
+ markChanged();
249
+ if (finalContent.length === 0) {
250
+ out.push({
251
+ ...msg,
252
+ content: [{
315
253
  type: "text",
316
- text: b.text
317
- });
318
- else if (b.type === "image_url") {
319
- const imageUrl = b.image_url?.url;
320
- if (imageUrl?.startsWith("data:")) {
321
- const [meta, data] = imageUrl.slice(5).split(",", 2);
322
- const mediaType = meta.replace(";base64", "");
323
- content.push({
324
- type: "image",
325
- mediaType,
326
- data
327
- });
328
- }
329
- }
330
- }
331
- return {
332
- role,
333
- content
334
- };
254
+ text: ORPHANED_TOOL_RESULT_MARKER
255
+ }]
256
+ });
257
+ return;
335
258
  }
336
- return {
337
- role,
338
- content
339
- };
259
+ out.push({
260
+ ...msg,
261
+ content: finalContent
262
+ });
340
263
  }
341
264
  /**
342
- * Single source of truth for whether a signature-bearing reasoning block
343
- * (`thinking` / `redacted_thinking`) may be forwarded to a provider. Both
344
- * wire builders ({@link toAnthropic}, the OpenAI assistant-message shaper)
345
- * and the host-facing {@link sanitizeHistoryForProvider} apply this same
346
- * policy — keeping them one function means the sanitizer can never drift
347
- * from what the wire actually sends.
265
+ * Re-mint tool-call ids that collide with ids already present in the
266
+ * transcript (or with an earlier sibling in the same turn), BEFORE the turn
267
+ * is persisted and its tools dispatched.
348
268
  *
349
- * Rules, each driven by an API constraint hit in production:
269
+ * Why this exists: some providers/serving stacks emit non-globally-unique
270
+ * tool_use ids (per-turn counters like `call_0`, retry-replay duplicates,
271
+ * Kimi-K2.6 on Fireworks). The wire-time pairing pass
272
+ * ({@link ensureToolResultPairing}) can only STRIP such duplicates — which
273
+ * erases the model's tool calls and results from the wire view, stalls
274
+ * progress, and re-accumulates repairs quadratically because the persisted
275
+ * session keeps the corrupted ids. Re-minting at persist time keeps the
276
+ * pairing intact instead: the call, its execution, and its tool_result all
277
+ * carry the fresh id, so the pairing pass has nothing to strip on any later
278
+ * turn.
350
279
  *
351
- * - target `anthropic`:
352
- * - `redacted_thinking` → keep (native Anthropic block; must be echoed).
353
- * - `thinking` tagged `openai` → drop (OpenAI `encrypted_content` is not
354
- * a valid Anthropic HMAC; 400s).
355
- * - unsigned `thinking` → drop (`ThinkingBlockParam` requires a
356
- * signature; unsigned blocks come from non-signing producers).
357
- * - signed but UNTAGGED `thinking` (pre-tagging sessions) → keep: it is
358
- * most plausibly legacy Anthropic output, and the signature-400
359
- * recovery converges if it is not.
280
+ * Fresh ids are `${oldId}_r{n}` with `n` bumped until unique — deterministic
281
+ * shape, Anthropic-wire-safe charset (given a safe input id), and visibly
282
+ * traceable back to the original.
360
283
  *
361
- * - target `openai`:
362
- * - `redacted_thinking` → drop (Anthropic-only block type).
363
- * - `thinking` tagged `anthropic` → drop (an Anthropic HMAC is not
364
- * valid `encrypted_content`).
365
- * - signed but UNTAGGED `thinking` → strip-signature: unlike the
366
- * Anthropic direction there is no signature-error recovery on this
367
- * path, so forwarding a possibly-foreign signature risks an
368
- * unrecoverable 400 — the reasoning text survives, only the opaque
369
- * signature is withheld.
284
+ * Alignment contract: `toolCalls` and the `tool_call` blocks inside
285
+ * `content` are two views of the same emission and MUST be in the same
286
+ * order (true for every provider — both are built from one source array).
287
+ * Occurrences are matched positionally per old id so intra-turn duplicates
288
+ * map to distinct fresh ids on both views.
289
+ *
290
+ * Pure: returns the input references unchanged when no id collides.
370
291
  */
371
- function reasoningWireAction(block, target) {
372
- if (block.type === "redacted_thinking") return target === "anthropic" ? "keep" : "drop";
373
- if (target === "anthropic") {
374
- if (block.signatureProducer === "openai") return "drop";
375
- if (!block.signature) return "drop";
376
- return "keep";
377
- }
378
- if (block.signatureProducer === "anthropic") return "drop";
379
- if (block.signature && block.signatureProducer !== "openai") return "strip-signature";
380
- return "keep";
292
+ function remintDuplicateToolCallIds(toolCalls, content, priorIds) {
293
+ const seen = new Set(priorIds);
294
+ const reminted = [];
295
+ const finalIdQueues = /* @__PURE__ */ new Map();
296
+ let changed = false;
297
+ const outCalls = toolCalls.map((tc) => {
298
+ let finalId = tc.id;
299
+ if (seen.has(tc.id)) {
300
+ let n = 1;
301
+ while (seen.has(`${tc.id}_r${n}`)) n++;
302
+ finalId = `${tc.id}_r${n}`;
303
+ reminted.push({
304
+ oldId: tc.id,
305
+ newId: finalId
306
+ });
307
+ changed = true;
308
+ }
309
+ seen.add(finalId);
310
+ const queue = finalIdQueues.get(tc.id);
311
+ if (queue) queue.push(finalId);
312
+ else finalIdQueues.set(tc.id, [finalId]);
313
+ return finalId === tc.id ? tc : {
314
+ ...tc,
315
+ id: finalId
316
+ };
317
+ });
318
+ if (!changed) return {
319
+ toolCalls,
320
+ content,
321
+ reminted
322
+ };
323
+ return {
324
+ toolCalls: outCalls,
325
+ content: content.map((block) => {
326
+ if (block.type !== "tool_call") return block;
327
+ const finalId = finalIdQueues.get(block.id)?.shift();
328
+ if (finalId === void 0 || finalId === block.id) return block;
329
+ return {
330
+ ...block,
331
+ id: finalId
332
+ };
333
+ }),
334
+ reminted
335
+ };
381
336
  }
382
337
  /**
383
- * Host-facing history sanitizer — applies {@link reasoningWireAction} to a
384
- * turn set BEFORE it is handed to an agent targeting `target`. The in-loop
385
- * wire builders already enforce the same policy per call, so this is for
386
- * hosts that seed history from an external durable store (or hydrate
387
- * pre-provenance-tagging sessions) and want the poison removed from their
388
- * source of truth in one call instead of re-deriving block semantics.
338
+ * Drop-only normalization of persisted history: delete structurally invalid
339
+ * tool blocks so the transcript never re-triggers wire-time pairing repairs
340
+ * turn after turn.
389
341
  *
390
- * Returns new turn objects only where something changed (unchanged turns
391
- * pass through by reference). A turn whose content empties keeps the same
392
- * neutral marker the wire repair uses, so role boundaries stay intact.
342
+ * Unlike {@link ensureToolResultPairing} — which SYNTHESIZES placeholder
343
+ * content and must therefore stay wire-only (fabricated results would poison
344
+ * persisted history and training data) — this pass performs pure deletions of
345
+ * blocks that are provably invalid regardless of interpretation:
346
+ *
347
+ * | Corruption | Action |
348
+ * |------------|--------|
349
+ * | duplicate `tool_call` id within one run lane (across or within turns) | drop the later instance |
350
+ * | duplicate `tool_result.callId` within one turn | drop the later instance |
351
+ * | `tool_result` with no matching `tool_call` in the immediately preceding same-lane assistant turn | drop the block |
352
+ * | turn emptied by the drops above (reasoning-only remainders count as empty) | drop the whole turn |
353
+ *
354
+ * Duplicate detection is scoped to the WIRE-VIEW lane: tool_call ids only
355
+ * collide when both instances reach one model request. By default a turn's
356
+ * lane is its raw `runId` — correct for parent/subagent interleaving, where
357
+ * providers minting per-conversation counter ids (`call_0`, `call_1`)
358
+ * legitimately reuse the same id across lanes. Callers that know the run
359
+ * topology (see `createSession`) pass `laneForRun` to widen lanes to true
360
+ * wire views: all depth-0 runs share the main conversation view, and a
361
+ * resumed subagent shares its resume-chain ancestors' view — duplicates
362
+ * across those runIds DO reach a single request (wire-time
363
+ * `ensureToolResultPairing` mode 4 would strip them on every send) and are
364
+ * healed here.
365
+ *
366
+ * Orphan `tool_use` blocks (a call whose result was never persisted —
367
+ * interrupted run) are deliberately KEPT: they are authentic model output,
368
+ * and the wire-time pass patches them with a synthetic result on every send
369
+ * without touching history.
370
+ *
371
+ * Deletions cascade by design: dropping a duplicate `tool_call` orphans its
372
+ * `tool_result`, which is then dropped too; a turn emptied by either is
373
+ * removed entirely.
374
+ *
375
+ * Safe to persist: deterministic, idempotent (re-running on healed output is
376
+ * a no-op returning the same reference), and fabricates nothing.
377
+ *
378
+ * Pure: does not mutate input turns or their content arrays.
393
379
  */
394
- function sanitizeHistoryForProvider(turns, target) {
395
- let removed = 0;
396
- let stripped = 0;
397
- return {
398
- turns: turns.map((turn) => {
399
- let changed = false;
400
- const content = [];
380
+ function dropInvalidToolPairings(turns, options = {}) {
381
+ if (turns.length === 0) return turns;
382
+ const onDrop = options.onDrop ?? (() => {});
383
+ const laneForRun = options.laneForRun ?? ((runId) => runId ?? "");
384
+ const laneOf = (turn) => laneForRun(turn.runId);
385
+ const laneKey = (turn, id) => `${laneOf(turn)}\u0000${id}`;
386
+ const firstSeenAt = /* @__PURE__ */ new Map();
387
+ for (let i = 0; i < turns.length; i++) {
388
+ const turn = turns[i];
389
+ if (turn.role !== "assistant") continue;
390
+ for (const block of turn.content) if (block.type === "tool_call" && !firstSeenAt.has(laneKey(turn, block.id))) firstSeenAt.set(laneKey(turn, block.id), i);
391
+ }
392
+ let changed = false;
393
+ const out = [];
394
+ for (let i = 0; i < turns.length; i++) {
395
+ const turn = turns[i];
396
+ if (turn.role === "assistant") {
397
+ const kept = [];
398
+ const seenHere = /* @__PURE__ */ new Set();
399
+ let dropped = false;
401
400
  for (const block of turn.content) {
402
- if (block.type !== "thinking" && block.type !== "redacted_thinking") {
403
- content.push(block);
404
- continue;
405
- }
406
- const action = reasoningWireAction(block, target);
407
- if (action === "drop") {
408
- removed += 1;
409
- changed = true;
410
- continue;
411
- }
412
- if (action === "strip-signature" && block.type === "thinking") {
413
- const { signature: _sig, signatureProducer: _producer, ...rest } = block;
414
- content.push(rest);
415
- stripped += 1;
416
- changed = true;
417
- continue;
401
+ if (block.type === "tool_call") {
402
+ if (firstSeenAt.get(laneKey(turn, block.id)) !== i || seenHere.has(block.id)) {
403
+ dropped = true;
404
+ onDrop({
405
+ kind: "duplicate-tool-call",
406
+ callId: block.id,
407
+ index: i
408
+ });
409
+ continue;
410
+ }
411
+ seenHere.add(block.id);
418
412
  }
419
- content.push(block);
413
+ kept.push(block);
420
414
  }
421
- if (!changed) return turn;
422
- return {
415
+ if (!dropped) {
416
+ out.push(turn);
417
+ continue;
418
+ }
419
+ changed = true;
420
+ const onlyReasoning = kept.every((b) => b.type === "thinking" || b.type === "redacted_thinking" || b.type === "provider_reasoning");
421
+ if (kept.length === 0 || onlyReasoning) {
422
+ onDrop({
423
+ kind: "empty-turn",
424
+ index: i
425
+ });
426
+ continue;
427
+ }
428
+ out.push({
423
429
  ...turn,
424
- content: content.length > 0 ? content : [{
425
- type: "text",
426
- text: "[historical reasoning omitted]"
427
- }]
428
- };
429
- }),
430
- removed,
431
- stripped
432
- };
433
- }
434
- function toAnthropic(msg) {
435
- const nonEmpty = msg.content.filter((b) => {
436
- if (b.type === "provider_reasoning") return false;
437
- if (b.type !== "thinking" && b.type !== "redacted_thinking") return true;
438
- return reasoningWireAction(b, "anthropic") === "keep";
439
- }).flatMap((block) => {
440
- switch (block.type) {
441
- case "text": return [{
442
- type: "text",
443
- text: block.text
444
- }];
445
- case "image":
446
- assertResolvedMediaBlock(block, "Anthropic wire messages");
447
- return [{
448
- type: "image",
449
- source: anthropicImageSource(block.mediaType, block.data)
450
- }];
451
- case "audio": return [{
452
- type: "text",
453
- text: `[audio omitted: ${block.mediaType}]`
454
- }];
455
- case "video": return [{
456
- type: "text",
457
- text: `[video omitted: ${block.mediaType}]`
458
- }];
459
- case "document":
460
- assertResolvedMediaBlock(block, "Anthropic wire messages");
461
- return anthropicDocumentBlocks(block);
462
- case "tool_call": return [{
463
- type: "tool_use",
464
- id: sanitizeAnthropicToolId(block.id),
465
- name: block.name,
466
- input: block.input
467
- }];
468
- case "tool_result": {
469
- const out = {
470
- type: "tool_result",
471
- tool_use_id: sanitizeAnthropicToolId(block.callId),
472
- content: encodeAnthropicToolResultContent(block.output)
473
- };
474
- if (block.isError) out.is_error = true;
475
- return [out, ...(typeof block.output === "string" ? [] : block.output.filter((part) => part.type === "document" && typeof part.data === "string")).flatMap(anthropicDocumentBlocks)];
476
- }
477
- case "thinking": return [{
478
- type: "thinking",
479
- thinking: block.text,
480
- signature: block.signature ?? ""
481
- }];
482
- case "redacted_thinking": return [{
483
- type: "redacted_thinking",
484
- data: block.data
485
- }];
486
- case "server_tool_use": return [{
487
- type: "server_tool_use",
488
- id: sanitizeAnthropicToolId(block.id),
489
- name: block.name,
490
- input: block.input
491
- }];
492
- case "server_tool_result": return [{
493
- type: "web_search_tool_result",
494
- tool_use_id: sanitizeAnthropicToolId(block.toolUseId),
495
- content: block.content
496
- }];
497
- default: return [{
498
- type: "text",
499
- text: ""
500
- }];
430
+ content: kept
431
+ });
432
+ continue;
501
433
  }
502
- }).filter((b) => !(b.type === "text" && b.text.length === 0));
503
- if (nonEmpty.length === 0) return {
504
- role: msg.role,
505
- content: ""
506
- };
507
- const first = nonEmpty[0];
508
- if (nonEmpty.length === 1 && first.type === "text") return {
509
- role: msg.role,
510
- content: first.text
511
- };
512
- return {
513
- role: msg.role,
514
- content: nonEmpty
515
- };
516
- }
517
- function toOpenAI(msg) {
518
- const toolCalls = msg.content.filter((b) => b.type === "tool_call");
519
- const toolResults = msg.content.filter((b) => b.type === "tool_result");
520
- const textBlocks = msg.content.filter((b) => b.type === "text");
521
- const imageBlocks = msg.content.filter((b) => b.type === "image");
522
- const audioBlocks = msg.content.filter((b) => b.type === "audio");
523
- const videoBlocks = msg.content.filter((b) => b.type === "video");
524
- const documentBlocks = msg.content.filter((b) => b.type === "document");
525
- if (toolResults.length > 0) {
526
- const siblingText = textBlocks.map((b) => b.text).join("\n");
527
- return {
528
- role: msg.role,
529
- content: {
530
- _tag: TOOL_RESULTS_TAG,
531
- results: toolResults.map((tr) => ({
532
- tool_call_id: tr.callId,
533
- content: tr.output
534
- })),
535
- ...siblingText ? { text: siblingText } : {}
434
+ if (turn.role !== "user") {
435
+ out.push(turn);
436
+ continue;
437
+ }
438
+ const lane = laneOf(turn);
439
+ let prev = null;
440
+ for (let j = out.length - 1; j >= 0; j--) {
441
+ const candidate = out[j];
442
+ if (candidate.role === "system" || candidate.role === "command") continue;
443
+ if (laneOf(candidate) !== lane) continue;
444
+ prev = candidate;
445
+ break;
446
+ }
447
+ const validIds = prev && prev.role === "assistant" ? collectIds(prev.content, "tool_call", (b) => b.id) : /* @__PURE__ */ new Set();
448
+ const kept = [];
449
+ const seenCallIds = /* @__PURE__ */ new Set();
450
+ let dropped = false;
451
+ for (const block of turn.content) {
452
+ if (block.type !== "tool_result") {
453
+ kept.push(block);
454
+ continue;
536
455
  }
537
- };
538
- }
539
- if (toolCalls.length > 0) {
540
- const textContent = textBlocks.length > 0 ? textBlocks[0].text : null;
541
- return {
542
- role: msg.role,
543
- content: {
544
- _tag: ASSISTANT_TOOL_CALLS_TAG,
545
- text: textContent,
546
- tool_calls: toolCalls.map((tc) => ({
547
- id: tc.id,
548
- type: "function",
549
- function: {
550
- name: tc.name,
551
- arguments: JSON.stringify(tc.input)
552
- }
553
- }))
456
+ if (seenCallIds.has(block.callId)) {
457
+ dropped = true;
458
+ onDrop({
459
+ kind: "duplicate-tool-result",
460
+ callId: block.callId,
461
+ index: i
462
+ });
463
+ continue;
554
464
  }
555
- };
556
- }
557
- if (imageBlocks.length > 0 || audioBlocks.length > 0 || videoBlocks.length > 0 || documentBlocks.length > 0) {
558
- const parts = [];
559
- for (const img of imageBlocks) {
560
- assertResolvedMediaBlock(img, "OpenAI-compatible wire messages");
561
- parts.push({
562
- type: "image_url",
563
- image_url: { url: `data:${img.mediaType};base64,${img.data}` }
564
- });
565
- }
566
- for (const b of audioBlocks) parts.push({
567
- type: "input_audio",
568
- input_audio: {
569
- data: b.data,
570
- format: audioFormatFromMediaType(b.mediaType)
465
+ seenCallIds.add(block.callId);
466
+ if (!validIds.has(block.callId)) {
467
+ dropped = true;
468
+ onDrop({
469
+ kind: "orphan-tool-result",
470
+ callId: block.callId,
471
+ index: i
472
+ });
473
+ continue;
571
474
  }
572
- });
573
- for (const b of videoBlocks) parts.push({
574
- type: "video_url",
575
- video_url: { url: `data:${b.mediaType};base64,${b.data}` }
576
- });
577
- for (const b of documentBlocks) {
578
- assertResolvedMediaBlock(b, "OpenAI-compatible wire messages");
579
- parts.push({
580
- type: "text",
581
- text: documentMarker(b)
475
+ kept.push(block);
476
+ }
477
+ if (!dropped) {
478
+ out.push(turn);
479
+ continue;
480
+ }
481
+ changed = true;
482
+ if (kept.length === 0) {
483
+ onDrop({
484
+ kind: "empty-turn",
485
+ index: i
582
486
  });
487
+ continue;
583
488
  }
584
- for (const b of textBlocks) parts.push({
585
- type: "text",
586
- text: b.text
489
+ out.push({
490
+ ...turn,
491
+ content: kept
587
492
  });
588
- return {
589
- role: msg.role,
590
- content: parts
591
- };
592
493
  }
593
- if (textBlocks.length === 1) return {
594
- role: msg.role,
595
- content: textBlocks[0].text
596
- };
597
- if (textBlocks.length > 1) return {
598
- role: msg.role,
599
- content: textBlocks.map((b) => ({
600
- type: "text",
601
- text: b.text
602
- }))
603
- };
494
+ return changed ? out : turns;
495
+ }
496
+ function syntheticResultBlock(callId) {
604
497
  return {
605
- role: msg.role,
606
- content: null
498
+ type: "tool_result",
499
+ callId,
500
+ output: SYNTHETIC_TOOL_RESULT_PLACEHOLDER,
501
+ isError: true
607
502
  };
608
503
  }
504
+ function collectResultIds(content) {
505
+ const ids = /* @__PURE__ */ new Set();
506
+ for (const block of content) if (block.type === "tool_result") ids.add(block.callId);
507
+ return ids;
508
+ }
509
+ function collectIds(content, type, getId) {
510
+ const ids = /* @__PURE__ */ new Set();
511
+ for (const block of content) if (block.type === type) ids.add(getId(block));
512
+ return ids;
513
+ }
514
+ //#endregion
515
+ //#region src/session/messages.ts
609
516
  /**
610
- * Placeholder content inserted into a synthetic `tool_result` when the harness
611
- * has to repair an orphan `tool_use`. Exported so downstream consumers
612
- * (training-data collectors, HFI submission) can reject any payload containing
613
- * it — the marker satisfies the wire-level pairing contract structurally but
614
- * the content itself is fake and would poison fine-tuning data.
517
+ * Decode an Anthropic `tool_result.content` wire value back to zidane's canonical
518
+ * `string | ToolResultContent[]` shape.
519
+ *
520
+ * Anthropic accepts three shapes in the wire: `string`, `Array<{type:'text',text}>`,
521
+ * or `Array<{type:'text'|'image', ...}>`. We collapse to `string` only when every
522
+ * block is text to keep simple cases readable.
615
523
  */
616
- const SYNTHETIC_TOOL_RESULT_PLACEHOLDER = "[Tool result missing due to internal error]";
617
- /**
618
- * Replacement text for an assistant message whose every content block was
619
- * stripped during pairing repair (e.g. the only blocks were orphan
620
- * `tool_call`s). Providers reject empty `content` arrays — the marker keeps
621
- * the turn shape valid while signaling "this assistant turn lost its
622
- * outputs" to the model so it can recover.
623
- */
624
- const TOOL_USE_INTERRUPTED_MARKER = "[Tool use interrupted]";
625
- /**
626
- * Replacement text for a user message whose every content block was a
627
- * `tool_result` with no matching upstream `tool_call` (e.g. the assistant
628
- * pair was stripped by an earlier compaction). Same role as
629
- * {@link TOOL_USE_INTERRUPTED_MARKER} on the user side.
630
- */
631
- const ORPHANED_TOOL_RESULT_MARKER = "[Orphaned tool result removed due to conversation resume]";
524
+ function decodeAnthropicToolResultContent(content) {
525
+ if (typeof content === "string") return content;
526
+ if (!Array.isArray(content)) return JSON.stringify(content);
527
+ const blocks = [];
528
+ for (const raw of content) {
529
+ if (!raw || typeof raw !== "object") continue;
530
+ const b = raw;
531
+ if (b.type === "text" && typeof b.text === "string") {
532
+ blocks.push({
533
+ type: "text",
534
+ text: b.text
535
+ });
536
+ continue;
537
+ }
538
+ if (b.type === "image" && b.source && typeof b.source === "object") {
539
+ const src = b.source;
540
+ if (src.type === "base64" && typeof src.data === "string" && typeof src.media_type === "string") {
541
+ blocks.push({
542
+ type: "image",
543
+ mediaType: src.media_type,
544
+ data: src.data
545
+ });
546
+ continue;
547
+ }
548
+ }
549
+ blocks.push({
550
+ type: "text",
551
+ text: JSON.stringify(raw)
552
+ });
553
+ }
554
+ if (blocks.length === 0) return "";
555
+ if (!blocks.some((b) => b.type !== "text")) return blocks.map((b) => b.text).join("\n");
556
+ return blocks;
557
+ }
632
558
  /**
633
- * Defensive repair pass that rewrites a message list so it satisfies the
634
- * wire-level `tool_use` ↔ `tool_result` adjacency contract every modern
635
- * provider enforces.
636
- *
637
- * Anthropic 400s on orphans with `'tool_use' ids were found without
638
- * 'tool_result' blocks immediately after` (and its inverse, `tool_result
639
- * must be preceded by a tool_call with the same toolCallId`). OpenAI's
640
- * Chat Completions API rejects most mismatches with a 400 too.
641
- *
642
- * Six repair modes — modeled after Anthropic's Claude Code defenses:
643
- *
644
- * | # | Corruption | Repair |
645
- * |---|------------|--------|
646
- * | 1 | assistant `tool_use` whose next user msg lacks a matching `tool_result` | **Prepend** a synthetic `tool_result` block carrying {@link SYNTHETIC_TOOL_RESULT_PLACEHOLDER} with `isError: true` |
647
- * | 2 | assistant `tool_use` followed by nothing (or by a non-user msg) | **Insert** a new synthetic user message with the same placeholder |
648
- * | 3 | user `tool_result` with no preceding assistant `tool_call` | **Strip** the orphan block; if it empties the msg, replace with {@link ORPHANED_TOOL_RESULT_MARKER} text block |
649
- * | 4 | duplicate `tool_call.id` across assistant messages (CC-1212) | **Strip** later instances + their matching tool_results |
650
- * | 5 | duplicate `tool_result.callId` within a single user message | **Dedupe** by `callId`, keep first |
651
- * | 6 | assistant message emptied by mode-4 stripping | **Replace** content with {@link TOOL_USE_INTERRUPTED_MARKER} text block |
559
+ * Build an Anthropic `image.source` block, reconciling the declared media
560
+ * type against the actual magic bytes.
652
561
  *
653
- * **Repair, not drop.** Earlier versions of this pass simply dropped orphan
654
- * blocks, which (a) silently rewrote history the model had reasoned over and
655
- * (b) cascaded — dropping an assistant tool_call would orphan its
656
- * tool_result, which would then get dropped too, removing any trace of
657
- * "what the model tried to do" from the transcript. The repair-based pass
658
- * preserves the model's tool_use shape and patches the dangling result with
659
- * an `is_error` placeholder so the model sees "I tried X, the result was
660
- * lost" and can retry intelligently.
562
+ * Anthropic's API rejects the entire request when `media_type` disagrees
563
+ * with the payload (`messages.N.content.K.image.source.base64: The image
564
+ * was specified using the X media type, but the image appears to be a
565
+ * Y image`). The upstream sources of `{type:'image'}` blocks each sniff
566
+ * before producing the block (read_file / MCP / TUI paste), but this
567
+ * wire-boundary reconciliation is a final safety net for any future code
568
+ * path or host-supplied prompt part that slips through without sniffing.
661
569
  *
662
- * Adjacency contract: `tool_result` blocks must live in the user message
663
- * IMMEDIATELY following the assistant message that emitted the matching
664
- * `tool_use`. The pass enforces strict adjacency — a tool_result two
665
- * messages downstream of its tool_call is still an orphan.
570
+ * The reconciler is conservative: it overrides only when the magic bytes
571
+ * positively identify a *different* known format. Unknown / too-short
572
+ * payloads pass `mediaType` through unchanged.
573
+ */
574
+ function anthropicImageSource(mediaType, data) {
575
+ return {
576
+ type: "base64",
577
+ media_type: reconcileImageMediaType(mediaType, data),
578
+ data
579
+ };
580
+ }
581
+ function anthropicDocumentBlocks(doc) {
582
+ if (doc.encoding === "base64" && doc.mediaType === "application/pdf") return [{
583
+ type: "document",
584
+ source: {
585
+ type: "base64",
586
+ media_type: "application/pdf",
587
+ data: doc.data
588
+ },
589
+ ...doc.name ? { title: doc.name } : {}
590
+ }];
591
+ return [{
592
+ type: "text",
593
+ text: `${doc.name ? `<attachment name="${doc.name}" media_type="${doc.mediaType}" encoding="${doc.encoding}">` : `<attachment media_type="${doc.mediaType}" encoding="${doc.encoding}">`}\n${doc.data}\n</attachment>`
594
+ }];
595
+ }
596
+ function documentMarker(doc) {
597
+ return documentBlockMarker(doc, "document attached");
598
+ }
599
+ function isContentRefBlock(block) {
600
+ return (block.type === "image" || block.type === "document") && typeof block.ref === "string";
601
+ }
602
+ function unresolvedContentRefError(block) {
603
+ return /* @__PURE__ */ new Error(`Cannot project content ref "${block.ref}" to provider wire messages without resolveContentRef.`);
604
+ }
605
+ /**
606
+ * Coerce a tool-call id to Anthropic's wire constraint `^[a-zA-Z0-9_-]+$`.
666
607
  *
667
- * Idempotent: returns the input reference unchanged when no repairs were
668
- * necessary. Re-running on already-repaired output is a no-op.
608
+ * Sessions started under another provider (OpenAI-compat / OpenRouter /
609
+ * Cerebras) or carrying MCP-namespaced calls can hold ids with `.`, `:`,
610
+ * `/` or other characters that 400 the request the moment the user switches
611
+ * to an Anthropic model (`messages.N.content.M.tool_use.id: String should
612
+ * match pattern '^[a-zA-Z0-9_-]+$'`).
669
613
  *
670
- * Pure: does not mutate input messages or their content arrays — every
671
- * repair allocates a fresh array / object.
614
+ * The mapping is deterministic — every disallowed char becomes `_` — so a
615
+ * `tool_use` and its paired `tool_result` (sanitized identically below) keep
616
+ * matching ids and the pairing survives. When sanitization actually changed
617
+ * the id, a short hash of the ORIGINAL id is appended so distinct inputs
618
+ * that would collapse to the same replacement (`call.1` vs `call_1` vs
619
+ * `call:1`) stay distinct on the wire. Empty ids get a stable placeholder
620
+ * so the block stays well-formed.
672
621
  */
673
- function ensureToolResultPairing(messages, options = {}) {
674
- if (messages.length === 0) return messages;
675
- const fireRepair = options.onRepair ?? (() => {});
676
- const firstSeenAt = /* @__PURE__ */ new Map();
677
- for (let i = 0; i < messages.length; i++) {
678
- const msg = messages[i];
679
- if (msg.role !== "assistant") continue;
680
- for (const block of msg.content) if (block.type === "tool_call" && !firstSeenAt.has(block.id)) firstSeenAt.set(block.id, i);
681
- }
682
- const pendingOrphansByUserIndex = /* @__PURE__ */ new Map();
683
- let changed = false;
684
- const markChanged = () => {
685
- changed = true;
686
- };
687
- const out = [];
688
- for (let i = 0; i < messages.length; i++) {
689
- const msg = messages[i];
690
- if (msg.role === "assistant") processAssistantMessage({
691
- index: i,
692
- msg,
693
- messages,
694
- firstSeenAt,
695
- pendingOrphansByUserIndex,
696
- out,
697
- fireRepair,
698
- markChanged
699
- });
700
- else processUserMessage({
701
- index: i,
702
- msg,
703
- pendingOrphansByUserIndex,
704
- out,
705
- fireRepair,
706
- markChanged
707
- });
708
- }
709
- return changed ? out : messages;
622
+ const ANTHROPIC_TOOL_ID_INVALID = /[^\w-]/g;
623
+ function sanitizeAnthropicToolId(id) {
624
+ if (!id) return "tool_0";
625
+ const sanitized = id.replace(ANTHROPIC_TOOL_ID_INVALID, "_");
626
+ if (sanitized === id) return id;
627
+ return `${sanitized}_${fnv1aBase36(id)}`;
710
628
  }
711
629
  /**
712
- * Process one assistant message: strip mode-4 duplicates, queue mode-1 / 2
713
- * orphan-tool-use repairs for the next user message (or insert a synthetic
714
- * one), and emit a mode-6 marker when dedup empties the message.
630
+ * Encode zidane's canonical `string | ToolResultContent[]` shape to Anthropic's
631
+ * wire format for `tool_result.content`.
715
632
  */
716
- function processAssistantMessage(args) {
717
- const { index, msg, messages, firstSeenAt, pendingOrphansByUserIndex, out, fireRepair, markChanged } = args;
718
- const dedupedContent = [];
719
- const seenHere = /* @__PURE__ */ new Set();
720
- let dedupedChanged = false;
721
- for (const block of msg.content) {
722
- if (block.type === "tool_call") {
723
- const isCrossMsgDup = firstSeenAt.get(block.id) !== index;
724
- const isIntraMsgDup = seenHere.has(block.id);
725
- if (isCrossMsgDup || isIntraMsgDup) {
726
- dedupedChanged = true;
727
- fireRepair({
728
- mode: "duplicate-tool-use-strip",
729
- callId: block.id,
730
- messageIndex: index
731
- });
732
- continue;
733
- }
734
- seenHere.add(block.id);
633
+ function encodeAnthropicToolResultContent(output) {
634
+ if (typeof output === "string") return output;
635
+ return output.map((b) => {
636
+ if (b.type === "text") return {
637
+ type: "text",
638
+ text: b.text
639
+ };
640
+ if (b.type === "document") {
641
+ assertResolvedMediaBlock(b, "Anthropic wire messages");
642
+ return {
643
+ type: "text",
644
+ text: documentMarker(b)
645
+ };
735
646
  }
736
- dedupedContent.push(block);
737
- }
738
- const surviving = collectIds(dedupedContent, "tool_call", (b) => b.id);
739
- const next = messages[index + 1];
740
- const nextIsUser = next && next.role === "user";
741
- const nextResultIds = nextIsUser ? collectResultIds(next.content) : /* @__PURE__ */ new Set();
742
- const orphans = [];
743
- for (const id of surviving) if (!nextResultIds.has(id)) orphans.push(id);
744
- emitAssistantAfterDedup({
745
- index,
746
- msg,
747
- dedupedContent,
748
- dedupedChanged,
749
- out,
750
- fireRepair,
751
- markChanged
752
- });
753
- if (orphans.length === 0) return;
754
- if (nextIsUser) {
755
- markChanged();
756
- pendingOrphansByUserIndex.set(index + 1, orphans);
757
- for (const callId of orphans) fireRepair({
758
- mode: "orphan-tool-use-prepend",
759
- callId,
760
- messageIndex: index
761
- });
762
- return;
763
- }
764
- markChanged();
765
- out.push({
766
- role: "user",
767
- content: orphans.map((callId) => syntheticResultBlock(callId))
768
- });
769
- for (const callId of orphans) fireRepair({
770
- mode: "orphan-tool-use-append",
771
- callId,
772
- messageIndex: index
647
+ if (b.type === "audio") return {
648
+ type: "text",
649
+ text: `[audio omitted: ${b.mediaType}]`
650
+ };
651
+ if (b.type === "video") return {
652
+ type: "text",
653
+ text: `[video omitted: ${b.mediaType}]`
654
+ };
655
+ assertResolvedMediaBlock(b, "Anthropic wire messages");
656
+ return {
657
+ type: "image",
658
+ source: anthropicImageSource(b.mediaType, b.data)
659
+ };
773
660
  });
774
661
  }
775
- function emitAssistantAfterDedup(args) {
776
- const { index, msg, dedupedContent, dedupedChanged, out, fireRepair, markChanged } = args;
777
- if (!dedupedChanged) {
778
- out.push(msg);
779
- return;
662
+ function fromAnthropic(msg) {
663
+ const role = msg.role;
664
+ const content = [];
665
+ if (typeof msg.content === "string") {
666
+ content.push({
667
+ type: "text",
668
+ text: msg.content
669
+ });
670
+ return {
671
+ role,
672
+ content
673
+ };
780
674
  }
781
- markChanged();
782
- if (dedupedContent.length === 0) {
783
- out.push({
784
- ...msg,
785
- content: [{
786
- type: "text",
787
- text: TOOL_USE_INTERRUPTED_MARKER
788
- }]
675
+ if (Array.isArray(msg.content)) for (const block of msg.content) {
676
+ if (!block || typeof block !== "object") continue;
677
+ const b = block;
678
+ if (b.type === "text") content.push({
679
+ type: "text",
680
+ text: b.text
789
681
  });
790
- fireRepair({
791
- mode: "empty-assistant-marker",
792
- messageIndex: index
682
+ else if (b.type === "image") {
683
+ const source = b.source;
684
+ if (source?.type === "base64") content.push({
685
+ type: "image",
686
+ mediaType: source.media_type,
687
+ data: source.data
688
+ });
689
+ } else if (b.type === "document") {
690
+ const source = b.source;
691
+ if (source?.type === "base64" && typeof source.media_type === "string" && typeof source.data === "string") content.push({
692
+ type: "document",
693
+ mediaType: source.media_type,
694
+ data: source.data,
695
+ encoding: "base64",
696
+ ...typeof b.title === "string" ? { name: b.title } : {}
697
+ });
698
+ else if (source?.type === "text" && typeof source.media_type === "string" && typeof source.data === "string") content.push({
699
+ type: "document",
700
+ mediaType: source.media_type,
701
+ data: source.data,
702
+ encoding: "text",
703
+ ...typeof b.title === "string" ? { name: b.title } : {}
704
+ });
705
+ } else if (b.type === "tool_use") content.push({
706
+ type: "tool_call",
707
+ id: b.id,
708
+ name: b.name,
709
+ input: b.input
710
+ });
711
+ else if (b.type === "tool_result") {
712
+ const output = decodeAnthropicToolResultContent(b.content);
713
+ const block = {
714
+ type: "tool_result",
715
+ callId: b.tool_use_id,
716
+ output
717
+ };
718
+ if (b.is_error === true) block.isError = true;
719
+ content.push(block);
720
+ } else if (b.type === "thinking") {
721
+ const block = {
722
+ type: "thinking",
723
+ text: b.thinking ?? ""
724
+ };
725
+ if (typeof b.signature === "string") {
726
+ block.signature = b.signature;
727
+ block.signatureProducer = "anthropic";
728
+ }
729
+ content.push(block);
730
+ } else if (b.type === "redacted_thinking") content.push({
731
+ type: "redacted_thinking",
732
+ data: b.data ?? ""
733
+ });
734
+ else if (b.type === "server_tool_use") content.push({
735
+ type: "server_tool_use",
736
+ producer: "anthropic",
737
+ id: b.id ?? "",
738
+ name: b.name ?? "",
739
+ input: b.input ?? {}
740
+ });
741
+ else if (b.type === "web_search_tool_result") content.push({
742
+ type: "server_tool_result",
743
+ producer: "anthropic",
744
+ toolUseId: b.tool_use_id ?? "",
745
+ toolName: "web_search",
746
+ content: b.content
793
747
  });
794
- return;
795
748
  }
796
- out.push({
797
- ...msg,
798
- content: dedupedContent
799
- });
749
+ return {
750
+ role,
751
+ content
752
+ };
800
753
  }
801
- /**
802
- * Process one user message: dedup mode-5 duplicate tool_results, strip
803
- * mode-3 orphans whose tool_call is missing from the previously-emitted
804
- * assistant message, and prepend any synthetic results queued by the prior
805
- * assistant's mode-1 repair.
806
- */
807
- function processUserMessage(args) {
808
- const { index, msg, pendingOrphansByUserIndex, out, fireRepair, markChanged } = args;
809
- const queuedOrphans = pendingOrphansByUserIndex.get(index);
810
- pendingOrphansByUserIndex.delete(index);
811
- const prev = out.length > 0 ? out[out.length - 1] : null;
812
- const validIds = prev && prev.role === "assistant" ? collectIds(prev.content, "tool_call", (b) => b.id) : /* @__PURE__ */ new Set();
813
- const seenCallIds = /* @__PURE__ */ new Set();
814
- let modifiedHere = false;
815
- const afterDedup = [];
816
- for (const block of msg.content) {
817
- if (block.type !== "tool_result") {
818
- afterDedup.push(block);
819
- continue;
820
- }
821
- if (seenCallIds.has(block.callId)) {
822
- modifiedHere = true;
823
- fireRepair({
824
- mode: "duplicate-tool-result-strip",
825
- callId: block.callId,
826
- messageIndex: index
754
+ function fromOpenAI(msg) {
755
+ const role = msg.role;
756
+ const content = [];
757
+ const c = msg.content;
758
+ if (c == null) return {
759
+ role,
760
+ content
761
+ };
762
+ if (typeof c === "string") {
763
+ content.push({
764
+ type: "text",
765
+ text: c
766
+ });
767
+ return {
768
+ role,
769
+ content
770
+ };
771
+ }
772
+ if (typeof c === "object" && !Array.isArray(c) && c._tag === "__zidane_assistant_tc__") {
773
+ const tagged = c;
774
+ if (typeof tagged.text === "string" && tagged.text) content.push({
775
+ type: "text",
776
+ text: tagged.text
777
+ });
778
+ if (Array.isArray(tagged.tool_calls)) for (const raw of tagged.tool_calls) {
779
+ if (!raw || typeof raw !== "object") continue;
780
+ const tc = raw;
781
+ const rawArgs = tc.function?.arguments;
782
+ let input = {};
783
+ if (typeof rawArgs === "string") try {
784
+ input = JSON.parse(rawArgs);
785
+ } catch {
786
+ input = {};
787
+ }
788
+ else if (rawArgs && typeof rawArgs === "object") input = rawArgs;
789
+ content.push({
790
+ type: "tool_call",
791
+ id: tc.id ?? "",
792
+ name: tc.function?.name ?? "",
793
+ input
827
794
  });
828
- continue;
829
795
  }
830
- seenCallIds.add(block.callId);
831
- afterDedup.push(block);
796
+ return {
797
+ role,
798
+ content
799
+ };
832
800
  }
833
- const afterStrip = [];
834
- for (const block of afterDedup) {
835
- if (block.type === "tool_result" && !validIds.has(block.callId)) {
836
- modifiedHere = true;
837
- fireRepair({
838
- mode: "orphan-tool-result-strip",
839
- callId: block.callId,
840
- messageIndex: index
801
+ if (typeof c === "object" && !Array.isArray(c) && c._tag === "__zidane_tool_results__") {
802
+ const tagged = c;
803
+ if (Array.isArray(tagged.results)) for (const raw of tagged.results) {
804
+ if (!raw || typeof raw !== "object") continue;
805
+ const r = raw;
806
+ content.push({
807
+ type: "tool_result",
808
+ callId: r.tool_call_id ?? "",
809
+ output: r.content ?? ""
841
810
  });
842
- continue;
843
811
  }
844
- afterStrip.push(block);
845
- }
846
- const finalContent = queuedOrphans ? [...queuedOrphans.map(syntheticResultBlock), ...afterStrip] : afterStrip;
847
- if (!modifiedHere && queuedOrphans === void 0) {
848
- out.push(msg);
849
- return;
812
+ if (typeof tagged.text === "string" && tagged.text) content.push({
813
+ type: "text",
814
+ text: tagged.text
815
+ });
816
+ return {
817
+ role,
818
+ content
819
+ };
850
820
  }
851
- markChanged();
852
- if (finalContent.length === 0) {
853
- out.push({
854
- ...msg,
855
- content: [{
821
+ if (Array.isArray(c)) {
822
+ for (const block of c) {
823
+ if (!block || typeof block !== "object") continue;
824
+ const b = block;
825
+ if (b.type === "text") content.push({
856
826
  type: "text",
857
- text: ORPHANED_TOOL_RESULT_MARKER
858
- }]
859
- });
860
- return;
827
+ text: b.text
828
+ });
829
+ else if (b.type === "image_url") {
830
+ const imageUrl = b.image_url?.url;
831
+ if (imageUrl?.startsWith("data:")) {
832
+ const [meta, data] = imageUrl.slice(5).split(",", 2);
833
+ const mediaType = meta.replace(";base64", "");
834
+ content.push({
835
+ type: "image",
836
+ mediaType,
837
+ data
838
+ });
839
+ }
840
+ }
841
+ }
842
+ return {
843
+ role,
844
+ content
845
+ };
861
846
  }
862
- out.push({
863
- ...msg,
864
- content: finalContent
865
- });
847
+ return {
848
+ role,
849
+ content
850
+ };
866
851
  }
867
852
  /**
868
- * Re-mint tool-call ids that collide with ids already present in the
869
- * transcript (or with an earlier sibling in the same turn), BEFORE the turn
870
- * is persisted and its tools dispatched.
853
+ * Single source of truth for whether a signature-bearing reasoning block
854
+ * (`thinking` / `redacted_thinking`) may be forwarded to a provider. Both
855
+ * wire builders ({@link toAnthropic}, the OpenAI assistant-message shaper)
856
+ * and the host-facing {@link sanitizeHistoryForProvider} apply this same
857
+ * policy — keeping them one function means the sanitizer can never drift
858
+ * from what the wire actually sends.
871
859
  *
872
- * Why this exists: some providers/serving stacks emit non-globally-unique
873
- * tool_use ids (per-turn counters like `call_0`, retry-replay duplicates,
874
- * Kimi-K2.6 on Fireworks). The wire-time pairing pass
875
- * ({@link ensureToolResultPairing}) can only STRIP such duplicates — which
876
- * erases the model's tool calls and results from the wire view, stalls
877
- * progress, and re-accumulates repairs quadratically because the persisted
878
- * session keeps the corrupted ids. Re-minting at persist time keeps the
879
- * pairing intact instead: the call, its execution, and its tool_result all
880
- * carry the fresh id, so the pairing pass has nothing to strip on any later
881
- * turn.
860
+ * Rules, each driven by an API constraint hit in production:
882
861
  *
883
- * Fresh ids are `${oldId}_r{n}` with `n` bumped until unique — deterministic
884
- * shape, Anthropic-wire-safe charset (given a safe input id), and visibly
885
- * traceable back to the original.
862
+ * - target `anthropic`:
863
+ * - `redacted_thinking` → keep (native Anthropic block; must be echoed).
864
+ * - `thinking` tagged `openai` → drop (OpenAI `encrypted_content` is not
865
+ * a valid Anthropic HMAC; 400s).
866
+ * - unsigned `thinking` → drop (`ThinkingBlockParam` requires a
867
+ * signature; unsigned blocks come from non-signing producers).
868
+ * - signed but UNTAGGED `thinking` (pre-tagging sessions) → keep: it is
869
+ * most plausibly legacy Anthropic output, and the signature-400
870
+ * recovery converges if it is not.
886
871
  *
887
- * Alignment contract: `toolCalls` and the `tool_call` blocks inside
888
- * `content` are two views of the same emission and MUST be in the same
889
- * order (true for every provider — both are built from one source array).
890
- * Occurrences are matched positionally per old id so intra-turn duplicates
891
- * map to distinct fresh ids on both views.
872
+ * - target `openai`:
873
+ * - `redacted_thinking` → drop (Anthropic-only block type).
874
+ * - `thinking` tagged `anthropic` → drop (an Anthropic HMAC is not
875
+ * valid `encrypted_content`).
876
+ * - signed but UNTAGGED `thinking` → strip-signature: unlike the
877
+ * Anthropic direction there is no signature-error recovery on this
878
+ * path, so forwarding a possibly-foreign signature risks an
879
+ * unrecoverable 400 — the reasoning text survives, only the opaque
880
+ * signature is withheld.
881
+ */
882
+ function reasoningWireAction(block, target) {
883
+ if (block.type === "redacted_thinking") return target === "anthropic" ? "keep" : "drop";
884
+ if (target === "anthropic") {
885
+ if (block.signatureProducer === "openai") return "drop";
886
+ if (!block.signature) return "drop";
887
+ return "keep";
888
+ }
889
+ if (block.signatureProducer === "anthropic") return "drop";
890
+ if (block.signature && block.signatureProducer !== "openai") return "strip-signature";
891
+ return "keep";
892
+ }
893
+ /**
894
+ * Host-facing history sanitizer — applies {@link reasoningWireAction} to a
895
+ * turn set BEFORE it is handed to an agent targeting `target`. The in-loop
896
+ * wire builders already enforce the same policy per call, so this is for
897
+ * hosts that seed history from an external durable store (or hydrate
898
+ * pre-provenance-tagging sessions) and want the poison removed from their
899
+ * source of truth in one call instead of re-deriving block semantics.
892
900
  *
893
- * Pure: returns the input references unchanged when no id collides.
901
+ * Returns new turn objects only where something changed (unchanged turns
902
+ * pass through by reference). A turn whose content empties keeps the same
903
+ * neutral marker the wire repair uses, so role boundaries stay intact.
894
904
  */
895
- function remintDuplicateToolCallIds(toolCalls, content, priorIds) {
896
- const seen = new Set(priorIds);
897
- const reminted = [];
898
- const finalIdQueues = /* @__PURE__ */ new Map();
899
- let changed = false;
900
- const outCalls = toolCalls.map((tc) => {
901
- let finalId = tc.id;
902
- if (seen.has(tc.id)) {
903
- let n = 1;
904
- while (seen.has(`${tc.id}_r${n}`)) n++;
905
- finalId = `${tc.id}_r${n}`;
906
- reminted.push({
907
- oldId: tc.id,
908
- newId: finalId
909
- });
910
- changed = true;
905
+ function sanitizeHistoryForProvider(turns, target) {
906
+ let removed = 0;
907
+ let stripped = 0;
908
+ return {
909
+ turns: turns.map((turn) => {
910
+ let changed = false;
911
+ const content = [];
912
+ for (const block of turn.content) {
913
+ if (block.type !== "thinking" && block.type !== "redacted_thinking") {
914
+ content.push(block);
915
+ continue;
916
+ }
917
+ const action = reasoningWireAction(block, target);
918
+ if (action === "drop") {
919
+ removed += 1;
920
+ changed = true;
921
+ continue;
922
+ }
923
+ if (action === "strip-signature" && block.type === "thinking") {
924
+ const { signature: _sig, signatureProducer: _producer, ...rest } = block;
925
+ content.push(rest);
926
+ stripped += 1;
927
+ changed = true;
928
+ continue;
929
+ }
930
+ content.push(block);
931
+ }
932
+ if (!changed) return turn;
933
+ return {
934
+ ...turn,
935
+ content: content.length > 0 ? content : [{
936
+ type: "text",
937
+ text: "[historical reasoning omitted]"
938
+ }]
939
+ };
940
+ }),
941
+ removed,
942
+ stripped
943
+ };
944
+ }
945
+ function toAnthropic(msg) {
946
+ const nonEmpty = msg.content.filter((b) => {
947
+ if (b.type === "provider_reasoning") return false;
948
+ if (b.type !== "thinking" && b.type !== "redacted_thinking") return true;
949
+ return reasoningWireAction(b, "anthropic") === "keep";
950
+ }).flatMap((block) => {
951
+ switch (block.type) {
952
+ case "text": return [{
953
+ type: "text",
954
+ text: block.text
955
+ }];
956
+ case "image":
957
+ assertResolvedMediaBlock(block, "Anthropic wire messages");
958
+ return [{
959
+ type: "image",
960
+ source: anthropicImageSource(block.mediaType, block.data)
961
+ }];
962
+ case "audio": return [{
963
+ type: "text",
964
+ text: `[audio omitted: ${block.mediaType}]`
965
+ }];
966
+ case "video": return [{
967
+ type: "text",
968
+ text: `[video omitted: ${block.mediaType}]`
969
+ }];
970
+ case "document":
971
+ assertResolvedMediaBlock(block, "Anthropic wire messages");
972
+ return anthropicDocumentBlocks(block);
973
+ case "tool_call": return [{
974
+ type: "tool_use",
975
+ id: sanitizeAnthropicToolId(block.id),
976
+ name: block.name,
977
+ input: block.input
978
+ }];
979
+ case "tool_result": {
980
+ const out = {
981
+ type: "tool_result",
982
+ tool_use_id: sanitizeAnthropicToolId(block.callId),
983
+ content: encodeAnthropicToolResultContent(block.output)
984
+ };
985
+ if (block.isError) out.is_error = true;
986
+ return [out, ...(typeof block.output === "string" ? [] : block.output.filter((part) => part.type === "document" && typeof part.data === "string")).flatMap(anthropicDocumentBlocks)];
987
+ }
988
+ case "thinking": return [{
989
+ type: "thinking",
990
+ thinking: block.text,
991
+ signature: block.signature ?? ""
992
+ }];
993
+ case "redacted_thinking": return [{
994
+ type: "redacted_thinking",
995
+ data: block.data
996
+ }];
997
+ case "server_tool_use": return [{
998
+ type: "server_tool_use",
999
+ id: sanitizeAnthropicToolId(block.id),
1000
+ name: block.name,
1001
+ input: block.input
1002
+ }];
1003
+ case "server_tool_result": return [{
1004
+ type: "web_search_tool_result",
1005
+ tool_use_id: sanitizeAnthropicToolId(block.toolUseId),
1006
+ content: block.content
1007
+ }];
1008
+ default: return [{
1009
+ type: "text",
1010
+ text: ""
1011
+ }];
911
1012
  }
912
- seen.add(finalId);
913
- const queue = finalIdQueues.get(tc.id);
914
- if (queue) queue.push(finalId);
915
- else finalIdQueues.set(tc.id, [finalId]);
916
- return finalId === tc.id ? tc : {
917
- ...tc,
918
- id: finalId
919
- };
920
- });
921
- if (!changed) return {
922
- toolCalls,
923
- content,
924
- reminted
1013
+ }).filter((b) => !(b.type === "text" && b.text.length === 0));
1014
+ if (nonEmpty.length === 0) return {
1015
+ role: msg.role,
1016
+ content: ""
1017
+ };
1018
+ const first = nonEmpty[0];
1019
+ if (nonEmpty.length === 1 && first.type === "text") return {
1020
+ role: msg.role,
1021
+ content: first.text
925
1022
  };
926
1023
  return {
927
- toolCalls: outCalls,
928
- content: content.map((block) => {
929
- if (block.type !== "tool_call") return block;
930
- const finalId = finalIdQueues.get(block.id)?.shift();
931
- if (finalId === void 0 || finalId === block.id) return block;
932
- return {
933
- ...block,
934
- id: finalId
935
- };
936
- }),
937
- reminted
1024
+ role: msg.role,
1025
+ content: nonEmpty
938
1026
  };
939
1027
  }
940
- /**
941
- * Drop-only normalization of persisted history: delete structurally invalid
942
- * tool blocks so the transcript never re-triggers wire-time pairing repairs
943
- * turn after turn.
944
- *
945
- * Unlike {@link ensureToolResultPairing} — which SYNTHESIZES placeholder
946
- * content and must therefore stay wire-only (fabricated results would poison
947
- * persisted history and training data) — this pass performs pure deletions of
948
- * blocks that are provably invalid regardless of interpretation:
949
- *
950
- * | Corruption | Action |
951
- * |------------|--------|
952
- * | duplicate `tool_call` id within one run lane (across or within turns) | drop the later instance |
953
- * | duplicate `tool_result.callId` within one turn | drop the later instance |
954
- * | `tool_result` with no matching `tool_call` in the immediately preceding same-lane assistant turn | drop the block |
955
- * | turn emptied by the drops above (reasoning-only remainders count as empty) | drop the whole turn |
956
- *
957
- * Duplicate detection is scoped to the WIRE-VIEW lane: tool_call ids only
958
- * collide when both instances reach one model request. By default a turn's
959
- * lane is its raw `runId` — correct for parent/subagent interleaving, where
960
- * providers minting per-conversation counter ids (`call_0`, `call_1`)
961
- * legitimately reuse the same id across lanes. Callers that know the run
962
- * topology (see `createSession`) pass `laneForRun` to widen lanes to true
963
- * wire views: all depth-0 runs share the main conversation view, and a
964
- * resumed subagent shares its resume-chain ancestors' view — duplicates
965
- * across those runIds DO reach a single request (wire-time
966
- * `ensureToolResultPairing` mode 4 would strip them on every send) and are
967
- * healed here.
968
- *
969
- * Orphan `tool_use` blocks (a call whose result was never persisted —
970
- * interrupted run) are deliberately KEPT: they are authentic model output,
971
- * and the wire-time pass patches them with a synthetic result on every send
972
- * without touching history.
973
- *
974
- * Deletions cascade by design: dropping a duplicate `tool_call` orphans its
975
- * `tool_result`, which is then dropped too; a turn emptied by either is
976
- * removed entirely.
977
- *
978
- * Safe to persist: deterministic, idempotent (re-running on healed output is
979
- * a no-op returning the same reference), and fabricates nothing.
980
- *
981
- * Pure: does not mutate input turns or their content arrays.
982
- */
983
- function dropInvalidToolPairings(turns, options = {}) {
984
- if (turns.length === 0) return turns;
985
- const onDrop = options.onDrop ?? (() => {});
986
- const laneForRun = options.laneForRun ?? ((runId) => runId ?? "");
987
- const laneOf = (turn) => laneForRun(turn.runId);
988
- const laneKey = (turn, id) => `${laneOf(turn)}\u0000${id}`;
989
- const firstSeenAt = /* @__PURE__ */ new Map();
990
- for (let i = 0; i < turns.length; i++) {
991
- const turn = turns[i];
992
- if (turn.role !== "assistant") continue;
993
- for (const block of turn.content) if (block.type === "tool_call" && !firstSeenAt.has(laneKey(turn, block.id))) firstSeenAt.set(laneKey(turn, block.id), i);
1028
+ function toOpenAI(msg) {
1029
+ const toolCalls = msg.content.filter((b) => b.type === "tool_call");
1030
+ const toolResults = msg.content.filter((b) => b.type === "tool_result");
1031
+ const textBlocks = msg.content.filter((b) => b.type === "text");
1032
+ const imageBlocks = msg.content.filter((b) => b.type === "image");
1033
+ const audioBlocks = msg.content.filter((b) => b.type === "audio");
1034
+ const videoBlocks = msg.content.filter((b) => b.type === "video");
1035
+ const documentBlocks = msg.content.filter((b) => b.type === "document");
1036
+ if (toolResults.length > 0) {
1037
+ const siblingText = textBlocks.map((b) => b.text).join("\n");
1038
+ return {
1039
+ role: msg.role,
1040
+ content: {
1041
+ _tag: TOOL_RESULTS_TAG,
1042
+ results: toolResults.map((tr) => ({
1043
+ tool_call_id: tr.callId,
1044
+ content: tr.output
1045
+ })),
1046
+ ...siblingText ? { text: siblingText } : {}
1047
+ }
1048
+ };
994
1049
  }
995
- let changed = false;
996
- const out = [];
997
- for (let i = 0; i < turns.length; i++) {
998
- const turn = turns[i];
999
- if (turn.role === "assistant") {
1000
- const kept = [];
1001
- const seenHere = /* @__PURE__ */ new Set();
1002
- let dropped = false;
1003
- for (const block of turn.content) {
1004
- if (block.type === "tool_call") {
1005
- if (firstSeenAt.get(laneKey(turn, block.id)) !== i || seenHere.has(block.id)) {
1006
- dropped = true;
1007
- onDrop({
1008
- kind: "duplicate-tool-call",
1009
- callId: block.id,
1010
- index: i
1011
- });
1012
- continue;
1050
+ if (toolCalls.length > 0) {
1051
+ const textContent = textBlocks.length > 0 ? textBlocks[0].text : null;
1052
+ return {
1053
+ role: msg.role,
1054
+ content: {
1055
+ _tag: ASSISTANT_TOOL_CALLS_TAG,
1056
+ text: textContent,
1057
+ tool_calls: toolCalls.map((tc) => ({
1058
+ id: tc.id,
1059
+ type: "function",
1060
+ function: {
1061
+ name: tc.name,
1062
+ arguments: JSON.stringify(tc.input)
1013
1063
  }
1014
- seenHere.add(block.id);
1015
- }
1016
- kept.push(block);
1017
- }
1018
- if (!dropped) {
1019
- out.push(turn);
1020
- continue;
1021
- }
1022
- changed = true;
1023
- const onlyReasoning = kept.every((b) => b.type === "thinking" || b.type === "redacted_thinking" || b.type === "provider_reasoning");
1024
- if (kept.length === 0 || onlyReasoning) {
1025
- onDrop({
1026
- kind: "empty-turn",
1027
- index: i
1028
- });
1029
- continue;
1064
+ }))
1030
1065
  }
1031
- out.push({
1032
- ...turn,
1033
- content: kept
1066
+ };
1067
+ }
1068
+ if (imageBlocks.length > 0 || audioBlocks.length > 0 || videoBlocks.length > 0 || documentBlocks.length > 0) {
1069
+ const parts = [];
1070
+ for (const img of imageBlocks) {
1071
+ assertResolvedMediaBlock(img, "OpenAI-compatible wire messages");
1072
+ parts.push({
1073
+ type: "image_url",
1074
+ image_url: { url: `data:${img.mediaType};base64,${img.data}` }
1034
1075
  });
1035
- continue;
1036
- }
1037
- if (turn.role !== "user") {
1038
- out.push(turn);
1039
- continue;
1040
- }
1041
- const lane = laneOf(turn);
1042
- let prev = null;
1043
- for (let j = out.length - 1; j >= 0; j--) {
1044
- const candidate = out[j];
1045
- if (candidate.role === "system" || candidate.role === "command") continue;
1046
- if (laneOf(candidate) !== lane) continue;
1047
- prev = candidate;
1048
- break;
1049
1076
  }
1050
- const validIds = prev && prev.role === "assistant" ? collectIds(prev.content, "tool_call", (b) => b.id) : /* @__PURE__ */ new Set();
1051
- const kept = [];
1052
- const seenCallIds = /* @__PURE__ */ new Set();
1053
- let dropped = false;
1054
- for (const block of turn.content) {
1055
- if (block.type !== "tool_result") {
1056
- kept.push(block);
1057
- continue;
1058
- }
1059
- if (seenCallIds.has(block.callId)) {
1060
- dropped = true;
1061
- onDrop({
1062
- kind: "duplicate-tool-result",
1063
- callId: block.callId,
1064
- index: i
1065
- });
1066
- continue;
1067
- }
1068
- seenCallIds.add(block.callId);
1069
- if (!validIds.has(block.callId)) {
1070
- dropped = true;
1071
- onDrop({
1072
- kind: "orphan-tool-result",
1073
- callId: block.callId,
1074
- index: i
1075
- });
1076
- continue;
1077
+ for (const b of audioBlocks) parts.push({
1078
+ type: "input_audio",
1079
+ input_audio: {
1080
+ data: b.data,
1081
+ format: audioFormatFromMediaType(b.mediaType)
1077
1082
  }
1078
- kept.push(block);
1079
- }
1080
- if (!dropped) {
1081
- out.push(turn);
1082
- continue;
1083
- }
1084
- changed = true;
1085
- if (kept.length === 0) {
1086
- onDrop({
1087
- kind: "empty-turn",
1088
- index: i
1083
+ });
1084
+ for (const b of videoBlocks) parts.push({
1085
+ type: "video_url",
1086
+ video_url: { url: `data:${b.mediaType};base64,${b.data}` }
1087
+ });
1088
+ for (const b of documentBlocks) {
1089
+ assertResolvedMediaBlock(b, "OpenAI-compatible wire messages");
1090
+ parts.push({
1091
+ type: "text",
1092
+ text: documentMarker(b)
1089
1093
  });
1090
- continue;
1091
1094
  }
1092
- out.push({
1093
- ...turn,
1094
- content: kept
1095
+ for (const b of textBlocks) parts.push({
1096
+ type: "text",
1097
+ text: b.text
1095
1098
  });
1099
+ return {
1100
+ role: msg.role,
1101
+ content: parts
1102
+ };
1096
1103
  }
1097
- return changed ? out : turns;
1098
- }
1099
- function syntheticResultBlock(callId) {
1104
+ if (textBlocks.length === 1) return {
1105
+ role: msg.role,
1106
+ content: textBlocks[0].text
1107
+ };
1108
+ if (textBlocks.length > 1) return {
1109
+ role: msg.role,
1110
+ content: textBlocks.map((b) => ({
1111
+ type: "text",
1112
+ text: b.text
1113
+ }))
1114
+ };
1100
1115
  return {
1101
- type: "tool_result",
1102
- callId,
1103
- output: SYNTHETIC_TOOL_RESULT_PLACEHOLDER,
1104
- isError: true
1116
+ role: msg.role,
1117
+ content: null
1105
1118
  };
1106
1119
  }
1107
- function collectResultIds(content) {
1108
- const ids = /* @__PURE__ */ new Set();
1109
- for (const block of content) if (block.type === "tool_result") ids.add(block.callId);
1110
- return ids;
1111
- }
1112
- function collectIds(content, type, getId) {
1113
- const ids = /* @__PURE__ */ new Set();
1114
- for (const block of content) if (block.type === type) ids.add(getId(block));
1115
- return ids;
1116
- }
1117
1120
  /**
1118
1121
  * Drop ASSISTANT turns whose every `tool_call` block is unresolved
1119
1122
  * (no matching `tool_result` block anywhere later in the transcript).
@@ -1405,6 +1408,6 @@ function autoDetectAndConvert(msg) {
1405
1408
  return fromAnthropic(msg);
1406
1409
  }
1407
1410
  //#endregion
1408
- export { toOpenAI as _, detectTurnInterruption as a, ensureToolResultPairing as c, fromOpenAI as d, reasoningWireAction as f, toAnthropic as g, sanitizeHistoryForProvider as h, autoDetectAndConvert as i, filterUnresolvedToolUses as l, resolveContentRefsInMessages as m, SYNTHETIC_TOOL_RESULT_PLACEHOLDER as n, dropInvalidToolPairings as o, remintDuplicateToolCallIds as p, TOOL_USE_INTERRUPTED_MARKER as r, ensureEndsWithUserMessage as s, ORPHANED_TOOL_RESULT_MARKER as t, fromAnthropic as u, toWireMessages as v };
1411
+ export { ensureToolResultPairing as _, fromAnthropic as a, resolveContentRefsInMessages as c, toOpenAI as d, toWireMessages as f, dropInvalidToolPairings as g, TOOL_USE_INTERRUPTED_MARKER as h, filterUnresolvedToolUses as i, sanitizeHistoryForProvider as l, SYNTHETIC_TOOL_RESULT_PLACEHOLDER as m, detectTurnInterruption as n, fromOpenAI as o, ORPHANED_TOOL_RESULT_MARKER as p, ensureEndsWithUserMessage as r, reasoningWireAction as s, autoDetectAndConvert as t, toAnthropic as u, remintDuplicateToolCallIds as v };
1409
1412
 
1410
- //# sourceMappingURL=messages-2jcOA_5R.js.map
1413
+ //# sourceMappingURL=messages-BqiwcD2Q.js.map