zidane 9.1.1 → 9.1.3

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 (279) hide show
  1. package/dist/{acp-Di9WvMDF.js → acp-CeCavNlg.js} +7 -7
  2. package/dist/{acp-Di9WvMDF.js.map → acp-CeCavNlg.js.map} +1 -1
  3. package/dist/acp-cli.js +5 -5
  4. package/dist/acp.d.ts +2 -2
  5. package/dist/acp.js +1 -1
  6. package/dist/{agent-DJhpTS-B.js → agent-Dh_XZ7fj.js} +74 -19
  7. package/dist/agent-Dh_XZ7fj.js.map +1 -0
  8. package/dist/agent.d.ts +1 -1
  9. package/dist/agent.js +1 -1
  10. package/dist/{auth-store-port-DFgf22oo.js → auth-store-port-BKpjlJ8a.js} +2 -2
  11. package/dist/{auth-store-port-DFgf22oo.js.map → auth-store-port-BKpjlJ8a.js.map} +1 -1
  12. package/dist/{auto-update-restart-BFB7Ek87.js → auto-update-restart-CQJtndMG.js} +277 -41
  13. package/dist/auto-update-restart-CQJtndMG.js.map +1 -0
  14. package/dist/{browser-BcoCCB_n.js → browser-BkdeKnxO.js} +56 -2
  15. package/dist/browser-BkdeKnxO.js.map +1 -0
  16. package/dist/{browser-entry-ypi8CyE-.d.ts → browser-entry-BCwruqNa.d.ts} +10 -10
  17. package/dist/{browser-entry-ypi8CyE-.d.ts.map → browser-entry-BCwruqNa.d.ts.map} +1 -1
  18. package/dist/channels.d.ts +5 -5
  19. package/dist/chat/browser.d.ts +4 -4
  20. package/dist/chat/browser.js +6 -6
  21. package/dist/chat/pure.d.ts +5 -5
  22. package/dist/chat/pure.js +8 -8
  23. package/dist/chat.d.ts +5 -5
  24. package/dist/chat.js +55 -55
  25. package/dist/chat.js.map +1 -1
  26. package/dist/{cli-shared-CabruH8Z.js → cli-shared-DeRHgYvl.js} +4 -4
  27. package/dist/{cli-shared-CabruH8Z.js.map → cli-shared-DeRHgYvl.js.map} +1 -1
  28. package/dist/cli-style-DBEm6CyT.js +105 -0
  29. package/dist/cli-style-DBEm6CyT.js.map +1 -0
  30. package/dist/{client-core-CIixwznM.d.ts → client-core-C_4m_oL6.d.ts} +4 -4
  31. package/dist/{client-core-CIixwznM.d.ts.map → client-core-C_4m_oL6.d.ts.map} +1 -1
  32. package/dist/{client-CCaIXWue.d.ts → client-ygTASGis.d.ts} +2 -2
  33. package/dist/{client-CCaIXWue.d.ts.map → client-ygTASGis.d.ts.map} +1 -1
  34. package/dist/codemode-worker.js +8 -5
  35. package/dist/codemode-worker.js.map +1 -1
  36. package/dist/codemode.d.ts +16 -0
  37. package/dist/codemode.d.ts.map +1 -1
  38. package/dist/codemode.js +2 -2
  39. package/dist/{connection-loop-QmWL-fJf.d.ts → connection-loop-D_pRONCr.d.ts} +4 -4
  40. package/dist/{connection-loop-QmWL-fJf.d.ts.map → connection-loop-D_pRONCr.d.ts.map} +1 -1
  41. package/dist/contexts/daytona.d.ts +3 -3
  42. package/dist/contexts/docker.d.ts +1 -1
  43. package/dist/contexts/e2b.d.ts +2 -2
  44. package/dist/contexts/sandbox.d.ts +1 -1
  45. package/dist/{contexts-C72b0mMa.js → contexts-DLZTSRHU.js} +17 -106
  46. package/dist/contexts-DLZTSRHU.js.map +1 -0
  47. package/dist/contexts.d.ts +4 -4
  48. package/dist/contexts.js +2 -2
  49. package/dist/{credentials-wwETW8rI.js → credentials-Bqg-qNBM.js} +2 -2
  50. package/dist/{credentials-wwETW8rI.js.map → credentials-Bqg-qNBM.js.map} +1 -1
  51. package/dist/{cwd-picker-CcekODCt.js → cwd-picker-BsHA6HKv.js} +4 -4
  52. package/dist/{cwd-picker-CcekODCt.js.map → cwd-picker-BsHA6HKv.js.map} +1 -1
  53. package/dist/daemon-cli.js +4 -3
  54. package/dist/daemon-cli.js.map +1 -1
  55. package/dist/{decisions-CXmK0nz9.js → decisions-hsw5XS2g.js} +3 -1
  56. package/dist/decisions-hsw5XS2g.js.map +1 -0
  57. package/dist/{discovery-DycXFxdA.js → discovery-CX7nbMCn.js} +4 -4
  58. package/dist/{discovery-DycXFxdA.js.map → discovery-CX7nbMCn.js.map} +1 -1
  59. package/dist/durable/sqlite.d.ts +2 -2
  60. package/dist/durable.d.ts +1 -1
  61. package/dist/{engine-VQ86XGdG.js → engine-CxBGgU94.js} +6 -6
  62. package/dist/{engine-VQ86XGdG.js.map → engine-CxBGgU94.js.map} +1 -1
  63. package/dist/{eval-Bod0I1W_.js → eval-CfGUDAkN.js} +3 -3
  64. package/dist/{eval-Bod0I1W_.js.map → eval-CfGUDAkN.js.map} +1 -1
  65. package/dist/eval.d.ts +1 -1
  66. package/dist/eval.js +1 -1
  67. package/dist/{executor-C7RQrkEq.js → executor-j_LFRBwS.js} +4 -4
  68. package/dist/{executor-C7RQrkEq.js.map → executor-j_LFRBwS.js.map} +1 -1
  69. package/dist/{extension-mark-BqKdEZwF.js → extension-mark-_rhkDWL5.js} +9 -6
  70. package/dist/extension-mark-_rhkDWL5.js.map +1 -0
  71. package/dist/extensions/testing.d.ts +1 -1
  72. package/dist/extensions/testing.js +2 -2
  73. package/dist/{extensions-BCx4QuGB.js → extensions-ADlhBQ_1.js} +8 -8
  74. package/dist/{extensions-BCx4QuGB.js.map → extensions-ADlhBQ_1.js.map} +1 -1
  75. package/dist/{extensions-client-D8tEAcMw.d.ts → extensions-client-D4Q2XRKV.d.ts} +31 -5
  76. package/dist/extensions-client-D4Q2XRKV.d.ts.map +1 -0
  77. package/dist/extensions.d.ts +1 -1
  78. package/dist/extensions.js +6 -6
  79. package/dist/{fold-rules-DvLK8pbf.d.ts → fold-rules-1DpDwG9h.d.ts} +2 -2
  80. package/dist/{fold-rules-DvLK8pbf.d.ts.map → fold-rules-1DpDwG9h.d.ts.map} +1 -1
  81. package/dist/{google-Crva32sk.js → google-B-Dhmzzf.js} +2 -2
  82. package/dist/{google-Crva32sk.js.map → google-B-Dhmzzf.js.map} +1 -1
  83. package/dist/{headless-CCm-jLLT.js → headless-CQm19BL0.js} +4 -4
  84. package/dist/{headless-CCm-jLLT.js.map → headless-CQm19BL0.js.map} +1 -1
  85. package/dist/headless.d.ts +1 -1
  86. package/dist/headless.js +1 -1
  87. package/dist/history-worker.js +1 -1
  88. package/dist/{host-wO_6GW0y.js → host-C61ntFBC.js} +276 -103
  89. package/dist/host-C61ntFBC.js.map +1 -0
  90. package/dist/{host-BHMs9jsN.js → host-DEoEzQal.js} +13 -4
  91. package/dist/host-DEoEzQal.js.map +1 -0
  92. package/dist/{host-scoped-settings-CrbcqOkP.js → host-scoped-settings-WBYaCork.js} +2 -2
  93. package/dist/{host-scoped-settings-CrbcqOkP.js.map → host-scoped-settings-WBYaCork.js.map} +1 -1
  94. package/dist/{host-signals-Jsjd_-4g.js → host-signals-Cb2TlGtN.js} +5 -5
  95. package/dist/{host-signals-Jsjd_-4g.js.map → host-signals-Cb2TlGtN.js.map} +1 -1
  96. package/dist/{host-version-CNAr9pov.js → host-version-BIx4CuBM.js} +2 -2
  97. package/dist/{host-version-CNAr9pov.js.map → host-version-BIx4CuBM.js.map} +1 -1
  98. package/dist/{index-C6nnKu32.d.ts → index-B0PLo9jn.d.ts} +4 -4
  99. package/dist/{index-C6nnKu32.d.ts.map → index-B0PLo9jn.d.ts.map} +1 -1
  100. package/dist/{index-Djnc8Peh.d.ts → index-De4x-s76.d.ts} +14 -14
  101. package/dist/{index-Djnc8Peh.d.ts.map → index-De4x-s76.d.ts.map} +1 -1
  102. package/dist/{index-CGbOWa7y.d.ts → index-WlE93Q6d.d.ts} +411 -3
  103. package/dist/index-WlE93Q6d.d.ts.map +1 -0
  104. package/dist/{index-CCf_WeTQ.d.ts → index-f-XWIoYo.d.ts} +2 -2
  105. package/dist/{index-CCf_WeTQ.d.ts.map → index-f-XWIoYo.d.ts.map} +1 -1
  106. package/dist/index.d.ts +5 -5
  107. package/dist/index.js +17 -17
  108. package/dist/ipc/browser-types.d.ts +304 -7
  109. package/dist/ipc/browser-types.d.ts.map +1 -1
  110. package/dist/ipc/browser.d.ts +4 -4
  111. package/dist/ipc/browser.js +2 -2
  112. package/dist/ipc/remote.d.ts +6 -6
  113. package/dist/ipc/remote.js +3 -3
  114. package/dist/{ipc-BzWhIybr.js → ipc-DiH_P-wr.js} +4 -4
  115. package/dist/{ipc-BzWhIybr.js.map → ipc-DiH_P-wr.js.map} +1 -1
  116. package/dist/ipc.d.ts +10 -10
  117. package/dist/ipc.js +8 -8
  118. package/dist/{json-Dj-YATkZ.js → json-BjClr43H.js} +2 -20
  119. package/dist/json-BjClr43H.js.map +1 -0
  120. package/dist/{links-CJG_rFvf.js → links-BbC_lOnn.js} +9 -9
  121. package/dist/{links-CJG_rFvf.js.map → links-BbC_lOnn.js.map} +1 -1
  122. package/dist/{loaded-entry-DTURJo_R.js → loaded-entry-C0vkCVNQ.js} +2 -2
  123. package/dist/{loaded-entry-DTURJo_R.js.map → loaded-entry-C0vkCVNQ.js.map} +1 -1
  124. package/dist/mcp.d.ts +1 -1
  125. package/dist/{media-type-BtA0cbfI.d.ts → media-type-ZSmAGHvg.d.ts} +188 -7
  126. package/dist/media-type-ZSmAGHvg.d.ts.map +1 -0
  127. package/dist/{memory-DuVGrzbl.js → memory-C3w8nO8f.js} +132 -31
  128. package/dist/memory-C3w8nO8f.js.map +1 -0
  129. package/dist/model-test-CAtJ76kM.js.map +1 -1
  130. package/dist/{move-session-kylExJ5H.js → move-session-D7ILECs-.js} +4 -4
  131. package/dist/{move-session-kylExJ5H.js.map → move-session-D7ILECs-.js.map} +1 -1
  132. package/dist/{nested-call-record-DlzPeTB7.js → nested-call-record-OayszZBQ.js} +4 -3
  133. package/dist/nested-call-record-OayszZBQ.js.map +1 -0
  134. package/dist/output/stream-json.d.ts +1 -1
  135. package/dist/output/terminal.d.ts +1 -1
  136. package/dist/{path-display-COVV71Yu.js → path-display-DRAEpKOU.js} +3 -3
  137. package/dist/{path-display-COVV71Yu.js.map → path-display-DRAEpKOU.js.map} +1 -1
  138. package/dist/plugins.d.ts +1 -1
  139. package/dist/{prelude-CVKoUzV8.js → prelude-BwiSR1uA.js} +16 -5
  140. package/dist/prelude-BwiSR1uA.js.map +1 -0
  141. package/dist/{presets-CBt6rFfS.js → presets-CMY43nUP.js} +3 -3
  142. package/dist/{presets-CBt6rFfS.js.map → presets-CMY43nUP.js.map} +1 -1
  143. package/dist/presets.d.ts +1 -1
  144. package/dist/presets.js +1 -1
  145. package/dist/{protocol-CEVqBO-q.d.ts → protocol-C2lqKuMd.d.ts} +50 -7
  146. package/dist/protocol-C2lqKuMd.d.ts.map +1 -0
  147. package/dist/protocol-DOGyHgLQ.js.map +1 -1
  148. package/dist/protocol-NSllElLw.js.map +1 -1
  149. package/dist/providers/anthropic.d.ts +1 -1
  150. package/dist/providers/arcee.d.ts +1 -1
  151. package/dist/providers/baseten.d.ts +1 -1
  152. package/dist/providers/cerebras.d.ts +1 -1
  153. package/dist/providers/google.d.ts +1 -1
  154. package/dist/providers/google.js +1 -1
  155. package/dist/providers/local.d.ts +1 -1
  156. package/dist/providers/neuralwatt.d.ts +1 -1
  157. package/dist/providers/openai-compat.d.ts +1 -1
  158. package/dist/providers/openai.d.ts +1 -1
  159. package/dist/providers/openrouter.d.ts +1 -1
  160. package/dist/providers/xai.d.ts +1 -1
  161. package/dist/{providers-Z0TaBp1O.js → providers-CpXV_uLE.js} +5 -5
  162. package/dist/providers-CpXV_uLE.js.map +1 -0
  163. package/dist/providers.d.ts +1 -1
  164. package/dist/providers.js +1 -1
  165. package/dist/{readonly-db-D7Z-k0pQ.js → readonly-db-DrbT8tX6.js} +2 -2
  166. package/dist/{readonly-db-D7Z-k0pQ.js.map → readonly-db-DrbT8tX6.js.map} +1 -1
  167. package/dist/{registry-BKjeBnoy.js → registry-CJHvmvaP.js} +2 -2
  168. package/dist/{registry-BKjeBnoy.js.map → registry-CJHvmvaP.js.map} +1 -1
  169. package/dist/{registry-B6Ww76iW.js → registry-CN6JlZKj.js} +2 -2
  170. package/dist/{registry-B6Ww76iW.js.map → registry-CN6JlZKj.js.map} +1 -1
  171. package/dist/{registry-B9INJwJR.js → registry-CnnKaY9s.js} +324 -39
  172. package/dist/registry-CnnKaY9s.js.map +1 -0
  173. package/dist/{remote-hosts-BMWoHCWO.js → remote-hosts-DoO9CJUQ.js} +4 -4
  174. package/dist/{remote-hosts-BMWoHCWO.js.map → remote-hosts-DoO9CJUQ.js.map} +1 -1
  175. package/dist/{repo-url-D4Dr8NfZ.js → repo-url-DLjjuVk_.js} +169 -2
  176. package/dist/repo-url-DLjjuVk_.js.map +1 -0
  177. package/dist/restate.d.ts +2 -2
  178. package/dist/{sandbox-BwiPGKF7.d.ts → sandbox-B4OW6oNx.d.ts} +2 -2
  179. package/dist/{sandbox-BwiPGKF7.d.ts.map → sandbox-B4OW6oNx.d.ts.map} +1 -1
  180. package/dist/script-progress-CrZ507Qq.js +900 -0
  181. package/dist/script-progress-CrZ507Qq.js.map +1 -0
  182. package/dist/session/sqlite.d.ts +1 -1
  183. package/dist/session.d.ts +1 -1
  184. package/dist/{setting-BQBSyUq0.js → setting-D_FpKFMw.js} +6 -6
  185. package/dist/{setting-BQBSyUq0.js.map → setting-D_FpKFMw.js.map} +1 -1
  186. package/dist/{settings-catalog-4IU2i_jG.js → settings-catalog-DEZlXP51.js} +14 -2
  187. package/dist/settings-catalog-DEZlXP51.js.map +1 -0
  188. package/dist/{settings-keys-iI_Y-7MC.js → settings-keys-CW2UgHgb.js} +3 -3
  189. package/dist/{settings-keys-iI_Y-7MC.js.map → settings-keys-CW2UgHgb.js.map} +1 -1
  190. package/dist/{skills-CFXRItJ6.js → skills-CEbosUlM.js} +2 -2
  191. package/dist/{skills-CFXRItJ6.js.map → skills-CEbosUlM.js.map} +1 -1
  192. package/dist/skills.d.ts +1 -1
  193. package/dist/skills.js +1 -1
  194. package/dist/{source-Bl1Z501r.js → source-3ZnEPOSa.js} +3 -2
  195. package/dist/source-3ZnEPOSa.js.map +1 -0
  196. package/dist/{sqlite-runtime-BZ2L4RaG.d.ts → sqlite-runtime-CS_LBaAN.d.ts} +2 -2
  197. package/dist/sqlite-runtime-CS_LBaAN.d.ts.map +1 -0
  198. package/dist/{src-BY_ND2xn.js → src-BmTXDta8.js} +2 -2
  199. package/dist/{src-BY_ND2xn.js.map → src-BmTXDta8.js.map} +1 -1
  200. package/dist/{store-C6gI7FfZ.js → store-ByCDaKgJ.js} +14 -3
  201. package/dist/store-ByCDaKgJ.js.map +1 -0
  202. package/dist/{system-freeze-m5o1sGIM.js → system-freeze-XgDPIwQS.js} +3 -3
  203. package/dist/{system-freeze-m5o1sGIM.js.map → system-freeze-XgDPIwQS.js.map} +1 -1
  204. package/dist/{todo-edits-CbukbjyV.js → todo-edits-Dyj32ds6.js} +4 -3
  205. package/dist/todo-edits-Dyj32ds6.js.map +1 -0
  206. package/dist/tools/fetch-url.d.ts +1 -1
  207. package/dist/tools/web-search.d.ts +1 -1
  208. package/dist/{tools-CVTGhggj.js → tools-BsrGQ8K4.js} +2 -2
  209. package/dist/{tools-CVTGhggj.js.map → tools-BsrGQ8K4.js.map} +1 -1
  210. package/dist/tools.d.ts +1 -1
  211. package/dist/tools.js +3 -3
  212. package/dist/{transcript-anchors-CiSi_UBn.js → transcript-anchors-DMjvyUjk.js} +3 -3
  213. package/dist/{transcript-anchors-CiSi_UBn.js.map → transcript-anchors-DMjvyUjk.js.map} +1 -1
  214. package/dist/{transcript-reconcile-jbSBjLq2.js → transcript-reconcile-DpQO85wl.js} +3 -3
  215. package/dist/{transcript-reconcile-jbSBjLq2.js.map → transcript-reconcile-DpQO85wl.js.map} +1 -1
  216. package/dist/tui.d.ts +9 -9
  217. package/dist/tui.d.ts.map +1 -1
  218. package/dist/tui.js +289 -38
  219. package/dist/tui.js.map +1 -1
  220. package/dist/{types-bDQagoiL.d.ts → types-2HrgolQz.d.ts} +20 -8
  221. package/dist/types-2HrgolQz.d.ts.map +1 -0
  222. package/dist/types-4q1bFG53.js +122 -0
  223. package/dist/types-4q1bFG53.js.map +1 -0
  224. package/dist/{types-DnRd9_cA.d.ts → types-SXqk6hLD.d.ts} +3 -3
  225. package/dist/{types-DnRd9_cA.d.ts.map → types-SXqk6hLD.d.ts.map} +1 -1
  226. package/dist/types-myxheQkw.js.map +1 -1
  227. package/dist/types.d.ts +3 -3
  228. package/dist/{usage-summary-BfcXjiSM.js → usage-summary-BM7B3JdI.js} +2 -2
  229. package/dist/{usage-summary-BfcXjiSM.js.map → usage-summary-BM7B3JdI.js.map} +1 -1
  230. package/dist/{version-DqqqvHDP.js → version-szdW1Od9.js} +2 -2
  231. package/dist/{version-DqqqvHDP.js.map → version-szdW1Od9.js.map} +1 -1
  232. package/dist/{wait-task-BX4NcOOm.js → wait-task-BiI5UaZQ.js} +13 -3
  233. package/dist/wait-task-BiI5UaZQ.js.map +1 -0
  234. package/dist/{wire-CLm2rMK0.js → wire-C3l9jl9x.js} +2 -2
  235. package/dist/{wire-CLm2rMK0.js.map → wire-C3l9jl9x.js.map} +1 -1
  236. package/dist/{write-file-0E6TUJ9A.js → write-file-Dquu7MzB.js} +5 -3
  237. package/dist/write-file-Dquu7MzB.js.map +1 -0
  238. package/dist/{ws-DfGGYQ4W.js → ws-CEHi0CiB.js} +4 -4
  239. package/dist/{ws-DfGGYQ4W.js.map → ws-CEHi0CiB.js.map} +1 -1
  240. package/dist/{ws-client-CFMbhsNq.js → ws-client-t8XNWxcq.js} +2 -2
  241. package/dist/{ws-client-CFMbhsNq.js.map → ws-client-t8XNWxcq.js.map} +1 -1
  242. package/docs/CHAT.md +55 -0
  243. package/docs/CODEMODE.md +37 -0
  244. package/docs/EXTENSIONS.md +8 -6
  245. package/docs/RELEASE-NOTES-NEXT.md +48 -1
  246. package/docs/RELEASE.md +9 -1
  247. package/package.json +2 -3
  248. package/dist/agent-DJhpTS-B.js.map +0 -1
  249. package/dist/auto-update-restart-BFB7Ek87.js.map +0 -1
  250. package/dist/browser-BcoCCB_n.js.map +0 -1
  251. package/dist/contexts-C72b0mMa.js.map +0 -1
  252. package/dist/decisions-CXmK0nz9.js.map +0 -1
  253. package/dist/extension-mark-BqKdEZwF.js.map +0 -1
  254. package/dist/extensions-client-D8tEAcMw.d.ts.map +0 -1
  255. package/dist/host-BHMs9jsN.js.map +0 -1
  256. package/dist/host-wO_6GW0y.js.map +0 -1
  257. package/dist/index-CGbOWa7y.d.ts.map +0 -1
  258. package/dist/json-Dj-YATkZ.js.map +0 -1
  259. package/dist/media-type-BtA0cbfI.d.ts.map +0 -1
  260. package/dist/memory-DuVGrzbl.js.map +0 -1
  261. package/dist/nested-call-events-BYk8XvGX.js +0 -182
  262. package/dist/nested-call-events-BYk8XvGX.js.map +0 -1
  263. package/dist/nested-call-record-DlzPeTB7.js.map +0 -1
  264. package/dist/prelude-CVKoUzV8.js.map +0 -1
  265. package/dist/protocol-CEVqBO-q.d.ts.map +0 -1
  266. package/dist/providers-Z0TaBp1O.js.map +0 -1
  267. package/dist/registry-B9INJwJR.js.map +0 -1
  268. package/dist/repo-url-D4Dr8NfZ.js.map +0 -1
  269. package/dist/settings-catalog-4IU2i_jG.js.map +0 -1
  270. package/dist/source-Bl1Z501r.js.map +0 -1
  271. package/dist/sqlite-runtime-BZ2L4RaG.d.ts.map +0 -1
  272. package/dist/store-C6gI7FfZ.js.map +0 -1
  273. package/dist/todo-edits-CbukbjyV.js.map +0 -1
  274. package/dist/types-DTMEhKJO.js +0 -14
  275. package/dist/types-DTMEhKJO.js.map +0 -1
  276. package/dist/types-bDQagoiL.d.ts.map +0 -1
  277. package/dist/wait-task-BX4NcOOm.js.map +0 -1
  278. package/dist/write-file-0E6TUJ9A.js.map +0 -1
  279. package/scripts/eval.ts +0 -179
@@ -1 +1 @@
1
- {"version":3,"file":"types-myxheQkw.js","names":[],"sources":["../src/types.ts"],"sourcesContent":["/**\n * Shared types for the agent system.\n */\n\nimport type { Provider, ToolCall, ToolResult } from './providers'\nimport type { ModelCatalog } from './providers/model-catalog'\nimport type { TextOrigin } from './text-origin'\nimport type { ThinkingLevel } from './thinking-levels'\nimport type { ToolDef } from './tools/types'\nimport { Buffer } from 'node:buffer'\n\nexport type { TextOrigin } from './text-origin'\n\n// ---------------------------------------------------------------------------\n// Thinking / Reasoning\n// ---------------------------------------------------------------------------\n\n/** Thinking / extended-reasoning levels: see `thinking-levels.ts` (dependency-free, for the browser-safe surfaces). */\nexport { isThinkingLevel, THINKING_LEVELS } from './thinking-levels'\nexport type { ThinkingLevel } from './thinking-levels'\n\n// ---------------------------------------------------------------------------\n// Clock / determinism seam\n// ---------------------------------------------------------------------------\n\n/**\n * Time + UUID source. Defaults to `Date.now()` and `crypto.randomUUID()`.\n *\n * Scoped to **journaled metadata** — every callsite that lands in\n * `SessionTurn.id`, `SessionTurn.createdAt`, `runId`, `turnId`, or hook\n * payloads consumers may persist. Live-only measurements (TTFT deltas,\n * `elapsed` counters) keep `Date.now()` directly so they reflect real\n * wall-clock progress.\n *\n * `now()` may return a `Promise<number>`: the loop awaits it at every\n * callsite. `randomUUID()` stays synchronous so a clock can provide\n * replay-stable identifiers without async metadata plumbing.\n *\n * Precedence: `AgentRunOptions.clock` > `AgentOptions.clock` >\n * {@link DEFAULT_AGENT_CLOCK}. A run on a replay-mode durable runtime\n * (`AgentOptions.durable`) takes its time and ids from the runtime instead,\n * so a replay regenerates byte-identical session metadata.\n */\nexport interface AgentClock {\n /**\n * Current wall-clock time in ms (epoch). May be async.\n */\n now: () => number | Promise<number>\n /**\n * RFC-4122 v4 UUID string. Always sync.\n */\n randomUUID: () => string\n}\n\n/** Native clock backing the default (Date.now + crypto.randomUUID). */\nexport const DEFAULT_AGENT_CLOCK: AgentClock = {\n now: () => Date.now(),\n randomUUID: () => crypto.randomUUID(),\n}\n\n// ---------------------------------------------------------------------------\n// MCP server configuration\n// ---------------------------------------------------------------------------\n\n/**\n * Slim shape of an upstream MCP tool descriptor — what `client.listTools()`\n * returns per entry. Exposed publicly so hosts can persist the schemas\n * between runs and feed them back via {@link McpServerConfig.cachedTools}\n * to skip the `tools/list` round-trip on subsequent bootstraps.\n */\nexport interface McpToolSchema {\n /** Tool name on the server, without the `mcp_<server>_` prefix. */\n name: string\n /** Human-readable description of the tool. */\n description?: string | null\n /** JSON Schema of the tool's input. */\n inputSchema?: unknown\n}\n\n/** {@link McpServerConfig.oauth}. */\nexport interface McpOAuthClientConfig {\n /** Client ID of the pre-registered OAuth app. `${VAR}` placeholders expand. */\n clientId: string\n /** Client secret. Leave it out of config files when possible. `${VAR}` placeholders expand. */\n clientSecret?: string\n /** Fixed loopback port for the OAuth redirect (`http://127.0.0.1:<port>/callback`). Random when omitted. */\n callbackPort?: number\n /** Space-separated scopes to request. */\n scope?: string\n}\n\n/** Configuration of one MCP server the agent connects to and exposes as tools. */\nexport interface McpServerConfig {\n /** Display name (used for tool namespacing) */\n name: string\n /** Transport type */\n transport: 'stdio' | 'sse' | 'streamable-http'\n /** For stdio: command to run */\n command?: string\n /** For stdio: command arguments */\n args?: string[]\n /**\n * For stdio: environment variables to pass to the server process.\n *\n * Merged on top of the MCP SDK's default inherited environment — a safety\n * whitelist (`PATH`, `HOME`, `LANG`, `SHELL`, `USER` on POSIX; `APPDATA`,\n * `PATH`, ... on Win32). Setting this to `{}` does not strip `PATH` from\n * the child process. Set {@link McpServerConfig.strictEnv} to `true` to\n * pass `env` verbatim with no inherited defaults.\n */\n env?: Record<string, string>\n /**\n * When true, {@link McpServerConfig.env} is passed verbatim to the spawned\n * process — the MCP SDK's default inherited environment (`PATH`, `HOME`, ...)\n * is NOT merged in. Most consumers should leave this off; the default merge\n * prevents `spawn ENOENT` when a stdio server declares an `env` without\n * restating `PATH`.\n */\n strictEnv?: boolean\n /**\n * For stdio: working directory of the spawned child process. Forwarded to\n * `StdioClientTransport`'s `cwd` (which is `spawn`'s `cwd`).\n *\n * When omitted, the child inherits the parent's cwd (`process.cwd()` at\n * spawn time). Set this when the server resolves paths relative to its\n * own cwd (e.g. an MCP server reading `db/migrations/...`)\n * and the project directory differs from where the agent was launched.\n *\n * Pass an absolute path. Relative values are forwarded as-is to the SDK,\n * which resolves them against the parent process's cwd.\n */\n cwd?: string\n /**\n * For stdio: WHERE the server process runs.\n *\n * - `'host'` (default) — spawned as a child of the agent process, on the\n * host filesystem, with the host's env and network.\n * - `'context'` — spawned inside the agent's `ExecutionContext`, via\n * {@link ExecutionContext.execStream}.\n *\n * Why this is opt-in rather than automatic: with a sandboxed context\n * (Daytona / E2B / Docker) a host-spawned server and the agent's own\n * tools see DIFFERENT filesystems, so a path the model discovered via\n * `list_files` doesn't exist for the server, and an MCP write lands\n * where `read_file` can't see it. `'context'` closes that gap — but it\n * is wrong for plenty of servers: a GitHub / Linear / Sentry MCP wants\n * the host's credentials and only talks to the network, and moving it\n * into the sandbox would break it for no benefit. Only servers that\n * touch the workspace (filesystem, git, build tooling) want `'context'`.\n *\n * Three consequences worth knowing before flipping this:\n * - The context's image must contain the server's runtime (`node`,\n * `uvx`, …). A missing binary surfaces as the server exiting\n * immediately at bootstrap.\n * - Environment: the server inherits the CONTEXT's environment with\n * `env` merged on top — NOT the host path's `getDefaultEnvironment()`\n * whitelist. On the default process context the context environment\n * IS the host's full `process.env`, so a context-scoped server sees\n * every host variable unless you set `strictEnv: true`, which limits\n * it to exactly `env` (via `env -i`, same semantics as on the host).\n * - This is filesystem COHERENCE, not isolation. Remote-transport MCP\n * servers can never be moved into the context, so the agent's reach\n * is still not bounded by the sandbox.\n *\n * Ignored for `sse` / `streamable-http` (nothing local to relocate).\n * Requires a context implementing `execStream`; anything else is a\n * bootstrap error rather than a silent fallback to `'host'`.\n */\n scope?: 'host' | 'context'\n /**\n * For a host-spawned stdio server run through an MCP server pool\n * (`ConnectMcpServersOptions.pool`): whether every session whose config\n * matches shares one process (`true`, the default), or each session gets\n * its own (`false`: the session and its subagents still share one). Set it\n * `false` for a server that keeps per-user state in its process (a browser\n * it drives, an open document). Ignored without a pool, for remote servers\n * and for `scope: 'context'`, which are never shared.\n */\n shared?: boolean\n /** For sse/streamable-http: server URL */\n url?: string\n /** Optional headers for HTTP transports */\n headers?: Record<string, string>\n /**\n * OAuth 2.1 authentication (sse / streamable-http only).\n *\n * - `'oauth'` — enables the SDK's OAuth flow with RFC 9728 protected-resource\n * metadata discovery, RFC 8414 / OIDC authorization-server metadata, RFC 7591\n * dynamic client registration, PKCE, and refresh-token rotation. Tokens persist\n * between runs via the host's credential store.\n * - `undefined` (default) — no OAuth. The host may still auto-promote a server\n * to OAuth on `UnauthorizedError` IF no static `Authorization` header is set\n * (the headers check stops us from second-guessing user-managed bearer tokens).\n *\n * Recognized aliases at parse time: Cursor's `authMethod: 'mcpOAuth'` maps to\n * `auth: 'oauth'` so `~/.cursor/mcp.json` pastes work unchanged.\n */\n auth?: 'oauth'\n /**\n * Pre-registered OAuth client, for auth servers without RFC 7591 dynamic\n * client registration (Slack's `mcp.slack.com`, most enterprise IdPs).\n * Setting it implies `auth: 'oauth'`; the SDK then skips registration and\n * uses this client for the authorization-code + refresh flows.\n *\n * - `clientId` — the app's client ID. `${VAR}` placeholders expand.\n * - `clientSecret` — optional; prefer leaving it out of the file: hosts\n * keep a secret entered in their UI in the MCP credential store instead.\n * `${VAR}` placeholders expand, so `\"${SLACK_CLIENT_SECRET}\"` works too.\n * - `callbackPort` — pins the loopback redirect to\n * `http://127.0.0.1:<port>/callback`, the URL to register with the\n * provider (they reject redirects that don't match). Default: random.\n * - `scope` — space-separated scopes to request.\n */\n oauth?: McpOAuthClientConfig\n /**\n * Timeout in milliseconds for MCP server bootstrap (connect + tool discovery).\n *\n * Zidane connects MCP servers lazily on the first `run()`. Without a\n * bootstrap timeout, a slow or hung server can delay the first provider call\n * for an arbitrarily long time even when that MCP server is never used.\n *\n * Default: `10000`.\n */\n bootstrapTimeout?: number\n /** Timeout in milliseconds for MCP tool calls (default: 30000) */\n toolTimeout?: number\n /**\n * Timeout in milliseconds for this server's shutdown inside\n * `McpConnection.close()` (default: 5000).\n *\n * Each server's close is raced against this bound so a hung transport\n * close, an MCP server ignoring stdin EOF, or a wedged `mcp:close` hook\n * listener cannot block `agent.destroy()` indefinitely. When the bound\n * fires on a stdio server, the child process is SIGKILLed so the\n * abandoned close cannot leak a subprocess into a long-lived host.\n */\n closeTimeout?: number\n /**\n * Per-tool timeout overrides in milliseconds, keyed by the server-side\n * tool name (NOT the namespaced `mcp_<server>_<tool>` agent name). Takes\n * precedence over `toolTimeout` for matching tools.\n *\n * Use case: one server exposing both fast metadata calls and slow\n * operations (`list_tables` vs `apply_migration`) — a single per-server\n * timeout either kills legitimate long calls or lets fast ones hang.\n */\n toolTimeouts?: Record<string, number>\n /**\n * Allow-list of tool names to expose. Names match the upstream tool name\n * (NOT the namespaced `mcp_{server}_{tool}` form). Tools not in the list are\n * dropped before registration — the model never sees them in its catalog and\n * the wire cost of advertising them is avoided.\n *\n * Mutually exclusive with {@link McpServerConfig.disabledTools} — passing both\n * throws at bootstrap time.\n *\n * Composes with {@link McpServerConfig.toolFilter}: allow-list applies first,\n * then the predicate. Composes with the `mcp:tools:filter` hook: config-side\n * filters apply first, then the hook can further narrow the list.\n */\n enabledTools?: string[]\n /**\n * Deny-list of tool names. Tools matching are dropped before registration.\n * Same matching semantics as {@link McpServerConfig.enabledTools}.\n */\n disabledTools?: string[]\n /**\n * Custom predicate run on each upstream tool. Return `true` to keep, `false`\n * to drop. Receives the raw `listTools()` payload — useful for filtering by\n * description, schema shape, or other metadata that an allow/deny list can't\n * express.\n *\n * Runs after the allow/deny filter but before the `mcp:tools:filter` hook.\n */\n toolFilter?: (tool: { name: string, description?: string | null, inputSchema?: unknown }) => boolean\n /**\n * Per-server override for {@link AgentBehavior.toolDisclosure}. When set,\n * this server's tools follow this disclosure mode regardless of the\n * agent-wide default. Useful when one big MCP server (200+ tools) should\n * stay lazy while smaller servers stay eager.\n *\n * Default: inherits from `behavior.toolDisclosure`.\n */\n disclosure?: 'eager' | 'lazy'\n /**\n * Pre-cached tool schemas to advertise without issuing `tools/list` at\n * bootstrap. The connection is still established (the SDK's `connect()`\n * is needed for `tools/call`) — only the discovery round-trip is\n * skipped. Schemas are trusted as-is; the host owns invalidation\n * (typical cache key: `(server identity, server version)`). If the\n * server later returns `MethodNotFound` for a cached tool, the host\n * should drop the entry from its cache so the next bootstrap re-lists.\n *\n * Compatible with every transport, every auth mode, and with\n * {@link McpServerConfig.lazyConnect}. Composes with the existing\n * `enabledTools` / `disabledTools` / `toolFilter` filters — those run\n * over the cached schemas exactly as they would over `listTools()`\n * output.\n */\n cachedTools?: McpToolSchema[]\n /**\n * Defer the `client.connect(transport)` call until the first\n * `tools/call` reaches this server. Bootstrap registers the server's\n * tools using {@link McpServerConfig.cachedTools} without touching\n * the network, taking MCP setup off the critical path of\n * `agent.run()`. The first invocation pays the connect cost\n * (~200-500ms typically); every subsequent call reuses the live\n * client.\n *\n * Requires {@link McpServerConfig.cachedTools} — without schemas in\n * hand there is nothing to advertise to the model, so deferring the\n * connection has no purpose. Bootstrap rejects the config otherwise.\n *\n * **Incompatible with `auth: 'oauth'`**: the OAuth handshake (token\n * refresh / RFC 9728 metadata discovery) can fail in ways that\n * fire `mcp:auth:required` at bootstrap so the host can surface a\n * login affordance *before* the model commits to calling a tool.\n * Deferring that to first call means an auth failure surfaces mid-run\n * as a tool-result error, which the model can't recover from without\n * a fresh prompt. Bootstrap rejects the combination so the error is\n * loud and proximate to the misconfiguration. Use OAuth servers\n * without `lazyConnect` (with `cachedTools` alone, if you want to\n * skip the `tools/list` round-trip).\n *\n * On connect failure (network error, transport refused), the cached\n * promise is dropped so the next `tools/call` retries. The model\n * sees the failure as a normal tool error. Subsequent calls remain\n * eligible to succeed once the upstream is reachable again.\n */\n lazyConnect?: boolean\n /**\n * Suppress `${VAR}` expansion at the transport boundary.\n *\n * Set by the Agent Plugins loader (`plugins/mcp.ts`), whose placeholder\n * rules are DELIBERATELY narrower than zidane's: the spec expands only\n * `${PLUGIN_ROOT}` / `${PLUGIN_DATA}`, and only inside `args`, `env`\n * values and `cwd` — never in `command`, `url` or `headers`. Those two\n * placeholders are already substituted by the time the config gets here,\n * so a second pass would (a) expand the wrong fields and (b) let an\n * ambient `PLUGIN_ROOT` env var reach fields the spec says must keep the\n * literal text.\n *\n * Hand-written `mcps.json` entries leave this unset and keep the full\n * `${VAR}` / `${VAR:-default}` / `${env:VAR}` behavior.\n */\n preExpanded?: boolean\n /**\n * Pin {@link McpServerConfig.headers} to the configured URL's origin\n * (sse / streamable-http only).\n *\n * When set, redirects are followed manually and the configured headers\n * are STRIPPED the moment a hop leaves the original origin, so a\n * cooperating-then-redirecting server cannot harvest a plugin's bearer\n * token. Set by the Agent Plugins loader, which must satisfy the spec's\n * \"never forward configured headers to a different origin\" rule; opt in\n * manually for any other server whose headers carry a secret.\n */\n pinHeaderOrigin?: boolean\n}\n\n// ---------------------------------------------------------------------------\n// Tool execution\n// ---------------------------------------------------------------------------\n\n/**\n * Configuration for the loop's retry-with-backoff policy around\n * `provider.stream()`. See {@link AgentBehavior.retry}.\n */\nexport interface RetryConfig {\n /**\n * Maximum number of attempts including the initial call. `1` disables retry.\n * Values below `1`, non-finite, or non-integer fall back to the default.\n *\n * Default: `3`.\n */\n maxAttempts?: number\n /**\n * Initial backoff delay in milliseconds. Subsequent delays double\n * (exponential) before jitter is applied and the cap enforced.\n *\n * Default: `1000`.\n */\n initialDelayMs?: number\n /**\n * Upper bound on any single backoff delay in milliseconds. Caps both the\n * exponential schedule and any `retry-after` header the server returns.\n *\n * Default: `30_000`.\n */\n maxDelayMs?: number\n}\n\n/**\n * Per-tool argument hasher used by {@link AgentBehavior.dedupTools}.\n *\n * Returning a non-empty string activates dedup with that string as the\n * cache key. Returning `undefined` (or `''` / non-string) opts THIS call\n * out — the tool runs normally and no state is recorded. See\n * `dedupTools` for the full contract.\n */\nexport type DedupHasher = (input: Record<string, unknown>) => string | undefined\n\n/** Stall pattern that can trigger a progress nudge. See {@link AgentBehavior.progressNudges}. */\nexport type ProgressNudgeKind = 'readOnlyTurns' | 'failedMutations' | 'noopMutations' | 'validationRejects' | 'toolErrors' | 'singleCallTurns'\n\n/** Message a progress nudge steers the model with: a fixed string, or a function of the nudge `kind` and its trigger `count`. */\nexport type ProgressNudgeMessage = string | ((ctx: { kind: ProgressNudgeKind, count: number }) => string)\n\n/**\n * Pluggable client-side context compactor — see\n * {@link AgentBehavior.compactStrategy}. The loop invokes this in place\n * of the built-in tail elision when `compactStrategy` is a function.\n *\n * Contract:\n * - Operates on the wire-level message list — the post-alias / post-\n * sanitize / post-`context:transform` view the provider will see.\n * The framework treats the returned array as the new wire payload;\n * `ctx.session` / `ctx.turns` are never modified.\n * - Must preserve `tool_use ↔ tool_result` adjacency. Stranding a\n * `tool_use` without its matching `tool_result` (or vice versa) will\n * trip the loop's pair-repair pass and either repair the violation\n * or throw (`strictToolPairing`).\n * - The trailing user message must remain — providers reject\n * assistant-tail wire payloads. If unsure, leave the last user turn\n * intact and operate on the prefix only.\n * - May return the input verbatim (e.g. when the total stays under the\n * host's threshold).\n * - Sync or async. The loop awaits the result, so a long-running LLM\n * summarizer will block the next turn — keep latency in mind.\n *\n * `ctx.threshold` and `ctx.keepTurns` are forwarded from\n * `behavior.compactThreshold` / `behavior.compactKeepTurns` so hosts\n * can reuse the same knobs the built-in tail mode uses.\n * `ctx.totalBytes` is the pre-compaction total — the same number the\n * tail mode uses to gate its threshold check, exposed so the function\n * can short-circuit on under-threshold inputs without recomputing.\n */\nexport type CompactFunction = (\n messages: SessionMessage[],\n ctx: {\n /**\n * Effective byte threshold: `behavior.compactThreshold` (default\n * `131_072`), raised by `compactThresholdWindowFraction` when the run\n * resolved a context window.\n */\n threshold: number\n /** Resolved `behavior.compactKeepTurns` (default `4` when unset). */\n keepTurns: number\n /**\n * Pre-compaction sum of `tool_result` output bytes across `messages`\n * — the same heuristic the built-in tail mode uses to decide\n * whether to act. Forwarded so the function can `return messages`\n * verbatim on a quick under-threshold check.\n */\n totalBytes: number\n },\n) => SessionMessage[] | Promise<SessionMessage[]>\n\n/**\n * Loop-native auto-compaction config (`AgentBehavior.autoCompact`).\n *\n * Distinct from `compactStrategy` (wire-only tail elision of `tool_result`\n * text). Auto-compaction is the heavy lever: when the last turn's input\n * usage crosses {@link AutoCompactConfig.threshold} of the effective context\n * window, the loop runs a real LLM summary (`compactConversation`) over the\n * older turns, appends a `compact-summary` marker + restoration turns to the\n * session mid-run, and the next turn's wire view is the summary plus the\n * preserved tail. It mutates the session (durable summary turn), whereas\n * `compactStrategy` never touches `session.turns`.\n *\n * The two compose: `compactStrategy: 'tail'` trims tool-output bytes every\n * turn; `autoCompact` summarizes the narrative when the window fills.\n */\nexport interface AutoCompactConfig {\n /**\n * Fraction of the EFFECTIVE context window (raw window minus the output\n * reserve) the last turn's input usage must reach to fire. Must be in the\n * open interval `(0, 1)`; out-of-range disables the trigger. Default: `0.85`.\n */\n threshold?: number\n /**\n * Raw model context window in tokens. The SDK core has no model catalog,\n * so when omitted it falls back to `provider.meta.contextWindow` (a\n * number, if the provider advertises one). When neither resolves, the\n * trigger is inert — it can't compute a usage ratio. Chat hosts inject\n * the picked model's window here from their catalog.\n */\n contextWindow?: number\n /**\n * Trailing turns kept verbatim (passed to `compactConversation` as\n * `scope: 'tail'` `keepTurns`). Defaults to `compactKeepTurns` when set,\n * else `4`.\n */\n keepTurns?: number\n /**\n * Hysteresis floor — minimum fraction of the window that must be consumed\n * by NEW content after a compaction before another can fire, so the\n * post-compact bounce doesn't immediately re-trigger. Default: `0.1`.\n */\n minGrowthFraction?: number\n /**\n * Re-inject recently-read files + active skills as synthetic tool-call\n * pairs after the summary marker (via `buildPostCompactAttachments`) so\n * the model keeps its working set. Default: `true`.\n */\n restore?: boolean\n /**\n * Consecutive-failure circuit breaker. After this many failed compaction\n * attempts in a single run, the loop stops trying for the rest of the run\n * (the reactive `context_exceeded` net still protects overflow). A\n * successful compaction resets the counter. Default: `2`.\n */\n maxFailures?: number\n /**\n * Provider the summary call runs on. Defaults to the run's main\n * provider. Pair with {@link model} to summarize on a different\n * (cheaper / larger-window) model than the one driving the\n * conversation. The trigger threshold still measures the MAIN model's\n * window — it gauges the live conversation, not the summarizer.\n */\n provider?: Provider\n /**\n * Model id for the summary call — resolved against {@link provider}\n * when set, else against the run's main provider. Defaults to the\n * run's main model.\n */\n model?: string\n /**\n * Release the in-memory bodies of turns each compaction summary replaces:\n * large text `tool_result` outputs are written host-side to\n * `<persistDir>/released/<callId>.txt` (exempt from the persist-dir LRU\n * cap — once the stub round-trips into the session store the blob is the\n * only remaining copy, so it lives and dies with the session) and the\n * session's in-memory copy is swapped for a `<persisted-output …>` stub.\n * Replaced turns never reach the wire again, so without this a\n * long-lived host retains every pre-compaction tool output for the\n * process's life. Requires `persistDir`; content stays retrievable from\n * the stub's path. Off by default — hosts that need the lossless\n * inline transcript (SFT capture, exporters) should leave it off.\n */\n releaseReplacedTurns?: boolean\n}\n\n/**\n * `AgentBehavior.autoCompact` value: `true` enables loop-native\n * auto-compaction with defaults, `false` / omitted disables it, and an\n * {@link AutoCompactConfig} enables it with overrides.\n */\nexport type AutoCompact = boolean | AutoCompactConfig\n\n/**\n * Per-tool dedup configuration. Two equivalent forms:\n *\n * - **Hasher only**: a bare function. The framework runs\n * in `'replay'` mode — identical re-calls replay the prior result.\n * - **Config object**: explicit `hasher` plus a `mode`:\n * - `'replay'` (default) — replay the cached result on identical re-\n * calls. Cheap loop, but doesn't BREAK the loop — the model keeps\n * seeing the same tool_result and keeps trying.\n * - `'block-after'` — replay identical calls until the total count\n * reaches `threshold`, then refuse with `Blocked: <reason>` via\n * `tool:gate`. Designed to\n * break tight loops where the model fixates on a single failing\n * call. The block lands on the Nth identical call (i.e. once the\n * total count reaches `threshold`); the cap is sticky — every\n * subsequent identical call also blocks until a different input\n * resets the counter.\n *\n * `reason` is the model-visible refusal text. The framework formats the\n * final tool_result as `Blocked: <reason>` (see `tool:gate` docs).\n * Strings are used verbatim; the callback form receives the input and\n * the (now-blocking) repeat count so it can produce per-invocation\n * messaging like `Blocked: 4 identical \"shell\" calls — try a different\n * approach`. Default reason is generic but actionable.\n */\nexport type DedupToolConfig\n = | DedupHasher\n | {\n hasher: DedupHasher\n mode?: 'replay' | 'block-after'\n /**\n * For `mode: 'block-after'` only — total count of identical calls\n * (including the first dispatch) at which the gate switches from\n * replay to block. Threshold `4` blocks the 4th identical call;\n * the first 3 (one dispatch + two replays) still go through.\n *\n * Ignored for `mode: 'replay'`. Default: `4`. Values `<= 1` are\n * clamped to `2` (anything less would block the very first replay,\n * which would behave the same as not having a hasher at all).\n */\n threshold?: number\n /**\n * Refusal text rendered into the model-visible tool_result as\n * `Blocked: <reason>`. Either a literal string or a callback that\n * receives the call's input and the repeat count (1-indexed total\n * of identical calls so far, including this blocking one).\n *\n * Default: a generic \"identical call repeated N times\" message.\n */\n reason?: string | ((input: Record<string, unknown>, count: number) => string)\n /**\n * Scope of the identical-call counter.\n *\n * - `'session'` (default) — one entry per `(session, toolName)`.\n * Correct for stateless / pure tools (`read_file`, `grep`)\n * where the same input yields the same result regardless of which\n * run issued it.\n * - `'run'` — one entry per `(session, runId, toolName)`. Required\n * for tools whose identical-input semantics are run-local: a\n * parent and a subagent can legitimately write the *same*\n * `todowrite` payload into their *own* lists, and session-scoping\n * would conflate the two (replaying the parent's result to the\n * child, or counting the child's writes against the parent's\n * block threshold). Falls back to session-scope for calls with no\n * `runId` on the hook context (hand-built contexts / direct\n * invocations).\n *\n * Default: `'session'`.\n */\n scope?: 'session' | 'run'\n }\n\n/**\n * Per-tool predicate selecting which tools the {@link RepeatGuardConfig}\n * watches. A bare string matches that exact canonical name; a function\n * receives the canonical name and returns `true` to track it.\n */\nexport type RepeatGuardToolMatcher = string | ((name: string) => boolean)\n\n/**\n * Configuration for the consecutive-identical-call repeat guard. See\n * {@link AgentBehavior.repeatGuard} for the full contract.\n */\nexport interface RepeatGuardConfig {\n /**\n * Consecutive identical calls at which the guard refuses the call via\n * `tool:gate` `block`. Clamped to `>= 2`. Default: `4` (`read_file` uses\n * `6` unless this is explicitly configured).\n */\n blockThreshold?: number\n /**\n * Consecutive identical calls at which the guard aborts the whole run via\n * the agent's `AbortController` (the loop then fires `agent:abort`). Must\n * be `>` the block threshold to be reachable; clamped up if not. Set to a\n * non-positive value or `Infinity` to disable the abort escalation\n * (block-only). Default: `8` (`read_file` uses `12` unless this is\n * explicitly configured).\n */\n abortThreshold?: number\n /**\n * Which tools to track. Each entry is a {@link RepeatGuardToolMatcher}.\n * When unset, the default tracks the built-in `shell` and `read_file`\n * tools. An empty array disables the guard.\n */\n tools?: readonly RepeatGuardToolMatcher[]\n /**\n * Collapse a call's input to a streak key. Identical keys for the same\n * tool extend the streak. Return `undefined` to opt the call out of\n * streak tracking. When unset, the built-in normalizer is used (shell\n * command canonicalization for `shell`-shaped tools, JSON for everything\n * else).\n */\n normalize?: (name: string, input: Record<string, unknown>) => string | undefined\n /**\n * Model-visible steering text at the block threshold. Used as the\n * `Blocked: <message>` reason for `thresholdAction: 'block'`, or appended\n * to the successful tool result for `thresholdAction: 'steer'`. String used\n * verbatim; callback receives the tool name and observed repeat count.\n * Default: a generic pivot suggestion.\n */\n steeringMessage?: string | ((name: string, count: number) => string)\n /**\n * Legacy alias for {@link steeringMessage}. Preserved for callers already\n * customizing the old block-only message.\n */\n blockReason?: string | ((name: string, count: number) => string)\n /**\n * What to do at `blockThreshold`.\n *\n * - `'block'` (default) refuses the call via `tool:gate`, returning\n * `Blocked: <steering message>`.\n * - `'steer'` lets the tool execute, then appends the steering message to\n * the tool result. `abortThreshold` still blocks and aborts the run.\n */\n thresholdAction?: 'block' | 'steer'\n /**\n * Repeat detector strategy.\n *\n * - `'chain'` — strict back-to-back chain counting. Any intervening tool\n * call breaks the chain.\n * - `'window'` — sliding-window counting. Interleaved calls can accumulate.\n * - `'both'` — run both counters and trip when either reaches the threshold.\n *\n * Default: `'chain'`. For compatibility, setting {@link windowSize} without\n * `mode` keeps the old `'window'` behavior.\n */\n mode?: 'chain' | 'window' | 'both'\n /**\n * Sliding-window size for `mode: 'window'` / `mode: 'both'`.\n *\n * When set (`>= 2`), a call's count is the number of occurrences of its\n * normalized key among that tool's last `windowSize` tracked calls\n * (including this one). A *different* payload of the same tool no longer\n * resets the count. This is opt-in non-chain behavior for consumers that\n * explicitly want to catch interleaved repeats.\n *\n * The effective window is clamped up to the abort threshold (or the\n * block threshold when abort is disabled) so the thresholds stay\n * reachable.\n *\n * Default: `undefined` for `mode: 'chain'`; a conservative built-in default\n * is used when `mode` requests window counting without an explicit size.\n */\n windowSize?: number\n /**\n * Count calls a PRIOR `tool:gate` handler already blocked (tool-budget\n * cap, skill allowed-tools refusal) toward the streak.\n *\n * Why you'd want it on: a model hammering a budget-blocked tool with the\n * identical payload is the canonical stuck loop, but those attempts never\n * reach this guard's counter when this is off — the abort escalation is\n * unreachable and the run burns turns until an external cap\n * (`maxTurns`). With it on, blocked attempts keep marching the streak\n * toward `abortThreshold`; the guard never overwrites the prior gate's\n * refusal reason and only acts at the abort escalation.\n *\n * Calls answered by a prior gate's `result` substitute (dedup replay)\n * are never counted regardless — the model received a real payload.\n *\n * Default: `false` (blocked calls don't count).\n */\n countBlockedCalls?: boolean\n /**\n * Universal repeat ceiling — the last-resort chain breaker. Independent of\n * {@link tools}, it counts exact back-to-back `(tool, args)` repeats across\n * EVERY tool and aborts the run when one recurs this many times in a row.\n * Catches untracked tool chains and tools stuck behind a sticky block (it\n * counts blocked attempts, unlike {@link countBlockedCalls}; never a\n * `result` substitute).\n *\n * Set high enough that honest retries don't trip it. The main false-positive\n * risk is a tool that's legitimately polled with identical args many times\n * (a status/wait tool) — exclude those via {@link globalCeilingExclude}.\n *\n * A non-positive or non-finite value disables it. Default: `12`.\n */\n globalCeiling?: number\n /**\n * Deprecated no-op kept for config compatibility. The {@link globalCeiling}\n * uses strict chain counting, not a sliding window.\n */\n globalCeilingWindow?: number\n /**\n * Tools exempt from the {@link globalCeiling} (e.g. status/poll/wait tools\n * legitimately called with identical args many times). Each entry is a\n * {@link RepeatGuardToolMatcher}. `undefined` (default) exempts nothing; an\n * empty array also exempts nothing.\n */\n globalCeilingExclude?: readonly RepeatGuardToolMatcher[]\n}\n\n/**\n * Strategy for handling reads made stale by a later mutation in the same run.\n * See {@link AgentBehavior.elideStaleReads} for full semantics and the measured\n * cache economics of each mode.\n */\nexport type StaleReadPolicy = 'stub' | 'notice'\n\n/** Hard-limit budget for inline media. See {@link AgentBehavior.mediaBudget}. */\nexport interface MediaBudget {\n /** Inline image / document blocks allowed per request. */\n maxItems: number\n /** Base64 bytes of inline media allowed per request. */\n maxBytes: number\n}\n\n/**\n * Prompt-cache entry lifetime. See {@link AgentBehavior.cacheTtl}.\n */\nexport type CacheTtl = '5m' | '1h'\n\n/**\n * Geometric thinking-budget taper: full budget through `afterTurn`, then\n * multiplied by `factor` each subsequent turn, clamped to `floor`.\n *\n * Emits several distinct budgets on the way down, and on Anthropic each\n * distinct `thinking.budget_tokens` value is a full prompt-cache re-bill (the\n * budget is part of the cache key — measured, see\n * {@link AgentBehavior.thinkingDecay}). Prefer {@link StepThinkingDecay} when\n * caching is on.\n */\nexport interface GeometricThinkingDecay {\n /** Last turn that gets the full thinking budget. */\n afterTurn: number\n /** Multiplier applied to the budget on each turn after `afterTurn`. */\n factor: number\n /** Lowest thinking budget the taper reaches. */\n floor: number\n}\n\n/**\n * Single-step thinking-budget taper: full budget through `afterTurn`, then\n * `floor` for the remainder of the run.\n *\n * Exactly ONE budget change per run, so exactly one prompt-cache invalidation\n * — the cache-aware schedule. Distinguished from\n * {@link GeometricThinkingDecay} by the absence of `factor`.\n */\nexport interface StepThinkingDecay {\n /** Last turn that gets the full thinking budget. */\n afterTurn: number\n /** Thinking budget used after `afterTurn`. */\n floor: number\n /** Always absent. Distinguishes this schedule from {@link GeometricThinkingDecay}. */\n factor?: undefined\n}\n\n/**\n * Thinking-budget decay schedule. See {@link AgentBehavior.thinkingDecay} for\n * semantics and the measured prompt-cache cost of each form.\n */\nexport type ThinkingDecay\n = | GeometricThinkingDecay\n | StepThinkingDecay\n | ((runTurn: number, baseBudget: number) => number)\n\n/** Run behavior settings: limits, guards, caching, compaction and recovery policies. Set agent-wide on {@link AgentOptions.behavior} and override per run. */\nexport interface AgentBehavior {\n /**\n * Override model metadata (output ceiling, pricing, context window,\n * reasoning support) for this agent — and per run, like every other\n * behavior field.\n *\n * Resolution is first-hit-wins: run behavior → agent behavior → the\n * process-wide `setModelCatalog()` → pi-ai's bundled registry → local\n * fallback tables. Entries merge FIELD-LEVEL over the bundled entry, and\n * everything stays keyed by `(provider, modelId)`, so overriding one\n * provider leaves the others fully bundled.\n *\n * Accepts a table keyed `\"<provider>:<modelId>\"` or a\n * `{ get, list }` source — see `ModelCatalog`.\n *\n * ```ts\n * createAgent({ provider, behavior: {\n * modelCatalog: { 'anthropic:my-gateway-alias': { maxTokens: 128000 } },\n * } })\n * ```\n *\n * Note: the chat/TUI model picker resolves metadata before any agent\n * exists, so it reads only the process-wide catalog.\n *\n * Default: unset — the bundled registry is authoritative.\n */\n modelCatalog?: ModelCatalog\n /**\n * Maximum number of tools that may be in flight concurrently within a\n * single assistant turn. The scheduler dispatches concurrency-safe tools\n * (`ToolDef.isConcurrencySafe`) in parallel up to this cap; unsafe tools\n * act as barriers (wait for the fleet to drain, then run alone).\n *\n * Default: `10`. Set to `1` to force fully sequential dispatch regardless\n * of per-tool flags — useful for deterministic debugging / eval-grade\n * runs. Values `< 1` are clamped to `1`.\n */\n maxConcurrentTools?: number\n /**\n * Optional per-turn tool batch scheduler. When unset, Zidane uses its\n * native sliding-window scheduler (on a replay-mode durable runtime, its\n * journaled one, which joins concurrent calls through the runtime).\n */\n toolBatchExecutor?: ToolBatchExecutor\n /**\n * Max agent loop iterations.\n *\n * Default: unlimited (Infinity). The loop runs until the model signals\n * completion (no tool calls / `end_turn`), the abort signal fires, or this\n * cap is hit. Set a finite value as a safety net for runaway loops.\n */\n maxTurns?: number\n /**\n * Inject a synthetic user message when this many turns remain before\n * `maxTurns`, telling the model to wrap up.\n *\n * Without it, a run that grinds into the cap dies silently mid-loop —\n * the model never learns it was on a clock, produces no closing summary,\n * and the user gets a truncated session (observed in production: a\n * 200-turn run terminated by the cap during a read→edit ping-pong, with\n * no final answer). The warning gives the model a deterministic chance\n * to land the work: it fires once, after the turn that leaves exactly\n * this many turns remaining, riding the same steer pathway as\n * `toolBudgets` nudges.\n *\n * Only meaningful when `maxTurns` is finite. Values `>= maxTurns` or\n * non-positive are ignored. Default: `undefined` (no warning).\n */\n maxTurnsWarning?: number\n /**\n * Optional custom message for {@link maxTurnsWarning}. When unset, the loop\n * uses its built-in wrap-up warning. String form is sent verbatim; function\n * form receives the remaining/maximum turn counts and can tailor the nudge.\n */\n maxTurnsWarningMessage?: string | ((ctx: { remaining: number, maxTurns: number }) => string)\n /**\n * Run-level cost ceiling, expressed in USD. After each turn, the sum of\n * `TurnUsage.cost` across the run is compared against this value; if the\n * total has crossed the cap, the loop throws `AgentBudgetExceededError`\n * with `limit: 'cost'`. The agent finalizes the session run as\n * `'aborted'` so partial spend is recorded.\n *\n * Complements {@link maxTurns}: a single expensive turn (large context\n * window, deep reasoning) can blow past a turn-count cap but cost is\n * the unit operators actually want to bound for unattended runs.\n *\n * Checked **post-turn**, so the run may exceed by up to one turn's\n * spend before tripping — soft cap, not exact. Default: unbounded.\n * Set `0` to disable explicitly (negative / `NaN` are ignored).\n *\n * Requires the provider to populate `TurnUsage.cost` (every built-in\n * provider does). For providers that don't, the check silently no-ops\n * and {@link maxTotalTokens} is the right knob instead.\n */\n maxCostUsd?: number\n /**\n * Run-level token ceiling — sum of `TurnUsage.input + TurnUsage.output`\n * across the run. Same post-turn semantic + soft-cap behavior as\n * {@link maxCostUsd}; throws `AgentBudgetExceededError` with\n * `limit: 'tokens'` when exceeded.\n *\n * Cache reads / creations are **not** included in the sum — they're\n * billed at a steep discount, so counting them at par would make the\n * ceiling under-estimate the run's true affordability. Operators\n * wanting a stricter accounting should override via the `usage` hook.\n *\n * Default: unbounded. Set `0` to disable explicitly.\n */\n maxTotalTokens?: number\n /**\n * Run-level wall-clock ceiling in milliseconds. The loop records the\n * run's start via `AgentClock.now()` and compares elapsed time against\n * this value after each turn; when crossed it throws\n * `AgentBudgetExceededError` with `limit: 'wallClock'`. Same post-turn\n * semantic + soft-cap behavior as {@link maxCostUsd} — a single long\n * turn can overshoot the deadline before the check runs.\n *\n * Time is read through the run's clock, so a run on a replay-mode\n * durable runtime (whose clock is journaled) gets a\n * replay-stable deadline: the breach trips at the same turn boundary\n * on every replay of the same invocation. This is the supported\n * replacement for host-side timer + `agent.abort()` deadlines, which\n * cannot be made journal-safe from outside the loop.\n *\n * Default: unbounded. Set `0` to disable explicitly (negative / `NaN`\n * are ignored).\n */\n maxWallMs?: number\n /**\n * Provider first-response watchdog in milliseconds.\n *\n * Starts at `stream:start` and is cleared by the first text/thinking/server\n * tool callback or by the provider returning a tool-only/final result. If it\n * expires, the loop aborts the provider request signal and surfaces a typed\n * provider error instead of leaving `agent.run()` pending forever.\n *\n * `maxWallMs`, when lower, bounds this value too. Set `0` to disable.\n * Default: 300000 (5 minutes).\n */\n providerStreamStartTimeoutMs?: number\n /**\n * Provider total stream watchdog in milliseconds.\n *\n * Bounds the whole `provider.stream()` call, including streams that already\n * produced partial output but never settle. If it expires, the loop aborts\n * the provider request signal and surfaces the timeout as a provider error.\n *\n * `maxWallMs`, when lower, bounds this value too. Set `0` to disable.\n * Default: 1800000 (30 minutes).\n */\n providerStreamTimeoutMs?: number\n /**\n * Provider stream inactivity watchdog in milliseconds.\n *\n * Bounds the gap between observable stream events (text / thinking /\n * server-tool / tool-call-argument deltas) AFTER the first one arrived —\n * the window `providerStreamStartTimeoutMs` does not cover. A stream that\n * goes silent mid-generation (upstream disconnect the SDK never surfaces,\n * a wedged proxy) otherwise hangs until `providerStreamTimeoutMs` (30 min\n * default). If it expires, the loop aborts the provider request and\n * surfaces a typed timeout; the retry path treats it like the other\n * watchdogs (retryable, and eligible for fallback, only when no text or\n * tool-call content streamed and no server-side tool executed; reasoning\n * alone is dropped and regenerated).\n *\n * Off by default: legitimate silent gaps exist (server-side tool\n * execution, very long prefill on providers that emit nothing until\n * done), so hosts opt in with a value tuned to their provider mix —\n * interactive products serving well-behaved streaming APIs can run this\n * as low as 60-90s. `maxWallMs`, when lower, bounds this value too.\n * Set `0` (or leave unset) to disable.\n */\n providerStreamIdleTimeoutMs?: number\n /**\n * MCP connect watchdog in milliseconds.\n *\n * The built-in `connectMcpServers()` has per-server bootstrap timeouts. This\n * aggregate guard covers the whole connect step, built-in or custom, so an\n * embedder's `mcpConnector` that returns a promise that never settles cannot\n * block the first model call.\n *\n * `maxWallMs`, when lower, bounds this value during `run()`. Set `0` to\n * disable. Default: 120000 (2 minutes).\n */\n mcpConnectTimeoutMs?: number\n /**\n * Watchdog for awaited hook dispatch (in milliseconds).\n *\n * Bounds every awaited `hooks.callHook(...)` the agent loop and run\n * lifecycle await — `context:transform` / `system:transform` /\n * `turn:before` / `stream:start` / `turn:after` / `stream:end` /\n * `tool-results:after`, plus the run-setup (`session:start`,\n * `agent:start`) and run-teardown (`agent:done`, `session:end`,\n * `session:turns`, `skills:deactivate`) hooks. A host handler that never\n * resolves would otherwise hang the first turn (before the provider\n * watchdog can run) or strand the `running` gate during cleanup.\n *\n * State-mutating pre-stream hooks surface a timeout as a typed\n * `OperationTimeoutError` (fail-closed — a half-applied transform must\n * not reach the wire); observability/lifecycle hooks log under\n * `ZIDANE_DEBUG` and continue. Generous by design — the goal is catching\n * infinite hangs, not policing slow handlers.\n *\n * Set `0` to disable. Default: 300000 (5 minutes).\n */\n hookTimeoutMs?: number\n /**\n * Watchdog for the first `executionContext.spawn()` (in milliseconds).\n *\n * The process context spawns synchronously, but docker / sandbox / e2b /\n * daytona contexts do daemon + network round-trips (image pull, container\n * create/start) with no inherent timeout. This bounds that first spawn so\n * a hung daemon surfaces a typed `OperationTimeoutError` instead of a run\n * that never starts responding.\n *\n * `maxWallMs`, when lower, bounds this value too. Set `0` to disable.\n * Default: 300000 (5 minutes). Raise for hosts doing large first-time\n * image pulls on slow links.\n */\n spawnTimeoutMs?: number\n /**\n * Watchdog for `agent.destroy()` teardown awaits (in milliseconds).\n *\n * Bounds each awaited teardown step (pending MCP warmup, MCP connection\n * `close()`, execution-context `destroy()`) so a custom connector / sandbox\n * whose `close()` never settles can't strand `destroy()` — which hosts\n * await before releasing their own run gate (TUI session switch). Steps\n * that time out are abandoned and teardown continues. Set `0` to disable.\n * Default: 30000 (30 seconds).\n */\n destroyTimeoutMs?: number\n /**\n * Bounded retry-with-backoff policy applied around `provider.stream()`.\n *\n * Triggers only on errors the provider classifies as `retryable: true`\n * (typically 429 / 5xx HTTP responses and mid-stream SSE errors like\n * Anthropic's `overloaded_error`). Terminal failures (auth, invalid\n * request, context exceeded, aborts) skip retry and propagate.\n *\n * Skipped when text or tool-call content has already streamed this turn,\n * or a server-side tool ran — replaying over partial output would produce\n * a confusing transcript. Reasoning alone does not block a retry: it is\n * dropped (see `discardedThinking` on the `stream:retry` hook) and the\n * next attempt generates its own.\n * Mid-stream continuation (replaying with the partial assistant response\n * appended for the model to continue from) is a separate, larger change.\n *\n * The Anthropic SDK already retries pre-stream errors up to twice\n * internally; this loop-level retry catches mid-stream failures and\n * other providers that don't ship an SDK-side retry.\n *\n * Default: 3 attempts, 1s → 2s → 4s exponential with full jitter,\n * capped at 30s per delay, with `retry-after` / `retry-after-ms`\n * headers honored when present. Set `maxAttempts: 1` to disable.\n */\n retry?: RetryConfig\n /**\n * Custom `toolBatchExecutor` watchdog in milliseconds.\n *\n * Native tool execution keeps its existing per-tool/cancellation semantics.\n * This only bounds a host-provided batch executor; on timeout the executor's\n * signal is aborted and the pending tool calls receive error tool results so\n * a tool-only assistant turn cannot hang the run forever.\n *\n * Set `0` / `undefined` to disable. Default: disabled.\n */\n toolBatchTimeoutMs?: number\n /**\n * Hard cap on output tokens per LLM response. When unset, the provider uses\n * a model-driven default — Anthropic sends the model's max output (the field\n * is mandatory there), OpenAI-style providers omit it so the model runs to a\n * natural stop. Set a number to impose a fixed per-turn output ceiling.\n */\n maxTokens?: number\n /** Thinking token budget — overrides the level-based default when set */\n thinkingBudget?: number\n /**\n * Total thinking budget across the whole task, in tokens. Anthropic\n * adaptive-thinking only — emitted as\n * `output_config.task_budget = { type: 'tokens', total }` alongside the\n * level-derived `effort` hint. Ignored where a task budget has no meaning:\n * non-Anthropic providers, `thinking: 'off'`, and the explicit-budget\n * `enabled` path (`thinkingBudget` set on a non-adaptive level). A\n * whole-task cap, so it is never routed through `thinkingDecay`.\n */\n taskBudget?: number\n /**\n * Emit the level-derived `output_config.effort` hint on Anthropic requests\n * (default: on). Set `false` for Anthropic models that reject the effort\n * field: the level still decides whether thinking is on (`'off'` vs the\n * rest) and an explicit `thinkingBudget` still routes to the `enabled`\n * path — but no `output_config.effort` is sent, so without a\n * `thinkingBudget` every budgeted level (`minimal`..`max`) produces the\n * same plain-adaptive request and the effort distinctions between them are\n * lost. Anthropic-only; other providers derive their reasoning fields from\n * the level itself and ignore this.\n *\n * Run-wide: spawned subagents inherit the behavior, so disabling the hint\n * also strips it from any Anthropic model a child runs on, including ones\n * that accept the effort field.\n */\n effortHint?: boolean\n /**\n * When a provider streams more thinking text than expected, optionally nudge\n * the next continuing turn toward concrete progress. This is advisory and\n * non-interrupting: same-turn tool calls are still allowed to finish.\n */\n thinkingOverrunPolicy?: 'off' | 'steer-next-turn' | {\n mode?: 'off' | 'steer-next-turn'\n thresholdTokens?: number\n message?: string\n }\n /**\n * Default model-specific options applied to every run, keyed by option id\n * (e.g. `{ fast: true }`). A per-run {@link AgentRunOptions.modelOptions}\n * takes precedence when set. See `StreamOptions.modelOptions`.\n */\n modelOptions?: Record<string, boolean>\n /** JSON Schema for structured output enforcement */\n schema?: Record<string, unknown>\n /**\n * Enable provider prompt caching. When on (default), the provider marks the\n * system prompt, tools, and the last stable message with cache breakpoints so\n * the shared prefix is served from cache across turns.\n *\n * - Anthropic: `cache_control: { type: 'ephemeral' }` on the last `system`\n * content part, the last tool, and the last message content part.\n * - OpenAI-compatible / OpenRouter: same shape — honored by Anthropic-backed\n * OpenRouter routes and by Gemini; ignored (no-op) by providers that cache\n * automatically (OpenAI, DeepSeek, Grok, Groq, Moonshot).\n *\n * Usage is surfaced via `TurnUsage.cacheRead` / `TurnUsage.cacheCreation`.\n *\n * Default: `true`.\n */\n cache?: boolean\n /**\n * Lifetime of the provider's prompt-cache entries.\n *\n * - `'5m'` (default) — the provider's standard ephemeral cache. Writes bill\n * at 1.25x input; each hit refreshes the window for free.\n * - `'1h'` — Anthropic's extended cache (`cache_control: { type: 'ephemeral',\n * ttl: '1h' }` plus the `extended-cache-ttl-2025-04-11` beta header).\n * Writes bill at 2x input; reads stay at ~0.1x. Also forwarded on\n * `openrouter` (the `cacheBreakpoints` openai-compat route), which accepts\n * the `ttl` field on Anthropic-family models without a beta header.\n *\n * The 5-minute window is the dominant cache cost for INTERACTIVE sessions: a\n * user who reads output, thinks, and types for six minutes loses the entire\n * cached prefix — a 100% re-bill on the next turn — no matter how\n * byte-stable the wire is. `'1h'` converts that idle-loss into a cheap read\n * for the price of costlier writes; it pays for itself as soon as one\n * would-be expiry per hour is avoided. Headless back-to-back runs (seconds\n * between turns) should stay on `'5m'` — they never idle long enough to\n * benefit from the 2x write premium.\n *\n * Applied uniformly to every breakpoint (Anthropic requires 1h blocks to\n * precede 5m blocks in a request; a uniform TTL sidesteps the ordering\n * constraint). Ignored by providers without a TTL concept. No-op when\n * `cache` is off, and degrades to `'5m'` on the Anthropic OAuth (Claude\n * Code persona) path — only api-key auth has been verified against the\n * extended-TTL beta, and an unaccepted beta would 400 every request.\n *\n * CAVEAT — verify the benefit on your account before relying on it. The\n * request shape is byte-verified (`ttl` + beta header on the wire), but in\n * our live measurement (2026-07, sonnet-4-6, direct API) a 1h entry did NOT\n * survive a 6-minute idle; whether that is model support, account tier, or\n * plain eviction is not distinguishable from outside. The gated idle test in\n * `test-inference/cache-stability.inference.test.ts` (`INFERENCE_SLOW=1`)\n * measures it for your credentials.\n *\n * Default: `'5m'`.\n */\n cacheTtl?: CacheTtl\n /**\n * Soft per-turn cap on total tool-output bytes. When the sum of `outputBytes`\n * across a turn's tool results exceeds this value, the loop injects a\n * synthetic user message instructing the model to summarize before calling\n * more tools, and fires the `budget:exceeded` hook.\n *\n * Measured **post-`tool:transform`** so consumer truncation counts toward the\n * budget. Off by default (undefined / `0` disables the check). A reasonable\n * starting value for OSS-model integrations is `32768`.\n */\n toolOutputBudget?: number\n /**\n * Canonical tool names whose output is exempt from\n * {@link AgentBehavior.toolOutputBudget} accounting. Their text-equivalent\n * bytes don't count toward the per-turn cap and the \"summarize before\n * calling more tools\" nudge is not triggered by them alone.\n *\n * Intended for tools whose entire purpose is to LOAD context into the\n * conversation — penalising them via the budget creates the exact\n * failure mode the budget is meant to prevent (the model gets steered\n * away from the very tool it just called to make progress):\n *\n * - `tool_search` — returns full `inputSchema` payloads for MCP tools the\n * model needs to call next. A Notion MCP server with 20+ tools easily\n * exceeds 64 KiB of schema JSON in a single discovery call.\n * - `skills_use` / `skills_read` — inject skill content; that text IS\n * the value of the call.\n *\n * For MCP tools, key by the namespaced wire name (`mcp_<server>_<tool>`).\n * The matching happens on the canonical (registry-key) name, so aliases\n * are stable.\n *\n * Default: `undefined` — every tool counts. `DEFAULT_BUDGET_EXCLUDE_TOOLS`\n * (`zidane/chat`) is a ready-made list of the context-loading tools.\n */\n toolOutputBudgetExcludeTools?: readonly string[]\n /**\n * Default `read_file` slice limits for calls that omit `limit` / `maxBytes`.\n * Explicit tool-call arguments still win. SDK default remains the tool's\n * built-in defaults; hosts can tighten these for context-sensitive profiles.\n * `maxBytes` caps text reads only; image and document reads keep the\n * tool's attachment cap.\n */\n readFileDefaults?: {\n limit?: number\n maxBytes?: number\n }\n /**\n * Deduplicate identical re-reads of the same file in `read_file`. When the\n * model re-reads a file with the same slice and the bytes haven't changed\n * since the last read in this session, the tool returns a short stub\n * instead of re-emitting the full content. Pairs with the read-before-edit\n * guard in `edit` / `multi_edit`.\n *\n * Requires a session (set via `createSession()`); without one, the flag is\n * a no-op since per-session state has nowhere to live.\n *\n * Default: `true`.\n */\n dedupReads?: boolean\n /**\n * Taper the thinking budget over the course of a run. Late turns are\n * usually checkpoint / cleanup work where reasoning rarely pays for\n * itself; early turns benefit most. Three forms — see {@link ThinkingDecay}:\n *\n * - **Step** — `{ afterTurn, floor }`: full budget through `afterTurn`, then\n * `floor` for the rest of the run. ONE budget change per run. This is the\n * cache-aware schedule — prefer it whenever prompt caching is on.\n * - **Geometric** — `{ afterTurn, factor, floor }`: multiply by `factor`\n * each turn past `afterTurn`, clamped to `floor`. Example\n * `{ afterTurn: 5, factor: 0.5, floor: 1024 }` with a base budget of 8192:\n * turns 1-5 = 8192, turn 6 = 4096, turn 7 = 2048, turn 8+ = 1024.\n * - **Function** — `(runTurn, baseBudget) => number`. Arbitrary curves;\n * `runTurn` is 1-indexed, run-relative (resumed sessions reset).\n *\n * ## Prompt-cache cost — measured, not theoretical\n *\n * On Anthropic, `thinking.budget_tokens` is part of the prompt-cache key:\n * a request with a different budget reads NOTHING back — `cacheRead: 0`,\n * scaffold included — and re-bills the entire prefix at the cache-write\n * rate (measured live; pinned in\n * `test-inference/cache-stability.inference.test.ts`). Every distinct value\n * a schedule emits is therefore one full re-bill: the geometric example\n * above costs three (4096, 2048, 1024), a step schedule costs exactly one,\n * and a function that returns a fresh value per turn costs one PER TURN —\n * a per-turn curve silently disables caching for the rest of the run.\n *\n * No-op when `thinkingBudget` is unset. Applies to the per-turn thinking\n * budget: the Anthropic explicit-budget `enabled` path and openai-compat\n * `max_tokens` padding / `reasoning.max_tokens`. It does NOT touch\n * `taskBudget` (a whole-task cap — per-turn decay is meaningless there).\n * It is also skipped for `thinking: 'adaptive'` on providers that treat\n * `thinkingBudget` as a whole-response envelope cap there (Anthropic —\n * `ProviderCapabilities.adaptiveBudgetIsEnvelope`): decaying an envelope cap\n * would strangle text/tool output, not just thinking. On providers where\n * adaptive + `thinkingBudget` is a genuine per-turn thinking budget (e.g.\n * openai-compat `reasoning.max_tokens`), adaptive still decays.\n *\n * Default: `undefined` (no decay).\n */\n thinkingDecay?: ThinkingDecay\n /**\n * Policy for full-file writes to paths that already exist. The guard is a\n * `tool:gate` refusal that points the model toward `read_file` + `edit` /\n * `multi_edit`.\n */\n writeFileExistingPolicy?: 'allow' | 'block-with-edit-recipe' | {\n mode?: 'allow' | 'block-with-edit-recipe'\n allowPaths?: readonly string[]\n denyPaths?: readonly string[]\n }\n /**\n * Steer when a model emits a textual tool call instead of a provider-native\n * tool call. Execution of parsed text calls is intentionally not part of this\n * public contract.\n */\n textToolCallRescue?: false | {\n mode?: 'steer'\n }\n /**\n * Steer when the model calls a tool name that is not registered. The unknown\n * tool result remains an error; this only queues a bounded follow-up nudge\n * with closest advertised tool-name suggestions.\n */\n unknownToolRecovery?: false | {\n mode?: 'steer'\n maxSuggestions?: number\n maxNudges?: number\n message?: string | ((ctx: { called: string, suggestions: readonly string[] }) => string)\n }\n /**\n * Advisory progress nudges for broad stalls that exact repeat guards do not\n * cover: several read-only turns, failed/no-op mutations, validation rejects,\n * or tool errors. All nudges are non-interrupting and off by default.\n */\n progressNudges?: false | {\n mode?: 'steer'\n readOnlyTurns?: number | false\n failedMutations?: number | false\n noopMutations?: number | false\n validationRejects?: number | false\n toolErrors?: number | false\n /**\n * Nudge after N consecutive turns that each carried exactly ONE read-only\n * tool call. Independent reads shipped one per round trip pay full\n * provider latency each; the nudge steers the model to batch them into a\n * single response. Off by default — aimed at models that ignore static\n * batching doctrine (measured in production: ~1.3 calls/round-trip vs\n * ~2.5 for models that batch, tripling turn wall time).\n */\n singleCallTurns?: number | false\n readOnlyTools?: readonly string[]\n maxNudges?: number\n messages?: Partial<Record<ProgressNudgeKind, ProgressNudgeMessage>>\n }\n /**\n * Per-tool soft call budget for this run. Keyed by **canonical** tool name.\n * On the first call after the run-cumulative dispatched count for that tool\n * reaches `max`, the framework fires `onExceed`:\n *\n * - `'steer'` (default) — let the call execute, but emit a synthetic user\n * message after the turn that nudges the model away from re-calling the\n * tool. Reuses the existing post-turn steer pathway used by\n * `toolOutputBudget`. Fires `tool-budget:exceeded` with `mode: 'steer'`.\n * - `'block'` — refuse the call via `tool:gate` `block`. The model sees a\n * `Blocked: <reason>` tool result. Fires `tool-budget:exceeded` with\n * `mode: 'block'`.\n * - **Function** — `(ctx) => { mode, message }`. The consumer supplies the\n * steering / refusal text and chooses the mode dynamically.\n *\n * Counts include both real dispatches and dedup replays.\n * Excludes calls already blocked by an earlier gate (skill allow-list,\n * consumer hook). Tool dispatched by spawned subagents has its own per-run\n * counter — child counts never charge the parent.\n *\n * For MCP tools, key by the namespaced wire name (`mcp_<server>_<tool>`).\n *\n * Atomic in parallel mode: the middleware tracks its own per-tool\n * approval counter, incremented synchronously at gate-time. A\n * 4-call parallel batch against `max: 2` will let the first 2 through\n * and refuse the rest, even though the loop's `runToolCounts` only\n * propagates between calls (not within a single batch's gate fan-out).\n *\n * Default: `undefined` (no budget enforcement).\n */\n toolBudgets?: Record<string, {\n max: number\n onExceed?: 'steer' | 'block' | ((ctx: {\n tool: string\n count: number\n max: number\n }) => { mode: 'steer' | 'block', message: string })\n /**\n * Hard ceiling for `'steer'` mode. Steer is advisory — the call still\n * executes and only a one-time nudge is queued — and models routinely\n * ignore it (observed in production: a `shell 100/100` steer followed\n * by 30+ more shell calls until the run died at the turn cap). When\n * the approved-call count reaches `hardMax`, the gate force-blocks\n * regardless of the resolved steer mode (including function-form\n * `onExceed` that returned `'steer'`), firing `tool-budget:exceeded`\n * with `mode: 'block'`.\n *\n * Pair with `repeatGuard.countBlockedCalls: true` so identical\n * retries against the hard ceiling escalate to a run abort instead\n * of burning turns.\n *\n * Ignored in `'block'` mode (already blocking at `max`). Must be\n * `> max` to be meaningful; non-positive / non-finite disables.\n * Default: `undefined` (steer never escalates).\n */\n hardMax?: number\n }>\n /**\n * Consecutive-identical-call repeat guard. Unlike {@link dedupTools} (keyed\n * on argument hash, replays/blocks per the consumer's hasher) and\n * {@link toolBudgets} (run-cumulative count regardless of payload), the\n * repeat guard watches a **run-local chain of back-to-back identical\n * tool payloads** and escalates:\n *\n * - At `blockThreshold` (default 4; `read_file` default 6)\n * consecutive identical calls: either refuse the call via `tool:gate`\n * `block` or let it execute and append a steering message, depending on\n * {@link RepeatGuardConfig.thresholdAction}. Fires\n * `repeat-guard:exceeded` with `action: 'block' | 'steer'`.\n * - At `abortThreshold` (default 8; `read_file` default 12)\n * consecutive identical calls: abort the whole run via the agent's\n * `AbortController` (the loop then fires `agent:abort`, the same path a\n * user cancel takes). A `repeat-guard:exceeded` event with\n * `action: 'abort'` fires first so consumers can distinguish a guard abort\n * from a user cancel. This is the only layer that stops a model which\n * ignores the `Blocked:` reason and keeps retrying.\n *\n * \"Consecutive\" means the next tool call in the run is the same tracked\n * tool with the same normalized payload. Any intervening tool call, tracked\n * or not, breaks the chain.\n *\n * `mode: 'window'` intentionally relaxes that definition. `mode: 'both'`\n * keeps the chain guard while also catching interleaved repeats inside\n * {@link windowSize}.\n *\n * `tools` selects which tools the guard watches. Each entry is a predicate\n * over the canonical tool name; a string entry matches that exact name. The\n * default (when {@link tools} is unset) tracks the built-in `shell` and\n * `read_file` tools — auto-approved retry / re-read loop magnets. Provide\n * your own list to widen / narrow the set; an empty list disables the guard.\n *\n * `normalize` lets you collapse semantically-identical payloads to one\n * streak key. The built-in normalizer for `shell`-shaped tools strips\n * `cd <path> &&` prefixes, trailing `& sleep N ; <verify>` tails, and short\n * flags. Return `undefined` to opt a specific call out of streak tracking\n * entirely.\n *\n * Streak state is run-local and in-memory (no session required), torn down\n * when the run ends.\n *\n * Default: `undefined` (no repeat guard). The built-in chat profiles enable\n * it with the default thresholds.\n */\n repeatGuard?: RepeatGuardConfig\n /**\n * Generic per-tool argument deduplication. Keyed by the tool's **canonical**\n * name (alias-stable). Each entry is a hasher: `(input) => string | undefined`.\n *\n * **Hasher contract** — three return values, three meanings:\n *\n * | Return | Meaning |\n * |-------------------------|------------------------------------------------------------------------|\n * | a non-empty string | Cache key for this call. Equal keys (most-recent-only, this session) |\n * | | replay the prior recorded result without re-dispatching the tool. |\n * | `undefined` | **Skip dedup for this call.** The tool runs normally; nothing recorded.|\n * | `''` / non-string | Treated identically to `undefined` (defensive: no dedup, no error). |\n *\n * The `undefined` opt-out is the way to say *\"this specific call is not\n * cacheable\"* (timestamps in input, randomness baked in, debug flags). It\n * is **not** the same as `JSON.stringify(input)` — that would dedup against\n * the verbatim input. Pick one explicitly:\n *\n * ```ts\n * // Always cache by full input — every identical re-call dedups.\n * dedupTools: { my_pure_tool: input => JSON.stringify(input) }\n *\n * // Cache by a normalized subset; non-cacheable shapes opt out.\n * dedupTools: {\n * run_query: (input) => {\n * const q = typeof input.query === 'string' ? input.query.trim().toLowerCase() : undefined\n * if (!q || q.includes('now()') || q.includes('random()')) return undefined\n * return q\n * },\n * }\n * ```\n *\n * On a hit, the previously-recorded result is replayed as the tool_result\n * without dispatching the tool. The substitution flows through `tool:gate`\n * `result`, so `tool:after` and `tool:transform` still fire.\n *\n * Error results are NEVER recorded (and therefore never replayed): a\n * failure is a state-dependent verdict, and replaying it would freeze it —\n * e.g. an `edit` rejected by `requireReadBeforeEdit` would keep failing\n * with \"has not been read\" even after the model reads the file, as long\n * as the retry hashes to the same key. Failed calls always re-execute.\n *\n * Requires a session (`createSession()`); without one, the map is a silent\n * no-op since per-session state has nowhere to live. Tools with side\n * effects or non-deterministic outputs (network, time, randomness) MUST\n * NOT be listed — there is no safety net beyond the consumer's hasher.\n *\n * For MCP tools, key by the namespaced wire name (`mcp_<server>_<tool>`).\n * Concurrency-safe siblings ({@link ToolDef.isConcurrencySafe}) in the\n * SAME assistant turn race against each other — none can dedup against\n * a sibling that started in the same batch. Unsafe tools act as barriers\n * and honor submission order within a turn, so an unsafe-but-listed tool\n * follows the cache cleanly.\n *\n * **Cache policy**: only the most recent `(hash, result)` per tool is\n * retained. Interleaved patterns (input A, input B, input A) miss on the\n * second A because B overwrote it. Sufficient for the common spam-the-\n * same-call loop; consumers needing a richer cache should hook\n * `tool:gate` directly.\n *\n * Default: `undefined` (no per-tool dedup).\n */\n dedupTools?: Record<string, DedupToolConfig>\n /**\n * Require `read_file` before `edit` / `multi_edit` on the same path.\n * Eliminates the silent-corruption failure mode where a model \"remembers\"\n * stale content and applies a substring edit against bytes that have\n * moved.\n *\n * Never-read files are refused outright. Content DRIFT (read, but the\n * bytes changed since — a formatter, codegen, an editor auto-save) is\n * anchor-tolerant: the hazard drift creates is a mis-anchored\n * `old_string`, so if the edit still resolves VERBATIM against the\n * current bytes it proceeds with a disclosure note; when resolution\n * fails — or only a fuzzy recovery matches, the one combination where\n * mis-anchoring is genuinely plausible — the drift is refused\n * (\"re-read the file\"), escalating on repeat refusals to name the\n * likely external writer. Flat drift refusals are\n * what taught models to abandon `edit` for shell one-liners that bypass\n * every guard.\n *\n * Requires a session. Off by default; turn it on for stricter eval-grade\n * runs where silent edit corruption would invalidate the result.\n *\n * Default: `false`.\n */\n requireReadBeforeEdit?: boolean\n /**\n * Count plain shell file reads toward the read-state that\n * {@link AgentBehavior.requireReadBeforeEdit} checks. After a successful\n * `shell` call, files read in full view by `cat`, `head`, `tail`, `nl` or\n * `sed -n '<range>p'` (segments joined by `&&` / `;`, an optional leading\n * `cd dir`) are recorded with their current content hash. Piped segments,\n * redirects, command substitution and anything else the parser can't\n * classify record nothing.\n *\n * Entries are marked stale for `read_file` dedup, so a later `read_file`\n * always returns the full body instead of claiming the shell output is\n * already visible. Only matters with `requireReadBeforeEdit` on: it stops\n * the gate refusing files the model demonstrably read through `shell`.\n * Not rebuilt by durable-replay read-state hydration. Shell calls made\n * from codemode scripts count too.\n *\n * Default: `true` (set `false` to make only `read_file` count).\n */\n trackShellReads?: boolean\n /**\n * Append a one-line pointer to the dedicated tool when a `shell` call does\n * work that tool covers: a pure file read (`read_file`), a pure content\n * search (`grep`), or a scripted file edit — python/perl/`sed -i`/heredoc\n * writes (`edit` / `multi_edit`). Shown only when that tool is advertised,\n * at most `maxPerKind` times per kind per session (default 2), so it fades\n * once the model has seen it.\n *\n * `true` uses the defaults; an object tunes `maxPerKind`.\n *\n * Default: off.\n */\n shellToolNudges?: boolean | { maxPerKind?: number }\n /**\n * Client-side context compaction strategy. Use this for non-Anthropic\n * providers (OSS via cerebras / openai-compat / openrouter) that don't\n * have a server-side equivalent. Anthropic users should prefer the\n * server-side `context-management-2025-06-27` beta — see\n * `AnthropicParams.contextManagement`.\n *\n * - `'off'` (default) — no client-side compaction.\n * - `'tail'` — when total tool-output bytes in the persisted history\n * exceed `compactThreshold`, replace older `tool_result` outputs with a\n * short stub, keeping the newest `compactKeepTurns` turns intact. The\n * compaction is applied to the wire-level message list only; the\n * underlying session turns are not modified.\n * - **Function** ({@link CompactFunction}) — a pluggable replacement\n * that the loop calls instead of the built-in tail elision. The\n * function receives the post-`context:transform` wire messages and\n * returns the compacted view (sync or async). Use this to plug in\n * an LLM summarizer (\"`<analysis>` / `<summary>`\" style), a custom\n * semantic-aware compactor, or anything else hosts need beyond the\n * built-ins. The framework never modifies session turns — the same\n * wire-only contract as `'tail'` applies.\n *\n * Preserved thinking (Anthropic): every wire rewrite of an earlier\n * message also invalidates each replayed thinking block after it — the\n * API drops them server-side (`provider:input-transformations`, reason\n * `prefix_binding_mismatch`) and the model answers without that\n * reasoning. The chunk-snapped frontier bounds this to once per chunk,\n * but the cost of `'tail'` on Anthropic is cache rewrite + lost\n * reasoning; server-side context management has neither.\n *\n * Default: `'off'`.\n */\n compactStrategy?: 'off' | 'tail' | CompactFunction\n /**\n * Context window (tokens) of the model serving the run, for window-relative\n * wire budgets such as {@link compactThresholdWindowFraction}. The SDK core\n * has no model catalog; chat hosts inject the picked model's window here\n * every run, independent of whether auto-compaction is enabled. Resolution\n * order in the loop: this → `autoCompact.contextWindow` → the active\n * `ModelTarget.contextWindow` → `provider.meta.contextWindow`. Does not by\n * itself enable auto-compaction.\n */\n contextWindow?: number\n /**\n * Soft byte threshold that triggers tail compaction when\n * `compactStrategy === 'tail'`. Counts the post-`context:transform` bytes\n * of `tool_result` outputs across all messages. Default: `131_072` (128\n * KiB). Ignored when compaction is off. See\n * {@link compactThresholdWindowFraction} for the context-window-aware floor.\n */\n compactThreshold?: number\n /**\n * Scale the tail-compaction threshold with the model's context window. When\n * set and the run resolved a window (`autoCompact.contextWindow`, or the\n * provider's `meta.contextWindow`), the effective threshold becomes\n * `max(compactThreshold, fraction × contextWindow × 4 bytes/token)`; with\n * no window the byte threshold stands alone.\n *\n * Why: every frontier step of tail compaction rewrites an early message on\n * the wire, which re-bills the entire suffix at the cache-write rate. On a\n * 1M-context model a 128 KiB byte threshold trips on ordinary tool-heavy\n * sessions and each step costs ~50x what keeping those bytes cached would\n * (measured: 117k-token re-bills on a 157k-token session). A fraction of\n * the window keeps small-context OSS models on the byte threshold while\n * large-context runs leave compaction to the autocompact ceiling.\n *\n * `0.25` → 1M window: ~1 MiB; 200k: ~200 KiB; 128k: 128 KiB (byte floor\n * wins). Default: `undefined` (byte threshold only).\n */\n compactThresholdWindowFraction?: number\n /**\n * Hysteresis for `compactStrategy: 'tail'`. When set (`0 < f < 1`), once\n * kept tool output exceeds the threshold the frontier trims it down to\n * `f × threshold` in one step, then waits until it exceeds the threshold\n * again, instead of advancing a chunk every few turns. The frontier is a\n * pure function of the message prefix, so appending messages never moves an\n * earlier step: each step re-bills the suffix once, and steps are spaced by\n * about `(1 - f) × threshold` of new tool output. `compactKeepTurns` stays\n * a floor. Out-of-range values are ignored. Default: `undefined` (keep the\n * largest recent suffix under the threshold, re-evaluated every turn).\n */\n compactLowWaterFraction?: number\n /**\n * Skip client-side `compactStrategy: 'tail'` elision when the run matches:\n * the serving provider's `name` is in `providers` (any provider when\n * unset) AND the active context window is at least `minContextWindow`\n * tokens (any window when unset; an unknown window never matches a set\n * minimum). Auto-compaction stays the ceiling.\n *\n * Why: on Anthropic models with preserved thinking, every stubbed\n * `tool_result` makes the API drop every later replayed thinking block, and\n * on large windows each frontier step re-bills most of the prompt.\n * Default: `undefined` (never skip).\n */\n compactSkip?: {\n providers?: readonly string[]\n minContextWindow?: number\n }\n /**\n * Number of trailing turns to leave untouched during tail compaction. The\n * most-recent `compactKeepTurns` user/assistant messages are not eligible\n * for elision so the model keeps the freshest tool context. Default: `4`.\n */\n compactKeepTurns?: number\n /**\n * Loop-native auto-compaction. When enabled, the agent loop watches each\n * turn's input usage and — once it crosses the configured fraction of the\n * effective context window — runs a real LLM summary over the older turns\n * mid-run, appends a `compact-summary` marker (+ optional file/skill\n * restoration) to the session, and continues. The next turn's wire view\n * is the summary plus the preserved tail.\n *\n * `true` enables with defaults (`threshold: 0.85`, restore on); an\n * {@link AutoCompactConfig} overrides individual knobs. Requires a window\n * source — `autoCompact.contextWindow` or `provider.meta.contextWindow` —\n * or the trigger stays inert. Reuses the same `shouldAutoCompact` policy\n * the chat hosts use.\n *\n * Distinct from `compactStrategy` (per-turn wire-only tool-output trim)\n * and from the always-on reactive `context_exceeded` recovery. Observe\n * via the `compact:start` / `compact:end` hooks.\n *\n * Default: `undefined` (off). The built-in chat profiles enable it.\n */\n autoCompact?: AutoCompact\n /**\n * Prefix every line of `read_file` output with its 1-indexed line number\n * followed by a tab (`<N>\\t<content>`) — the compact `cat -n`-style\n * format Claude Code emits. The `edit` tool strips the prefix from\n * `old_string` / `new_string` so the model can paste back a numbered\n * chunk verbatim without breaking the match.\n *\n * Set `false` to opt out — useful for callers piping `read_file` into\n * downstream parsers that don't recognize the prefix. Reads inside codemode\n * scripts skip the prefix whatever this says, since the script parses the\n * text. Per-call `read_file({ lineNumbers })` overrides both defaults.\n *\n * Default: `true`.\n */\n readLineNumbers?: boolean\n /**\n * How to handle older `read_file` results once a successful `edit` /\n * `multi_edit` / `write_file` later in the same run modified the same path.\n *\n * - `'stub'` (or legacy `true`) — replace the stale read's `tool_result`\n * with a short stub on the **wire**; persisted session turns keep the\n * original content. Reclaims the context the model would otherwise carry\n * forward \"in case it needs it\", but REWRITES THE MESSAGE PREFIX at the\n * stale read's position — a prefix cache invalidates everything from that\n * point, so each newly-stale read re-bills the conversation at the\n * cache-write rate. Measured on a 129-turn production trace: 13\n * invalidations, ~0.25M re-billed chars. On Anthropic the same rewrite\n * also drops every replayed thinking block after the stale read\n * (preserved-thinking prefix binding; surfaced as\n * `provider:input-transformations`), so each stub costs reasoning too.\n * Prefer `'notice'` there.\n *\n * - `'notice'` — append-only. The stale read keeps its bytes; instead, a\n * `<system-reminder>` naming the stale path is appended to the mutating\n * tool's own result the moment the mutation succeeds. Nothing earlier in\n * the transcript ever changes, so the prompt cache never breaks. The\n * trade: stale file bodies stay in context (billed at the ~0.1x\n * cache-read rate) and the transcript reaches compaction thresholds\n * sooner. On the same trace this halves the remaining cache spend —\n * keeping stale bytes cached is cheaper than re-billing the conversation\n * to delete them unless the run outlives ~12.5x(prefix/staleBytes) turns.\n *\n * - `false` / `undefined` — off: no stubs, no notices.\n *\n * Detection is identical and conservative in both modes — only a\n * tool_result confirming success (`Edited …`, `Created …`, `Updated …`)\n * marks prior reads stale. Failed edits and `No change needed` write_file\n * calls do NOT. Write safety never depends on this knob:\n * `requireReadBeforeEdit` compares content hashes at edit time.\n *\n * Default: `false`. The built-in chat profiles leave it off.\n */\n elideStaleReads?: boolean | StaleReadPolicy\n /**\n * Age base64 media (top-level `image` / `document` blocks AND image /\n * document parts nested in `tool_result` outputs) off the **wire** once it\n * is older than the last `mediaKeepTurns` messages. The aged-out block is\n * replaced with a short text marker carrying a re-resolve handle\n * (`attachment://<hash>`); the persisted session keeps the original bytes\n * untouched, so the model can pull any image back on demand with\n * `read_file path=\"attachment://<hash>\"`.\n *\n * This is the long-run de-bloat lever: a screenshot or pasted image is only\n * carried as base64 for a few recent turns instead of riding the prompt\n * forever. Distinct from `compactStrategy: 'tail'`, which only stubs\n * `tool_result` *text* and never counts (or trims) media bytes.\n *\n * The frontier is chunk-snapped for prompt-cache stability — media drops\n * out in discrete jumps, not one block per turn. `0` ages out ALL media;\n * `undefined` (default) disables the pass entirely (media rides every turn).\n *\n * Prefer {@link AgentBehavior.mediaBudget} on prompt-cached providers: each\n * step here rewrites an earlier message, and on a long session that re-bills\n * the whole conversation at the cache-write rate — far more than keeping a\n * ~1.6k-token screenshot as a cache read for the rest of the run.\n *\n * Takes precedence over `mediaBudget` when both are set at the same layer\n * (a run's `behavior` overrides the agent's as a pair), so an explicit\n * window keeps working on top of profiles that default to the budget.\n *\n * Preserved thinking (Anthropic): each age-out step rewrites an earlier\n * message, which drops every replayed thinking block after the frontier\n * server-side (`provider:input-transformations`). Same trade as\n * `compactStrategy: 'tail'` — once per chunk, not per turn.\n *\n * Default: `undefined` (off).\n */\n mediaKeepTurns?: number\n /**\n * Keep media inline and age it out only near the provider's hard request\n * limits. While the inline image / document count stays ≤ `maxItems` and\n * their base64 bytes ≤ `maxBytes`, nothing is touched. Past either, the\n * OLDEST media ages out (same recoverable `attachment://<hash>` marker as\n * {@link AgentBehavior.mediaKeepTurns}) in steps of half that limit, so the\n * prefix is rewritten once per half a budget of new media instead of once\n * per image. Stateless: the aged set is a pure function of the message\n * list, so it only ever grows within a run.\n *\n * Images still wider or taller than 2000 px (formats\n * {@link AgentBehavior.maxImageEdge} can't re-encode) keep the\n * `mediaKeepTurns`-style 8-message window: once a request carries more than\n * 20 images, Anthropic rejects the whole request if any one exceeds 2000 px.\n *\n * `maxItems` is lowered to the provider's\n * `capabilities.maxMediaPerRequest` when that is smaller (Bedrock: 20).\n * Ignored when `mediaKeepTurns` is set.\n *\n * Default: `undefined` (off). The built-in chat profiles, `runHeadless`\n * and the Restate start runner use `{ maxItems: 80, maxBytes: 20 MiB }`.\n */\n mediaBudget?: MediaBudget\n /**\n * Downscale inline PNG images whose longest edge exceeds this many pixels,\n * once, when they enter the session (tool results and prompt images). The\n * persisted turn holds the shrunk bytes, so the prompt cache sees the same\n * prefix on every request. Other formats and PNGs the built-in decoder\n * doesn't handle (interlaced, 16-bit) pass through untouched.\n *\n * `2000` is the largest edge Anthropic accepts once a request carries more\n * than 20 images, so media can stay inline without that rejection. Claude\n * 4.7+ models see up to 2576 px and older ones 1568 px, so it keeps most of\n * the detail either way while cutting upload size.\n *\n * Default: `undefined` (off). The built-in chat profiles, `runHeadless`\n * and the Restate start runner use `2000`.\n */\n maxImageEdge?: number\n /**\n * Separator used for provider-facing MCP tool names.\n *\n * Internal/canonical names stay `mcp_<server>_<tool>` for sessions, hooks,\n * budgets, and dispatch. This option only controls the wire name shown to\n * the model via generated aliases. Explicit `toolAliases` still win.\n *\n * - `'_'` (default): `mcp_server_tool`\n * - `'__'`: `mcp__server__tool`\n */\n mcpToolNameSeparator?: '_' | '__'\n /**\n * Tool disclosure strategy. Controls whether the model sees every tool's\n * full `inputSchema` in its tool list every turn (\"eager\") or whether MCP\n * tools are advertised as a name+description catalog in the system prompt\n * and only get full schemas after being surfaced via the `tool_search`\n * native tool (\"lazy\" / progressive disclosure).\n *\n * Native tools (those passed to `createAgent({ tools })`) and skill tools\n * are always eager — they are core to the agent and cheap. Only MCP tools\n * are eligible for lazy disclosure.\n *\n * When `'lazy'`, the agent:\n * - Appends a `<searchable_tools>` section to the system prompt listing\n * every MCP tool by `name` + `description` only (no `inputSchema`).\n * - Auto-injects a `tool_search` native tool (opt out via\n * {@link AgentBehavior.toolSearch}) the model uses to load schemas on\n * demand. Surfaced tools persist for the rest of the run.\n * - Rebuilds the wire-level tool list each turn in two phases: every\n * initially-eager tool in registry order (the cache-stable prefix),\n * then every dynamically-unlocked tool in **unlock order** at the\n * tail (`toolSearch.mode: 'unlock'`).\n *\n * Trade-off of unlock mode: providers hash the request prefix as\n * `tools → system → messages`, so ANY change to the tools array — even a\n * tail append — re-bills the system prompt and every message. Each\n * discovery wave therefore costs one full prompt-cache rewrite; on a long\n * conversation that dwarfs the schema bytes lazy disclosure saved.\n * `toolSearch.mode: 'dispatch'` (the default) removes the rewrite\n * entirely: surfaced tools are invoked through a fixed `call_tool`\n * envelope and the tools array never changes. See\n * {@link AgentBehavior.toolSearch}.\n *\n * Default: `'eager'`.\n */\n toolDisclosure?: 'eager' | 'lazy'\n /**\n * Fine-grained config for the `tool_search` tool auto-injected when\n * {@link AgentBehavior.toolDisclosure} is `'lazy'`. No-op in eager mode.\n *\n * - `tool: false` — opt out of the auto-injection entirely. Use when the\n * host wants to ship a custom discovery tool. Note that the catalog\n * text drops the call-to-action prose in this case so the model isn't\n * pointed at a non-existent tool.\n * - `limit` — default cap on results returned per `tool_search` call when\n * the model omits the parameter. Default: `20`.\n * - `mode` — how a surfaced tool becomes callable:\n * - `'unlock'`: its schema is appended to the wire-level tools array on\n * the next turn. Simple, but every discovery wave changes the tools\n * array and rewrites the whole prompt cache. On Anthropic it also\n * invalidates every replayed thinking block (preserved-thinking\n * prefix binding), so the model loses its earlier reasoning at each\n * wave.\n * - `'dispatch'`: the tools array stays byte-stable for the run. A\n * fixed `call_tool { name, arguments }` native tool is added to the\n * eager set; `tool_search` still returns the schema into the\n * transcript and the model calls the tool through the envelope. The\n * loop unwraps it before dispatch, so gating, argument validation\n * (against the inner tool's schema), hooks, telemetry and transcript\n * events all see the inner tool exactly as with a direct call.\n *\n * Note on host-defined `tool_search`: a tool the host registers under the\n * name `tool_search` (or under any alias whose canonical is `tool_search`)\n * will shadow the auto-injected one — the catalog text will point at the\n * host's wire name, but driving the unlock flow requires either using\n * `createToolSearchTool({ catalog, unlocked })` from `tools/tool-search`\n * (which internally mutates the unlock set) or fully opting out via\n * `toolSearch.tool: false` and treating discovery as a host-side concern.\n * A bare host tool that doesn't touch the unlock set will not advance the\n * lazy disclosure state and the hard gate will keep refusing lazy calls.\n *\n * Default: `undefined` (auto-inject with the default limit). `mode`\n * unset resolves to `'dispatch'` on every provider; set `'unlock'`\n * explicitly to append surfaced tools instead.\n */\n toolSearch?: {\n tool?: false\n limit?: number\n mode?: 'unlock' | 'dispatch'\n /**\n * How the system prompt lists lazy tools. `'full'` (default) gives every\n * tool's description; `'names'` lists names by server and tells the\n * model to call the discovery tool for details, which shrinks the prompt\n * when many MCP tools are connected. Ignored without a discovery tool\n * (`tool: false`) and while codemode is on (`codemode.catalog` decides).\n */\n catalog?: 'full' | 'names'\n }\n /**\n * Opt in to the `codemode` tool: the model writes JavaScript that calls the\n * run's other tools from a QuickJS sandbox, and only the script's output\n * enters the context (see `src/tools/codemode/`). Needs the optional peer\n * dependency `quickjs-wasi`.\n *\n * - `true` / `{ mode: 'on' }`: `codemode` is added next to the usual tools.\n * - `{ mode: 'only' }`: the native tools scripts can call leave the wire\n * and are declared inside the `codemode` description instead (within\n * `inlineBudget` estimated tokens, default 3000), and the injected\n * `tool_search` / `call_tool` are dropped — scripts discover MCP tools\n * with `searchTools()`.\n * - `catalog`: how the system prompt lists lazy (MCP) tools while codemode\n * is on. `'names'` (default) lists names by server, since scripts find\n * and describe tools with `searchTools()` / `describeTool()`; `'full'`\n * keeps every tool's description.\n * - `directCallNotes`: in `'only'` mode, a direct call to a tool declared for\n * scripts still runs, and its text result gets one line naming the script\n * form (at most twice per tool in a conversation; never on `read_file` or\n * JSON results). `false` leaves results as the tools return them.\n * Default: `true`.\n *\n * Not injected when `AgentRunOptions.tools` replaces the registry, or when\n * the host registers its own `codemode`. Default: off.\n */\n codemode?: boolean | { mode?: 'on' | 'only', inlineBudget?: number, catalog?: 'names' | 'full', directCallNotes?: boolean }\n /**\n * Render the per-server `instructions` payload from the MCP `initialize`\n * handshake as a `# MCP Server Instructions` section appended to the\n * system prompt. Each server may return free-form guidance (e.g. \"the\n * database is already provisioned, use `apply_migration` directly\")\n * that materially changes how the model uses the server's tools.\n *\n * Matches the Claude Code SDK's default. Without this, models often\n * try to bootstrap state the server already advertises as ready —\n * e.g. running `npx some-cli init` in a loop when the MCP server's\n * instructions would have said \"skip the init, use `apply_migration`\".\n *\n * The section is built once per run and embedded into the system\n * prompt next to the skills + searchable-tools catalogs, so it rides\n * the same system-prompt cache breakpoint. Server insertion order\n * matches `mcpServers` config order for byte-stable output.\n *\n * Only eager-connect servers contribute. `lazyConnect: true` servers\n * are excluded by design — pulling their instructions would force the\n * connection the host asked to defer. Empty / whitespace-only payloads\n * are normalized to \"no instructions\" so a server that returns `\"\"`\n * doesn't produce a stub heading.\n *\n * Default: `true`. Set `false` for paranoid embedders that want full\n * control of the system prompt or need to avoid leaking server\n * advisories into untrusted contexts.\n */\n surfaceMcpInstructions?: boolean\n /**\n * Hard cap on consecutive Anthropic `pause_turn` recoveries within a\n * single run. Anthropic 4.6+ may return `stop_reason: 'pause_turn'`\n * mid-turn for server-side reasons; the loop injects a \"Please continue.\"\n * user message and re-enters. With some 4.6 streaming edge cases the\n * model returns empty pauses indefinitely, which without this cap spins\n * forever burning tokens (the host's only escape was an external abort).\n *\n * When the consecutive-empty-pause counter reaches this value, the loop\n * exits cleanly. The last turn's `TurnUsage.finishReason` stays\n * `'pause'` so consumers can detect pause exhaustion from `stats`. The\n * `agent:abort` hook does NOT fire — this is graceful termination, not\n * user cancel.\n *\n * Only turns with no tool calls, no text, AND no thinking count toward\n * the cap (the case Claude Code's v2 SDK collapses to `stop` outright).\n * A pause turn with text resets the counter — productive turns are\n * never penalised.\n *\n * Defaults to `0` (UNLIMITED) — empty pauses are vanishingly rare and\n * silently ending a live trajectory is worse than the tail risk of a spin.\n * Set a positive integer to re-arm the cap.\n */\n maxConsecutivePauseTurns?: number\n /**\n * Cap on consecutive refusal recoveries within a single run. A turn the\n * provider ends with `finishReason: 'content-filter'` (e.g. Anthropic\n * `stop_reason: refusal`) and no tool calls would otherwise end the run\n * silently mid-task — an empty assistant turn and a \"completed\" status\n * that reads as a stalled session. The loop injects a synthetic continue\n * (marked via `SessionTurn.injected` so replays render it as a notice, not\n * a user prompt) and re-enters; refusals are usually transient.\n *\n * Every resume re-bills the full input context, so keep the cap small.\n * When consecutive refusal resumes reach it, the loop exits cleanly with\n * the last turn's `TurnUsage.finishReason` still `'content-filter'` so\n * consumers can surface the refusal from `stats`.\n *\n * NOTE: semantics deliberately differ from\n * {@link AgentBehavior.maxConsecutivePauseTurns} — `0` DISABLES the\n * recovery entirely (a refusal ends the run immediately); unlimited\n * refusal retries are never offered.\n *\n * Default: `3`.\n */\n maxConsecutiveRefusalResumes?: number\n /**\n * Auto-resume on output-cap truncation. When `true`, a turn that hits\n * `max_tokens` (`finishReason: 'length'`) without completing a tool call\n * injects a synthetic \"Please continue.\" and re-enters the loop so the model\n * finishes its output, instead of the run silently ending mid-trajectory.\n * The loop bounds consecutive resumes internally so a stuck model can't loop\n * forever. Off by default; pairs with the model-driven `maxTokens` envelope\n * that already prevents most truncation. Context-window exhaustion (also\n * surfaced as `'length'`) is absorbed by auto-compaction on the resumed turn.\n */\n resumeOnMaxTokens?: boolean\n /**\n * Cap on consecutive `resumeOnMaxTokens` resumes. Every resume re-bills the\n * full input context and can re-spend the entire output budget, so the cap\n * is what turns a stuck model from an unbounded burn into a bounded one.\n * When consecutive length resumes reach it, the loop exits cleanly with the\n * last turn's `TurnUsage.finishReason` still `'length'` so consumers can\n * surface the truncation from `stats`.\n *\n * Only bounds turns that produced visible text: a resumed turn with no text\n * and no tool calls (a pure reasoning spin that restarts from scratch every\n * resume) is bounded separately and much tighter — after 2 consecutive such\n * turns the run fails with {@link AgentLengthResumeExhaustedError} instead\n * of silently completing.\n *\n * NOTE: semantics match {@link AgentBehavior.maxConsecutiveRefusalResumes} —\n * `0` DISABLES the recovery entirely (a truncated turn ends the run\n * immediately, as if `resumeOnMaxTokens` were off); unlimited resumes are\n * never offered.\n *\n * Default: `8`.\n */\n maxConsecutiveLengthResumes?: number\n /**\n * Persist completed turns to the session store **incrementally**, after\n * every `turn:after` / `tool-results:after`. This is session-turn\n * persistence — unrelated to the `persist*` knobs above, which spill large\n * tool-result blobs to disk.\n *\n * - `true` (default) — append each turn to the store as it completes. A\n * tool_use block is always durable alongside its matching tool_result, so\n * a mid-run process death resumes cleanly from the last completed turn.\n * Maximum crash-recovery granularity.\n * - `false` — skip the per-turn append hooks; turns are persisted **once at\n * run end** via the flush that already runs on every exit path (complete /\n * abort / error). The seeded user prompt is still persisted up-front, so a\n * crash keeps the prompt but loses the in-flight run's generated turns.\n *\n * Why turn it off: on a session store whose `appendTurns` re-serializes the\n * whole session per call (file-map, remote), per-turn persistence is\n * O(turns) writes of an O(turns)-sized payload — quadratic I/O over a long\n * run. Hosts that save at the request boundary anyway (and accept the\n * coarser crash-recovery granularity) get O(1) writes per run by disabling\n * it. No effect on sessionless runs (nothing to persist) and the store stays\n * attached either way, so `loadSession` / resume / explicit `session.save()`\n * are unaffected.\n *\n * Default: `true`.\n */\n persistTurns?: boolean\n /**\n * Persist large `tool_result` outputs to disk and replace the in-message\n * content with a `<persisted-output>` stub (preview + filesystem path).\n * When the post-`tool:transform` byte size of a tool's result exceeds\n * this threshold, the framework writes the full payload to\n * `<persistDir>/<callId>.txt` and substitutes a fixed-format stub so the\n * model sees a 2 KiB preview plus the path it can `read_file`.\n *\n * The substitution happens at emit time (just after `tool:transform` runs)\n * and the stub flows into `session.turns` directly — so every subsequent\n * turn re-emits the same bytes, keeping the prompt-cache prefix stable.\n *\n * Set `0` / `undefined` to disable. Built-in chat profiles default to\n * `8192`. Tools listed in {@link AgentBehavior.persistExcludeTools} bypass\n * regardless of size — typically because their output is intentionally\n * short or persisting would be circular (e.g. `read_file`).\n *\n * Requires {@link AgentBehavior.persistDir} to be set; without a target\n * directory the framework silently skips persistence (no throw, no\n * substitution) since there's nowhere to write the blob.\n *\n * Default: `undefined` (off).\n */\n persistThreshold?: number\n /**\n * Canonical tool names to exclude from disk persistence regardless of\n * output size. The framework bypasses persistence for any tool whose\n * canonical name appears in this list — useful for tools whose results\n * are intentionally part of the prompt (`skills_use`), short envelopes\n * (`tool_search`, `present_plan`, `ask_user`), or where persistence\n * would be circular (`read_file`, whose pagination already serves the\n * same use case).\n *\n * Default: `undefined` (no exclusions). The chat-layer built-in profiles\n * set their own list — see `src/chat/agents.ts`.\n */\n persistExcludeTools?: readonly string[]\n /**\n * Directory under which persisted tool-result blobs land. Each call's\n * payload is written to `<persistDir>/<callId>.txt` (one file per\n * `tool_use` id, atomic via write-then-rename).\n *\n * The chat layer resolves this to `<userDir>/tool-results/<sessionId>/`\n * at session activation; SDK consumers pass an absolute path. Required\n * when {@link AgentBehavior.persistThreshold} is non-zero — when unset\n * the framework treats persistence as disabled.\n *\n * Default: `undefined`.\n */\n persistDir?: string\n /**\n * Soft byte-cap on the cumulative size of persisted blobs under\n * {@link persistDir} for THIS session. After every successful blob\n * write the framework sums the directory's `*.txt` payloads and, if\n * the total exceeds this value, removes oldest-first by mtime until\n * the remainder is at or below the cap.\n *\n * The just-written blob is never evicted in the same sweep (its mtime\n * is the newest), so a single oversize result still lands and gets\n * pointed at by its `<persisted-output>` stub — the LRU is a steady-\n * state housekeeping mechanism, not a per-call admission gate.\n *\n * Long unattended runs can otherwise grow `<userDir>/tool-results/<sessionId>/`\n * without bound; session-delete cleanup runs on demand, not on\n * schedule.\n *\n * Default: `undefined` (no cap). Set `0` to disable explicitly; the\n * eviction step is a no-op for non-positive / non-finite values.\n */\n persistMaxBytes?: number\n /**\n * Absolute directory where the `shell` tool's background mode (the\n * `run_in_background: true` flag) appends output log files. One file\n * per task: `<tasksDir>/<task-id>.<context-timestamp>.log`. The model\n * gets the absolute path back in the tool result and reads incremental\n * output via the regular `read_file` tool.\n *\n * The chat layer resolves this to `<userDir>/sessions/<sessionId>/tasks/` at\n * session activation; SDK consumers pass an absolute path. When unset,\n * `shell({ run_in_background: true })` surfaces a clean error to the\n * model so the framework doesn't silently fall back to a path the user\n * didn't pick.\n *\n * Default: `undefined`.\n */\n tasksDir?: string\n /**\n * Steering threshold for blocking `shell` calls, in milliseconds.\n *\n * When a foreground `shell` call requests a `timeout` ABOVE this value\n * and background mode is actually available (schema exposes\n * `run_in_background`, `tasksDir` is set, the execution context\n * supports it), the call is refused with a steering message telling\n * the model to re-issue with `run_in_background: true` (and\n * `wait_task` when the next step depends on the result). This kills\n * the \"blocking shell pinned to a transport timeout\" footgun — a model\n * that picks a 600–900s blocking wait (e.g. `while pgrep …; do sleep`)\n * holds its whole turn hostage to one exec call regardless of how good\n * the background-mode prompt is.\n *\n * When background mode is NOT available the blocking call proceeds\n * unmodified — refusing it would leave the model with no alternative.\n *\n * Default: `undefined` (off). Set `0` / negative to disable explicitly.\n */\n shellLongTimeoutMs?: number\n /**\n * Hide the built-in `shell` tool's `run_in_background` field + the\n * background-mode paragraphs in its description, even when\n * {@link AgentBehavior.tasksDir} is set. The model never sees the flag\n * and won't try to use it.\n *\n * Combined with the implicit gate on `tasksDir` (background mode also\n * auto-hides when `tasksDir` is unset, since the host hasn't wired the\n * log dir), this gives two ways to opt out:\n *\n * - **Implicit**: don't set `tasksDir`. The host hasn't opted in.\n * - **Explicit**: set `disableBackgroundTasks: true`. The host has\n * `tasksDir` for some other reason (legacy, fixture) but doesn't\n * want the model spawning background work.\n *\n * The `'non-durable'` form is capability-aware: backgrounding is\n * disabled only when the execution context's\n * `capabilities.detachedTasks` is NOT `'durable'`. It is the default on a\n * replay-mode durable runtime (`AgentOptions.durable`), so a\n * remote context whose tasks survive worker crashes keeps background\n * mode, while host-local `ProcessContext` tasks — which would not\n * survive process death — stay disabled.\n *\n * Only applies to the framework-provided `shell` tool (identity check\n * against the exported `shell` constant). Hosts who wrap or replace\n * the shell tool own their spec — the auto-disable doesn't touch\n * tool defs the agent doesn't recognize.\n *\n * Default: `false`.\n */\n disableBackgroundTasks?: boolean | 'non-durable'\n /**\n * Byte cap on each background task's output log. Past the cap the\n * task KEEPS RUNNING; further output is dropped and a\n * `<output-truncated bytes-dropped=\"N\"/>` marker is appended to the\n * log when the task settles. Guards long unattended runs (dev\n * servers, training jobs) from filling the disk.\n *\n * Default: `undefined` (no cap).\n */\n backgroundOutputCap?: number\n /**\n * Stall watchdog for background tasks: when a task produces no output\n * for this many milliseconds, a `background:stall` hook fires and a\n * one-shot `<task-stall>` notification is queued for the model's next\n * turn — typically prompting it to inspect the log and kill a task\n * stuck at an interactive `(y/n)` prompt. The process is NOT killed.\n * The watchdog re-arms after fresh output.\n *\n * Default: `undefined` (no watchdog).\n */\n backgroundStallWatchdogMs?: number\n /**\n * Namespaced bag for host-only configuration. The framework never\n * reads these — they ride along through behavior resolution\n * (run-level keys shallow-merge over agent-level ones) and surface on\n * `ToolContext.behavior.extensions` so custom tools / hooks can read\n * host policy without the host monkey-patching `AgentBehavior` with\n * unknown top-level keys.\n *\n * Default: `undefined`.\n */\n extensions?: Record<string, unknown>\n /**\n * Fail-fast instead of repair when the pre-send pairing pass detects\n * corruption (orphan `tool_use` / `tool_result`, duplicate ids,\n * compaction-stranded blocks). Throws {@link AgentToolPairingError} from\n * the next `agent.run()` turn carrying the structured repair list the\n * loop would have performed.\n *\n * Use case: training-data collectors that must reject any transcript\n * containing the synthetic `SYNTHETIC_TOOL_RESULT_PLACEHOLDER` rather\n * than ship poisoned data to the fine-tuning pipeline. User-facing chat\n * sessions should leave this off (the repair-on-the-fly behavior is the\n * point of the pass).\n *\n * Telemetry note: `pairing:repair` still fires for every repair before\n * the throw, so observability handlers see exactly what would have\n * happened.\n *\n * Default: `false`.\n */\n strictToolPairing?: boolean\n /**\n * Per-turn ceiling on pre-send pairing repairs before the run is failed\n * with {@link AgentToolPairingError}.\n *\n * The pairing pass repairs the WIRE view only — the persisted session\n * keeps its corrupted turns by design (\"don't rewrite the past\"). When a\n * provider systematically reuses tool_use ids, every turn re-heals the\n * entire history, the model never sees its own results (they were\n * stripped as orphans), and the repair count grows quadratically while\n * the run makes zero forward progress. This breaker converts that\n * invisible loop into a typed failure carrying the full repair list.\n *\n * Sizing guidance: legitimate corruption (a crash window, a single buggy\n * turn) produces a handful of repairs per turn; storms produce hundreds\n * per turn within a few turns. `200` is a safe ceiling that can't misfire\n * on healthy sessions.\n *\n * Default: `undefined` (disabled: repair silently every turn, never\n * throw). Opt in to get the breaker.\n */\n maxPairingRepairsPerTurn?: number\n /**\n * Egress allowlist for the web tools (`fetch_url`, `web_search`). When set\n * to a non-empty list, those tools refuse any request whose target host\n * isn't covered — on `fetch_url` this is checked for the initial URL AND\n * every redirect hop (alongside the SSRF blocklist); on `web_search` it\n * gates the keyless DuckDuckGo / Tavily / Brave backends so a sandboxed or\n * compliance-bound host can pin egress to approved domains.\n *\n * Matching is host-suffix based and case-insensitive: an entry `example.com`\n * matches `example.com` and any subdomain (`docs.example.com`), but not\n * `notexample.com`. Ports are ignored (match on hostname only). Leading\n * dots are tolerated (`.example.com` ≡ `example.com`).\n *\n * Default: `undefined` (no allowlist — every public host is reachable,\n * subject to the SSRF blocklist). An empty array is treated the same as\n * `undefined` so a host can't accidentally lock itself out with `[]`.\n */\n fetchUrlAllowHosts?: readonly string[]\n /**\n * Enable an in-process response cache for the `fetch_url` tool, with this\n * TTL in milliseconds. Keyed on the requested URL string. When a fan-out\n * re-fetches the same page within the window (e.g. spidering a docs site\n * across parallel tool calls), the cached body is returned without a second\n * network round-trip.\n *\n * The cache is bounded by entry count and total bytes (fixed internal caps)\n * and is **process-local** — it never persists across restarts and is not\n * shared between agents. Only successful `2xx` text responses are cached;\n * redirects, errors, and aborts are not.\n *\n * Default: `undefined` / `0` → caching disabled (every call is a fresh\n * fetch — the stateless default). Set e.g. `900_000` (15 min) to enable.\n */\n fetchUrlCacheTtlMs?: number\n}\n\nexport interface ToolBatchExecutionContext {\n toolCalls: readonly ToolCall[]\n turnId: string\n maxConcurrentTools: number\n signal: AbortSignal\n steeringQueue: readonly string[]\n isConcurrencySafe: (index: number) => boolean\n execute: (index: number) => Promise<ToolResult>\n interruptedResult: (index: number) => ToolResult\n skippedResult: (index: number) => ToolResult\n}\n\nexport type ToolBatchExecutor = (ctx: ToolBatchExecutionContext) => Promise<ToolResult[]>\n\n// ---------------------------------------------------------------------------\n// Prompt parts (multimodal input)\n// ---------------------------------------------------------------------------\n\n/**\n * One block of a multimodal user prompt.\n *\n * `agent.run({ prompt })` accepts either a plain string (treated as a single\n * text part) or an array of these parts for multimodal inputs.\n *\n * `document` parts are routed per provider: PDF-style mime types are sent as\n * native document blocks when the provider supports them; text documents are\n * inlined as text with an attachment header. Providers that cannot handle an\n * image or document throw early.\n */\nexport type PromptPart\n = | PromptTextPart\n | PromptImagePart\n | PromptAudioPart\n | PromptVideoPart\n | PromptDocumentPart\n\n/** Text part of a multimodal prompt. */\nexport interface PromptTextPart {\n /** Block discriminator, always `'text'`. */\n type: 'text'\n /** Text of the prompt part. */\n text: string\n}\n\n/** Image attachment in a multimodal prompt. */\nexport interface PromptImagePart {\n /** Block discriminator, always `'image'`. */\n type: 'image'\n /** IANA media type (e.g. `image/png`, `image/jpeg`) */\n mediaType: string\n /** Base64-encoded payload */\n data: string\n /** Optional display name */\n name?: string\n}\n\n/** Audio attachment in a multimodal prompt. */\nexport interface PromptAudioPart {\n /** Block discriminator, always `'audio'`. */\n type: 'audio'\n /** IANA media type (e.g. `audio/wav`, `audio/mpeg`) */\n mediaType: string\n /** Base64-encoded payload (no data-URI prefix) */\n data: string\n /** Optional display name */\n name?: string\n}\n\n/** Video attachment in a multimodal prompt. */\nexport interface PromptVideoPart {\n /** Block discriminator, always `'video'`. */\n type: 'video'\n /** IANA media type (e.g. `video/mp4`, `video/webm`) */\n mediaType: string\n /** Base64-encoded payload (no data-URI prefix) */\n data: string\n /** Optional display name */\n name?: string\n}\n\n/** Document attachment in a multimodal prompt. */\nexport interface PromptDocumentPart {\n /** Block discriminator, always `'document'`. */\n type: 'document'\n /** IANA media type (e.g. `application/pdf`, `text/plain`) */\n mediaType: string\n /** Either a base64-encoded payload (`encoding: 'base64'`) or raw text (`encoding: 'text'`) */\n data: string\n /** How `data` is encoded: base64 bytes or raw text. */\n encoding: 'base64' | 'text'\n /** Optional display name used in attachment headers */\n name?: string\n}\n\n// ---------------------------------------------------------------------------\n// Canonical message format (used throughout the agent system)\n// ---------------------------------------------------------------------------\n\n/**\n * A single block of structured tool-result content.\n *\n * MCP servers can return a mix of text, image, audio, video, and resource blocks.\n * Tools return `string` for the common text-only case or `ToolResultContent[]` when\n * they need to preserve non-text content (e.g. screenshots from a browser MCP).\n *\n * Providers that support native multi-part tool results (Anthropic, OpenAI Codex via\n * pi-ai) route image blocks into their wire format verbatim; OpenAI-compat providers\n * route them via a companion-user-message fallback when the underlying model/endpoint\n * does not accept images inside tool-role messages.\n */\nexport type ToolResultContent\n = | ToolResultTextContent\n | ToolResultImageContent\n | ToolResultImageRefContent\n | ToolResultAudioContent\n | ToolResultVideoContent\n | ToolResultDocumentContent\n | ToolResultDocumentRefContent\n\n/**\n * Structured side-effect outcome for mutating tools (write_file, edit,\n * multi_edit, and custom tools that opt in via `ToolContext.reportOutcome`).\n *\n * Surfaced on `tool:after.outcome` so hosts can build no-op-resistant loop\n * guards (\"did this edit actually change bytes?\") WITHOUT string-matching the\n * result prose (`Created `/`Updated `/`No change needed` …), which is fragile\n * across versions. Absent on `tool:after` for read-only tools and any tool\n * that never calls `reportOutcome`.\n *\n * - `created` — a new file/resource was written.\n * - `updated` — an existing file/resource changed (full overwrite differed).\n * - `edited` — a surgical in-place change applied at least one replacement.\n * - `noop` — the call succeeded but changed zero bytes (idempotent write,\n * replacement that produced identical content).\n * - `failed` — the mutation was attempted but did not take effect (error,\n * stale-read rejection, no-match). Pairs with `isError` on the\n * wire result; reported explicitly so guards don't have to infer.\n */\nexport type ToolOutcome = 'created' | 'updated' | 'edited' | 'noop' | 'failed'\n\n/** JSON scalar: a string, number, boolean or `null`. */\nexport type JsonPrimitive = string | number | boolean | null\n/** Any value that survives `JSON.stringify` and `JSON.parse` unchanged. */\nexport type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue }\n/**\n * Provider/model-invisible metadata associated with a tool result. Hooks use\n * this to persist telemetry about transforms (compression, redaction, etc.)\n * without adding model-visible text to the result body. Hooks can read it; the\n * model never sees it.\n */\nexport type ToolResultMetadata = Record<string, JsonValue>\n\n/** Result of {@link sanitizeToolResultMetadata}. */\nexport interface ToolResultMetadataSanitizeResult {\n /** The JSON-safe metadata. Absent when nothing survived sanitizing. */\n metadata?: ToolResultMetadata\n /** One message per value that was stringified, dropped or truncated. */\n warnings: readonly string[]\n}\n\nconst DEFAULT_TOOL_RESULT_METADATA_MAX_BYTES = 16_384\nconst MAX_METADATA_DEPTH = 8\n\n/**\n * Coerce an arbitrary record into {@link ToolResultMetadata}. Non-finite numbers,\n * functions, symbols and `undefined` are dropped. `bigint` values become strings.\n * Cyclic values and values nested deeper than 8 levels are dropped. When the\n * JSON form exceeds `maxBytes`, whole top-level keys are dropped. Each of these\n * changes adds an entry to `warnings`.\n *\n * @param input - Record to sanitize.\n * @param maxBytes - UTF-8 byte cap for the JSON form of the result. Defaults to 16384.\n * @returns The sanitized `metadata` (absent when empty) and the `warnings`.\n */\nexport function sanitizeToolResultMetadata(\n input: Record<string, unknown>,\n maxBytes = DEFAULT_TOOL_RESULT_METADATA_MAX_BYTES,\n): ToolResultMetadataSanitizeResult {\n const warnings: string[] = []\n const out: ToolResultMetadata = {}\n const seen = new WeakSet<object>()\n for (const [key, value] of Object.entries(input)) {\n const sanitized = sanitizeMetadataValue(value, { seen, warnings, depth: 0 })\n if (sanitized !== undefined)\n out[key] = sanitized\n }\n const bounded = enforceMetadataSizeLimit(out, maxBytes, warnings)\n return Object.keys(bounded).length > 0\n ? { metadata: bounded, warnings }\n : { warnings }\n}\n\nfunction sanitizeMetadataValue(\n value: unknown,\n ctx: { seen: WeakSet<object>, warnings: string[], depth: number },\n): JsonValue | undefined {\n if (value === null)\n return null\n if (typeof value === 'string' || typeof value === 'boolean')\n return value\n if (typeof value === 'number')\n return Number.isFinite(value) ? value : undefined\n if (typeof value === 'bigint') {\n ctx.warnings.push('bigint metadata value stringified')\n return value.toString()\n }\n if (typeof value !== 'object')\n return undefined\n if (ctx.depth >= MAX_METADATA_DEPTH) {\n ctx.warnings.push('metadata value exceeded max depth')\n return undefined\n }\n if (ctx.seen.has(value)) {\n ctx.warnings.push('cyclic metadata value dropped')\n return undefined\n }\n ctx.seen.add(value)\n try {\n if (Array.isArray(value)) {\n const arr: JsonValue[] = []\n for (const item of value) {\n const next = sanitizeMetadataValue(item, { ...ctx, depth: ctx.depth + 1 })\n if (next !== undefined)\n arr.push(next)\n }\n return arr\n }\n const obj: { [key: string]: JsonValue } = {}\n for (const [key, child] of Object.entries(value)) {\n const next = sanitizeMetadataValue(child, { ...ctx, depth: ctx.depth + 1 })\n if (next !== undefined)\n obj[key] = next\n }\n return obj\n }\n finally {\n ctx.seen.delete(value)\n }\n}\n\nfunction enforceMetadataSizeLimit(\n input: ToolResultMetadata,\n maxBytes: number,\n warnings: string[],\n): ToolResultMetadata {\n if (Buffer.byteLength(JSON.stringify(input), 'utf8') <= maxBytes)\n return input\n const out: ToolResultMetadata = {}\n for (const [key, value] of Object.entries(input)) {\n out[key] = value\n if (Buffer.byteLength(JSON.stringify(out), 'utf8') > maxBytes)\n delete out[key]\n }\n warnings.push('metadata exceeded size limit and was truncated')\n return out\n}\n\n/** Text tool-result block. */\nexport interface ToolResultTextContent {\n /** Block discriminator, always `'text'`. */\n type: 'text'\n /** Text of the block. */\n text: string\n}\n\n/** Image tool-result block with inline base64 `data`. */\nexport interface ToolResultImageContent {\n /** Block discriminator, always `'image'`. */\n type: 'image'\n /** IANA media type (e.g. `image/png`, `image/jpeg`) */\n mediaType: string\n /** Base64-encoded payload */\n data: string\n /** Absent on inline blocks. */\n ref?: never\n /** Optional display name used in attachment headers */\n name?: string\n}\n\n/** Image tool-result block that carries a content reference instead of inline data. */\nexport interface ToolResultImageRefContent {\n /** Block discriminator, always `'image'`. */\n type: 'image'\n /** Opaque host-owned content reference, resolved only at provider wire time. */\n ref: string\n /** IANA media type (e.g. `image/png`, `image/jpeg`) */\n mediaType: string\n /** Optional display name used in attachment headers */\n name?: string\n /** Absent on ref blocks. The payload is resolved from `ref`. */\n data?: never\n}\n\n/** Audio tool-result block with inline base64 `data`. */\nexport interface ToolResultAudioContent {\n /** Block discriminator, always `'audio'`. */\n type: 'audio'\n /** IANA media type (e.g. `audio/wav`, `audio/mpeg`) */\n mediaType: string\n /** Base64-encoded payload */\n data: string\n}\n\n/** Video tool-result block with inline base64 `data`. */\nexport interface ToolResultVideoContent {\n /** Block discriminator, always `'video'`. */\n type: 'video'\n /** IANA media type (e.g. `video/mp4`, `video/webm`) */\n mediaType: string\n /** Base64-encoded payload */\n data: string\n}\n\n/** Document tool-result block with inline `data`. */\nexport interface ToolResultDocumentContent {\n /** Block discriminator, always `'document'`. */\n type: 'document'\n /** IANA media type (e.g. `application/pdf`, `text/plain`) */\n mediaType: string\n /** Base64-encoded payload or raw text, according to `encoding`. */\n data: string\n /** Absent on inline blocks. */\n ref?: never\n /** How `data` is encoded: base64 bytes or raw text. */\n encoding: 'base64' | 'text'\n /** Optional display name/title for providers that surface document metadata. */\n name?: string\n}\n\n/** Document tool-result block that carries a content reference instead of inline data. */\nexport interface ToolResultDocumentRefContent {\n /** Block discriminator, always `'document'`. */\n type: 'document'\n /** Opaque host-owned content reference, resolved only at provider wire time. */\n ref: string\n /** IANA media type (e.g. `application/pdf`, `text/plain`) */\n mediaType: string\n /** How the resolved payload is encoded. */\n encoding: 'base64' | 'text'\n /** Optional display name/title for providers that surface document metadata. */\n name?: string\n /** Absent on ref blocks. The payload is resolved from `ref`. */\n data?: never\n}\n\n/** Block passed to a {@link ResolveContentRef} callback. */\nexport interface ResolveContentRefBlock {\n /** Kind of content the reference points to. */\n type: 'image' | 'document'\n /** Opaque host-owned content reference to resolve. */\n ref: string\n /** IANA media type of the referenced content. */\n mediaType: string\n /** How `data` is encoded, for document blocks. */\n encoding?: 'base64' | 'text'\n /** Optional display name. */\n name?: string\n}\n\n/** Host callback that resolves a content reference to its payload. Called at provider wire time with the block that holds the `ref`. */\nexport type ResolveContentRef = (block: ResolveContentRefBlock) => Promise<{ data: string }>\n\n/**\n * Narrow an image/document block to one that carries resolved `data`, throwing\n * loudly if it still holds an unresolved `ref`.\n *\n * Wire serializers (provider formatters, the headless transcript projector)\n * call this as a defense-in-depth guard: the agent loop resolves content refs\n * upstream via the `content-ref:resolve` hook, so a surviving `ref` here means\n * a caller bypassed that path (e.g. a direct `toWireMessages()` without a\n * resolver). `target` names the wire format for a useful error.\n */\nexport function assertResolvedMediaBlock<T extends {\n type: 'image' | 'document'\n mediaType: string\n data?: string\n ref?: string\n encoding?: 'base64' | 'text'\n name?: string\n}>(block: T, target: string): asserts block is T & { data: string } {\n if (typeof block.data === 'string')\n return\n throw new Error(`Cannot project unresolved content ref \"${block.ref ?? '<missing>'}\" to ${target}.`)\n}\n\n/**\n * Render a short, human-readable marker for a document block.\n *\n * Used wherever a document attachment can't (or shouldn't) carry its raw bytes:\n * - providers without document support degrade to a `[document omitted: …]` note,\n * - the loop sanitizer strips bytes for non-capable models,\n * - Anthropic emits an `[document attached: …]` note inside `tool_result` while\n * the bytes ride along as a sibling document block.\n *\n * `reason` is the prefix text after the leading bracket (e.g. `\"document\n * omitted\"`); the helper appends the optional name, media type, and size so\n * every call site shares one format instead of re-deriving the size label.\n */\nexport function documentBlockMarker(\n doc: Pick<ToolResultDocumentContent | ToolResultDocumentRefContent, 'mediaType' | 'data' | 'ref' | 'encoding' | 'name'>,\n reason: string,\n): string {\n const sizeLabel = typeof doc.data === 'string'\n ? (doc.encoding === 'base64' ? `${doc.data.length} b64 bytes` : `${doc.data.length} chars`)\n : `ref ${doc.ref}`\n return `[${reason}: ${doc.name ? `${doc.name}, ` : ''}${doc.mediaType}, ${sizeLabel}]`\n}\n\n/**\n * Lossy flattener — converts `ToolResultContent[]` (or a plain string) to a single\n * string. Non-text blocks are replaced with markers such as\n * `[image: <media>, <n> b64 bytes]` (a `ref` block shows `ref <ref>` in place of the size).\n *\n * Use at UI boundaries where a string is required; providers that understand\n * structured content should route the array through without flattening.\n *\n * @param content - Plain string or structured tool-result blocks.\n */\nexport function toolResultToText(content: string | ToolResultContent[]): string {\n if (typeof content === 'string')\n return content\n return content\n .map((block) => {\n if (block.type === 'text')\n return block.text\n if (block.type === 'image') {\n return typeof block.data === 'string'\n ? `[image: ${block.mediaType}, ${block.data.length} b64 bytes]`\n : `[image: ${block.mediaType}, ref ${block.ref}]`\n }\n if (block.type === 'audio')\n return `[audio: ${block.mediaType}, ${block.data.length} b64 bytes]`\n if (block.type === 'video')\n return `[video: ${block.mediaType}, ${block.data.length} b64 bytes]`\n return typeof block.data === 'string'\n ? `[document: ${block.mediaType}, ${block.data.length} ${block.encoding === 'base64' ? 'b64 bytes' : 'chars'}${block.name ? `, ${block.name}` : ''}]`\n : `[document: ${block.mediaType}, ref ${block.ref}${block.name ? `, ${block.name}` : ''}]`\n })\n .join('\\n')\n}\n\n/**\n * Whether a value is a structurally valid {@link ToolResultContent} block —\n * one of the recognized `text` / `image` / `audio` / `video` / `document`\n * shapes with its required fields present and correctly typed.\n *\n * Used by {@link coerceToolResultContent} to spot malformed blocks before they\n * reach a provider wire serializer (which can throw on, e.g., an `image` block\n * with no `data` / `ref`).\n */\nexport function isToolResultContentBlock(value: unknown): value is ToolResultContent {\n if (!value || typeof value !== 'object')\n return false\n const block = value as Record<string, unknown>\n const hasMedia = typeof block.mediaType === 'string'\n const hasPayload = typeof block.data === 'string' || typeof block.ref === 'string'\n switch (block.type) {\n case 'text':\n return typeof block.text === 'string'\n case 'image':\n return hasMedia && hasPayload\n case 'document':\n return hasMedia && hasPayload && (block.encoding === 'base64' || block.encoding === 'text')\n case 'audio':\n case 'video':\n return hasMedia && typeof block.data === 'string'\n default:\n return false\n }\n}\n\n/**\n * Collapse malformed structured tool-result blocks to a text placeholder so a\n * downstream wire serializer never has to project an `{ type: 'image' }` with\n * no payload (or a foreign block shape). Plain strings and already-valid block\n * arrays pass through untouched (same array reference when nothing changed), so\n * this is a cheap no-op on the happy path.\n */\nexport function coerceToolResultContent(content: string | ToolResultContent[]): string | ToolResultContent[] {\n if (typeof content === 'string' || !Array.isArray(content))\n return content\n let changed = false\n const coerced = content.map((block): ToolResultContent => {\n if (isToolResultContentBlock(block))\n return block\n changed = true\n const type = block && typeof block === 'object' && typeof (block as { type?: unknown }).type === 'string'\n ? (block as { type: string }).type\n : 'unknown'\n return { type: 'text', text: `[dropped malformed ${type} tool-result block]` }\n })\n return changed ? coerced : content\n}\n\n/**\n * Approximate **text-budget size** of a tool output, in bytes.\n *\n * Structured attachment blocks count as the short markers produced by\n * `toolResultToText`, not as raw base64 payload bytes. The per-turn\n * `toolOutputBudget` is a readability/context-loading steering guard; a\n * vision/PDF-capable provider can receive attachment bytes without needing the\n * model to summarize them as if they were text output.\n *\n * @param content - Plain string or structured tool-result blocks.\n */\nexport function toolOutputBudgetByteLength(content: string | ToolResultContent[]): number {\n return Buffer.byteLength(toolResultToText(content))\n}\n\n/**\n * Approximate **wire payload size** of a tool output, in bytes.\n *\n * - Plain text: UTF-8 byte length.\n * - Structured content: text blocks contribute their UTF-8 byte length; image\n * blocks contribute their **base64 character length** — a proxy for the\n * serialized request-body footprint, NOT for tokens. Vision encoders\n * tokenize decoded pixels (geometry-dependent; e.g. Anthropic ≈ `w·h/750`,\n * OpenAI ≈ 85 + 170/tile), which has no meaningful relationship to base64\n * length.\n *\n * Used by the agent loop to populate `outputBytes` on `tool:after`,\n * `tool:transform`, `mcp:tool:after`, and `mcp:tool:transform` hooks so\n * consumers can size-budget tool output without re-counting bytes themselves.\n * Suitable for wire-size telemetry; the per-turn `toolOutputBudget` uses\n * `toolOutputBudgetByteLength` so attachments do not trigger text-output\n * summarization nudges by themselves. NOT a substitute for provider-side\n * context-window accounting — defer to server-side context management (e.g.\n * Anthropic's `context-management-*` beta) when token accuracy matters.\n *\n * @param content - Plain string or structured tool-result blocks.\n */\nexport function toolOutputByteLength(content: string | ToolResultContent[]): number {\n if (typeof content === 'string')\n return Buffer.byteLength(content)\n let total = 0\n for (const block of content) {\n if (block.type === 'text')\n total += Buffer.byteLength(block.text)\n else if (block.type === 'image')\n total += typeof block.data === 'string' ? block.data.length : block.ref.length\n else if (block.type === 'document')\n total += typeof block.data === 'string' ? block.data.length : block.ref.length\n else\n total += block.data.length\n }\n return total\n}\n\n/**\n * Route that minted a Gemini-wire thought signature: `google` (Gemini API),\n * `google-vertex` (Vertex AI), or the scope a `googleCompatible` backend\n * (an extension's Gemini-wire provider) tags its own with. Signatures replay\n * only to the scope that minted them; every other wire treats them as foreign.\n */\nexport type GeminiSignatureScope = 'google' | 'google-vertex' | (string & Record<never, never>)\n\n/**\n * Provider that minted a block's `signature`. Signatures are provider-bound\n * (Anthropic HMAC, OpenAI `encrypted_content`, Gemini thought signatures) and\n * are dropped or stripped on cross-provider hops to avoid 400s. Gemini's are\n * keyed by the route that issued them (see {@link GeminiSignatureScope}).\n */\nexport type SignatureProducer = 'anthropic' | 'openai' | GeminiSignatureScope\n\n/**\n * Marks a text block as a note from the harness to the model: a mode change,\n * an environment update, changed project instructions, a new effort level.\n * Notes ride a user turn after the user's own content and are never edited\n * afterwards, so announcing a change appends to the conversation instead of\n * rewriting its cached prefix. Anthropic models that accept mid-conversation\n * system messages receive notes as `role: \"system\"` messages; other\n * providers receive them as `<system-reminder>` text.\n */\nexport interface SystemNote {\n /** What the note is about, e.g. `'mode:plan'`, `'env'`, `'instructions'`, `'effort'`. Hosts dedupe on it. */\n kind: string\n /** One-line transcript label. The transcript shows the note text when it is absent. */\n label?: string\n /** Hash of the state the note announces, so a host can tell what the model was last told. */\n digest?: string\n /**\n * Effort the conversation runs at from this note on. Anthropic sends it as\n * a per-message `output_config.effort` where the model supports that, which\n * keeps the request's top-level effort and its prompt cache; otherwise the\n * Anthropic provider applies it as the top-level effort. Other providers\n * ignore it.\n */\n effort?: 'low' | 'medium' | 'high' | 'xhigh' | 'max'\n}\n\n/**\n * The sections the SDK appended to a run's fresh system prompt, exactly as\n * appended; a section is `undefined` when the run had none.\n */\nexport interface FrozenPromptSections {\n /** The skills catalog (`<available_skills>`). */\n skills?: string\n /** The searchable tools catalog (`<searchable_tools>`). */\n tools?: string\n /** The MCP server instructions (`# MCP Server Instructions`). */\n mcp?: string\n}\n\n/** See {@link AgentRunOptions.systemFreeze}. */\nexport interface SystemFreezeOptions {\n /**\n * The frozen system prompt, sent verbatim. As a function, it receives the\n * run's fresh prompt and the sections the SDK appended to it, and returns\n * the prompt to send, or `undefined` to send the fresh one (for instance\n * when the host's own base prompt changed and the frozen text is stale).\n */\n system?: string | ((fresh: string, sections: FrozenPromptSections) => string | undefined)\n /** Called with the run's fresh system prompt, and the sections the SDK appended to it, when the run sends the fresh prompt. */\n onCapture?: (fresh: string, sections: FrozenPromptSections) => void | Promise<void>\n /** Notes announcing how the fresh prompt (and the sections the SDK appended to it) differs from the frozen one. Called only when the run sends a frozen prompt. */\n notes?: (fresh: string, sections: FrozenPromptSections) => ReadonlyArray<SystemNote & { text: string }> | Promise<ReadonlyArray<SystemNote & { text: string }>>\n}\n\n/**\n * Content block of a session message: text, media, a tool call, a tool result, or thinking.\n *\n * `text`, `tool_call` and `thinking` blocks can carry a `signature`: opaque\n * state the producing API wants back with that block (a Gemini thought\n * signature, an Anthropic thinking HMAC, OpenAI encrypted reasoning), tagged\n * with its {@link SignatureProducer}. Only that provider's wire builder reads\n * it; the others map blocks field by field and never send it.\n */\nexport type SessionContentBlock\n = | { type: 'text', text: string, signature?: string, signatureProducer?: GeminiSignatureScope, systemNote?: SystemNote, origin?: TextOrigin }\n | { type: 'image', mediaType: string, data: string, name?: string, ref?: never }\n | { type: 'image', mediaType: string, ref: string, name?: string, data?: never }\n | { type: 'audio', mediaType: string, data: string, name?: string }\n | { type: 'video', mediaType: string, data: string, name?: string }\n | { type: 'document', mediaType: string, data: string, encoding: 'base64' | 'text', name?: string, ref?: never }\n | { type: 'document', mediaType: string, ref: string, encoding: 'base64' | 'text', name?: string, data?: never }\n | {\n type: 'tool_call'\n id: string\n name: string\n input: Record<string, unknown>\n /**\n * Gemini thought signature of this function call. Gemini 3 rejects a\n * tool-use step whose calls come back without theirs, so the Google\n * provider persists it here; other providers never send it.\n */\n signature?: string\n /** Route that minted `signature`. */\n signatureProducer?: GeminiSignatureScope\n }\n | {\n type: 'tool_result'\n callId: string\n /**\n * Tool output — either a plain string (text-only, the common case) or a structured\n * array of content blocks (text + image for multimodal tools such as screenshots).\n */\n output: string | ToolResultContent[]\n isError?: boolean\n metadata?: ToolResultMetadata\n /**\n * Calls this tool made through `ToolContext.callTool` (a codemode\n * script's `tools.*`), bounded. Never sent to providers: it is how a\n * reloaded transcript shows what the call did. See {@link NestedToolCallRecord}.\n */\n calls?: NestedToolCallRecord[]\n /**\n * Wall time of the call in milliseconds, as the loop measured it. Never\n * sent to providers: it is how a reloaded transcript shows how long the\n * call took. Absent on older sessions and in replay mode (a\n * durable run rebuilds its turns identically on replay).\n */\n durationMs?: number\n }\n | {\n type: 'thinking'\n text: string\n signature?: string\n /**\n * Provider that minted `signature` (see {@link SignatureProducer}). Unset\n * means legacy/unknown, most plausibly Anthropic output from before\n * producers were recorded.\n */\n signatureProducer?: SignatureProducer\n }\n | { type: 'redacted_thinking', data: string }\n | {\n /**\n * Opaque round-trip envelope for reasoning state minted by OpenRouter.\n * The gateway expects its own `reasoning_details` array echoed back\n * verbatim on the next turn so the upstream model can resume an\n * extended-reasoning chain across tool calls.\n *\n * Stored opaquely because the items are provider-bound (Anthropic HMAC\n * signatures, OpenAI `encrypted_content`, model-specific summary formats\n * — all flowing through the gateway's normalized envelope).\n */\n type: 'provider_reasoning'\n producer: 'openrouter'\n details: unknown[]\n /**\n * Model id that produced the details. Reasoning is bound to a specific\n * upstream route — a model switch on the next turn invalidates the\n * embedded signatures, so the sender drops the block on mismatch.\n */\n model?: string\n }\n | {\n /**\n * Plain reasoning text emitted by vLLM-style reasoning parsers\n * (`reasoning_content` / `reasoning`) and echoed back on assistant\n * messages for hosts such as Arcee Trinity-Thinking. The server re-wraps\n * the string in `<think>...</think>` at tokenization so the model resumes\n * its prior reasoning across tool calls.\n */\n type: 'provider_reasoning'\n producer: 'vllm'\n reasoningText: string\n /**\n * Model id that produced the reasoning. A model switch invalidates the\n * prior chain state, so the sender drops the block on mismatch.\n */\n model?: string\n }\n | {\n /**\n * Anthropic server-side tool invocation block (currently `web_search`).\n * Captured opaquely so the model's reasoning chain stays intact across\n * turns — Anthropic's docs require `encrypted_content` and\n * `encrypted_index` to be passed back on follow-up turns or citation\n * context is lost. Dropped on cross-provider sends.\n */\n type: 'server_tool_use'\n producer: 'anthropic'\n id: string\n name: string\n input: Record<string, unknown>\n }\n | {\n /**\n * Anthropic server-side tool result block. Pairs with `server_tool_use`\n * and carries the search hits Anthropic injected into the assistant\n * message. The full content array is preserved as `unknown[]` so\n * `encrypted_content` / `encrypted_index` survive a session round-trip\n * verbatim.\n */\n type: 'server_tool_result'\n producer: 'anthropic'\n toolUseId: string\n toolName: string\n content: unknown\n }\n | {\n /**\n * Compaction marker. Inserted by `compactConversation()` to replace a\n * prefix of turns with an LLM-generated summary.\n *\n * The marker lives in `session.turns` and renders in the transcript —\n * the user can still scroll back to see the original turns. From the\n * agent loop's wire-level perspective, every turn whose id appears in\n * `replacesTurnIds` is dropped, and this block's `summary` text is\n * sent to the model as a single user message in their place.\n *\n * The marker turn carries `role: 'user'` so it sits naturally at a\n * conversational boundary. Only the latest `compact-summary` block in\n * the session is honored — earlier markers are subsumed by later\n * ones (their `replacesTurnIds` are a strict prefix).\n */\n type: 'compact-summary'\n /** Turn ids the summary replaces, in chronological order. */\n replacesTurnIds: readonly string[]\n /** The summary text sent to the model in place of the elided turns. */\n summary: string\n /** Model id used to produce the summary. */\n model: string\n /** Token usage from the summary call. */\n usage: TurnUsage\n /** Unix-ms when compaction completed. */\n compactedAt: number\n }\n\n/** One message of a session conversation, as sent to the provider. */\nexport interface SessionMessage {\n /** Author of the message. */\n role: 'user' | 'assistant'\n /** Content blocks of the message, in order. */\n content: SessionContentBlock[]\n}\n\n/** One persisted turn of a session: a user prompt, an assistant response, or a system or command entry. */\nexport interface SessionTurn {\n /** Unique turn id. Provider-loop turns are minted from AgentClock.randomUUID(). */\n id: string\n /** Run that produced this turn (e.g. 'run_1') */\n runId?: string\n /**\n * Conversational role, plus `'command'` — a display-only turn emitted by a\n * `/` command's local output. `'command'` turns are persisted + rendered\n * but excluded from every model-message builder (they never reach the LLM),\n * mirroring how `'system'` turns are filtered from the wire.\n */\n role: 'user' | 'assistant' | 'system' | 'command'\n /** Content blocks of the turn, in order. */\n content: SessionContentBlock[]\n /** Token usage — only present on assistant turns */\n usage?: TurnUsage\n /** Unix timestamp (Date.now()) when the turn was created */\n createdAt: number\n /**\n * Display-only replacement for transcript rendering — the full `content`\n * still reaches the model unchanged. Set by command context notes\n * (`ctx.appendContext`) so a replayed session renders the same one-line\n * marker the live path showed (with the full note as a collapsed detail)\n * instead of duplicating bulky note bodies as a wall of user text.\n * Mirrors `SessionRun.origin.display`, at turn granularity, for turns\n * that belong to no run.\n */\n display?: string\n /**\n * Set on a `role: 'user'` turn the agent loop injected rather than the user\n * typed (a refusal retry, an output-limit or pause \"Please continue.\", a\n * tool-output-budget warning). The content still reaches the model; hosts\n * use the marker to keep the turn out of everything that treats user turns\n * as prompts (transcript bubbles, prompt matching, usage counts).\n */\n injected?: InjectedTurnReason\n /**\n * On a `role: 'assistant'` turn: model output a process death cut off,\n * rebuilt when its durable run resumed. Shown in the transcript, left out\n * of the model context; the request is sent again.\n */\n aborted?: true\n /**\n * On a `role: 'system'` turn: the conversation's frozen system prompt, which\n * `content` holds as one text block. The latest snapshot for a prompt key\n * wins. Recorded by hosts that pass {@link AgentRunOptions.systemFreeze}.\n */\n systemSnapshot?: SystemPromptSnapshot\n}\n\n/** Why the agent loop injected a user turn (see {@link SessionTurn.injected}). */\nexport type InjectedTurnReason = 'refusal-retry' | 'length-resume' | 'pause-resume' | 'tool-output-budget'\n\n/**\n * A frozen system prompt's bookkeeping, carried by a `role: 'system'` turn\n * (see {@link SessionTurn.systemSnapshot}).\n */\nexport interface SystemPromptSnapshot {\n /** Format version of the snapshot record. */\n v: 1\n /** Which composition the prompt belongs to (a host profile id, an agent name…). Omitted: the conversation's only one. */\n key?: string\n /** No prompt is frozen for `key` from this turn on (for every key when `key` is omitted): the next run captures a fresh one. */\n cleared?: boolean\n /** Digest of each section the host tracks, by section kind (`'skills'`, `'tools'`, `'mcp'`, plus the host's own). */\n digests: Record<string, string>\n /** Thinking level of the run that captured the prompt. */\n effort?: ThinkingLevel\n /** Host data stored with the snapshot; the SDK never reads it. */\n host?: Record<string, unknown>\n}\n\n/**\n * Whether a turn is sent to the model on the wire. `system` is carried\n * out-of-band and `command` is display-only `/`-command output — neither\n * reaches the LLM. This is the SINGLE decision point every turns→messages\n * builder must funnel through (see `turnsToMessages` in `loop-wire.ts`,\n * `toWireMessages`, the headless mappers, and compaction).\n *\n * The check is deliberately POSITIVE and its result type is pinned to\n * `SessionMessage['role']`, so a future non-conversational role is excluded\n * by default (fail-safe) — the class of bug that a `role !== 'system'` gap or\n * a lying `t is …` predicate would otherwise reintroduce.\n *\n * An `aborted` assistant turn (output a process death cut off, see\n * {@link SessionTurn.aborted}) is display-only too: the request is sent again.\n */\nexport function isWireTurn<T extends { role: SessionTurn['role'], aborted?: true }>(\n turn: T,\n): turn is T & { role: SessionMessage['role'] } {\n return (turn.role === 'user' || turn.role === 'assistant') && turn.aborted !== true\n}\n\n// ---------------------------------------------------------------------------\n// Agent run options\n// ---------------------------------------------------------------------------\n\n/**\n * Per-run hook registrations. Each entry can be a single handler or an array of handlers.\n * Keys are `AgentHooks` event names (loose-typed here to avoid a circular import; `agent-run.ts`\n * validates the event names at run time, and {@link AgentHookMap} is the strongly-typed form).\n */\nexport type RunHookMap = Record<string, ((ctx: any) => unknown) | ((ctx: any) => unknown)[]>\n\n/**\n * One entry of the run-level model fallback list — a model id optionally\n * pinned to a specific provider (referenced by `Provider.name` against the\n * agent's provider list). Entries without `provider` fan out across every\n * configured provider, in provider order.\n */\nexport interface ModelTargetSpec {\n /** Name of the provider to pin the model to. Fans out across every provider when omitted. */\n provider?: string\n /** Model id. */\n model: string\n /**\n * Context window (tokens) of this target's model, for hosts that size\n * auto-compaction from their own model catalog (the SDK core has none).\n * When a fallback advances onto this target, an explicit run-level\n * `autoCompact.contextWindow` is re-based onto this value so the\n * compaction trigger tracks the ACTIVE model, not the primary's window.\n * Omitted: the window only re-derives when it was provider-meta-sourced\n * to begin with.\n */\n contextWindow?: number\n}\n\n/**\n * A resolved provider+model pair the loop can dispatch a turn to. The run\n * resolves `AgentRunOptions.model` against the agent's providers into an\n * ordered list of these; `targets[0]` is the primary and the rest are\n * fallbacks tried in order when a stream fails terminally.\n */\nexport interface ModelTarget {\n /** Provider that serves the model. */\n provider: Provider\n /** Model id sent to the provider. */\n model: string\n /** Host-supplied window of this target's model — see {@link ModelTargetSpec.contextWindow}. */\n contextWindow?: number\n}\n\n/** Options for one {@link Agent.run} call. */\nexport interface AgentRunOptions {\n /**\n * Model selection for this run.\n *\n * - `string` — a model id. With a single provider the id runs on that\n * provider; with multiple providers the id is tried on EVERY provider in order\n * (fallback chain).\n * - `Array<string | ModelTargetSpec>` — an ordered fallback list. Object\n * entries pin a model to one provider by name; bare strings (and object\n * entries without `provider`) fan out across all providers.\n * - omitted — each provider's `meta.defaultModel`, in provider order.\n *\n * The run starts on the first resolved target and advances down the list\n * when a stream fails terminally (see the `model:fallback` hook). Once\n * advanced, the run stays on that target (run-sticky); the next `run()`\n * starts from the primary again.\n */\n model?: string | Array<string | ModelTargetSpec>\n /**\n * User prompt. Optional when resuming a session with existing turns.\n *\n * Accepts either a plain string (single text part) or an array of `PromptPart`s for\n * multimodal inputs (text, images, documents). See {@link PromptPart}.\n */\n prompt?: string | PromptPart[]\n /**\n * Notes for the model, appended to this run's prompt turn after the prompt\n * as text blocks marked with {@link SystemNote}. Use them to announce a\n * change (mode, environment, instructions, effort) without editing the\n * system prompt. Ignored when the run seeds no prompt turn.\n */\n systemNotes?: ReadonlyArray<SystemNote & { text: string }>\n /**\n * Keep the conversation's system prompt byte-stable across runs and agent\n * rebuilds, so provider prompt caches and replayed thinking stay valid. The\n * run composes its fresh prompt as usual (base, skills catalog,\n * searchable-tools catalog, MCP instructions), then:\n *\n * - with `system` set, sends that string instead and appends the notes\n * `notes(fresh, sections)` returns to this run's prompt turn, ahead of\n * {@link AgentRunOptions.systemNotes};\n * - otherwise sends the fresh prompt and hands it to `onCapture`, for the\n * host to persist as the conversation's frozen prompt.\n *\n * Both callbacks also get the sections the SDK appended\n * ({@link FrozenPromptSections}); the `zidane` root exports helpers\n * (`systemSnapshotTurn`, `latestSystemSnapshot`, `catalogChangeNotes`…) to\n * store snapshots and announce those sections' changes.\n * `system:transform` hooks still run per turn on top. Omitted: every run\n * sends its fresh prompt.\n */\n systemFreeze?: SystemFreezeOptions\n /** System prompt for this run. Falls back to {@link AgentOptions.system}, then to `'You are a helpful assistant.'`. */\n system?: string\n /**\n * Optional metadata describing which named sections the host baked into\n * `system` (e.g. the rendered AGENTS.md/CLAUDE.md \"Rules\" block). Purely\n * informational — used by `agent.getContextBreakdown()` to attribute system-\n * prompt tokens to a \"Rules\" bucket the agent can't otherwise isolate from\n * the opaque base string. Does not affect what's sent to the model.\n */\n contextSections?: {\n /** Rendered user-instructions / rules block text (a substring of `system`). */\n rulesBlock?: string\n /** Source files behind the rules block, for per-file breakdown rows. */\n rulesFiles?: { path: string, source: string }[]\n }\n /**\n * Thinking effort for this run. See {@link ThinkingLevel}.\n * @default 'off'\n */\n thinking?: ThinkingLevel\n /**\n * Enabled model-specific options for this run, keyed by option id (e.g.\n * `{ fast: true }` for Anthropic's fast mode). Options the active model\n * doesn't declare are silently ignored by the provider. Overrides\n * {@link AgentBehavior.modelOptions} when both are set.\n */\n modelOptions?: Record<string, boolean>\n /** Abort signal — when triggered, the run aborts the same way `agent.abort()` does */\n signal?: AbortSignal\n /**\n * Pause signal: when triggered, the run stops at the next turn boundary\n * (after the model call in flight and its tool results are saved), never\n * mid-turn, and returns with `stats.paused`. A later run on the same\n * session continues from those turns. A host uses it to restart (an\n * update) without losing work.\n */\n pauseSignal?: AbortSignal\n /**\n * How often a top-level checkpoint run (`zidane/durable`) renews its\n * session's lease, in ms. Default 60 s. Shorter: a run another process\n * took over notices sooner, and stops showing output its new owner now\n * shows (a channel run, whose reply lives in a shared conversation).\n */\n leaseRenewMs?: number\n /** Behavior overrides for this run (overrides agent defaults) */\n behavior?: AgentBehavior\n /**\n * Host data about this run's prompt (an extension `prompt` interceptor's\n * `metadata`). The agent never reads it: `run` interceptors see it on the\n * options they receive. Not persisted.\n */\n metadata?: Readonly<Record<string, JsonValue>>\n /** Tool overrides for this run. Pass {} for no tools. Omit to use agent tools. */\n tools?: Record<string, ToolDef>\n /**\n * Per-run hook registrations. Each hook is attached before the run starts and\n * detached in a finally block so handlers never leak across runs.\n *\n * Accepts either a single handler or an array (all handlers register).\n */\n hooks?: RunHookMap\n /**\n * Parent run id. Populated automatically by the `spawn` tool when the child\n * shares the parent's session; recorded on the resulting `SessionRun` so the\n * parent↔child run tree can be reconstructed from a persisted session.\n */\n parentRunId?: string\n /**\n * Tool-call id of the `spawn` call that created this run. The built-in\n * `spawn` tool populates it from its own `ToolContext.callId` for every\n * child it starts — no `persist` / shared session needed, unlike\n * `parentRunId`. Hosts that roll their own subagent (calling `agent.run`\n * with `depth` set from inside a tool) must pass it themselves; nothing\n * derives it, so `depth >= 1` alone does not imply it is set.\n *\n * Lands on every `ToolContext` / `ToolHookContext` / `McpToolHookContext`\n * of the run, so a host can attribute a subagent's tool call to the spawn\n * call that owns it — see {@link ToolHookContext.parentCallId}. Recorded\n * on the run (`SessionRun.parentCallId`), so a replay pairs the child\n * with its `spawn` call exactly.\n */\n parentCallId?: string\n /**\n * Resume a prior run's conversation: seed this run's turns from the\n * session turns tagged with this `runId` (filtered through\n * `filterUnresolvedToolUses` so a run killed mid-tool doesn't resume\n * with an orphan `tool_use`). Used by the spawn tool to continue a\n * stopped/failed subagent — the new run gets its own `runId`, with\n * `resumedFromRunId` recorded on the resulting `SessionRun` so the\n * chain stays reconstructible. Requires a session and a `prompt`\n * (the continuation instruction); throws when the referenced run has\n * no persisted turns.\n */\n resumeRunId?: string\n /**\n * Host-assigned display label persisted onto the resulting `SessionRun`\n * (`SessionRun.label`). The spawn tool passes its reserved `child-N`\n * label here so label → run resolution (spawn `resume`) and transcript\n * numbering read a stored fact instead of deriving it positionally.\n */\n runLabel?: string\n /**\n * The host's agent profile this run runs as, persisted onto the resulting\n * `SessionRun.agent` so a transcript can name it per run.\n */\n runAgent?: import('./session').SessionRunAgent\n /**\n * Zero-based subagent depth. 0 = top-level `agent.run()`, 1 = first-level\n * child spawned by a parent agent, and so on. Used by the spawn tool to\n * enforce `maxDepth` and to stamp `child:*` forwarded hook payloads.\n */\n depth?: number\n /**\n * Opaque trace-context carrier propagated from the parent agent's\n * tracer (typically a W3C `{ traceparent, tracestate }` map). The\n * child's `agent.run()` re-emits it on the `agent:start` hook so the\n * child's tracer can stitch its root span as a continuation of the\n * parent's spawn span. Empty / absent on top-level runs.\n *\n * Set automatically by the `spawn` tool when a parent's tracer wrote\n * into `SpawnHookContext.tracingContext` on `spawn:before`. SDK\n * consumers that drive subagents manually can populate this directly.\n */\n tracingContext?: Readonly<Record<string, string>>\n /**\n * Override the time + UUID source for this run only. Per-run wins over\n * agent-level ({@link AgentOptions.clock}); both fall back to\n * {@link DEFAULT_AGENT_CLOCK}. See {@link AgentClock}.\n */\n clock?: AgentClock\n}\n\n// ---------------------------------------------------------------------------\n// Agent stats\n// ---------------------------------------------------------------------------\n\n/**\n * Reason the provider gave for stopping the turn.\n *\n * - `'stop'` — natural turn end (`end_turn` / `stop_sequence`).\n * - `'tool-calls'` — model emitted tool_use blocks.\n * - `'length'` — `max_tokens` reached, or (Anthropic 4.6+) the response bumped\n * against the model's context window mid-stream\n * (`model_context_window_exceeded`). The partial response is preserved; the\n * loop emits this reason so consumers can prune/retry.\n * - `'content-filter'` — model refused.\n * - `'pause'` — Anthropic `pause_turn`: a server-side mid-turn pause for very\n * long thinking. The loop continues with a synthetic \"Please continue.\"\n * user message rather than terminating; consumers see the pause via this\n * finish reason on the prior assistant turn.\n * - `'error'` — provider classified the turn as failed.\n * - `'other'` — unknown / unmapped.\n */\nexport type TurnFinishReason = 'stop' | 'tool-calls' | 'length' | 'content-filter' | 'pause' | 'error' | 'other'\n\n/** Token usage and timing for one turn. */\nexport interface TurnUsage {\n /** Input tokens for the turn. Cache tokens are reported in `cacheRead` and `cacheCreation`. */\n input: number\n /** Output tokens for the turn. */\n output: number\n /** Tokens written to cache (Anthropic) */\n cacheCreation?: number\n /** Tokens read from cache (Anthropic) */\n cacheRead?: number\n /** Thinking/reasoning tokens used */\n thinking?: number\n /** Replayed thinking blocks the provider dropped before sampling this turn (Anthropic `input_transformations` entries of type `'thinking_dropped'`); absent when none. */\n thinkingDropped?: number\n /** Measured energy the provider reports for this request, in joules (Neuralwatt's `energy.energy_joules`); absent when the provider reports none. */\n energyJoules?: number\n /**\n * Cost in USD for this turn. Provider-reported when available\n * (OpenRouter, OpenAI via pi-ai); otherwise estimated from `modelId` ×\n * pi-ai's bundled price registry by `fillEstimatedCost` in\n * `src/providers/cost.ts`. Absent only when neither path could resolve\n * a price (unknown / unbundled model).\n */\n cost?: number\n /**\n * Provider key that served this turn, stamped by `fillEstimatedCost`\n * alongside the cost fill. Authoritative where present — downstream\n * spend attribution (e.g. the GUI usage dashboard) does not have to\n * GUESS the provider from the model id's shape, which mis-attributes\n * proxied/rebranded models (a Claude model via Bedrock ≠ Anthropic).\n * Absent on turns persisted before this field existed.\n */\n provider?: string\n /**\n * Why the model stopped this turn. Providers normalize native stop reasons to this union.\n * Absent when the provider did not surface a reason (e.g. mock turns).\n */\n finishReason?: TurnFinishReason\n /**\n * The model ID the provider ultimately used. May differ from the requested model when the\n * provider remaps aliases. Absent for providers that do not echo a model ID.\n */\n modelId?: string\n /**\n * Milliseconds from the moment the loop dispatched `provider.stream()`\n * (the `stream:start` hook firing) to the first observable byte of this\n * turn — earliest of `stream:text`, `stream:thinking`, or a tool_use\n * block. Captures the per-turn TTFT independently of run-relative TTFT\n * ({@link AgentStats.timeTillFirstTokenMs}, which only marks the first\n * turn).\n *\n * Useful for metrics histograms (`gen_ai.client.time_to_first_token`)\n * across long multi-turn runs where the run-level metric collapses to\n * the cold turn only. Absent for empty-stream turns and turns that\n * errored before any byte landed.\n */\n timeToFirstTokenMs?: number\n /**\n * Milliseconds from `provider.stream()` dispatch to the stream returning —\n * the whole provider round trip for this turn, retries included (same\n * span `timeToFirstTokenMs` is measured within). With `output` this gives\n * an honest generation speed: `output / (streamDurationMs −\n * timeToFirstTokenMs)` is tokens per second of actual generation, which a\n * run's wall-clock cannot provide (it includes tool execution). Absent\n * when the stream never returned.\n */\n streamDurationMs?: number\n}\n\n/** Token, cost and timing totals returned by {@link Agent.run}. */\nexport interface AgentStats {\n /** The run stopped at a turn boundary on `pauseSignal`, with work left: continue it with another run. */\n paused?: boolean\n /** `Agent.suspend` stopped the run: on a checkpoint runtime it stays resumable (`Agent.resume`). */\n suspended?: boolean\n /**\n * Cumulative input tokens across the parent agent loop **and** every\n * recursively-spawned sub-agent. Use this for billing / token-ledger\n * consumption.\n */\n totalIn: number\n /** Cumulative output tokens. Same semantics as {@link AgentStats.totalIn}. */\n totalOut: number\n /**\n * Cumulative cache-read tokens across the parent agent loop and every\n * recursively-spawned sub-agent. Surfaced at the top level (rather than\n * only per-`TurnUsage`) because Anthropic prices cache reads at a separate\n * line-item rate from regular input — billing-correct cost computation\n * needs this number directly. Always `0` for providers that don't report\n * cache usage.\n */\n totalCacheRead: number\n /**\n * Cumulative cache-creation tokens across the parent agent loop and every\n * recursively-spawned sub-agent. Same rationale as\n * {@link AgentStats.totalCacheRead} — separate Anthropic billing rate.\n * Always `0` for providers that don't report cache usage.\n */\n totalCacheCreation: number\n /**\n * Number of parent agent-loop turns. Children's turn counts live under\n * `children[].stats.turns` and are NOT folded in here — a single \"turns\"\n * number for the whole tree would conflate two different measures\n * (parent-loop iterations vs. tree-wide tool-call rounds).\n *\n * Tree-wide turn count: `flattenTurns(stats).length`.\n */\n turns: number\n /**\n * Wall-clock duration of the top-level `agent.run()` call, in milliseconds.\n * Children run during parent tool calls so this naturally subsumes child\n * wall time — sequential children inflate it, parallel children compress\n * into the parent's window.\n */\n elapsed: number\n /**\n * Per-turn usage breakdown for the **parent loop only**. Children's per-turn\n * usages live under `children[].stats.turnUsage`. Use {@link flattenTurns}\n * to walk the full tree.\n */\n turnUsage?: TurnUsage[]\n /**\n * Cumulative cost in USD — parent loop plus every recursively-spawned\n * sub-agent. Sums per-turn `TurnUsage.cost` reported by the provider.\n * Absent when neither parent nor any descendant reported a non-zero cost.\n */\n cost?: number\n /** Stats from child agents spawned during this run, in completion order. Recursive. */\n children?: ChildRunStats[]\n /** Structured output from schema enforcement (only present when behavior.schema is set) */\n output?: Record<string, unknown>\n /**\n * Milliseconds from the start of `agent.run()` to the first observable signal from the\n * provider (first `stream:text`, `stream:thinking`, or `tool:before` event).\n *\n * Absent when the run produced no observable signals (e.g. aborted before any stream event).\n */\n timeTillFirstTokenMs?: number\n}\n\n/** Stats of one spawned subagent run, passed to `spawn:complete` and listed in {@link AgentStats.children}. */\nexport interface ChildRunStats {\n /** Spawn id of the child. */\n id: string\n /** Task the child was given. */\n task: string\n /**\n * Tool-call id of the `spawn` call that produced this child — see\n * {@link SpawnHookContext.callId}. Lets a `spawn:complete` listener close\n * out the same grouping key it opened on `spawn:before`.\n */\n callId?: string\n /**\n * The child agent's full {@link AgentStats}. Cumulative for that child's\n * own subtree (child loop + its grandchildren). Do **not** sum\n * `ctx.stats.totalIn` across `spawn:complete` events to derive top-level\n * totals — `agent.run()`'s return value is the canonical cumulative root.\n */\n stats: AgentStats\n /**\n * Subagent depth when this child ran. 1 = direct child of the top-level\n * agent, 2 = grandchild, etc. Useful for telemetry that wants to group\n * runs by depth.\n */\n depth?: number\n /**\n * Terminal state of the child run. `'completed'` is the default. Exposed so\n * a parent reading `stats.children` can distinguish aborted/timed-out\n * children without re-parsing the returned string.\n */\n status?: 'completed' | 'aborted' | 'timeout' | 'error'\n /**\n * Final structured output when the child was run with `behavior.schema`.\n * Mirrors `AgentStats.output` but is surfaced here so the parent can read\n * it without peeking at the nested `stats` bag.\n */\n output?: Record<string, unknown>\n}\n\n// ---------------------------------------------------------------------------\n// Hook context types\n// ---------------------------------------------------------------------------\n\n/**\n * One call a tool made through `ToolContext.callTool`, as persisted on the\n * caller's `tool_result` (`calls`) and rebuilt into transcript rows on\n * reload. Bounded by `src/nested-call-record.ts`: `input` / `output` may be\n * cut (`inputBytes` / `outputTruncated` say so).\n */\nexport interface NestedToolCallRecord {\n /** `<caller call id>/<n>`, in the order the caller issued the calls. */\n id: string\n /** Canonical tool name. */\n name: string\n /** Arguments; absent (with {@link inputBytes}) when over the size bound. */\n input?: Record<string, unknown>\n /** JSON size of an input left out for size. */\n inputBytes?: number\n /** How the call ended. */\n status: 'ok' | 'error' | 'cancelled'\n /** Wall time of the call in milliseconds. */\n durationMs: number\n /** Start of the result text, as a transcript row shows it. */\n output?: string\n /** `true` when `output` is cut short of the full result text. */\n outputTruncated?: boolean\n /** The called tool runs a subagent (`DurableToolDef.runsNestedAgent`): its worker attaches to this call. */\n runsNestedAgent?: true\n}\n\n/**\n * Base context for tool execution hooks: the tool call a `tool:*` hook fires\n * for, with ids, names, input and the run it belongs to.\n *\n * `name` is the canonical tool identity — the spec name registered on the agent (or the\n * `mcp_{server}_{tool}` name for MCP tools). Hooks should policy-match against `name`.\n *\n * `displayName` is the outward-facing name — the alias surfaced to the LLM when\n * `AgentOptions.toolAliases` maps the canonical name; otherwise equal to `name`.\n * UI/telemetry adapters should emit `displayName`.\n *\n * Canonical vs. alias matters on session resume: `session.turns` persists canonical\n * names only, so renaming an alias cannot desync history.\n */\nexport interface ToolHookContext {\n /** Id of the turn that dispatched the call. */\n turnId: string\n /** Id of the tool call. */\n callId: string\n /** Canonical tool name (spec name). Stable across alias-map changes. */\n name: string\n /** Aliased (wire) name — equal to `name` when no alias is defined. */\n displayName: string\n /** Input the tool was called with. */\n input: Record<string, unknown>\n /**\n * The run this tool call belongs to (the `SessionRun.id`). Lets a single\n * `tool:*` listener disambiguate calls across parallel runs / subagent\n * trees without subscribing to the `child:tool:*` bubble events.\n */\n runId?: string\n /**\n * Parent run id when this tool call's agent is a subagent — i.e. the\n * `SessionRun.parentRunId` of the run that owns the call. Absent on\n * top-level runs. Useful for observability stitching: a UI grouping\n * subagent-scoped state (e.g. todowrite, edit batches) by parent\n * run can read this directly off `tool:before` / `tool:after`\n * without resolving the run row.\n */\n parentRunId?: string\n /**\n * Tool-call id of the `spawn` call that created the run this tool call\n * belongs to. Absent on top-level runs.\n *\n * Set for every child the built-in `spawn` tool starts — it comes from the\n * spawn tool's own call rather than from session-backed run bookkeeping,\n * so unlike `parentRunId` it survives `persist: false` spawns. It is NOT\n * an invariant of `depth >= 1`: a host that spawns by calling `agent.run`\n * with `depth` itself sees a subagent with no `parentCallId` unless it\n * passes `AgentRunOptions.parentCallId` too. Treat it as present-or-absent,\n * not as derivable from depth.\n *\n * Hosts mirroring subagent activity into a client protocol use it as the\n * \"parent tool use\" grouping key: absent at depth 0, and at depth >= 1 a\n * real tool-call id rather than a synthetic label. Where it *resolves*\n * depends on depth — at depth 1 it names a call the parent's own `tool:*`\n * events already carried, while at depth >= 2 it names a call in the\n * child's turn, which a top-level listener only ever saw as\n * `child:tool:*`. So it always groups correctly, but a lookup against the\n * top-level tool stream alone only hits for depth 1. This mirrors the\n * ancestry semantics of `childId` / `depth` on `child:*` events.\n */\n parentCallId?: string\n /**\n * Set when another tool issued this call through `ToolContext.callTool`\n * (the `codemode` tool): the id of that calling tool call. Nested calls\n * never appear in `session.turns` and the model never sees their result,\n * so consumers that render model-visible tool calls or coach the model on\n * them should skip these.\n */\n parentToolCallId?: string\n /** Set when the called tool runs a subagent (`DurableToolDef.runsNestedAgent`, e.g. `spawn`). */\n runsNestedAgent?: true\n /**\n * Subagent depth for this tool call. 0 = top-level, 1 = first-level\n * child, etc. Mirrors `ToolContext.depth` so hook consumers don't\n * have to cross-reference the tool context. Omitted on top-level\n * runs (treated as 0).\n */\n depth?: number\n}\n\n/**\n * Base context for MCP tool hooks.\n *\n * `tool` is the native tool name on the MCP server. `server` is the configured server\n * name. The canonical zidane-namespaced identity is `mcp_{server}_{tool}`.\n *\n * `displayName` equals the canonical namespaced name unless the agent has aliased\n * this MCP tool via `AgentOptions.toolAliases`; in which case `displayName` is the\n * alias that the LLM sees.\n */\nexport interface McpToolHookContext {\n /** Id of the turn that dispatched the call. */\n turnId: string\n /** Id of the tool call. */\n callId: string\n /** Configured name of the MCP server. */\n server: string\n /** Tool name on the MCP server, without the `mcp_<server>_` prefix. */\n tool: string\n /** Aliased wire name for this MCP tool, or the canonical `mcp_{server}_{tool}` name. */\n displayName: string\n /** Input the tool was called with. */\n input: Record<string, unknown>\n /** Owning run id — same semantics as `ToolHookContext.runId`. */\n runId?: string\n /** Parent run id when this tool call's agent is a subagent — see `ToolHookContext.parentRunId`. */\n parentRunId?: string\n /** Spawning tool call's id when this tool call's agent is a subagent — see `ToolHookContext.parentCallId`. */\n parentCallId?: string\n /** Subagent depth — see `ToolHookContext.depth`. */\n depth?: number\n}\n\n/** Base context for session hooks */\nexport interface SessionHookContext {\n /** Id of the session. */\n sessionId: string\n}\n\n/** `spawn:queued`: a top-level `spawn` call waiting for a free slot. */\nexport interface SpawnQueuedContext {\n /** Task of the waiting `spawn` call. */\n task: string\n /** The waiting `spawn` call's id (see {@link SpawnHookContext.callId}). */\n callId?: string\n /** The depth its child will run at (1: the top-level agent's child). */\n depth: number\n /** The slots taken and the cap, when the call started waiting. */\n waitingFor: { active: number, max: number }\n}\n\n/** Base context for spawn hooks */\nexport interface SpawnHookContext {\n /** Spawn id of the child. */\n id: string\n /** Task the child was given. */\n task: string\n /**\n * Tool-call id of the `spawn` call this event belongs to — the same value\n * the child's own tool events carry as `ToolHookContext.parentCallId`.\n * Gives hosts the `childId` -> spawn-call mapping at spawn time, which is\n * the only place to learn it for a child that never dispatches a tool (and\n * the only place at all under parallel spawns, where `tool:before` for two\n * spawn calls and their `spawn:before` events interleave).\n *\n * Absent for hand-fired payloads and for spawn-style tools invoked with a\n * `ToolContext` that carries no `callId`. The child's run records it as\n * `SessionRun.parentCallId`.\n */\n callId?: string\n /**\n * Child label of the prior run this spawn continues (e.g. `\"child-2\"`),\n * set when the spawn was invoked with `resume`. Hosts render it next to\n * the spawn-start marker (“child-4 (resumes child-2)”). Absent on\n * fresh spawns.\n */\n resumesChildId?: string\n /**\n * Model id the child runs on — the resolved spawn choice (per-call\n * `model` input > subagent-type default > tool option > inherited\n * parent model > child provider default). Set on `spawn:before` /\n * `spawn:error`; hosts surface it next to the spawn-start marker so\n * cost-tiered delegation is visible. Optional for backward\n * compatibility with hand-fired hook payloads.\n */\n model?: string\n /** `Provider.name` serving the child — differs from the parent's when the spawn routed to another provider. */\n provider?: string\n /**\n * Subagent depth for the spawn. 1 = direct child of the top-level agent.\n * Present on spawn:before/complete/error, and on their nested\n * `child:spawn:*` re-emissions (where it is ≥ 2).\n */\n depth?: number\n /**\n * Mutable trace-context carrier for parent → child span linkage. Empty\n * object by default; a parent tracer mutates it on `spawn:before` (e.g.\n * `ctx.tracingContext.traceparent = '00-…-…-01'`) and the `spawn` tool\n * forwards the populated object to the child via\n * `AgentRunOptions.tracingContext`. The child's tracer re-emits it on\n * `agent:start` so it can be used as parent context when opening the\n * child's root span.\n *\n * Opaque to the harness — keys / values are tracer-defined. Standard\n * choice is W3C Trace Context (`traceparent` + optional `tracestate`),\n * but Datadog / Sentry / B3 carriers work too.\n */\n tracingContext?: Record<string, string>\n}\n\n/**\n * Identity a nested spawn lifecycle event (`child:spawn:*`) adds to the\n * spawned agent's record.\n *\n * `child:spawn:*` events describe a spawn fired BY a subagent (the spawned\n * agent sits at depth ≥ 2). Their payload is the spawned agent's own record\n * — `id` / `depth` / `task` / `callId` are the grandchild's, exactly as its\n * spawner's `spawn:*` saw them — so one handler can serve both families.\n * Unlike the other `child:*` events there is deliberately NO `childId`: on\n * those, `childId` + `depth` name the agent that originated the event, and\n * here `depth` already belongs to the spawned agent. The spawner is named\n * by `parentChildId` instead (its depth is `depth - 1`).\n */\nexport interface ChildSpawnIdentity {\n /**\n * Id of the subagent that ran the `spawn` call (the spawned agent's\n * parent). Set by the first bubble hop and forwarded verbatim through\n * deeper hops, so a root listener sees the true spawner, not its own\n * direct child.\n */\n parentChildId: string\n}\n\n/**\n * Payload of `child:spawn:before` / `child:spawn:error`: the spawned agent's\n * {@link SpawnHookContext} plus {@link ChildSpawnIdentity}, minus the\n * mutable `tracingContext` carrier. Trace propagation into a nested child\n * is owned by its spawner's own tracer (the one listening on the spawner's\n * bus, which knows the spawner's active span); a root tracer writing into\n * the carrier would re-parent the grandchild under the ROOT's ambient span\n * and race the spawner's injection — so the bubbled copy carries none.\n */\nexport type ChildSpawnHookContext = Omit<SpawnHookContext, 'tracingContext'> & ChildSpawnIdentity\n\n/** Context for stream hooks */\nexport interface StreamHookContext {\n /** Id of the turn that is streaming. */\n turnId: string\n}\n\n/**\n * Cumulative in-flight usage a provider can surface mid-stream via\n * `StreamCallbacks.onUsage`. Every field is the running total so far, never\n * a delta; the loop keeps only the latest snapshot. Anthropic reports the\n * prompt side on `message_start` and the output count only in the final\n * `message_delta`; providers without mid-stream accounting never call it.\n * Every field is optional: omit `outputTokens` when the wire hasn't counted\n * it yet and the loop keeps its character estimate moving instead of\n * freezing the counter on a placeholder.\n */\nexport interface StreamUsageSnapshot {\n /** Output tokens counted so far. */\n outputTokens?: number\n /** Input tokens counted so far. */\n inputTokens?: number\n /** Cache-read tokens counted so far. */\n cacheRead?: number\n /** Cache-creation tokens counted so far. */\n cacheCreation?: number\n /**\n * `false` when the provider knows the traffic is flat-rate (subscription\n * OAuth): the loop then reports tokens without a price, matching the\n * turn-level rule that `usage.cost` stays absent on such turns.\n */\n billable?: boolean\n}\n\n/**\n * Fired (throttled) while a parent turn streams, then once more unthrottled\n * right before `stream:end`. Drives live footer counters: tokens streamed so\n * far and a running price for the turn. `turn:after` is the settle point —\n * its `usage.cost` REPLACES `estCostUsd`, it never adds to it.\n */\nexport interface StreamProgressHookContext extends StreamHookContext {\n /** Wall-clock since the provider stream opened. */\n elapsedMs: number\n /** Output tokens so far — provider-reported when available, else chars ÷ 4. */\n outputTokens: number\n /** Prompt-side figures when the provider reported them mid-stream. */\n inputTokens?: number\n /** Cache-read tokens, when the provider reported them mid-stream. */\n cacheRead?: number\n /** Cache-creation tokens, when the provider reported them mid-stream. */\n cacheCreation?: number\n /** `true` when `outputTokens` is the character heuristic, not a provider count. */\n estimated: boolean\n /**\n * Running USD estimate for THIS turn at catalog rates. Absent when the model\n * has no rate, or the provider flagged the traffic as flat-rate. The input\n * side is the provider's mid-stream figure when it sent one, otherwise the\n * previous assistant turn's prompt split as a proxy (the first turn of a\n * session prices output only until it settles).\n */\n estCostUsd?: number\n}\n\n/**\n * Base context for the compaction hooks (`compact:start` /\n * `compact:progress` / `compact:end`). Auto-compaction fires them at a\n * turn boundary inside a run when `behavior.autoCompact` is enabled and the\n * threshold trips; `agent.compact()` fires them between runs. Never during a\n * provider stream, so it carries run/turn coordinates rather than extending\n * {@link StreamHookContext}.\n */\nexport interface CompactHookContext {\n /** Run that triggered the compaction. For `reason: 'manual'`, the agent's latest run, or `''` before its first run. */\n runId: string\n /** Bound session id, when the agent has a session. */\n sessionId?: string\n /** 0-based index of the turn after which compaction fired. For `reason: 'manual'`, the latest run's last turn, or `0` before the first run. */\n turn: number\n /** Why compaction ran: `'auto'` for the threshold trigger inside a run, `'manual'` for `agent.compact()`. */\n reason: 'auto' | 'manual'\n}\n\n/**\n * Context for the `history:repair` hook — fired at most once per provider\n * stream call, AFTER the stream settles, when the loop's canonical history\n * was scrubbed of provider-rejected reasoning state (currently: Anthropic\n * signature-400 poisoning). Deliberately deferred to the settle boundary so\n * a replay-mode durable runtime never sees it inside a recorded model\n * step, where its context methods are unavailable.\n *\n * The in-memory runtime turns and the bound `Session` object are already\n * repaired when this fires; `persisted` reports whether `session.save()`\n * succeeded. Hosts that mirror turns into an EXTERNAL durable store must\n * apply the repair there too (using `turnIds`) — otherwise reloading from\n * that store re-poisons the next run. Observational; handlers cannot cancel\n * the repair.\n */\nexport interface HistoryRepairHookContext {\n /** What was repaired. Currently only Anthropic signature poisoning. */\n reason: 'invalid-anthropic-thinking-signature'\n /** Run whose stream call detected the poison. */\n runId: string\n /** Bound session id, when the run has a session. */\n sessionId?: string\n /** Ids of the turns whose content was scrubbed (runtime and/or session). */\n turnIds: string[]\n /** Total reasoning blocks removed across runtime + session turns. */\n removedBlocks: number\n /**\n * Whether the repaired session was durably saved to its store. Also\n * `false` when the session has no store at all (in-memory sessions have\n * nothing to persist to — the in-memory repair is their final state).\n */\n persisted: boolean\n}\n\n/**\n * Payload of the `'provider:input-transformations'` hook: replayed thinking\n * blocks the provider discarded server-side before sampling. Canonical history\n * is untouched; the model simply answered this turn without that reasoning.\n */\nexport interface InputTransformationsHookContext {\n runId: string\n turnId: string\n /** Bound session id, when the run has a session. */\n sessionId?: string\n /** One entry per dropped block, as reported by the provider. */\n transformations: import('./providers').InputTransformation[]\n /** Entries whose `reason` is `prefix_binding_mismatch` — host-side prefix edits. */\n prefixMismatches: number\n /** Entries whose `reason` is `model_binding_mismatch` — a model switch, not a bug. */\n modelMismatches: number\n}\n\n/** Fired right before the summary call starts. See `compact:start`. */\nexport interface CompactStartHookContext extends CompactHookContext {\n /** Fraction of the effective window the last turn's input reached. `0` when the window is unknown. */\n usedFraction: number\n /** Effective window (raw minus output reserve) used for the ratio. `0` when the window is unknown. */\n effectiveWindow: number\n}\n\n/**\n * Fired (throttled, best-effort) while the summary call streams. Carries a\n * rough cumulative output-token estimate so hosts can paint live progress\n * (\"Compacting… ~2.1k tokens\") instead of an opaque spinner — a summary on\n * a near-window prompt legitimately streams for minutes. Zero or more per\n * `compact:start`; never fired after the paired `compact:end`.\n */\nexport interface CompactProgressHookContext extends CompactHookContext {\n /** Rough token estimate of the summary streamed so far (chars ÷ 4). */\n estOutputTokens: number\n}\n\n/**\n * Fired exactly once after a `compact:start`, on every outcome. `status`\n * discriminates success (carries the result counts + post-compact size\n * estimate hosts use to refresh their context indicator) from failure.\n */\nexport type CompactEndHookContext = CompactHookContext & (\n | {\n status: 'success'\n /** Number of turn ids the summary replaced on the wire. */\n replacedCount: number\n /** Turns dropped by `prompt_too_long` head-truncation (not summarized). */\n droppedCount: number\n /** Files re-injected by post-compact restoration. */\n restoredFiles: number\n /** Skills re-injected by post-compact restoration. */\n restoredSkills: number\n /** Model that produced the summary. */\n summaryModel: string\n /**\n * Estimated post-compact effective token size (summary output +\n * restored attachments). Hosts latch this into their context indicator\n * so it drops immediately instead of waiting for the next real turn.\n */\n effectiveTokens: number\n durationMs: number\n }\n | {\n status: 'error'\n /** The thrown value from `compactConversation` / restoration. */\n error: unknown\n durationMs: number\n }\n)\n\n/** Context for OAuth refresh hooks */\nexport interface OAuthRefreshHookContext {\n /** Name of the provider whose credentials were refreshed. */\n provider: string\n /** OAuth provider id used to look up the refresh flow. */\n providerId: string\n /** Where the credentials came from: provider params or the shared credentials file. */\n source: 'params' | 'file'\n /** Credentials before the refresh. */\n previousCredentials: Record<string, unknown> & { access: string, refresh: string, expires: number }\n /** Credentials after the refresh. */\n credentials: Record<string, unknown> & { access: string, refresh: string, expires: number }\n}\n\n/** How a run ended: `'completed'`, `'aborted'` or `'error'`. */\nexport type SessionEndStatus = 'completed' | 'aborted' | 'error'\n"],"mappings":";;;AAuDA,MAAa,sBAAkC;CAC7C,WAAW,KAAK,IAAI;CACpB,kBAAkB,OAAO,WAAW;AACtC;AAq4EA,MAAM,yCAAyC;AAC/C,MAAM,qBAAqB;;;;;;;;;;;;AAa3B,SAAgB,2BACd,OACA,WAAW,wCACuB;CAClC,MAAM,WAAqB,CAAC;CAC5B,MAAM,MAA0B,CAAC;CACjC,MAAM,uBAAO,IAAI,QAAgB;CACjC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,GAAG;EAChD,MAAM,YAAY,sBAAsB,OAAO;GAAE;GAAM;GAAU,OAAO;EAAE,CAAC;EAC3E,IAAI,cAAc,KAAA,GAChB,IAAI,OAAO;CACf;CACA,MAAM,UAAU,yBAAyB,KAAK,UAAU,QAAQ;CAChE,OAAO,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,IACjC;EAAE,UAAU;EAAS;CAAS,IAC9B,EAAE,SAAS;AACjB;AAEA,SAAS,sBACP,OACA,KACuB;CACvB,IAAI,UAAU,MACZ,OAAO;CACT,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,WAChD,OAAO;CACT,IAAI,OAAO,UAAU,UACnB,OAAO,OAAO,SAAS,KAAK,IAAI,QAAQ,KAAA;CAC1C,IAAI,OAAO,UAAU,UAAU;EAC7B,IAAI,SAAS,KAAK,mCAAmC;EACrD,OAAO,MAAM,SAAS;CACxB;CACA,IAAI,OAAO,UAAU,UACnB,OAAO,KAAA;CACT,IAAI,IAAI,SAAS,oBAAoB;EACnC,IAAI,SAAS,KAAK,mCAAmC;EACrD;CACF;CACA,IAAI,IAAI,KAAK,IAAI,KAAK,GAAG;EACvB,IAAI,SAAS,KAAK,+BAA+B;EACjD;CACF;CACA,IAAI,KAAK,IAAI,KAAK;CAClB,IAAI;EACF,IAAI,MAAM,QAAQ,KAAK,GAAG;GACxB,MAAM,MAAmB,CAAC;GAC1B,KAAK,MAAM,QAAQ,OAAO;IACxB,MAAM,OAAO,sBAAsB,MAAM;KAAE,GAAG;KAAK,OAAO,IAAI,QAAQ;IAAE,CAAC;IACzE,IAAI,SAAS,KAAA,GACX,IAAI,KAAK,IAAI;GACjB;GACA,OAAO;EACT;EACA,MAAM,MAAoC,CAAC;EAC3C,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,GAAG;GAChD,MAAM,OAAO,sBAAsB,OAAO;IAAE,GAAG;IAAK,OAAO,IAAI,QAAQ;GAAE,CAAC;GAC1E,IAAI,SAAS,KAAA,GACX,IAAI,OAAO;EACf;EACA,OAAO;CACT,UACQ;EACN,IAAI,KAAK,OAAO,KAAK;CACvB;AACF;AAEA,SAAS,yBACP,OACA,UACA,UACoB;CACpB,IAAI,OAAO,WAAW,KAAK,UAAU,KAAK,GAAG,MAAM,KAAK,UACtD,OAAO;CACT,MAAM,MAA0B,CAAC;CACjC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,GAAG;EAChD,IAAI,OAAO;EACX,IAAI,OAAO,WAAW,KAAK,UAAU,GAAG,GAAG,MAAM,IAAI,UACnD,OAAO,IAAI;CACf;CACA,SAAS,KAAK,gDAAgD;CAC9D,OAAO;AACT;;;;;;;;;;;AAqHA,SAAgB,yBAOb,OAAU,QAAuD;CAClE,IAAI,OAAO,MAAM,SAAS,UACxB;CACF,MAAM,IAAI,MAAM,0CAA0C,MAAM,OAAO,YAAY,OAAO,OAAO,EAAE;AACrG;;;;;;;;;;;;;;AAeA,SAAgB,oBACd,KACA,QACQ;CACR,MAAM,YAAY,OAAO,IAAI,SAAS,WACjC,IAAI,aAAa,WAAW,GAAG,IAAI,KAAK,OAAO,cAAc,GAAG,IAAI,KAAK,OAAO,UACjF,OAAO,IAAI;CACf,OAAO,IAAI,OAAO,IAAI,IAAI,OAAO,GAAG,IAAI,KAAK,MAAM,KAAK,IAAI,UAAU,IAAI,UAAU;AACtF;;;;;;;;;;;AAYA,SAAgB,iBAAiB,SAA+C;CAC9E,IAAI,OAAO,YAAY,UACrB,OAAO;CACT,OAAO,QACJ,KAAK,UAAU;EACd,IAAI,MAAM,SAAS,QACjB,OAAO,MAAM;EACf,IAAI,MAAM,SAAS,SACjB,OAAO,OAAO,MAAM,SAAS,WACzB,WAAW,MAAM,UAAU,IAAI,MAAM,KAAK,OAAO,eACjD,WAAW,MAAM,UAAU,QAAQ,MAAM,IAAI;EAEnD,IAAI,MAAM,SAAS,SACjB,OAAO,WAAW,MAAM,UAAU,IAAI,MAAM,KAAK,OAAO;EAC1D,IAAI,MAAM,SAAS,SACjB,OAAO,WAAW,MAAM,UAAU,IAAI,MAAM,KAAK,OAAO;EAC1D,OAAO,OAAO,MAAM,SAAS,WACzB,cAAc,MAAM,UAAU,IAAI,MAAM,KAAK,OAAO,GAAG,MAAM,aAAa,WAAW,cAAc,UAAU,MAAM,OAAO,KAAK,MAAM,SAAS,GAAG,KACjJ,cAAc,MAAM,UAAU,QAAQ,MAAM,MAAM,MAAM,OAAO,KAAK,MAAM,SAAS,GAAG;CAC5F,CAAC,CAAC,CACD,KAAK,IAAI;AACd;;;;;;;;;;AAWA,SAAgB,yBAAyB,OAA4C;CACnF,IAAI,CAAC,SAAS,OAAO,UAAU,UAC7B,OAAO;CACT,MAAM,QAAQ;CACd,MAAM,WAAW,OAAO,MAAM,cAAc;CAC5C,MAAM,aAAa,OAAO,MAAM,SAAS,YAAY,OAAO,MAAM,QAAQ;CAC1E,QAAQ,MAAM,MAAd;EACE,KAAK,QACH,OAAO,OAAO,MAAM,SAAS;EAC/B,KAAK,SACH,OAAO,YAAY;EACrB,KAAK,YACH,OAAO,YAAY,eAAe,MAAM,aAAa,YAAY,MAAM,aAAa;EACtF,KAAK;EACL,KAAK,SACH,OAAO,YAAY,OAAO,MAAM,SAAS;EAC3C,SACE,OAAO;CACX;AACF;;;;;;;;AASA,SAAgB,wBAAwB,SAAqE;CAC3G,IAAI,OAAO,YAAY,YAAY,CAAC,MAAM,QAAQ,OAAO,GACvD,OAAO;CACT,IAAI,UAAU;CACd,MAAM,UAAU,QAAQ,KAAK,UAA6B;EACxD,IAAI,yBAAyB,KAAK,GAChC,OAAO;EACT,UAAU;EAIV,OAAO;GAAE,MAAM;GAAQ,MAAM,sBAHhB,SAAS,OAAO,UAAU,YAAY,OAAQ,MAA6B,SAAS,WAC5F,MAA2B,OAC5B,UACoD;EAAqB;CAC/E,CAAC;CACD,OAAO,UAAU,UAAU;AAC7B;;;;;;;;;;;;AAaA,SAAgB,2BAA2B,SAA+C;CACxF,OAAO,OAAO,WAAW,iBAAiB,OAAO,CAAC;AACpD;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,qBAAqB,SAA+C;CAClF,IAAI,OAAO,YAAY,UACrB,OAAO,OAAO,WAAW,OAAO;CAClC,IAAI,QAAQ;CACZ,KAAK,MAAM,SAAS,SAClB,IAAI,MAAM,SAAS,QACjB,SAAS,OAAO,WAAW,MAAM,IAAI;MAClC,IAAI,MAAM,SAAS,SACtB,SAAS,OAAO,MAAM,SAAS,WAAW,MAAM,KAAK,SAAS,MAAM,IAAI;MACrE,IAAI,MAAM,SAAS,YACtB,SAAS,OAAO,MAAM,SAAS,WAAW,MAAM,KAAK,SAAS,MAAM,IAAI;MAExE,SAAS,MAAM,KAAK;CAExB,OAAO;AACT;;;;;;;;;;;;;;;;AA0UA,SAAgB,WACd,MAC8C;CAC9C,QAAQ,KAAK,SAAS,UAAU,KAAK,SAAS,gBAAgB,KAAK,YAAY;AACjF"}
1
+ {"version":3,"file":"types-myxheQkw.js","names":[],"sources":["../src/types.ts"],"sourcesContent":["/**\n * Shared types for the agent system.\n */\n\nimport type { Provider, ToolCall, ToolResult } from './providers'\nimport type { ModelCatalog } from './providers/model-catalog'\nimport type { TextOrigin } from './text-origin'\nimport type { ThinkingLevel } from './thinking-levels'\nimport type { ToolDef } from './tools/types'\nimport { Buffer } from 'node:buffer'\n\nexport type { TextOrigin } from './text-origin'\n\n// ---------------------------------------------------------------------------\n// Thinking / Reasoning\n// ---------------------------------------------------------------------------\n\n/** Thinking / extended-reasoning levels: see `thinking-levels.ts` (dependency-free, for the browser-safe surfaces). */\nexport { isThinkingLevel, THINKING_LEVELS } from './thinking-levels'\nexport type { ThinkingLevel } from './thinking-levels'\n\n// ---------------------------------------------------------------------------\n// Clock / determinism seam\n// ---------------------------------------------------------------------------\n\n/**\n * Time + UUID source. Defaults to `Date.now()` and `crypto.randomUUID()`.\n *\n * Scoped to **journaled metadata** — every callsite that lands in\n * `SessionTurn.id`, `SessionTurn.createdAt`, `runId`, `turnId`, or hook\n * payloads consumers may persist. Live-only measurements (TTFT deltas,\n * `elapsed` counters) keep `Date.now()` directly so they reflect real\n * wall-clock progress.\n *\n * `now()` may return a `Promise<number>`: the loop awaits it at every\n * callsite. `randomUUID()` stays synchronous so a clock can provide\n * replay-stable identifiers without async metadata plumbing.\n *\n * Precedence: `AgentRunOptions.clock` > `AgentOptions.clock` >\n * {@link DEFAULT_AGENT_CLOCK}. A run on a replay-mode durable runtime\n * (`AgentOptions.durable`) takes its time and ids from the runtime instead,\n * so a replay regenerates byte-identical session metadata.\n */\nexport interface AgentClock {\n /**\n * Current wall-clock time in ms (epoch). May be async.\n */\n now: () => number | Promise<number>\n /**\n * RFC-4122 v4 UUID string. Always sync.\n */\n randomUUID: () => string\n}\n\n/** Native clock backing the default (Date.now + crypto.randomUUID). */\nexport const DEFAULT_AGENT_CLOCK: AgentClock = {\n now: () => Date.now(),\n randomUUID: () => crypto.randomUUID(),\n}\n\n// ---------------------------------------------------------------------------\n// MCP server configuration\n// ---------------------------------------------------------------------------\n\n/**\n * Slim shape of an upstream MCP tool descriptor — what `client.listTools()`\n * returns per entry. Exposed publicly so hosts can persist the schemas\n * between runs and feed them back via {@link McpServerConfig.cachedTools}\n * to skip the `tools/list` round-trip on subsequent bootstraps.\n */\nexport interface McpToolSchema {\n /** Tool name on the server, without the `mcp_<server>_` prefix. */\n name: string\n /** Human-readable description of the tool. */\n description?: string | null\n /** JSON Schema of the tool's input. */\n inputSchema?: unknown\n}\n\n/** {@link McpServerConfig.oauth}. */\nexport interface McpOAuthClientConfig {\n /** Client ID of the pre-registered OAuth app. `${VAR}` placeholders expand. */\n clientId: string\n /** Client secret. Leave it out of config files when possible. `${VAR}` placeholders expand. */\n clientSecret?: string\n /** Fixed loopback port for the OAuth redirect (`http://127.0.0.1:<port>/callback`). Random when omitted. */\n callbackPort?: number\n /** Space-separated scopes to request. */\n scope?: string\n}\n\n/** Configuration of one MCP server the agent connects to and exposes as tools. */\nexport interface McpServerConfig {\n /** Display name (used for tool namespacing) */\n name: string\n /** Transport type */\n transport: 'stdio' | 'sse' | 'streamable-http'\n /** For stdio: command to run */\n command?: string\n /** For stdio: command arguments */\n args?: string[]\n /**\n * For stdio: environment variables to pass to the server process.\n *\n * Merged on top of the MCP SDK's default inherited environment — a safety\n * whitelist (`PATH`, `HOME`, `LANG`, `SHELL`, `USER` on POSIX; `APPDATA`,\n * `PATH`, ... on Win32). Setting this to `{}` does not strip `PATH` from\n * the child process. Set {@link McpServerConfig.strictEnv} to `true` to\n * pass `env` verbatim with no inherited defaults.\n */\n env?: Record<string, string>\n /**\n * When true, {@link McpServerConfig.env} is passed verbatim to the spawned\n * process — the MCP SDK's default inherited environment (`PATH`, `HOME`, ...)\n * is NOT merged in. Most consumers should leave this off; the default merge\n * prevents `spawn ENOENT` when a stdio server declares an `env` without\n * restating `PATH`.\n */\n strictEnv?: boolean\n /**\n * For stdio: working directory of the spawned child process. Forwarded to\n * `StdioClientTransport`'s `cwd` (which is `spawn`'s `cwd`).\n *\n * When omitted, the child inherits the parent's cwd (`process.cwd()` at\n * spawn time). Set this when the server resolves paths relative to its\n * own cwd (e.g. an MCP server reading `db/migrations/...`)\n * and the project directory differs from where the agent was launched.\n *\n * Pass an absolute path. Relative values are forwarded as-is to the SDK,\n * which resolves them against the parent process's cwd.\n */\n cwd?: string\n /**\n * For stdio: WHERE the server process runs.\n *\n * - `'host'` (default) — spawned as a child of the agent process, on the\n * host filesystem, with the host's env and network.\n * - `'context'` — spawned inside the agent's `ExecutionContext`, via\n * {@link ExecutionContext.execStream}.\n *\n * Why this is opt-in rather than automatic: with a sandboxed context\n * (Daytona / E2B / Docker) a host-spawned server and the agent's own\n * tools see DIFFERENT filesystems, so a path the model discovered via\n * `list_files` doesn't exist for the server, and an MCP write lands\n * where `read_file` can't see it. `'context'` closes that gap — but it\n * is wrong for plenty of servers: a GitHub / Linear / Sentry MCP wants\n * the host's credentials and only talks to the network, and moving it\n * into the sandbox would break it for no benefit. Only servers that\n * touch the workspace (filesystem, git, build tooling) want `'context'`.\n *\n * Three consequences worth knowing before flipping this:\n * - The context's image must contain the server's runtime (`node`,\n * `uvx`, …). A missing binary surfaces as the server exiting\n * immediately at bootstrap.\n * - Environment: the server inherits the CONTEXT's environment with\n * `env` merged on top — NOT the host path's `getDefaultEnvironment()`\n * whitelist. On the default process context the context environment\n * IS the host's full `process.env`, so a context-scoped server sees\n * every host variable unless you set `strictEnv: true`, which limits\n * it to exactly `env` (via `env -i`, same semantics as on the host).\n * - This is filesystem COHERENCE, not isolation. Remote-transport MCP\n * servers can never be moved into the context, so the agent's reach\n * is still not bounded by the sandbox.\n *\n * Ignored for `sse` / `streamable-http` (nothing local to relocate).\n * Requires a context implementing `execStream`; anything else is a\n * bootstrap error rather than a silent fallback to `'host'`.\n */\n scope?: 'host' | 'context'\n /**\n * For a host-spawned stdio server run through an MCP server pool\n * (`ConnectMcpServersOptions.pool`): whether every session whose config\n * matches shares one process (`true`, the default), or each session gets\n * its own (`false`: the session and its subagents still share one). Set it\n * `false` for a server that keeps per-user state in its process (a browser\n * it drives, an open document). Ignored without a pool, for remote servers\n * and for `scope: 'context'`, which are never shared.\n */\n shared?: boolean\n /** For sse/streamable-http: server URL */\n url?: string\n /** Optional headers for HTTP transports */\n headers?: Record<string, string>\n /**\n * OAuth 2.1 authentication (sse / streamable-http only).\n *\n * - `'oauth'` — enables the SDK's OAuth flow with RFC 9728 protected-resource\n * metadata discovery, RFC 8414 / OIDC authorization-server metadata, RFC 7591\n * dynamic client registration, PKCE, and refresh-token rotation. Tokens persist\n * between runs via the host's credential store.\n * - `undefined` (default) — no OAuth. The host may still auto-promote a server\n * to OAuth on `UnauthorizedError` IF no static `Authorization` header is set\n * (the headers check stops us from second-guessing user-managed bearer tokens).\n *\n * Recognized aliases at parse time: Cursor's `authMethod: 'mcpOAuth'` maps to\n * `auth: 'oauth'` so `~/.cursor/mcp.json` pastes work unchanged.\n */\n auth?: 'oauth'\n /**\n * Pre-registered OAuth client, for auth servers without RFC 7591 dynamic\n * client registration (Slack's `mcp.slack.com`, most enterprise IdPs).\n * Setting it implies `auth: 'oauth'`; the SDK then skips registration and\n * uses this client for the authorization-code + refresh flows.\n *\n * - `clientId` — the app's client ID. `${VAR}` placeholders expand.\n * - `clientSecret` — optional; prefer leaving it out of the file: hosts\n * keep a secret entered in their UI in the MCP credential store instead.\n * `${VAR}` placeholders expand, so `\"${SLACK_CLIENT_SECRET}\"` works too.\n * - `callbackPort` — pins the loopback redirect to\n * `http://127.0.0.1:<port>/callback`, the URL to register with the\n * provider (they reject redirects that don't match). Default: random.\n * - `scope` — space-separated scopes to request.\n */\n oauth?: McpOAuthClientConfig\n /**\n * Timeout in milliseconds for MCP server bootstrap (connect + tool discovery).\n *\n * Zidane connects MCP servers lazily on the first `run()`. Without a\n * bootstrap timeout, a slow or hung server can delay the first provider call\n * for an arbitrarily long time even when that MCP server is never used.\n *\n * Default: `10000`.\n */\n bootstrapTimeout?: number\n /** Timeout in milliseconds for MCP tool calls (default: 30000) */\n toolTimeout?: number\n /**\n * Timeout in milliseconds for this server's shutdown inside\n * `McpConnection.close()` (default: 5000).\n *\n * Each server's close is raced against this bound so a hung transport\n * close, an MCP server ignoring stdin EOF, or a wedged `mcp:close` hook\n * listener cannot block `agent.destroy()` indefinitely. When the bound\n * fires on a stdio server, the child process is SIGKILLed so the\n * abandoned close cannot leak a subprocess into a long-lived host.\n */\n closeTimeout?: number\n /**\n * Per-tool timeout overrides in milliseconds, keyed by the server-side\n * tool name (NOT the namespaced `mcp_<server>_<tool>` agent name). Takes\n * precedence over `toolTimeout` for matching tools.\n *\n * Use case: one server exposing both fast metadata calls and slow\n * operations (`list_tables` vs `apply_migration`) — a single per-server\n * timeout either kills legitimate long calls or lets fast ones hang.\n */\n toolTimeouts?: Record<string, number>\n /**\n * Allow-list of tool names to expose. Names match the upstream tool name\n * (NOT the namespaced `mcp_{server}_{tool}` form). Tools not in the list are\n * dropped before registration — the model never sees them in its catalog and\n * the wire cost of advertising them is avoided.\n *\n * Mutually exclusive with {@link McpServerConfig.disabledTools} — passing both\n * throws at bootstrap time.\n *\n * Composes with {@link McpServerConfig.toolFilter}: allow-list applies first,\n * then the predicate. Composes with the `mcp:tools:filter` hook: config-side\n * filters apply first, then the hook can further narrow the list.\n */\n enabledTools?: string[]\n /**\n * Deny-list of tool names. Tools matching are dropped before registration.\n * Same matching semantics as {@link McpServerConfig.enabledTools}.\n */\n disabledTools?: string[]\n /**\n * Custom predicate run on each upstream tool. Return `true` to keep, `false`\n * to drop. Receives the raw `listTools()` payload — useful for filtering by\n * description, schema shape, or other metadata that an allow/deny list can't\n * express.\n *\n * Runs after the allow/deny filter but before the `mcp:tools:filter` hook.\n */\n toolFilter?: (tool: { name: string, description?: string | null, inputSchema?: unknown }) => boolean\n /**\n * Per-server override for {@link AgentBehavior.toolDisclosure}. When set,\n * this server's tools follow this disclosure mode regardless of the\n * agent-wide default. Useful when one big MCP server (200+ tools) should\n * stay lazy while smaller servers stay eager.\n *\n * Default: inherits from `behavior.toolDisclosure`.\n */\n disclosure?: 'eager' | 'lazy'\n /**\n * Pre-cached tool schemas to advertise without issuing `tools/list` at\n * bootstrap. The connection is still established (the SDK's `connect()`\n * is needed for `tools/call`) — only the discovery round-trip is\n * skipped. Schemas are trusted as-is; the host owns invalidation\n * (typical cache key: `(server identity, server version)`). If the\n * server later returns `MethodNotFound` for a cached tool, the host\n * should drop the entry from its cache so the next bootstrap re-lists.\n *\n * Compatible with every transport, every auth mode, and with\n * {@link McpServerConfig.lazyConnect}. Composes with the existing\n * `enabledTools` / `disabledTools` / `toolFilter` filters — those run\n * over the cached schemas exactly as they would over `listTools()`\n * output.\n */\n cachedTools?: McpToolSchema[]\n /**\n * Defer the `client.connect(transport)` call until the first\n * `tools/call` reaches this server. Bootstrap registers the server's\n * tools using {@link McpServerConfig.cachedTools} without touching\n * the network, taking MCP setup off the critical path of\n * `agent.run()`. The first invocation pays the connect cost\n * (~200-500ms typically); every subsequent call reuses the live\n * client.\n *\n * Requires {@link McpServerConfig.cachedTools} — without schemas in\n * hand there is nothing to advertise to the model, so deferring the\n * connection has no purpose. Bootstrap rejects the config otherwise.\n *\n * **Incompatible with `auth: 'oauth'`**: the OAuth handshake (token\n * refresh / RFC 9728 metadata discovery) can fail in ways that\n * fire `mcp:auth:required` at bootstrap so the host can surface a\n * login affordance *before* the model commits to calling a tool.\n * Deferring that to first call means an auth failure surfaces mid-run\n * as a tool-result error, which the model can't recover from without\n * a fresh prompt. Bootstrap rejects the combination so the error is\n * loud and proximate to the misconfiguration. Use OAuth servers\n * without `lazyConnect` (with `cachedTools` alone, if you want to\n * skip the `tools/list` round-trip).\n *\n * On connect failure (network error, transport refused), the cached\n * promise is dropped so the next `tools/call` retries. The model\n * sees the failure as a normal tool error. Subsequent calls remain\n * eligible to succeed once the upstream is reachable again.\n */\n lazyConnect?: boolean\n /**\n * Suppress `${VAR}` expansion at the transport boundary.\n *\n * Set by the Agent Plugins loader (`plugins/mcp.ts`), whose placeholder\n * rules are DELIBERATELY narrower than zidane's: the spec expands only\n * `${PLUGIN_ROOT}` / `${PLUGIN_DATA}`, and only inside `args`, `env`\n * values and `cwd` — never in `command`, `url` or `headers`. Those two\n * placeholders are already substituted by the time the config gets here,\n * so a second pass would (a) expand the wrong fields and (b) let an\n * ambient `PLUGIN_ROOT` env var reach fields the spec says must keep the\n * literal text.\n *\n * Hand-written `mcps.json` entries leave this unset and keep the full\n * `${VAR}` / `${VAR:-default}` / `${env:VAR}` behavior.\n */\n preExpanded?: boolean\n /**\n * Pin {@link McpServerConfig.headers} to the configured URL's origin\n * (sse / streamable-http only).\n *\n * When set, redirects are followed manually and the configured headers\n * are STRIPPED the moment a hop leaves the original origin, so a\n * cooperating-then-redirecting server cannot harvest a plugin's bearer\n * token. Set by the Agent Plugins loader, which must satisfy the spec's\n * \"never forward configured headers to a different origin\" rule; opt in\n * manually for any other server whose headers carry a secret.\n */\n pinHeaderOrigin?: boolean\n}\n\n// ---------------------------------------------------------------------------\n// Tool execution\n// ---------------------------------------------------------------------------\n\n/**\n * Configuration for the loop's retry-with-backoff policy around\n * `provider.stream()`. See {@link AgentBehavior.retry}.\n */\nexport interface RetryConfig {\n /**\n * Maximum number of attempts including the initial call. `1` disables retry.\n * Values below `1`, non-finite, or non-integer fall back to the default.\n *\n * Default: `3`.\n */\n maxAttempts?: number\n /**\n * Initial backoff delay in milliseconds. Subsequent delays double\n * (exponential) before jitter is applied and the cap enforced.\n *\n * Default: `1000`.\n */\n initialDelayMs?: number\n /**\n * Upper bound on any single backoff delay in milliseconds. Caps both the\n * exponential schedule and any `retry-after` header the server returns.\n *\n * Default: `30_000`.\n */\n maxDelayMs?: number\n}\n\n/**\n * Per-tool argument hasher used by {@link AgentBehavior.dedupTools}.\n *\n * Returning a non-empty string activates dedup with that string as the\n * cache key. Returning `undefined` (or `''` / non-string) opts THIS call\n * out — the tool runs normally and no state is recorded. See\n * `dedupTools` for the full contract.\n */\nexport type DedupHasher = (input: Record<string, unknown>) => string | undefined\n\n/** Stall pattern that can trigger a progress nudge. See {@link AgentBehavior.progressNudges}. */\nexport type ProgressNudgeKind = 'readOnlyTurns' | 'failedMutations' | 'noopMutations' | 'validationRejects' | 'toolErrors' | 'singleCallTurns'\n\n/** Message a progress nudge steers the model with: a fixed string, or a function of the nudge `kind` and its trigger `count`. */\nexport type ProgressNudgeMessage = string | ((ctx: { kind: ProgressNudgeKind, count: number }) => string)\n\n/**\n * Pluggable client-side context compactor — see\n * {@link AgentBehavior.compactStrategy}. The loop invokes this in place\n * of the built-in tail elision when `compactStrategy` is a function.\n *\n * Contract:\n * - Operates on the wire-level message list — the post-alias / post-\n * sanitize / post-`context:transform` view the provider will see.\n * The framework treats the returned array as the new wire payload;\n * `ctx.session` / `ctx.turns` are never modified.\n * - Must preserve `tool_use ↔ tool_result` adjacency. Stranding a\n * `tool_use` without its matching `tool_result` (or vice versa) will\n * trip the loop's pair-repair pass and either repair the violation\n * or throw (`strictToolPairing`).\n * - The trailing user message must remain — providers reject\n * assistant-tail wire payloads. If unsure, leave the last user turn\n * intact and operate on the prefix only.\n * - May return the input verbatim (e.g. when the total stays under the\n * host's threshold).\n * - Sync or async. The loop awaits the result, so a long-running LLM\n * summarizer will block the next turn — keep latency in mind.\n *\n * `ctx.threshold` and `ctx.keepTurns` are forwarded from\n * `behavior.compactThreshold` / `behavior.compactKeepTurns` so hosts\n * can reuse the same knobs the built-in tail mode uses.\n * `ctx.totalBytes` is the pre-compaction total — the same number the\n * tail mode uses to gate its threshold check, exposed so the function\n * can short-circuit on under-threshold inputs without recomputing.\n */\nexport type CompactFunction = (\n messages: SessionMessage[],\n ctx: {\n /**\n * Effective byte threshold: `behavior.compactThreshold` (default\n * `131_072`), raised by `compactThresholdWindowFraction` when the run\n * resolved a context window.\n */\n threshold: number\n /** Resolved `behavior.compactKeepTurns` (default `4` when unset). */\n keepTurns: number\n /**\n * Pre-compaction sum of `tool_result` output bytes across `messages`\n * — the same heuristic the built-in tail mode uses to decide\n * whether to act. Forwarded so the function can `return messages`\n * verbatim on a quick under-threshold check.\n */\n totalBytes: number\n },\n) => SessionMessage[] | Promise<SessionMessage[]>\n\n/**\n * Loop-native auto-compaction config (`AgentBehavior.autoCompact`).\n *\n * Distinct from `compactStrategy` (wire-only tail elision of `tool_result`\n * text). Auto-compaction is the heavy lever: when the last turn's input\n * usage crosses {@link AutoCompactConfig.threshold} of the effective context\n * window, the loop runs a real LLM summary (`compactConversation`) over the\n * older turns, appends a `compact-summary` marker + restoration turns to the\n * session mid-run, and the next turn's wire view is the summary plus the\n * preserved tail. It mutates the session (durable summary turn), whereas\n * `compactStrategy` never touches `session.turns`.\n *\n * The two compose: `compactStrategy: 'tail'` trims tool-output bytes every\n * turn; `autoCompact` summarizes the narrative when the window fills.\n */\nexport interface AutoCompactConfig {\n /**\n * Fraction of the EFFECTIVE context window (raw window minus the output\n * reserve) the last turn's input usage must reach to fire. Must be in the\n * open interval `(0, 1)`; out-of-range disables the trigger. Default: `0.85`.\n */\n threshold?: number\n /**\n * Raw model context window in tokens. The SDK core has no model catalog,\n * so when omitted it falls back to `provider.meta.contextWindow` (a\n * number, if the provider advertises one). When neither resolves, the\n * trigger is inert — it can't compute a usage ratio. Chat hosts inject\n * the picked model's window here from their catalog.\n */\n contextWindow?: number\n /**\n * Trailing turns kept verbatim (passed to `compactConversation` as\n * `scope: 'tail'` `keepTurns`). Defaults to `compactKeepTurns` when set,\n * else `4`.\n */\n keepTurns?: number\n /**\n * Hysteresis floor — minimum fraction of the window that must be consumed\n * by NEW content after a compaction before another can fire, so the\n * post-compact bounce doesn't immediately re-trigger. Default: `0.1`.\n */\n minGrowthFraction?: number\n /**\n * Re-inject recently-read files + active skills as synthetic tool-call\n * pairs after the summary marker (via `buildPostCompactAttachments`) so\n * the model keeps its working set. Default: `true`.\n */\n restore?: boolean\n /**\n * Consecutive-failure circuit breaker. After this many failed compaction\n * attempts in a single run, the loop stops trying for the rest of the run\n * (the reactive `context_exceeded` net still protects overflow). A\n * successful compaction resets the counter. Default: `2`.\n */\n maxFailures?: number\n /**\n * Provider the summary call runs on. Defaults to the run's main\n * provider. Pair with {@link model} to summarize on a different\n * (cheaper / larger-window) model than the one driving the\n * conversation. The trigger threshold still measures the MAIN model's\n * window — it gauges the live conversation, not the summarizer.\n */\n provider?: Provider\n /**\n * Model id for the summary call — resolved against {@link provider}\n * when set, else against the run's main provider. Defaults to the\n * run's main model.\n */\n model?: string\n /**\n * Release the in-memory bodies of turns each compaction summary replaces:\n * large text `tool_result` outputs are written host-side to\n * `<persistDir>/released/<callId>.txt` (exempt from the persist-dir LRU\n * cap — once the stub round-trips into the session store the blob is the\n * only remaining copy, so it lives and dies with the session) and the\n * session's in-memory copy is swapped for a `<persisted-output …>` stub.\n * Replaced turns never reach the wire again, so without this a\n * long-lived host retains every pre-compaction tool output for the\n * process's life. Requires `persistDir`; content stays retrievable from\n * the stub's path. Off by default — hosts that need the lossless\n * inline transcript (SFT capture, exporters) should leave it off.\n */\n releaseReplacedTurns?: boolean\n}\n\n/**\n * `AgentBehavior.autoCompact` value: `true` enables loop-native\n * auto-compaction with defaults, `false` / omitted disables it, and an\n * {@link AutoCompactConfig} enables it with overrides.\n */\nexport type AutoCompact = boolean | AutoCompactConfig\n\n/**\n * Per-tool dedup configuration. Two equivalent forms:\n *\n * - **Hasher only**: a bare function. The framework runs\n * in `'replay'` mode — identical re-calls replay the prior result.\n * - **Config object**: explicit `hasher` plus a `mode`:\n * - `'replay'` (default) — replay the cached result on identical re-\n * calls. Cheap loop, but doesn't BREAK the loop — the model keeps\n * seeing the same tool_result and keeps trying.\n * - `'block-after'` — replay identical calls until the total count\n * reaches `threshold`, then refuse with `Blocked: <reason>` via\n * `tool:gate`. Designed to\n * break tight loops where the model fixates on a single failing\n * call. The block lands on the Nth identical call (i.e. once the\n * total count reaches `threshold`); the cap is sticky — every\n * subsequent identical call also blocks until a different input\n * resets the counter.\n *\n * `reason` is the model-visible refusal text. The framework formats the\n * final tool_result as `Blocked: <reason>` (see `tool:gate` docs).\n * Strings are used verbatim; the callback form receives the input and\n * the (now-blocking) repeat count so it can produce per-invocation\n * messaging like `Blocked: 4 identical \"shell\" calls — try a different\n * approach`. Default reason is generic but actionable.\n */\nexport type DedupToolConfig\n = | DedupHasher\n | {\n hasher: DedupHasher\n mode?: 'replay' | 'block-after'\n /**\n * For `mode: 'block-after'` only — total count of identical calls\n * (including the first dispatch) at which the gate switches from\n * replay to block. Threshold `4` blocks the 4th identical call;\n * the first 3 (one dispatch + two replays) still go through.\n *\n * Ignored for `mode: 'replay'`. Default: `4`. Values `<= 1` are\n * clamped to `2` (anything less would block the very first replay,\n * which would behave the same as not having a hasher at all).\n */\n threshold?: number\n /**\n * Refusal text rendered into the model-visible tool_result as\n * `Blocked: <reason>`. Either a literal string or a callback that\n * receives the call's input and the repeat count (1-indexed total\n * of identical calls so far, including this blocking one).\n *\n * Default: a generic \"identical call repeated N times\" message.\n */\n reason?: string | ((input: Record<string, unknown>, count: number) => string)\n /**\n * Scope of the identical-call counter.\n *\n * - `'session'` (default) — one entry per `(session, toolName)`.\n * Correct for stateless / pure tools (`read_file`, `grep`)\n * where the same input yields the same result regardless of which\n * run issued it.\n * - `'run'` — one entry per `(session, runId, toolName)`. Required\n * for tools whose identical-input semantics are run-local: a\n * parent and a subagent can legitimately write the *same*\n * `todowrite` payload into their *own* lists, and session-scoping\n * would conflate the two (replaying the parent's result to the\n * child, or counting the child's writes against the parent's\n * block threshold). Falls back to session-scope for calls with no\n * `runId` on the hook context (hand-built contexts / direct\n * invocations).\n *\n * Default: `'session'`.\n */\n scope?: 'session' | 'run'\n }\n\n/**\n * Per-tool predicate selecting which tools the {@link RepeatGuardConfig}\n * watches. A bare string matches that exact canonical name; a function\n * receives the canonical name and returns `true` to track it.\n */\nexport type RepeatGuardToolMatcher = string | ((name: string) => boolean)\n\n/**\n * Configuration for the consecutive-identical-call repeat guard. See\n * {@link AgentBehavior.repeatGuard} for the full contract.\n */\nexport interface RepeatGuardConfig {\n /**\n * Consecutive identical calls at which the guard refuses the call via\n * `tool:gate` `block`. Clamped to `>= 2`. Default: `4` (`read_file` uses\n * `6` unless this is explicitly configured).\n */\n blockThreshold?: number\n /**\n * Consecutive identical calls at which the guard aborts the whole run via\n * the agent's `AbortController` (the loop then fires `agent:abort`). Must\n * be `>` the block threshold to be reachable; clamped up if not. Set to a\n * non-positive value or `Infinity` to disable the abort escalation\n * (block-only). Default: `8` (`read_file` uses `12` unless this is\n * explicitly configured).\n */\n abortThreshold?: number\n /**\n * Which tools to track. Each entry is a {@link RepeatGuardToolMatcher}.\n * When unset, the default tracks the built-in `shell` and `read_file`\n * tools. An empty array disables the guard.\n */\n tools?: readonly RepeatGuardToolMatcher[]\n /**\n * Collapse a call's input to a streak key. Identical keys for the same\n * tool extend the streak. Return `undefined` to opt the call out of\n * streak tracking. When unset, the built-in normalizer is used (shell\n * command canonicalization for `shell`-shaped tools, JSON for everything\n * else).\n */\n normalize?: (name: string, input: Record<string, unknown>) => string | undefined\n /**\n * Model-visible steering text at the block threshold. Used as the\n * `Blocked: <message>` reason for `thresholdAction: 'block'`, or appended\n * to the successful tool result for `thresholdAction: 'steer'`. String used\n * verbatim; callback receives the tool name and observed repeat count.\n * Default: a generic pivot suggestion.\n */\n steeringMessage?: string | ((name: string, count: number) => string)\n /**\n * Legacy alias for {@link steeringMessage}. Preserved for callers already\n * customizing the old block-only message.\n */\n blockReason?: string | ((name: string, count: number) => string)\n /**\n * What to do at `blockThreshold`.\n *\n * - `'block'` (default) refuses the call via `tool:gate`, returning\n * `Blocked: <steering message>`.\n * - `'steer'` lets the tool execute, then appends the steering message to\n * the tool result. `abortThreshold` still blocks and aborts the run.\n */\n thresholdAction?: 'block' | 'steer'\n /**\n * Repeat detector strategy.\n *\n * - `'chain'` — strict back-to-back chain counting. Any intervening tool\n * call breaks the chain.\n * - `'window'` — sliding-window counting. Interleaved calls can accumulate.\n * - `'both'` — run both counters and trip when either reaches the threshold.\n *\n * Default: `'chain'`. For compatibility, setting {@link windowSize} without\n * `mode` keeps the old `'window'` behavior.\n */\n mode?: 'chain' | 'window' | 'both'\n /**\n * Sliding-window size for `mode: 'window'` / `mode: 'both'`.\n *\n * When set (`>= 2`), a call's count is the number of occurrences of its\n * normalized key among that tool's last `windowSize` tracked calls\n * (including this one). A *different* payload of the same tool no longer\n * resets the count. This is opt-in non-chain behavior for consumers that\n * explicitly want to catch interleaved repeats.\n *\n * The effective window is clamped up to the abort threshold (or the\n * block threshold when abort is disabled) so the thresholds stay\n * reachable.\n *\n * Default: `undefined` for `mode: 'chain'`; a conservative built-in default\n * is used when `mode` requests window counting without an explicit size.\n */\n windowSize?: number\n /**\n * Count calls a PRIOR `tool:gate` handler already blocked (tool-budget\n * cap, skill allowed-tools refusal) toward the streak.\n *\n * Why you'd want it on: a model hammering a budget-blocked tool with the\n * identical payload is the canonical stuck loop, but those attempts never\n * reach this guard's counter when this is off — the abort escalation is\n * unreachable and the run burns turns until an external cap\n * (`maxTurns`). With it on, blocked attempts keep marching the streak\n * toward `abortThreshold`; the guard never overwrites the prior gate's\n * refusal reason and only acts at the abort escalation.\n *\n * Calls answered by a prior gate's `result` substitute (dedup replay)\n * are never counted regardless — the model received a real payload.\n *\n * Default: `false` (blocked calls don't count).\n */\n countBlockedCalls?: boolean\n /**\n * Universal repeat ceiling — the last-resort chain breaker. Independent of\n * {@link tools}, it counts exact back-to-back `(tool, args)` repeats across\n * EVERY tool and aborts the run when one recurs this many times in a row.\n * Catches untracked tool chains and tools stuck behind a sticky block (it\n * counts blocked attempts, unlike {@link countBlockedCalls}; never a\n * `result` substitute).\n *\n * Set high enough that honest retries don't trip it. The main false-positive\n * risk is a tool that's legitimately polled with identical args many times\n * (a status/wait tool) — exclude those via {@link globalCeilingExclude}.\n *\n * A non-positive or non-finite value disables it. Default: `12`.\n */\n globalCeiling?: number\n /**\n * Deprecated no-op kept for config compatibility. The {@link globalCeiling}\n * uses strict chain counting, not a sliding window.\n */\n globalCeilingWindow?: number\n /**\n * Tools exempt from the {@link globalCeiling} (e.g. status/poll/wait tools\n * legitimately called with identical args many times). Each entry is a\n * {@link RepeatGuardToolMatcher}. `undefined` (default) exempts nothing; an\n * empty array also exempts nothing.\n */\n globalCeilingExclude?: readonly RepeatGuardToolMatcher[]\n}\n\n/**\n * Strategy for handling reads made stale by a later mutation in the same run.\n * See {@link AgentBehavior.elideStaleReads} for full semantics and the measured\n * cache economics of each mode.\n */\nexport type StaleReadPolicy = 'stub' | 'notice'\n\n/** Hard-limit budget for inline media. See {@link AgentBehavior.mediaBudget}. */\nexport interface MediaBudget {\n /** Inline image / document blocks allowed per request. */\n maxItems: number\n /** Base64 bytes of inline media allowed per request. */\n maxBytes: number\n}\n\n/**\n * Prompt-cache entry lifetime. See {@link AgentBehavior.cacheTtl}.\n */\nexport type CacheTtl = '5m' | '1h'\n\n/**\n * Geometric thinking-budget taper: full budget through `afterTurn`, then\n * multiplied by `factor` each subsequent turn, clamped to `floor`.\n *\n * Emits several distinct budgets on the way down, and on Anthropic each\n * distinct `thinking.budget_tokens` value is a full prompt-cache re-bill (the\n * budget is part of the cache key — measured, see\n * {@link AgentBehavior.thinkingDecay}). Prefer {@link StepThinkingDecay} when\n * caching is on.\n */\nexport interface GeometricThinkingDecay {\n /** Last turn that gets the full thinking budget. */\n afterTurn: number\n /** Multiplier applied to the budget on each turn after `afterTurn`. */\n factor: number\n /** Lowest thinking budget the taper reaches. */\n floor: number\n}\n\n/**\n * Single-step thinking-budget taper: full budget through `afterTurn`, then\n * `floor` for the remainder of the run.\n *\n * Exactly ONE budget change per run, so exactly one prompt-cache invalidation\n * — the cache-aware schedule. Distinguished from\n * {@link GeometricThinkingDecay} by the absence of `factor`.\n */\nexport interface StepThinkingDecay {\n /** Last turn that gets the full thinking budget. */\n afterTurn: number\n /** Thinking budget used after `afterTurn`. */\n floor: number\n /** Always absent. Distinguishes this schedule from {@link GeometricThinkingDecay}. */\n factor?: undefined\n}\n\n/**\n * Thinking-budget decay schedule. See {@link AgentBehavior.thinkingDecay} for\n * semantics and the measured prompt-cache cost of each form.\n */\nexport type ThinkingDecay\n = | GeometricThinkingDecay\n | StepThinkingDecay\n | ((runTurn: number, baseBudget: number) => number)\n\n/** Run behavior settings: limits, guards, caching, compaction and recovery policies. Set agent-wide on {@link AgentOptions.behavior} and override per run. */\nexport interface AgentBehavior {\n /**\n * Override model metadata (output ceiling, pricing, context window,\n * reasoning support) for this agent — and per run, like every other\n * behavior field.\n *\n * Resolution is first-hit-wins: run behavior → agent behavior → the\n * process-wide `setModelCatalog()` → pi-ai's bundled registry → local\n * fallback tables. Entries merge FIELD-LEVEL over the bundled entry, and\n * everything stays keyed by `(provider, modelId)`, so overriding one\n * provider leaves the others fully bundled.\n *\n * Accepts a table keyed `\"<provider>:<modelId>\"` or a\n * `{ get, list }` source — see `ModelCatalog`.\n *\n * ```ts\n * createAgent({ provider, behavior: {\n * modelCatalog: { 'anthropic:my-gateway-alias': { maxTokens: 128000 } },\n * } })\n * ```\n *\n * Note: the chat/TUI model picker resolves metadata before any agent\n * exists, so it reads only the process-wide catalog.\n *\n * Default: unset — the bundled registry is authoritative.\n */\n modelCatalog?: ModelCatalog\n /**\n * Maximum number of tools that may be in flight concurrently within a\n * single assistant turn. The scheduler dispatches concurrency-safe tools\n * (`ToolDef.isConcurrencySafe`) in parallel up to this cap; unsafe tools\n * act as barriers (wait for the fleet to drain, then run alone).\n *\n * Default: `10`. Set to `1` to force fully sequential dispatch regardless\n * of per-tool flags — useful for deterministic debugging / eval-grade\n * runs. Values `< 1` are clamped to `1`.\n */\n maxConcurrentTools?: number\n /**\n * Optional per-turn tool batch scheduler. When unset, Zidane uses its\n * native sliding-window scheduler (on a replay-mode durable runtime, its\n * journaled one, which joins concurrent calls through the runtime).\n */\n toolBatchExecutor?: ToolBatchExecutor\n /**\n * Max agent loop iterations.\n *\n * Default: unlimited (Infinity). The loop runs until the model signals\n * completion (no tool calls / `end_turn`), the abort signal fires, or this\n * cap is hit. Set a finite value as a safety net for runaway loops.\n */\n maxTurns?: number\n /**\n * Inject a synthetic user message when this many turns remain before\n * `maxTurns`, telling the model to wrap up.\n *\n * Without it, a run that grinds into the cap dies silently mid-loop —\n * the model never learns it was on a clock, produces no closing summary,\n * and the user gets a truncated session (observed in production: a\n * 200-turn run terminated by the cap during a read→edit ping-pong, with\n * no final answer). The warning gives the model a deterministic chance\n * to land the work: it fires once, after the turn that leaves exactly\n * this many turns remaining, riding the same steer pathway as\n * `toolBudgets` nudges.\n *\n * Only meaningful when `maxTurns` is finite. Values `>= maxTurns` or\n * non-positive are ignored. Default: `undefined` (no warning).\n */\n maxTurnsWarning?: number\n /**\n * Optional custom message for {@link maxTurnsWarning}. When unset, the loop\n * uses its built-in wrap-up warning. String form is sent verbatim; function\n * form receives the remaining/maximum turn counts and can tailor the nudge.\n */\n maxTurnsWarningMessage?: string | ((ctx: { remaining: number, maxTurns: number }) => string)\n /**\n * Run-level cost ceiling, expressed in USD. After each turn, the sum of\n * `TurnUsage.cost` across the run is compared against this value; if the\n * total has crossed the cap, the loop throws `AgentBudgetExceededError`\n * with `limit: 'cost'`. The agent finalizes the session run as\n * `'aborted'` so partial spend is recorded.\n *\n * Complements {@link maxTurns}: a single expensive turn (large context\n * window, deep reasoning) can blow past a turn-count cap but cost is\n * the unit operators actually want to bound for unattended runs.\n *\n * Checked **post-turn**, so the run may exceed by up to one turn's\n * spend before tripping — soft cap, not exact. Default: unbounded.\n * Set `0` to disable explicitly (negative / `NaN` are ignored).\n *\n * Requires the provider to populate `TurnUsage.cost` (every built-in\n * provider does). For providers that don't, the check silently no-ops\n * and {@link maxTotalTokens} is the right knob instead.\n */\n maxCostUsd?: number\n /**\n * Run-level token ceiling — sum of `TurnUsage.input + TurnUsage.output`\n * across the run. Same post-turn semantic + soft-cap behavior as\n * {@link maxCostUsd}; throws `AgentBudgetExceededError` with\n * `limit: 'tokens'` when exceeded.\n *\n * Cache reads / creations are **not** included in the sum — they're\n * billed at a steep discount, so counting them at par would make the\n * ceiling under-estimate the run's true affordability. Operators\n * wanting a stricter accounting should override via the `usage` hook.\n *\n * Default: unbounded. Set `0` to disable explicitly.\n */\n maxTotalTokens?: number\n /**\n * Run-level wall-clock ceiling in milliseconds. The loop records the\n * run's start via `AgentClock.now()` and compares elapsed time against\n * this value after each turn; when crossed it throws\n * `AgentBudgetExceededError` with `limit: 'wallClock'`. Same post-turn\n * semantic + soft-cap behavior as {@link maxCostUsd} — a single long\n * turn can overshoot the deadline before the check runs.\n *\n * Time is read through the run's clock, so a run on a replay-mode\n * durable runtime (whose clock is journaled) gets a\n * replay-stable deadline: the breach trips at the same turn boundary\n * on every replay of the same invocation. This is the supported\n * replacement for host-side timer + `agent.abort()` deadlines, which\n * cannot be made journal-safe from outside the loop.\n *\n * Default: unbounded. Set `0` to disable explicitly (negative / `NaN`\n * are ignored).\n */\n maxWallMs?: number\n /**\n * Provider first-response watchdog in milliseconds.\n *\n * Starts at `stream:start` and is cleared by the first text/thinking/server\n * tool callback or by the provider returning a tool-only/final result. If it\n * expires, the loop aborts the provider request signal and surfaces a typed\n * provider error instead of leaving `agent.run()` pending forever.\n *\n * `maxWallMs`, when lower, bounds this value too. Set `0` to disable.\n * Default: 300000 (5 minutes).\n */\n providerStreamStartTimeoutMs?: number\n /**\n * Provider total stream watchdog in milliseconds.\n *\n * Bounds the whole `provider.stream()` call, including streams that already\n * produced partial output but never settle. If it expires, the loop aborts\n * the provider request signal and surfaces the timeout as a provider error.\n *\n * `maxWallMs`, when lower, bounds this value too. Set `0` to disable.\n * Default: 1800000 (30 minutes).\n */\n providerStreamTimeoutMs?: number\n /**\n * Provider stream inactivity watchdog in milliseconds.\n *\n * Bounds the gap between observable stream events (text / thinking /\n * server-tool / tool-call-argument deltas) AFTER the first one arrived —\n * the window `providerStreamStartTimeoutMs` does not cover. A stream that\n * goes silent mid-generation (upstream disconnect the SDK never surfaces,\n * a wedged proxy) otherwise hangs until `providerStreamTimeoutMs` (30 min\n * default). If it expires, the loop aborts the provider request and\n * surfaces a typed timeout; the retry path treats it like the other\n * watchdogs (retryable, and eligible for fallback, only when no text or\n * tool-call content streamed and no server-side tool executed; reasoning\n * alone is dropped and regenerated).\n *\n * Off by default: legitimate silent gaps exist (server-side tool\n * execution, very long prefill on providers that emit nothing until\n * done), so hosts opt in with a value tuned to their provider mix —\n * interactive products serving well-behaved streaming APIs can run this\n * as low as 60-90s. `maxWallMs`, when lower, bounds this value too.\n * Set `0` (or leave unset) to disable.\n */\n providerStreamIdleTimeoutMs?: number\n /**\n * MCP connect watchdog in milliseconds.\n *\n * The built-in `connectMcpServers()` has per-server bootstrap timeouts. This\n * aggregate guard covers the whole connect step, built-in or custom, so an\n * embedder's `mcpConnector` that returns a promise that never settles cannot\n * block the first model call.\n *\n * `maxWallMs`, when lower, bounds this value during `run()`. Set `0` to\n * disable. Default: 120000 (2 minutes).\n */\n mcpConnectTimeoutMs?: number\n /**\n * Watchdog for awaited hook dispatch (in milliseconds).\n *\n * Bounds every awaited `hooks.callHook(...)` the agent loop and run\n * lifecycle await — `context:transform` / `system:transform` /\n * `turn:before` / `stream:start` / `turn:after` / `stream:end` /\n * `tool-results:after`, plus the run-setup (`session:start`,\n * `agent:start`) and run-teardown (`agent:done`, `session:end`,\n * `session:turns`, `skills:deactivate`) hooks. A host handler that never\n * resolves would otherwise hang the first turn (before the provider\n * watchdog can run) or strand the `running` gate during cleanup.\n *\n * State-mutating pre-stream hooks surface a timeout as a typed\n * `OperationTimeoutError` (fail-closed — a half-applied transform must\n * not reach the wire); observability/lifecycle hooks log under\n * `ZIDANE_DEBUG` and continue. Generous by design — the goal is catching\n * infinite hangs, not policing slow handlers.\n *\n * Set `0` to disable. Default: 300000 (5 minutes).\n */\n hookTimeoutMs?: number\n /**\n * Watchdog for the first `executionContext.spawn()` (in milliseconds).\n *\n * The process context spawns synchronously, but docker / sandbox / e2b /\n * daytona contexts do daemon + network round-trips (image pull, container\n * create/start) with no inherent timeout. This bounds that first spawn so\n * a hung daemon surfaces a typed `OperationTimeoutError` instead of a run\n * that never starts responding.\n *\n * `maxWallMs`, when lower, bounds this value too. Set `0` to disable.\n * Default: 300000 (5 minutes). Raise for hosts doing large first-time\n * image pulls on slow links.\n */\n spawnTimeoutMs?: number\n /**\n * Watchdog for `agent.destroy()` teardown awaits (in milliseconds).\n *\n * Bounds each awaited teardown step (pending MCP warmup, MCP connection\n * `close()`, execution-context `destroy()`) so a custom connector / sandbox\n * whose `close()` never settles can't strand `destroy()` — which hosts\n * await before releasing their own run gate (TUI session switch). Steps\n * that time out are abandoned and teardown continues. Set `0` to disable.\n * Default: 30000 (30 seconds).\n */\n destroyTimeoutMs?: number\n /**\n * Bounded retry-with-backoff policy applied around `provider.stream()`.\n *\n * Triggers only on errors the provider classifies as `retryable: true`\n * (typically 429 / 5xx HTTP responses and mid-stream SSE errors like\n * Anthropic's `overloaded_error`). Terminal failures (auth, invalid\n * request, context exceeded, aborts) skip retry and propagate.\n *\n * Skipped when text or tool-call content has already streamed this turn,\n * or a server-side tool ran — replaying over partial output would produce\n * a confusing transcript. Reasoning alone does not block a retry: it is\n * dropped (see `discardedThinking` on the `stream:retry` hook) and the\n * next attempt generates its own.\n * Mid-stream continuation (replaying with the partial assistant response\n * appended for the model to continue from) is a separate, larger change.\n *\n * The Anthropic SDK already retries pre-stream errors up to twice\n * internally; this loop-level retry catches mid-stream failures and\n * other providers that don't ship an SDK-side retry.\n *\n * Default: 3 attempts, 1s → 2s → 4s exponential with full jitter,\n * capped at 30s per delay, with `retry-after` / `retry-after-ms`\n * headers honored when present. Set `maxAttempts: 1` to disable.\n */\n retry?: RetryConfig\n /**\n * Custom `toolBatchExecutor` watchdog in milliseconds.\n *\n * Native tool execution keeps its existing per-tool/cancellation semantics.\n * This only bounds a host-provided batch executor; on timeout the executor's\n * signal is aborted and the pending tool calls receive error tool results so\n * a tool-only assistant turn cannot hang the run forever.\n *\n * Set `0` / `undefined` to disable. Default: disabled.\n */\n toolBatchTimeoutMs?: number\n /**\n * Hard cap on output tokens per LLM response. When unset, the provider uses\n * a model-driven default — Anthropic sends the model's max output (the field\n * is mandatory there), OpenAI-style providers omit it so the model runs to a\n * natural stop. Set a number to impose a fixed per-turn output ceiling.\n */\n maxTokens?: number\n /** Thinking token budget — overrides the level-based default when set */\n thinkingBudget?: number\n /**\n * Total thinking budget across the whole task, in tokens. Anthropic\n * adaptive-thinking only — emitted as\n * `output_config.task_budget = { type: 'tokens', total }` alongside the\n * level-derived `effort` hint. Ignored where a task budget has no meaning:\n * non-Anthropic providers, `thinking: 'off'`, and the explicit-budget\n * `enabled` path (`thinkingBudget` set on a non-adaptive level). A\n * whole-task cap, so it is never routed through `thinkingDecay`.\n */\n taskBudget?: number\n /**\n * Emit the level-derived `output_config.effort` hint on Anthropic requests\n * (default: on). Set `false` for Anthropic models that reject the effort\n * field: the level still decides whether thinking is on (`'off'` vs the\n * rest) and an explicit `thinkingBudget` still routes to the `enabled`\n * path — but no `output_config.effort` is sent, so without a\n * `thinkingBudget` every budgeted level (`minimal`..`max`) produces the\n * same plain-adaptive request and the effort distinctions between them are\n * lost. Anthropic-only; other providers derive their reasoning fields from\n * the level itself and ignore this.\n *\n * Run-wide: spawned subagents inherit the behavior, so disabling the hint\n * also strips it from any Anthropic model a child runs on, including ones\n * that accept the effort field.\n */\n effortHint?: boolean\n /**\n * When a provider streams more thinking text than expected, optionally nudge\n * the next continuing turn toward concrete progress. This is advisory and\n * non-interrupting: same-turn tool calls are still allowed to finish.\n */\n thinkingOverrunPolicy?: 'off' | 'steer-next-turn' | {\n mode?: 'off' | 'steer-next-turn'\n thresholdTokens?: number\n message?: string\n }\n /**\n * Default model-specific options applied to every run, keyed by option id\n * (e.g. `{ fast: true }`). A per-run {@link AgentRunOptions.modelOptions}\n * takes precedence when set. See `StreamOptions.modelOptions`.\n */\n modelOptions?: Record<string, boolean>\n /** JSON Schema for structured output enforcement */\n schema?: Record<string, unknown>\n /**\n * Enable provider prompt caching. When on (default), the provider marks the\n * system prompt, tools, and the last stable message with cache breakpoints so\n * the shared prefix is served from cache across turns.\n *\n * - Anthropic: `cache_control: { type: 'ephemeral' }` on the last `system`\n * content part, the last tool, and the last message content part.\n * - OpenAI-compatible / OpenRouter: same shape — honored by Anthropic-backed\n * OpenRouter routes and by Gemini; ignored (no-op) by providers that cache\n * automatically (OpenAI, DeepSeek, Grok, Groq, Moonshot).\n *\n * Usage is surfaced via `TurnUsage.cacheRead` / `TurnUsage.cacheCreation`.\n *\n * Default: `true`.\n */\n cache?: boolean\n /**\n * Lifetime of the provider's prompt-cache entries.\n *\n * - `'5m'` (default) — the provider's standard ephemeral cache. Writes bill\n * at 1.25x input; each hit refreshes the window for free.\n * - `'1h'` — Anthropic's extended cache (`cache_control: { type: 'ephemeral',\n * ttl: '1h' }` plus the `extended-cache-ttl-2025-04-11` beta header).\n * Writes bill at 2x input; reads stay at ~0.1x. Also forwarded on\n * `openrouter` (the `cacheBreakpoints` openai-compat route), which accepts\n * the `ttl` field on Anthropic-family models without a beta header.\n *\n * The 5-minute window is the dominant cache cost for INTERACTIVE sessions: a\n * user who reads output, thinks, and types for six minutes loses the entire\n * cached prefix — a 100% re-bill on the next turn — no matter how\n * byte-stable the wire is. `'1h'` converts that idle-loss into a cheap read\n * for the price of costlier writes; it pays for itself as soon as one\n * would-be expiry per hour is avoided. Headless back-to-back runs (seconds\n * between turns) should stay on `'5m'` — they never idle long enough to\n * benefit from the 2x write premium.\n *\n * Applied uniformly to every breakpoint (Anthropic requires 1h blocks to\n * precede 5m blocks in a request; a uniform TTL sidesteps the ordering\n * constraint). Ignored by providers without a TTL concept. No-op when\n * `cache` is off, and degrades to `'5m'` on the Anthropic OAuth (Claude\n * Code persona) path — only api-key auth has been verified against the\n * extended-TTL beta, and an unaccepted beta would 400 every request.\n *\n * CAVEAT — verify the benefit on your account before relying on it. The\n * request shape is byte-verified (`ttl` + beta header on the wire), but in\n * our live measurement (2026-07, sonnet-4-6, direct API) a 1h entry did NOT\n * survive a 6-minute idle; whether that is model support, account tier, or\n * plain eviction is not distinguishable from outside. The gated idle test in\n * `test-inference/cache-stability.inference.test.ts` (`INFERENCE_SLOW=1`)\n * measures it for your credentials.\n *\n * Default: `'5m'`.\n */\n cacheTtl?: CacheTtl\n /**\n * Soft per-turn cap on total tool-output bytes. When the sum of `outputBytes`\n * across a turn's tool results exceeds this value, the loop injects a\n * synthetic user message instructing the model to summarize before calling\n * more tools, and fires the `budget:exceeded` hook.\n *\n * Measured **post-`tool:transform`** so consumer truncation counts toward the\n * budget. Off by default (undefined / `0` disables the check). A reasonable\n * starting value for OSS-model integrations is `32768`.\n */\n toolOutputBudget?: number\n /**\n * Canonical tool names whose output is exempt from\n * {@link AgentBehavior.toolOutputBudget} accounting. Their text-equivalent\n * bytes don't count toward the per-turn cap and the \"summarize before\n * calling more tools\" nudge is not triggered by them alone.\n *\n * Intended for tools whose entire purpose is to LOAD context into the\n * conversation — penalising them via the budget creates the exact\n * failure mode the budget is meant to prevent (the model gets steered\n * away from the very tool it just called to make progress):\n *\n * - `tool_search` — returns full `inputSchema` payloads for MCP tools the\n * model needs to call next. A Notion MCP server with 20+ tools easily\n * exceeds 64 KiB of schema JSON in a single discovery call.\n * - `skills_use` / `skills_read` — inject skill content; that text IS\n * the value of the call.\n *\n * For MCP tools, key by the namespaced wire name (`mcp_<server>_<tool>`).\n * The matching happens on the canonical (registry-key) name, so aliases\n * are stable.\n *\n * Default: `undefined` — every tool counts. `DEFAULT_BUDGET_EXCLUDE_TOOLS`\n * (`zidane/chat`) is a ready-made list of the context-loading tools.\n */\n toolOutputBudgetExcludeTools?: readonly string[]\n /**\n * Default `read_file` slice limits for calls that omit `limit` / `maxBytes`.\n * Explicit tool-call arguments still win. SDK default remains the tool's\n * built-in defaults; hosts can tighten these for context-sensitive profiles.\n * `maxBytes` caps text reads only; image and document reads keep the\n * tool's attachment cap.\n */\n readFileDefaults?: {\n limit?: number\n maxBytes?: number\n }\n /**\n * Deduplicate identical re-reads of the same file in `read_file`. When the\n * model re-reads a file with the same slice and the bytes haven't changed\n * since the last read in this session, the tool returns a short stub\n * instead of re-emitting the full content. Pairs with the read-before-edit\n * guard in `edit` / `multi_edit`.\n *\n * Requires a session (set via `createSession()`); without one, the flag is\n * a no-op since per-session state has nowhere to live.\n *\n * Default: `true`.\n */\n dedupReads?: boolean\n /**\n * Taper the thinking budget over the course of a run. Late turns are\n * usually checkpoint / cleanup work where reasoning rarely pays for\n * itself; early turns benefit most. Three forms — see {@link ThinkingDecay}:\n *\n * - **Step** — `{ afterTurn, floor }`: full budget through `afterTurn`, then\n * `floor` for the rest of the run. ONE budget change per run. This is the\n * cache-aware schedule — prefer it whenever prompt caching is on.\n * - **Geometric** — `{ afterTurn, factor, floor }`: multiply by `factor`\n * each turn past `afterTurn`, clamped to `floor`. Example\n * `{ afterTurn: 5, factor: 0.5, floor: 1024 }` with a base budget of 8192:\n * turns 1-5 = 8192, turn 6 = 4096, turn 7 = 2048, turn 8+ = 1024.\n * - **Function** — `(runTurn, baseBudget) => number`. Arbitrary curves;\n * `runTurn` is 1-indexed, run-relative (resumed sessions reset).\n *\n * ## Prompt-cache cost — measured, not theoretical\n *\n * On Anthropic, `thinking.budget_tokens` is part of the prompt-cache key:\n * a request with a different budget reads NOTHING back — `cacheRead: 0`,\n * scaffold included — and re-bills the entire prefix at the cache-write\n * rate (measured live; pinned in\n * `test-inference/cache-stability.inference.test.ts`). Every distinct value\n * a schedule emits is therefore one full re-bill: the geometric example\n * above costs three (4096, 2048, 1024), a step schedule costs exactly one,\n * and a function that returns a fresh value per turn costs one PER TURN —\n * a per-turn curve silently disables caching for the rest of the run.\n *\n * No-op when `thinkingBudget` is unset. Applies to the per-turn thinking\n * budget: the Anthropic explicit-budget `enabled` path and openai-compat\n * `max_tokens` padding / `reasoning.max_tokens`. It does NOT touch\n * `taskBudget` (a whole-task cap — per-turn decay is meaningless there).\n * It is also skipped for `thinking: 'adaptive'` on providers that treat\n * `thinkingBudget` as a whole-response envelope cap there (Anthropic —\n * `ProviderCapabilities.adaptiveBudgetIsEnvelope`): decaying an envelope cap\n * would strangle text/tool output, not just thinking. On providers where\n * adaptive + `thinkingBudget` is a genuine per-turn thinking budget (e.g.\n * openai-compat `reasoning.max_tokens`), adaptive still decays.\n *\n * Default: `undefined` (no decay).\n */\n thinkingDecay?: ThinkingDecay\n /**\n * Policy for full-file writes to paths that already exist. The guard is a\n * `tool:gate` refusal that points the model toward `read_file` + `edit` /\n * `multi_edit`.\n */\n writeFileExistingPolicy?: 'allow' | 'block-with-edit-recipe' | {\n mode?: 'allow' | 'block-with-edit-recipe'\n allowPaths?: readonly string[]\n denyPaths?: readonly string[]\n }\n /**\n * Steer when a model emits a textual tool call instead of a provider-native\n * tool call. Execution of parsed text calls is intentionally not part of this\n * public contract.\n */\n textToolCallRescue?: false | {\n mode?: 'steer'\n }\n /**\n * Steer when the model calls a tool name that is not registered. The unknown\n * tool result remains an error; this only queues a bounded follow-up nudge\n * with closest advertised tool-name suggestions.\n */\n unknownToolRecovery?: false | {\n mode?: 'steer'\n maxSuggestions?: number\n maxNudges?: number\n message?: string | ((ctx: { called: string, suggestions: readonly string[] }) => string)\n }\n /**\n * Advisory progress nudges for broad stalls that exact repeat guards do not\n * cover: several read-only turns, failed/no-op mutations, validation rejects,\n * or tool errors. All nudges are non-interrupting and off by default.\n */\n progressNudges?: false | {\n mode?: 'steer'\n readOnlyTurns?: number | false\n failedMutations?: number | false\n noopMutations?: number | false\n validationRejects?: number | false\n toolErrors?: number | false\n /**\n * Nudge after N consecutive turns that each carried exactly ONE read-only\n * tool call. Independent reads shipped one per round trip pay full\n * provider latency each; the nudge steers the model to batch them into a\n * single response. Off by default — aimed at models that ignore static\n * batching doctrine (measured in production: ~1.3 calls/round-trip vs\n * ~2.5 for models that batch, tripling turn wall time).\n */\n singleCallTurns?: number | false\n readOnlyTools?: readonly string[]\n maxNudges?: number\n messages?: Partial<Record<ProgressNudgeKind, ProgressNudgeMessage>>\n }\n /**\n * Per-tool soft call budget for this run. Keyed by **canonical** tool name.\n * On the first call after the run-cumulative dispatched count for that tool\n * reaches `max`, the framework fires `onExceed`:\n *\n * - `'steer'` (default) — let the call execute, but emit a synthetic user\n * message after the turn that nudges the model away from re-calling the\n * tool. Reuses the existing post-turn steer pathway used by\n * `toolOutputBudget`. Fires `tool-budget:exceeded` with `mode: 'steer'`.\n * - `'block'` — refuse the call via `tool:gate` `block`. The model sees a\n * `Blocked: <reason>` tool result. Fires `tool-budget:exceeded` with\n * `mode: 'block'`.\n * - **Function** — `(ctx) => { mode, message }`. The consumer supplies the\n * steering / refusal text and chooses the mode dynamically.\n *\n * Counts include both real dispatches and dedup replays.\n * Excludes calls already blocked by an earlier gate (skill allow-list,\n * consumer hook). Tool dispatched by spawned subagents has its own per-run\n * counter — child counts never charge the parent.\n *\n * For MCP tools, key by the namespaced wire name (`mcp_<server>_<tool>`).\n *\n * Atomic in parallel mode: the middleware tracks its own per-tool\n * approval counter, incremented synchronously at gate-time. A\n * 4-call parallel batch against `max: 2` will let the first 2 through\n * and refuse the rest, even though the loop's `runToolCounts` only\n * propagates between calls (not within a single batch's gate fan-out).\n *\n * Default: `undefined` (no budget enforcement).\n */\n toolBudgets?: Record<string, {\n max: number\n onExceed?: 'steer' | 'block' | ((ctx: {\n tool: string\n count: number\n max: number\n }) => { mode: 'steer' | 'block', message: string })\n /**\n * Hard ceiling for `'steer'` mode. Steer is advisory — the call still\n * executes and only a one-time nudge is queued — and models routinely\n * ignore it (observed in production: a `shell 100/100` steer followed\n * by 30+ more shell calls until the run died at the turn cap). When\n * the approved-call count reaches `hardMax`, the gate force-blocks\n * regardless of the resolved steer mode (including function-form\n * `onExceed` that returned `'steer'`), firing `tool-budget:exceeded`\n * with `mode: 'block'`.\n *\n * Pair with `repeatGuard.countBlockedCalls: true` so identical\n * retries against the hard ceiling escalate to a run abort instead\n * of burning turns.\n *\n * Ignored in `'block'` mode (already blocking at `max`). Must be\n * `> max` to be meaningful; non-positive / non-finite disables.\n * Default: `undefined` (steer never escalates).\n */\n hardMax?: number\n }>\n /**\n * Consecutive-identical-call repeat guard. Unlike {@link dedupTools} (keyed\n * on argument hash, replays/blocks per the consumer's hasher) and\n * {@link toolBudgets} (run-cumulative count regardless of payload), the\n * repeat guard watches a **run-local chain of back-to-back identical\n * tool payloads** and escalates:\n *\n * - At `blockThreshold` (default 4; `read_file` default 6)\n * consecutive identical calls: either refuse the call via `tool:gate`\n * `block` or let it execute and append a steering message, depending on\n * {@link RepeatGuardConfig.thresholdAction}. Fires\n * `repeat-guard:exceeded` with `action: 'block' | 'steer'`.\n * - At `abortThreshold` (default 8; `read_file` default 12)\n * consecutive identical calls: abort the whole run via the agent's\n * `AbortController` (the loop then fires `agent:abort`, the same path a\n * user cancel takes). A `repeat-guard:exceeded` event with\n * `action: 'abort'` fires first so consumers can distinguish a guard abort\n * from a user cancel. This is the only layer that stops a model which\n * ignores the `Blocked:` reason and keeps retrying.\n *\n * \"Consecutive\" means the next tool call in the run is the same tracked\n * tool with the same normalized payload. Any intervening tool call, tracked\n * or not, breaks the chain.\n *\n * `mode: 'window'` intentionally relaxes that definition. `mode: 'both'`\n * keeps the chain guard while also catching interleaved repeats inside\n * {@link windowSize}.\n *\n * `tools` selects which tools the guard watches. Each entry is a predicate\n * over the canonical tool name; a string entry matches that exact name. The\n * default (when {@link tools} is unset) tracks the built-in `shell` and\n * `read_file` tools — auto-approved retry / re-read loop magnets. Provide\n * your own list to widen / narrow the set; an empty list disables the guard.\n *\n * `normalize` lets you collapse semantically-identical payloads to one\n * streak key. The built-in normalizer for `shell`-shaped tools strips\n * `cd <path> &&` prefixes, trailing `& sleep N ; <verify>` tails, and short\n * flags. Return `undefined` to opt a specific call out of streak tracking\n * entirely.\n *\n * Streak state is run-local and in-memory (no session required), torn down\n * when the run ends.\n *\n * Default: `undefined` (no repeat guard). The built-in chat profiles enable\n * it with the default thresholds.\n */\n repeatGuard?: RepeatGuardConfig\n /**\n * Generic per-tool argument deduplication. Keyed by the tool's **canonical**\n * name (alias-stable). Each entry is a hasher: `(input) => string | undefined`.\n *\n * **Hasher contract** — three return values, three meanings:\n *\n * | Return | Meaning |\n * |-------------------------|------------------------------------------------------------------------|\n * | a non-empty string | Cache key for this call. Equal keys (most-recent-only, this session) |\n * | | replay the prior recorded result without re-dispatching the tool. |\n * | `undefined` | **Skip dedup for this call.** The tool runs normally; nothing recorded.|\n * | `''` / non-string | Treated identically to `undefined` (defensive: no dedup, no error). |\n *\n * The `undefined` opt-out is the way to say *\"this specific call is not\n * cacheable\"* (timestamps in input, randomness baked in, debug flags). It\n * is **not** the same as `JSON.stringify(input)` — that would dedup against\n * the verbatim input. Pick one explicitly:\n *\n * ```ts\n * // Always cache by full input — every identical re-call dedups.\n * dedupTools: { my_pure_tool: input => JSON.stringify(input) }\n *\n * // Cache by a normalized subset; non-cacheable shapes opt out.\n * dedupTools: {\n * run_query: (input) => {\n * const q = typeof input.query === 'string' ? input.query.trim().toLowerCase() : undefined\n * if (!q || q.includes('now()') || q.includes('random()')) return undefined\n * return q\n * },\n * }\n * ```\n *\n * On a hit, the previously-recorded result is replayed as the tool_result\n * without dispatching the tool. The substitution flows through `tool:gate`\n * `result`, so `tool:after` and `tool:transform` still fire.\n *\n * Error results are NEVER recorded (and therefore never replayed): a\n * failure is a state-dependent verdict, and replaying it would freeze it —\n * e.g. an `edit` rejected by `requireReadBeforeEdit` would keep failing\n * with \"has not been read\" even after the model reads the file, as long\n * as the retry hashes to the same key. Failed calls always re-execute.\n *\n * Requires a session (`createSession()`); without one, the map is a silent\n * no-op since per-session state has nowhere to live. Tools with side\n * effects or non-deterministic outputs (network, time, randomness) MUST\n * NOT be listed — there is no safety net beyond the consumer's hasher.\n *\n * For MCP tools, key by the namespaced wire name (`mcp_<server>_<tool>`).\n * Concurrency-safe siblings ({@link ToolDef.isConcurrencySafe}) in the\n * SAME assistant turn race against each other — none can dedup against\n * a sibling that started in the same batch. Unsafe tools act as barriers\n * and honor submission order within a turn, so an unsafe-but-listed tool\n * follows the cache cleanly.\n *\n * **Cache policy**: only the most recent `(hash, result)` per tool is\n * retained. Interleaved patterns (input A, input B, input A) miss on the\n * second A because B overwrote it. Sufficient for the common spam-the-\n * same-call loop; consumers needing a richer cache should hook\n * `tool:gate` directly.\n *\n * Default: `undefined` (no per-tool dedup).\n */\n dedupTools?: Record<string, DedupToolConfig>\n /**\n * Require `read_file` before `edit` / `multi_edit` on the same path.\n * Eliminates the silent-corruption failure mode where a model \"remembers\"\n * stale content and applies a substring edit against bytes that have\n * moved.\n *\n * Never-read files are refused outright. Content DRIFT (read, but the\n * bytes changed since — a formatter, codegen, an editor auto-save) is\n * anchor-tolerant: the hazard drift creates is a mis-anchored\n * `old_string`, so if the edit still resolves VERBATIM against the\n * current bytes it proceeds with a disclosure note; when resolution\n * fails — or only a fuzzy recovery matches, the one combination where\n * mis-anchoring is genuinely plausible — the drift is refused\n * (\"re-read the file\"), escalating on repeat refusals to name the\n * likely external writer. Flat drift refusals are\n * what taught models to abandon `edit` for shell one-liners that bypass\n * every guard.\n *\n * Requires a session. Off by default; turn it on for stricter eval-grade\n * runs where silent edit corruption would invalidate the result.\n *\n * Default: `false`.\n */\n requireReadBeforeEdit?: boolean\n /**\n * Count plain shell file reads toward the read-state that\n * {@link AgentBehavior.requireReadBeforeEdit} checks. After a successful\n * `shell` call, files read in full view by `cat`, `head`, `tail`, `nl` or\n * `sed -n '<range>p'` (segments joined by `&&` / `;`, an optional leading\n * `cd dir`) are recorded with their current content hash. Piped segments,\n * redirects, command substitution and anything else the parser can't\n * classify record nothing.\n *\n * Entries are marked stale for `read_file` dedup, so a later `read_file`\n * always returns the full body instead of claiming the shell output is\n * already visible. Only matters with `requireReadBeforeEdit` on: it stops\n * the gate refusing files the model demonstrably read through `shell`.\n * Not rebuilt by durable-replay read-state hydration. Shell calls made\n * from codemode scripts count too.\n *\n * Default: `true` (set `false` to make only `read_file` count).\n */\n trackShellReads?: boolean\n /**\n * Append a one-line pointer to the dedicated tool when a `shell` call does\n * work that tool covers: a pure file read (`read_file`), a pure content\n * search (`grep`), or a scripted file edit — python/perl/`sed -i`/heredoc\n * writes (`edit` / `multi_edit`). Shown only when that tool is advertised,\n * at most `maxPerKind` times per kind per session (default 2), so it fades\n * once the model has seen it.\n *\n * `true` uses the defaults; an object tunes `maxPerKind`.\n *\n * Default: off.\n */\n shellToolNudges?: boolean | { maxPerKind?: number }\n /**\n * Client-side context compaction strategy. Use this for non-Anthropic\n * providers (OSS via cerebras / openai-compat / openrouter) that don't\n * have a server-side equivalent. Anthropic users should prefer the\n * server-side `context-management-2025-06-27` beta — see\n * `AnthropicParams.contextManagement`.\n *\n * - `'off'` (default) — no client-side compaction.\n * - `'tail'` — when total tool-output bytes in the persisted history\n * exceed `compactThreshold`, replace older `tool_result` outputs with a\n * short stub, keeping the newest `compactKeepTurns` turns intact. The\n * compaction is applied to the wire-level message list only; the\n * underlying session turns are not modified.\n * - **Function** ({@link CompactFunction}) — a pluggable replacement\n * that the loop calls instead of the built-in tail elision. The\n * function receives the post-`context:transform` wire messages and\n * returns the compacted view (sync or async). Use this to plug in\n * an LLM summarizer (\"`<analysis>` / `<summary>`\" style), a custom\n * semantic-aware compactor, or anything else hosts need beyond the\n * built-ins. The framework never modifies session turns — the same\n * wire-only contract as `'tail'` applies.\n *\n * Preserved thinking (Anthropic): every wire rewrite of an earlier\n * message also invalidates each replayed thinking block after it — the\n * API drops them server-side (`provider:input-transformations`, reason\n * `prefix_binding_mismatch`) and the model answers without that\n * reasoning. The chunk-snapped frontier bounds this to once per chunk,\n * but the cost of `'tail'` on Anthropic is cache rewrite + lost\n * reasoning; server-side context management has neither.\n *\n * Default: `'off'`.\n */\n compactStrategy?: 'off' | 'tail' | CompactFunction\n /**\n * Context window (tokens) of the model serving the run, for window-relative\n * wire budgets such as {@link compactThresholdWindowFraction}. The SDK core\n * has no model catalog; chat hosts inject the picked model's window here\n * every run, independent of whether auto-compaction is enabled. Resolution\n * order in the loop: this → `autoCompact.contextWindow` → the active\n * `ModelTarget.contextWindow` → `provider.meta.contextWindow`. Does not by\n * itself enable auto-compaction.\n */\n contextWindow?: number\n /**\n * Soft byte threshold that triggers tail compaction when\n * `compactStrategy === 'tail'`. Counts the post-`context:transform` bytes\n * of `tool_result` outputs across all messages. Default: `131_072` (128\n * KiB). Ignored when compaction is off. See\n * {@link compactThresholdWindowFraction} for the context-window-aware floor.\n */\n compactThreshold?: number\n /**\n * Scale the tail-compaction threshold with the model's context window. When\n * set and the run resolved a window (`autoCompact.contextWindow`, or the\n * provider's `meta.contextWindow`), the effective threshold becomes\n * `max(compactThreshold, fraction × contextWindow × 4 bytes/token)`; with\n * no window the byte threshold stands alone.\n *\n * Why: every frontier step of tail compaction rewrites an early message on\n * the wire, which re-bills the entire suffix at the cache-write rate. On a\n * 1M-context model a 128 KiB byte threshold trips on ordinary tool-heavy\n * sessions and each step costs ~50x what keeping those bytes cached would\n * (measured: 117k-token re-bills on a 157k-token session). A fraction of\n * the window keeps small-context OSS models on the byte threshold while\n * large-context runs leave compaction to the autocompact ceiling.\n *\n * `0.25` → 1M window: ~1 MiB; 200k: ~200 KiB; 128k: 128 KiB (byte floor\n * wins). Default: `undefined` (byte threshold only).\n */\n compactThresholdWindowFraction?: number\n /**\n * Hysteresis for `compactStrategy: 'tail'`. When set (`0 < f < 1`), once\n * kept tool output exceeds the threshold the frontier trims it down to\n * `f × threshold` in one step, then waits until it exceeds the threshold\n * again, instead of advancing a chunk every few turns. The frontier is a\n * pure function of the message prefix, so appending messages never moves an\n * earlier step: each step re-bills the suffix once, and steps are spaced by\n * about `(1 - f) × threshold` of new tool output. `compactKeepTurns` stays\n * a floor. Out-of-range values are ignored. Default: `undefined` (keep the\n * largest recent suffix under the threshold, re-evaluated every turn).\n */\n compactLowWaterFraction?: number\n /**\n * Skip client-side `compactStrategy: 'tail'` elision when the run matches:\n * the serving provider's `name` is in `providers` (any provider when\n * unset) AND the active context window is at least `minContextWindow`\n * tokens (any window when unset; an unknown window never matches a set\n * minimum). Auto-compaction stays the ceiling.\n *\n * Why: on Anthropic models with preserved thinking, every stubbed\n * `tool_result` makes the API drop every later replayed thinking block, and\n * on large windows each frontier step re-bills most of the prompt.\n * Default: `undefined` (never skip).\n */\n compactSkip?: {\n providers?: readonly string[]\n minContextWindow?: number\n }\n /**\n * Number of trailing turns to leave untouched during tail compaction. The\n * most-recent `compactKeepTurns` user/assistant messages are not eligible\n * for elision so the model keeps the freshest tool context. Default: `4`.\n */\n compactKeepTurns?: number\n /**\n * Loop-native auto-compaction. When enabled, the agent loop watches each\n * turn's input usage and — once it crosses the configured fraction of the\n * effective context window — runs a real LLM summary over the older turns\n * mid-run, appends a `compact-summary` marker (+ optional file/skill\n * restoration) to the session, and continues. The next turn's wire view\n * is the summary plus the preserved tail.\n *\n * `true` enables with defaults (`threshold: 0.85`, restore on); an\n * {@link AutoCompactConfig} overrides individual knobs. Requires a window\n * source — `autoCompact.contextWindow` or `provider.meta.contextWindow` —\n * or the trigger stays inert. Reuses the same `shouldAutoCompact` policy\n * the chat hosts use.\n *\n * Distinct from `compactStrategy` (per-turn wire-only tool-output trim)\n * and from the always-on reactive `context_exceeded` recovery. Observe\n * via the `compact:start` / `compact:end` hooks.\n *\n * Default: `undefined` (off). The built-in chat profiles enable it.\n */\n autoCompact?: AutoCompact\n /**\n * Prefix every line of `read_file` output with its 1-indexed line number\n * followed by a tab (`<N>\\t<content>`) — the compact `cat -n`-style\n * format Claude Code emits. The `edit` tool strips the prefix from\n * `old_string` / `new_string` so the model can paste back a numbered\n * chunk verbatim without breaking the match.\n *\n * Set `false` to opt out — useful for callers piping `read_file` into\n * downstream parsers that don't recognize the prefix. Reads inside codemode\n * scripts skip the prefix whatever this says, since the script parses the\n * text. Per-call `read_file({ lineNumbers })` overrides both defaults.\n *\n * Default: `true`.\n */\n readLineNumbers?: boolean\n /**\n * How to handle older `read_file` results once a successful `edit` /\n * `multi_edit` / `write_file` later in the same run modified the same path.\n *\n * - `'stub'` (or legacy `true`) — replace the stale read's `tool_result`\n * with a short stub on the **wire**; persisted session turns keep the\n * original content. Reclaims the context the model would otherwise carry\n * forward \"in case it needs it\", but REWRITES THE MESSAGE PREFIX at the\n * stale read's position — a prefix cache invalidates everything from that\n * point, so each newly-stale read re-bills the conversation at the\n * cache-write rate. Measured on a 129-turn production trace: 13\n * invalidations, ~0.25M re-billed chars. On Anthropic the same rewrite\n * also drops every replayed thinking block after the stale read\n * (preserved-thinking prefix binding; surfaced as\n * `provider:input-transformations`), so each stub costs reasoning too.\n * Prefer `'notice'` there.\n *\n * - `'notice'` — append-only. The stale read keeps its bytes; instead, a\n * `<system-reminder>` naming the stale path is appended to the mutating\n * tool's own result the moment the mutation succeeds. Nothing earlier in\n * the transcript ever changes, so the prompt cache never breaks. The\n * trade: stale file bodies stay in context (billed at the ~0.1x\n * cache-read rate) and the transcript reaches compaction thresholds\n * sooner. On the same trace this halves the remaining cache spend —\n * keeping stale bytes cached is cheaper than re-billing the conversation\n * to delete them unless the run outlives ~12.5x(prefix/staleBytes) turns.\n *\n * - `false` / `undefined` — off: no stubs, no notices.\n *\n * Detection is identical and conservative in both modes — only a\n * tool_result confirming success (`Edited …`, `Created …`, `Updated …`)\n * marks prior reads stale. Failed edits and `No change needed` write_file\n * calls do NOT. Write safety never depends on this knob:\n * `requireReadBeforeEdit` compares content hashes at edit time.\n *\n * Default: `false`. The built-in chat profiles leave it off.\n */\n elideStaleReads?: boolean | StaleReadPolicy\n /**\n * Age base64 media (top-level `image` / `document` blocks AND image /\n * document parts nested in `tool_result` outputs) off the **wire** once it\n * is older than the last `mediaKeepTurns` messages. The aged-out block is\n * replaced with a short text marker carrying a re-resolve handle\n * (`attachment://<hash>`); the persisted session keeps the original bytes\n * untouched, so the model can pull any image back on demand with\n * `read_file path=\"attachment://<hash>\"`.\n *\n * This is the long-run de-bloat lever: a screenshot or pasted image is only\n * carried as base64 for a few recent turns instead of riding the prompt\n * forever. Distinct from `compactStrategy: 'tail'`, which only stubs\n * `tool_result` *text* and never counts (or trims) media bytes.\n *\n * The frontier is chunk-snapped for prompt-cache stability — media drops\n * out in discrete jumps, not one block per turn. `0` ages out ALL media;\n * `undefined` (default) disables the pass entirely (media rides every turn).\n *\n * Prefer {@link AgentBehavior.mediaBudget} on prompt-cached providers: each\n * step here rewrites an earlier message, and on a long session that re-bills\n * the whole conversation at the cache-write rate — far more than keeping a\n * ~1.6k-token screenshot as a cache read for the rest of the run.\n *\n * Takes precedence over `mediaBudget` when both are set at the same layer\n * (a run's `behavior` overrides the agent's as a pair), so an explicit\n * window keeps working on top of profiles that default to the budget.\n *\n * Preserved thinking (Anthropic): each age-out step rewrites an earlier\n * message, which drops every replayed thinking block after the frontier\n * server-side (`provider:input-transformations`). Same trade as\n * `compactStrategy: 'tail'` — once per chunk, not per turn.\n *\n * Default: `undefined` (off).\n */\n mediaKeepTurns?: number\n /**\n * Keep media inline and age it out only near the provider's hard request\n * limits. While the inline image / document count stays ≤ `maxItems` and\n * their base64 bytes ≤ `maxBytes`, nothing is touched. Past either, the\n * OLDEST media ages out (same recoverable `attachment://<hash>` marker as\n * {@link AgentBehavior.mediaKeepTurns}) in steps of half that limit, so the\n * prefix is rewritten once per half a budget of new media instead of once\n * per image. Stateless: the aged set is a pure function of the message\n * list, so it only ever grows within a run.\n *\n * Images still wider or taller than 2000 px (formats\n * {@link AgentBehavior.maxImageEdge} can't re-encode) keep the\n * `mediaKeepTurns`-style 8-message window: once a request carries more than\n * 20 images, Anthropic rejects the whole request if any one exceeds 2000 px.\n *\n * `maxItems` is lowered to the provider's\n * `capabilities.maxMediaPerRequest` when that is smaller (Bedrock: 20).\n * Ignored when `mediaKeepTurns` is set.\n *\n * Default: `undefined` (off). The built-in chat profiles, `runHeadless`\n * and the Restate start runner use `{ maxItems: 80, maxBytes: 20 MiB }`.\n */\n mediaBudget?: MediaBudget\n /**\n * Downscale inline PNG images whose longest edge exceeds this many pixels,\n * once, when they enter the session (tool results and prompt images). The\n * persisted turn holds the shrunk bytes, so the prompt cache sees the same\n * prefix on every request. Other formats and PNGs the built-in decoder\n * doesn't handle (interlaced, 16-bit) pass through untouched.\n *\n * `2000` is the largest edge Anthropic accepts once a request carries more\n * than 20 images, so media can stay inline without that rejection. Claude\n * 4.7+ models see up to 2576 px and older ones 1568 px, so it keeps most of\n * the detail either way while cutting upload size.\n *\n * Default: `undefined` (off). The built-in chat profiles, `runHeadless`\n * and the Restate start runner use `2000`.\n */\n maxImageEdge?: number\n /**\n * Separator used for provider-facing MCP tool names.\n *\n * Internal/canonical names stay `mcp_<server>_<tool>` for sessions, hooks,\n * budgets, and dispatch. This option only controls the wire name shown to\n * the model via generated aliases. Explicit `toolAliases` still win.\n *\n * - `'_'` (default): `mcp_server_tool`\n * - `'__'`: `mcp__server__tool`\n */\n mcpToolNameSeparator?: '_' | '__'\n /**\n * Tool disclosure strategy. Controls whether the model sees every tool's\n * full `inputSchema` in its tool list every turn (\"eager\") or whether MCP\n * tools are advertised as a name+description catalog in the system prompt\n * and only get full schemas after being surfaced via the `tool_search`\n * native tool (\"lazy\" / progressive disclosure).\n *\n * Native tools (those passed to `createAgent({ tools })`) and skill tools\n * are always eager — they are core to the agent and cheap. Only MCP tools\n * are eligible for lazy disclosure.\n *\n * When `'lazy'`, the agent:\n * - Appends a `<searchable_tools>` section to the system prompt listing\n * every MCP tool by `name` + `description` only (no `inputSchema`).\n * - Auto-injects a `tool_search` native tool (opt out via\n * {@link AgentBehavior.toolSearch}) the model uses to load schemas on\n * demand. Surfaced tools persist for the rest of the run.\n * - Rebuilds the wire-level tool list each turn in two phases: every\n * initially-eager tool in registry order (the cache-stable prefix),\n * then every dynamically-unlocked tool in **unlock order** at the\n * tail (`toolSearch.mode: 'unlock'`).\n *\n * Trade-off of unlock mode: providers hash the request prefix as\n * `tools → system → messages`, so ANY change to the tools array — even a\n * tail append — re-bills the system prompt and every message. Each\n * discovery wave therefore costs one full prompt-cache rewrite; on a long\n * conversation that dwarfs the schema bytes lazy disclosure saved.\n * `toolSearch.mode: 'dispatch'` (the default) removes the rewrite\n * entirely: surfaced tools are invoked through a fixed `call_tool`\n * envelope and the tools array never changes. See\n * {@link AgentBehavior.toolSearch}.\n *\n * Default: `'eager'`.\n */\n toolDisclosure?: 'eager' | 'lazy'\n /**\n * Fine-grained config for the `tool_search` tool auto-injected when\n * {@link AgentBehavior.toolDisclosure} is `'lazy'`. No-op in eager mode.\n *\n * - `tool: false` — opt out of the auto-injection entirely. Use when the\n * host wants to ship a custom discovery tool. Note that the catalog\n * text drops the call-to-action prose in this case so the model isn't\n * pointed at a non-existent tool.\n * - `limit` — default cap on results returned per `tool_search` call when\n * the model omits the parameter. Default: `20`.\n * - `mode` — how a surfaced tool becomes callable:\n * - `'unlock'`: its schema is appended to the wire-level tools array on\n * the next turn. Simple, but every discovery wave changes the tools\n * array and rewrites the whole prompt cache. On Anthropic it also\n * invalidates every replayed thinking block (preserved-thinking\n * prefix binding), so the model loses its earlier reasoning at each\n * wave.\n * - `'dispatch'`: the tools array stays byte-stable for the run. A\n * fixed `call_tool { name, arguments }` native tool is added to the\n * eager set; `tool_search` still returns the schema into the\n * transcript and the model calls the tool through the envelope. The\n * loop unwraps it before dispatch, so gating, argument validation\n * (against the inner tool's schema), hooks, telemetry and transcript\n * events all see the inner tool exactly as with a direct call.\n *\n * Note on host-defined `tool_search`: a tool the host registers under the\n * name `tool_search` (or under any alias whose canonical is `tool_search`)\n * will shadow the auto-injected one — the catalog text will point at the\n * host's wire name, but driving the unlock flow requires either using\n * `createToolSearchTool({ catalog, unlocked })` from `tools/tool-search`\n * (which internally mutates the unlock set) or fully opting out via\n * `toolSearch.tool: false` and treating discovery as a host-side concern.\n * A bare host tool that doesn't touch the unlock set will not advance the\n * lazy disclosure state and the hard gate will keep refusing lazy calls.\n *\n * Default: `undefined` (auto-inject with the default limit). `mode`\n * unset resolves to `'dispatch'` on every provider; set `'unlock'`\n * explicitly to append surfaced tools instead.\n */\n toolSearch?: {\n tool?: false\n limit?: number\n mode?: 'unlock' | 'dispatch'\n /**\n * How the system prompt lists lazy tools. `'full'` (default) gives every\n * tool's description; `'names'` lists names by server and tells the\n * model to call the discovery tool for details, which shrinks the prompt\n * when many MCP tools are connected. Ignored without a discovery tool\n * (`tool: false`) and while codemode is on (`codemode.catalog` decides).\n */\n catalog?: 'full' | 'names'\n }\n /**\n * Opt in to the `codemode` tool: the model writes JavaScript that calls the\n * run's other tools from a QuickJS sandbox, and only the script's output\n * enters the context (see `src/tools/codemode/`). Needs the optional peer\n * dependency `quickjs-wasi`.\n *\n * - `true` / `{ mode: 'on' }`: `codemode` is added next to the usual tools.\n * - `{ mode: 'only' }`: the native tools scripts can call leave the wire\n * and are declared inside the `codemode` description instead (within\n * `inlineBudget` estimated tokens, default 3000), and the injected\n * `tool_search` / `call_tool` are dropped — scripts discover MCP tools\n * with `searchTools()`.\n * - `catalog`: how the system prompt lists lazy (MCP) tools while codemode\n * is on. `'names'` (default) lists names by server, since scripts find\n * and describe tools with `searchTools()` / `describeTool()`; `'full'`\n * keeps every tool's description.\n * - `directCallNotes`: in `'only'` mode, a direct call to a tool declared for\n * scripts still runs, and its text result gets one line naming the script\n * form (at most twice per tool in a conversation; never on `read_file` or\n * JSON results). `false` leaves results as the tools return them.\n * Default: `true`.\n * - `streamProgress`: report each script's progress live (`tool:progress`):\n * what it prints, the output of its shell calls as they run, and the\n * script line of each call (`scriptLine` on the nested calls' hooks,\n * `line` on their records). The model's result does not change.\n * Default: `false`.\n *\n * Not injected when `AgentRunOptions.tools` replaces the registry, or when\n * the host registers its own `codemode`. Default: off.\n */\n codemode?: boolean | { mode?: 'on' | 'only', inlineBudget?: number, catalog?: 'names' | 'full', directCallNotes?: boolean, streamProgress?: boolean }\n /**\n * Render the per-server `instructions` payload from the MCP `initialize`\n * handshake as a `# MCP Server Instructions` section appended to the\n * system prompt. Each server may return free-form guidance (e.g. \"the\n * database is already provisioned, use `apply_migration` directly\")\n * that materially changes how the model uses the server's tools.\n *\n * Matches the Claude Code SDK's default. Without this, models often\n * try to bootstrap state the server already advertises as ready —\n * e.g. running `npx some-cli init` in a loop when the MCP server's\n * instructions would have said \"skip the init, use `apply_migration`\".\n *\n * The section is built once per run and embedded into the system\n * prompt next to the skills + searchable-tools catalogs, so it rides\n * the same system-prompt cache breakpoint. Server insertion order\n * matches `mcpServers` config order for byte-stable output.\n *\n * Only eager-connect servers contribute. `lazyConnect: true` servers\n * are excluded by design — pulling their instructions would force the\n * connection the host asked to defer. Empty / whitespace-only payloads\n * are normalized to \"no instructions\" so a server that returns `\"\"`\n * doesn't produce a stub heading.\n *\n * Default: `true`. Set `false` for paranoid embedders that want full\n * control of the system prompt or need to avoid leaking server\n * advisories into untrusted contexts.\n */\n surfaceMcpInstructions?: boolean\n /**\n * Hard cap on consecutive Anthropic `pause_turn` recoveries within a\n * single run. Anthropic 4.6+ may return `stop_reason: 'pause_turn'`\n * mid-turn for server-side reasons; the loop injects a \"Please continue.\"\n * user message and re-enters. With some 4.6 streaming edge cases the\n * model returns empty pauses indefinitely, which without this cap spins\n * forever burning tokens (the host's only escape was an external abort).\n *\n * When the consecutive-empty-pause counter reaches this value, the loop\n * exits cleanly. The last turn's `TurnUsage.finishReason` stays\n * `'pause'` so consumers can detect pause exhaustion from `stats`. The\n * `agent:abort` hook does NOT fire — this is graceful termination, not\n * user cancel.\n *\n * Only turns with no tool calls, no text, AND no thinking count toward\n * the cap (the case Claude Code's v2 SDK collapses to `stop` outright).\n * A pause turn with text resets the counter — productive turns are\n * never penalised.\n *\n * Defaults to `0` (UNLIMITED) — empty pauses are vanishingly rare and\n * silently ending a live trajectory is worse than the tail risk of a spin.\n * Set a positive integer to re-arm the cap.\n */\n maxConsecutivePauseTurns?: number\n /**\n * Cap on consecutive refusal recoveries within a single run. A turn the\n * provider ends with `finishReason: 'content-filter'` (e.g. Anthropic\n * `stop_reason: refusal`) and no tool calls would otherwise end the run\n * silently mid-task — an empty assistant turn and a \"completed\" status\n * that reads as a stalled session. The loop injects a synthetic continue\n * (marked via `SessionTurn.injected` so replays render it as a notice, not\n * a user prompt) and re-enters; refusals are usually transient.\n *\n * Every resume re-bills the full input context, so keep the cap small.\n * When consecutive refusal resumes reach it, the loop exits cleanly with\n * the last turn's `TurnUsage.finishReason` still `'content-filter'` so\n * consumers can surface the refusal from `stats`.\n *\n * NOTE: semantics deliberately differ from\n * {@link AgentBehavior.maxConsecutivePauseTurns} — `0` DISABLES the\n * recovery entirely (a refusal ends the run immediately); unlimited\n * refusal retries are never offered.\n *\n * Default: `3`.\n */\n maxConsecutiveRefusalResumes?: number\n /**\n * Auto-resume on output-cap truncation. When `true`, a turn that hits\n * `max_tokens` (`finishReason: 'length'`) without completing a tool call\n * injects a synthetic \"Please continue.\" and re-enters the loop so the model\n * finishes its output, instead of the run silently ending mid-trajectory.\n * The loop bounds consecutive resumes internally so a stuck model can't loop\n * forever. Off by default; pairs with the model-driven `maxTokens` envelope\n * that already prevents most truncation. Context-window exhaustion (also\n * surfaced as `'length'`) is absorbed by auto-compaction on the resumed turn.\n */\n resumeOnMaxTokens?: boolean\n /**\n * Cap on consecutive `resumeOnMaxTokens` resumes. Every resume re-bills the\n * full input context and can re-spend the entire output budget, so the cap\n * is what turns a stuck model from an unbounded burn into a bounded one.\n * When consecutive length resumes reach it, the loop exits cleanly with the\n * last turn's `TurnUsage.finishReason` still `'length'` so consumers can\n * surface the truncation from `stats`.\n *\n * Only bounds turns that produced visible text: a resumed turn with no text\n * and no tool calls (a pure reasoning spin that restarts from scratch every\n * resume) is bounded separately and much tighter — after 2 consecutive such\n * turns the run fails with {@link AgentLengthResumeExhaustedError} instead\n * of silently completing.\n *\n * NOTE: semantics match {@link AgentBehavior.maxConsecutiveRefusalResumes} —\n * `0` DISABLES the recovery entirely (a truncated turn ends the run\n * immediately, as if `resumeOnMaxTokens` were off); unlimited resumes are\n * never offered.\n *\n * Default: `8`.\n */\n maxConsecutiveLengthResumes?: number\n /**\n * Persist completed turns to the session store **incrementally**, after\n * every `turn:after` / `tool-results:after`. This is session-turn\n * persistence — unrelated to the `persist*` knobs above, which spill large\n * tool-result blobs to disk.\n *\n * - `true` (default) — append each turn to the store as it completes. A\n * tool_use block is always durable alongside its matching tool_result, so\n * a mid-run process death resumes cleanly from the last completed turn.\n * Maximum crash-recovery granularity.\n * - `false` — skip the per-turn append hooks; turns are persisted **once at\n * run end** via the flush that already runs on every exit path (complete /\n * abort / error). The seeded user prompt is still persisted up-front, so a\n * crash keeps the prompt but loses the in-flight run's generated turns.\n *\n * Why turn it off: on a session store whose `appendTurns` re-serializes the\n * whole session per call (file-map, remote), per-turn persistence is\n * O(turns) writes of an O(turns)-sized payload — quadratic I/O over a long\n * run. Hosts that save at the request boundary anyway (and accept the\n * coarser crash-recovery granularity) get O(1) writes per run by disabling\n * it. No effect on sessionless runs (nothing to persist) and the store stays\n * attached either way, so `loadSession` / resume / explicit `session.save()`\n * are unaffected.\n *\n * Default: `true`.\n */\n persistTurns?: boolean\n /**\n * Persist large `tool_result` outputs to disk and replace the in-message\n * content with a `<persisted-output>` stub (preview + filesystem path).\n * When the post-`tool:transform` byte size of a tool's result exceeds\n * this threshold, the framework writes the full payload to\n * `<persistDir>/<callId>.txt` and substitutes a fixed-format stub so the\n * model sees a 2 KiB preview plus the path it can `read_file`.\n *\n * The substitution happens at emit time (just after `tool:transform` runs)\n * and the stub flows into `session.turns` directly — so every subsequent\n * turn re-emits the same bytes, keeping the prompt-cache prefix stable.\n *\n * Set `0` / `undefined` to disable. Built-in chat profiles default to\n * `8192`. Tools listed in {@link AgentBehavior.persistExcludeTools} bypass\n * regardless of size — typically because their output is intentionally\n * short or persisting would be circular (e.g. `read_file`).\n *\n * Requires {@link AgentBehavior.persistDir} to be set; without a target\n * directory the framework silently skips persistence (no throw, no\n * substitution) since there's nowhere to write the blob.\n *\n * Default: `undefined` (off).\n */\n persistThreshold?: number\n /**\n * Canonical tool names to exclude from disk persistence regardless of\n * output size. The framework bypasses persistence for any tool whose\n * canonical name appears in this list — useful for tools whose results\n * are intentionally part of the prompt (`skills_use`), short envelopes\n * (`tool_search`, `present_plan`, `ask_user`), or where persistence\n * would be circular (`read_file`, whose pagination already serves the\n * same use case).\n *\n * Default: `undefined` (no exclusions). The chat-layer built-in profiles\n * set their own list — see `src/chat/agents.ts`.\n */\n persistExcludeTools?: readonly string[]\n /**\n * Directory under which persisted tool-result blobs land. Each call's\n * payload is written to `<persistDir>/<callId>.txt` (one file per\n * `tool_use` id, atomic via write-then-rename).\n *\n * The chat layer resolves this to `<userDir>/tool-results/<sessionId>/`\n * at session activation; SDK consumers pass an absolute path. Required\n * when {@link AgentBehavior.persistThreshold} is non-zero — when unset\n * the framework treats persistence as disabled.\n *\n * Default: `undefined`.\n */\n persistDir?: string\n /**\n * Soft byte-cap on the cumulative size of persisted blobs under\n * {@link persistDir} for THIS session. After every successful blob\n * write the framework sums the directory's `*.txt` payloads and, if\n * the total exceeds this value, removes oldest-first by mtime until\n * the remainder is at or below the cap.\n *\n * The just-written blob is never evicted in the same sweep (its mtime\n * is the newest), so a single oversize result still lands and gets\n * pointed at by its `<persisted-output>` stub — the LRU is a steady-\n * state housekeeping mechanism, not a per-call admission gate.\n *\n * Long unattended runs can otherwise grow `<userDir>/tool-results/<sessionId>/`\n * without bound; session-delete cleanup runs on demand, not on\n * schedule.\n *\n * Default: `undefined` (no cap). Set `0` to disable explicitly; the\n * eviction step is a no-op for non-positive / non-finite values.\n */\n persistMaxBytes?: number\n /**\n * Absolute directory where the `shell` tool's background mode (the\n * `run_in_background: true` flag) appends output log files. One file\n * per task: `<tasksDir>/<task-id>.<context-timestamp>.log`. The model\n * gets the absolute path back in the tool result and reads incremental\n * output via the regular `read_file` tool.\n *\n * The chat layer resolves this to `<userDir>/sessions/<sessionId>/tasks/` at\n * session activation; SDK consumers pass an absolute path. When unset,\n * `shell({ run_in_background: true })` surfaces a clean error to the\n * model so the framework doesn't silently fall back to a path the user\n * didn't pick.\n *\n * Default: `undefined`.\n */\n tasksDir?: string\n /**\n * Steering threshold for blocking `shell` calls, in milliseconds.\n *\n * When a foreground `shell` call requests a `timeout` ABOVE this value\n * and background mode is actually available (schema exposes\n * `run_in_background`, `tasksDir` is set, the execution context\n * supports it), the call is refused with a steering message telling\n * the model to re-issue with `run_in_background: true` (and\n * `wait_task` when the next step depends on the result). This kills\n * the \"blocking shell pinned to a transport timeout\" footgun — a model\n * that picks a 600–900s blocking wait (e.g. `while pgrep …; do sleep`)\n * holds its whole turn hostage to one exec call regardless of how good\n * the background-mode prompt is.\n *\n * When background mode is NOT available the blocking call proceeds\n * unmodified — refusing it would leave the model with no alternative.\n *\n * Default: `undefined` (off). Set `0` / negative to disable explicitly.\n */\n shellLongTimeoutMs?: number\n /**\n * Hide the built-in `shell` tool's `run_in_background` field + the\n * background-mode paragraphs in its description, even when\n * {@link AgentBehavior.tasksDir} is set. The model never sees the flag\n * and won't try to use it.\n *\n * Combined with the implicit gate on `tasksDir` (background mode also\n * auto-hides when `tasksDir` is unset, since the host hasn't wired the\n * log dir), this gives two ways to opt out:\n *\n * - **Implicit**: don't set `tasksDir`. The host hasn't opted in.\n * - **Explicit**: set `disableBackgroundTasks: true`. The host has\n * `tasksDir` for some other reason (legacy, fixture) but doesn't\n * want the model spawning background work.\n *\n * The `'non-durable'` form is capability-aware: backgrounding is\n * disabled only when the execution context's\n * `capabilities.detachedTasks` is NOT `'durable'`. It is the default on a\n * replay-mode durable runtime (`AgentOptions.durable`), so a\n * remote context whose tasks survive worker crashes keeps background\n * mode, while host-local `ProcessContext` tasks — which would not\n * survive process death — stay disabled.\n *\n * Only applies to the framework-provided `shell` tool (identity check\n * against the exported `shell` constant). Hosts who wrap or replace\n * the shell tool own their spec — the auto-disable doesn't touch\n * tool defs the agent doesn't recognize.\n *\n * Default: `false`.\n */\n disableBackgroundTasks?: boolean | 'non-durable'\n /**\n * Byte cap on each background task's output log. Past the cap the\n * task KEEPS RUNNING; further output is dropped and a\n * `<output-truncated bytes-dropped=\"N\"/>` marker is appended to the\n * log when the task settles. Guards long unattended runs (dev\n * servers, training jobs) from filling the disk.\n *\n * Default: `undefined` (no cap).\n */\n backgroundOutputCap?: number\n /**\n * Stall watchdog for background tasks: when a task produces no output\n * for this many milliseconds, a `background:stall` hook fires and a\n * one-shot `<task-stall>` notification is queued for the model's next\n * turn — typically prompting it to inspect the log and kill a task\n * stuck at an interactive `(y/n)` prompt. The process is NOT killed.\n * The watchdog re-arms after fresh output.\n *\n * Default: `undefined` (no watchdog).\n */\n backgroundStallWatchdogMs?: number\n /**\n * Namespaced bag for host-only configuration. The framework never\n * reads these — they ride along through behavior resolution\n * (run-level keys shallow-merge over agent-level ones) and surface on\n * `ToolContext.behavior.extensions` so custom tools / hooks can read\n * host policy without the host monkey-patching `AgentBehavior` with\n * unknown top-level keys.\n *\n * Default: `undefined`.\n */\n extensions?: Record<string, unknown>\n /**\n * Fail-fast instead of repair when the pre-send pairing pass detects\n * corruption (orphan `tool_use` / `tool_result`, duplicate ids,\n * compaction-stranded blocks). Throws {@link AgentToolPairingError} from\n * the next `agent.run()` turn carrying the structured repair list the\n * loop would have performed.\n *\n * Use case: training-data collectors that must reject any transcript\n * containing the synthetic `SYNTHETIC_TOOL_RESULT_PLACEHOLDER` rather\n * than ship poisoned data to the fine-tuning pipeline. User-facing chat\n * sessions should leave this off (the repair-on-the-fly behavior is the\n * point of the pass).\n *\n * Telemetry note: `pairing:repair` still fires for every repair before\n * the throw, so observability handlers see exactly what would have\n * happened.\n *\n * Default: `false`.\n */\n strictToolPairing?: boolean\n /**\n * Per-turn ceiling on pre-send pairing repairs before the run is failed\n * with {@link AgentToolPairingError}.\n *\n * The pairing pass repairs the WIRE view only — the persisted session\n * keeps its corrupted turns by design (\"don't rewrite the past\"). When a\n * provider systematically reuses tool_use ids, every turn re-heals the\n * entire history, the model never sees its own results (they were\n * stripped as orphans), and the repair count grows quadratically while\n * the run makes zero forward progress. This breaker converts that\n * invisible loop into a typed failure carrying the full repair list.\n *\n * Sizing guidance: legitimate corruption (a crash window, a single buggy\n * turn) produces a handful of repairs per turn; storms produce hundreds\n * per turn within a few turns. `200` is a safe ceiling that can't misfire\n * on healthy sessions.\n *\n * Default: `undefined` (disabled: repair silently every turn, never\n * throw). Opt in to get the breaker.\n */\n maxPairingRepairsPerTurn?: number\n /**\n * Egress allowlist for the web tools (`fetch_url`, `web_search`). When set\n * to a non-empty list, those tools refuse any request whose target host\n * isn't covered — on `fetch_url` this is checked for the initial URL AND\n * every redirect hop (alongside the SSRF blocklist); on `web_search` it\n * gates the keyless DuckDuckGo / Tavily / Brave backends so a sandboxed or\n * compliance-bound host can pin egress to approved domains.\n *\n * Matching is host-suffix based and case-insensitive: an entry `example.com`\n * matches `example.com` and any subdomain (`docs.example.com`), but not\n * `notexample.com`. Ports are ignored (match on hostname only). Leading\n * dots are tolerated (`.example.com` ≡ `example.com`).\n *\n * Default: `undefined` (no allowlist — every public host is reachable,\n * subject to the SSRF blocklist). An empty array is treated the same as\n * `undefined` so a host can't accidentally lock itself out with `[]`.\n */\n fetchUrlAllowHosts?: readonly string[]\n /**\n * Enable an in-process response cache for the `fetch_url` tool, with this\n * TTL in milliseconds. Keyed on the requested URL string. When a fan-out\n * re-fetches the same page within the window (e.g. spidering a docs site\n * across parallel tool calls), the cached body is returned without a second\n * network round-trip.\n *\n * The cache is bounded by entry count and total bytes (fixed internal caps)\n * and is **process-local** — it never persists across restarts and is not\n * shared between agents. Only successful `2xx` text responses are cached;\n * redirects, errors, and aborts are not.\n *\n * Default: `undefined` / `0` → caching disabled (every call is a fresh\n * fetch — the stateless default). Set e.g. `900_000` (15 min) to enable.\n */\n fetchUrlCacheTtlMs?: number\n}\n\nexport interface ToolBatchExecutionContext {\n toolCalls: readonly ToolCall[]\n turnId: string\n maxConcurrentTools: number\n signal: AbortSignal\n steeringQueue: readonly string[]\n isConcurrencySafe: (index: number) => boolean\n execute: (index: number) => Promise<ToolResult>\n interruptedResult: (index: number) => ToolResult\n skippedResult: (index: number) => ToolResult\n}\n\nexport type ToolBatchExecutor = (ctx: ToolBatchExecutionContext) => Promise<ToolResult[]>\n\n// ---------------------------------------------------------------------------\n// Prompt parts (multimodal input)\n// ---------------------------------------------------------------------------\n\n/**\n * One block of a multimodal user prompt.\n *\n * `agent.run({ prompt })` accepts either a plain string (treated as a single\n * text part) or an array of these parts for multimodal inputs.\n *\n * `document` parts are routed per provider: PDF-style mime types are sent as\n * native document blocks when the provider supports them; text documents are\n * inlined as text with an attachment header. Providers that cannot handle an\n * image or document throw early.\n */\nexport type PromptPart\n = | PromptTextPart\n | PromptImagePart\n | PromptAudioPart\n | PromptVideoPart\n | PromptDocumentPart\n\n/** Text part of a multimodal prompt. */\nexport interface PromptTextPart {\n /** Block discriminator, always `'text'`. */\n type: 'text'\n /** Text of the prompt part. */\n text: string\n}\n\n/** Image attachment in a multimodal prompt. */\nexport interface PromptImagePart {\n /** Block discriminator, always `'image'`. */\n type: 'image'\n /** IANA media type (e.g. `image/png`, `image/jpeg`) */\n mediaType: string\n /** Base64-encoded payload */\n data: string\n /** Optional display name */\n name?: string\n}\n\n/** Audio attachment in a multimodal prompt. */\nexport interface PromptAudioPart {\n /** Block discriminator, always `'audio'`. */\n type: 'audio'\n /** IANA media type (e.g. `audio/wav`, `audio/mpeg`) */\n mediaType: string\n /** Base64-encoded payload (no data-URI prefix) */\n data: string\n /** Optional display name */\n name?: string\n}\n\n/** Video attachment in a multimodal prompt. */\nexport interface PromptVideoPart {\n /** Block discriminator, always `'video'`. */\n type: 'video'\n /** IANA media type (e.g. `video/mp4`, `video/webm`) */\n mediaType: string\n /** Base64-encoded payload (no data-URI prefix) */\n data: string\n /** Optional display name */\n name?: string\n}\n\n/** Document attachment in a multimodal prompt. */\nexport interface PromptDocumentPart {\n /** Block discriminator, always `'document'`. */\n type: 'document'\n /** IANA media type (e.g. `application/pdf`, `text/plain`) */\n mediaType: string\n /** Either a base64-encoded payload (`encoding: 'base64'`) or raw text (`encoding: 'text'`) */\n data: string\n /** How `data` is encoded: base64 bytes or raw text. */\n encoding: 'base64' | 'text'\n /** Optional display name used in attachment headers */\n name?: string\n}\n\n// ---------------------------------------------------------------------------\n// Canonical message format (used throughout the agent system)\n// ---------------------------------------------------------------------------\n\n/**\n * A single block of structured tool-result content.\n *\n * MCP servers can return a mix of text, image, audio, video, and resource blocks.\n * Tools return `string` for the common text-only case or `ToolResultContent[]` when\n * they need to preserve non-text content (e.g. screenshots from a browser MCP).\n *\n * Providers that support native multi-part tool results (Anthropic, OpenAI Codex via\n * pi-ai) route image blocks into their wire format verbatim; OpenAI-compat providers\n * route them via a companion-user-message fallback when the underlying model/endpoint\n * does not accept images inside tool-role messages.\n */\nexport type ToolResultContent\n = | ToolResultTextContent\n | ToolResultImageContent\n | ToolResultImageRefContent\n | ToolResultAudioContent\n | ToolResultVideoContent\n | ToolResultDocumentContent\n | ToolResultDocumentRefContent\n\n/**\n * Structured side-effect outcome for mutating tools (write_file, edit,\n * multi_edit, and custom tools that opt in via `ToolContext.reportOutcome`).\n *\n * Surfaced on `tool:after.outcome` so hosts can build no-op-resistant loop\n * guards (\"did this edit actually change bytes?\") WITHOUT string-matching the\n * result prose (`Created `/`Updated `/`No change needed` …), which is fragile\n * across versions. Absent on `tool:after` for read-only tools and any tool\n * that never calls `reportOutcome`.\n *\n * - `created` — a new file/resource was written.\n * - `updated` — an existing file/resource changed (full overwrite differed).\n * - `edited` — a surgical in-place change applied at least one replacement.\n * - `noop` — the call succeeded but changed zero bytes (idempotent write,\n * replacement that produced identical content).\n * - `failed` — the mutation was attempted but did not take effect (error,\n * stale-read rejection, no-match). Pairs with `isError` on the\n * wire result; reported explicitly so guards don't have to infer.\n */\nexport type ToolOutcome = 'created' | 'updated' | 'edited' | 'noop' | 'failed'\n\n/**\n * Live output a running tool reports through `ToolContext.reportProgress`, as\n * the `tool:progress` hook. It never reaches the model or the session: the\n * tool's result is unchanged.\n */\nexport interface ToolProgress {\n /**\n * `output` and `console`: an item a codemode script printed (`text()`,\n * `console.*`). `stdout` and `stderr`: a chunk of a running command.\n */\n stream: 'output' | 'console' | 'stdout' | 'stderr'\n /** The new text. A chunk of a command can end mid-line. */\n text: string\n}\n\n/** JSON scalar: a string, number, boolean or `null`. */\nexport type JsonPrimitive = string | number | boolean | null\n/** Any value that survives `JSON.stringify` and `JSON.parse` unchanged. */\nexport type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue }\n/**\n * Provider/model-invisible metadata associated with a tool result. Hooks use\n * this to persist telemetry about transforms (compression, redaction, etc.)\n * without adding model-visible text to the result body. Hooks can read it; the\n * model never sees it.\n */\nexport type ToolResultMetadata = Record<string, JsonValue>\n\n/** Result of {@link sanitizeToolResultMetadata}. */\nexport interface ToolResultMetadataSanitizeResult {\n /** The JSON-safe metadata. Absent when nothing survived sanitizing. */\n metadata?: ToolResultMetadata\n /** One message per value that was stringified, dropped or truncated. */\n warnings: readonly string[]\n}\n\nconst DEFAULT_TOOL_RESULT_METADATA_MAX_BYTES = 16_384\nconst MAX_METADATA_DEPTH = 8\n\n/**\n * Coerce an arbitrary record into {@link ToolResultMetadata}. Non-finite numbers,\n * functions, symbols and `undefined` are dropped. `bigint` values become strings.\n * Cyclic values and values nested deeper than 8 levels are dropped. When the\n * JSON form exceeds `maxBytes`, whole top-level keys are dropped. Each of these\n * changes adds an entry to `warnings`.\n *\n * @param input - Record to sanitize.\n * @param maxBytes - UTF-8 byte cap for the JSON form of the result. Defaults to 16384.\n * @returns The sanitized `metadata` (absent when empty) and the `warnings`.\n */\nexport function sanitizeToolResultMetadata(\n input: Record<string, unknown>,\n maxBytes = DEFAULT_TOOL_RESULT_METADATA_MAX_BYTES,\n): ToolResultMetadataSanitizeResult {\n const warnings: string[] = []\n const out: ToolResultMetadata = {}\n const seen = new WeakSet<object>()\n for (const [key, value] of Object.entries(input)) {\n const sanitized = sanitizeMetadataValue(value, { seen, warnings, depth: 0 })\n if (sanitized !== undefined)\n out[key] = sanitized\n }\n const bounded = enforceMetadataSizeLimit(out, maxBytes, warnings)\n return Object.keys(bounded).length > 0\n ? { metadata: bounded, warnings }\n : { warnings }\n}\n\nfunction sanitizeMetadataValue(\n value: unknown,\n ctx: { seen: WeakSet<object>, warnings: string[], depth: number },\n): JsonValue | undefined {\n if (value === null)\n return null\n if (typeof value === 'string' || typeof value === 'boolean')\n return value\n if (typeof value === 'number')\n return Number.isFinite(value) ? value : undefined\n if (typeof value === 'bigint') {\n ctx.warnings.push('bigint metadata value stringified')\n return value.toString()\n }\n if (typeof value !== 'object')\n return undefined\n if (ctx.depth >= MAX_METADATA_DEPTH) {\n ctx.warnings.push('metadata value exceeded max depth')\n return undefined\n }\n if (ctx.seen.has(value)) {\n ctx.warnings.push('cyclic metadata value dropped')\n return undefined\n }\n ctx.seen.add(value)\n try {\n if (Array.isArray(value)) {\n const arr: JsonValue[] = []\n for (const item of value) {\n const next = sanitizeMetadataValue(item, { ...ctx, depth: ctx.depth + 1 })\n if (next !== undefined)\n arr.push(next)\n }\n return arr\n }\n const obj: { [key: string]: JsonValue } = {}\n for (const [key, child] of Object.entries(value)) {\n const next = sanitizeMetadataValue(child, { ...ctx, depth: ctx.depth + 1 })\n if (next !== undefined)\n obj[key] = next\n }\n return obj\n }\n finally {\n ctx.seen.delete(value)\n }\n}\n\nfunction enforceMetadataSizeLimit(\n input: ToolResultMetadata,\n maxBytes: number,\n warnings: string[],\n): ToolResultMetadata {\n if (Buffer.byteLength(JSON.stringify(input), 'utf8') <= maxBytes)\n return input\n const out: ToolResultMetadata = {}\n for (const [key, value] of Object.entries(input)) {\n out[key] = value\n if (Buffer.byteLength(JSON.stringify(out), 'utf8') > maxBytes)\n delete out[key]\n }\n warnings.push('metadata exceeded size limit and was truncated')\n return out\n}\n\n/** Text tool-result block. */\nexport interface ToolResultTextContent {\n /** Block discriminator, always `'text'`. */\n type: 'text'\n /** Text of the block. */\n text: string\n}\n\n/** Image tool-result block with inline base64 `data`. */\nexport interface ToolResultImageContent {\n /** Block discriminator, always `'image'`. */\n type: 'image'\n /** IANA media type (e.g. `image/png`, `image/jpeg`) */\n mediaType: string\n /** Base64-encoded payload */\n data: string\n /** Absent on inline blocks. */\n ref?: never\n /** Optional display name used in attachment headers */\n name?: string\n}\n\n/** Image tool-result block that carries a content reference instead of inline data. */\nexport interface ToolResultImageRefContent {\n /** Block discriminator, always `'image'`. */\n type: 'image'\n /** Opaque host-owned content reference, resolved only at provider wire time. */\n ref: string\n /** IANA media type (e.g. `image/png`, `image/jpeg`) */\n mediaType: string\n /** Optional display name used in attachment headers */\n name?: string\n /** Absent on ref blocks. The payload is resolved from `ref`. */\n data?: never\n}\n\n/** Audio tool-result block with inline base64 `data`. */\nexport interface ToolResultAudioContent {\n /** Block discriminator, always `'audio'`. */\n type: 'audio'\n /** IANA media type (e.g. `audio/wav`, `audio/mpeg`) */\n mediaType: string\n /** Base64-encoded payload */\n data: string\n}\n\n/** Video tool-result block with inline base64 `data`. */\nexport interface ToolResultVideoContent {\n /** Block discriminator, always `'video'`. */\n type: 'video'\n /** IANA media type (e.g. `video/mp4`, `video/webm`) */\n mediaType: string\n /** Base64-encoded payload */\n data: string\n}\n\n/** Document tool-result block with inline `data`. */\nexport interface ToolResultDocumentContent {\n /** Block discriminator, always `'document'`. */\n type: 'document'\n /** IANA media type (e.g. `application/pdf`, `text/plain`) */\n mediaType: string\n /** Base64-encoded payload or raw text, according to `encoding`. */\n data: string\n /** Absent on inline blocks. */\n ref?: never\n /** How `data` is encoded: base64 bytes or raw text. */\n encoding: 'base64' | 'text'\n /** Optional display name/title for providers that surface document metadata. */\n name?: string\n}\n\n/** Document tool-result block that carries a content reference instead of inline data. */\nexport interface ToolResultDocumentRefContent {\n /** Block discriminator, always `'document'`. */\n type: 'document'\n /** Opaque host-owned content reference, resolved only at provider wire time. */\n ref: string\n /** IANA media type (e.g. `application/pdf`, `text/plain`) */\n mediaType: string\n /** How the resolved payload is encoded. */\n encoding: 'base64' | 'text'\n /** Optional display name/title for providers that surface document metadata. */\n name?: string\n /** Absent on ref blocks. The payload is resolved from `ref`. */\n data?: never\n}\n\n/** Block passed to a {@link ResolveContentRef} callback. */\nexport interface ResolveContentRefBlock {\n /** Kind of content the reference points to. */\n type: 'image' | 'document'\n /** Opaque host-owned content reference to resolve. */\n ref: string\n /** IANA media type of the referenced content. */\n mediaType: string\n /** How `data` is encoded, for document blocks. */\n encoding?: 'base64' | 'text'\n /** Optional display name. */\n name?: string\n}\n\n/** Host callback that resolves a content reference to its payload. Called at provider wire time with the block that holds the `ref`. */\nexport type ResolveContentRef = (block: ResolveContentRefBlock) => Promise<{ data: string }>\n\n/**\n * Narrow an image/document block to one that carries resolved `data`, throwing\n * loudly if it still holds an unresolved `ref`.\n *\n * Wire serializers (provider formatters, the headless transcript projector)\n * call this as a defense-in-depth guard: the agent loop resolves content refs\n * upstream via the `content-ref:resolve` hook, so a surviving `ref` here means\n * a caller bypassed that path (e.g. a direct `toWireMessages()` without a\n * resolver). `target` names the wire format for a useful error.\n */\nexport function assertResolvedMediaBlock<T extends {\n type: 'image' | 'document'\n mediaType: string\n data?: string\n ref?: string\n encoding?: 'base64' | 'text'\n name?: string\n}>(block: T, target: string): asserts block is T & { data: string } {\n if (typeof block.data === 'string')\n return\n throw new Error(`Cannot project unresolved content ref \"${block.ref ?? '<missing>'}\" to ${target}.`)\n}\n\n/**\n * Render a short, human-readable marker for a document block.\n *\n * Used wherever a document attachment can't (or shouldn't) carry its raw bytes:\n * - providers without document support degrade to a `[document omitted: …]` note,\n * - the loop sanitizer strips bytes for non-capable models,\n * - Anthropic emits an `[document attached: …]` note inside `tool_result` while\n * the bytes ride along as a sibling document block.\n *\n * `reason` is the prefix text after the leading bracket (e.g. `\"document\n * omitted\"`); the helper appends the optional name, media type, and size so\n * every call site shares one format instead of re-deriving the size label.\n */\nexport function documentBlockMarker(\n doc: Pick<ToolResultDocumentContent | ToolResultDocumentRefContent, 'mediaType' | 'data' | 'ref' | 'encoding' | 'name'>,\n reason: string,\n): string {\n const sizeLabel = typeof doc.data === 'string'\n ? (doc.encoding === 'base64' ? `${doc.data.length} b64 bytes` : `${doc.data.length} chars`)\n : `ref ${doc.ref}`\n return `[${reason}: ${doc.name ? `${doc.name}, ` : ''}${doc.mediaType}, ${sizeLabel}]`\n}\n\n/**\n * Lossy flattener — converts `ToolResultContent[]` (or a plain string) to a single\n * string. Non-text blocks are replaced with markers such as\n * `[image: <media>, <n> b64 bytes]` (a `ref` block shows `ref <ref>` in place of the size).\n *\n * Use at UI boundaries where a string is required; providers that understand\n * structured content should route the array through without flattening.\n *\n * @param content - Plain string or structured tool-result blocks.\n */\nexport function toolResultToText(content: string | ToolResultContent[]): string {\n if (typeof content === 'string')\n return content\n return content\n .map((block) => {\n if (block.type === 'text')\n return block.text\n if (block.type === 'image') {\n return typeof block.data === 'string'\n ? `[image: ${block.mediaType}, ${block.data.length} b64 bytes]`\n : `[image: ${block.mediaType}, ref ${block.ref}]`\n }\n if (block.type === 'audio')\n return `[audio: ${block.mediaType}, ${block.data.length} b64 bytes]`\n if (block.type === 'video')\n return `[video: ${block.mediaType}, ${block.data.length} b64 bytes]`\n return typeof block.data === 'string'\n ? `[document: ${block.mediaType}, ${block.data.length} ${block.encoding === 'base64' ? 'b64 bytes' : 'chars'}${block.name ? `, ${block.name}` : ''}]`\n : `[document: ${block.mediaType}, ref ${block.ref}${block.name ? `, ${block.name}` : ''}]`\n })\n .join('\\n')\n}\n\n/**\n * Whether a value is a structurally valid {@link ToolResultContent} block —\n * one of the recognized `text` / `image` / `audio` / `video` / `document`\n * shapes with its required fields present and correctly typed.\n *\n * Used by {@link coerceToolResultContent} to spot malformed blocks before they\n * reach a provider wire serializer (which can throw on, e.g., an `image` block\n * with no `data` / `ref`).\n */\nexport function isToolResultContentBlock(value: unknown): value is ToolResultContent {\n if (!value || typeof value !== 'object')\n return false\n const block = value as Record<string, unknown>\n const hasMedia = typeof block.mediaType === 'string'\n const hasPayload = typeof block.data === 'string' || typeof block.ref === 'string'\n switch (block.type) {\n case 'text':\n return typeof block.text === 'string'\n case 'image':\n return hasMedia && hasPayload\n case 'document':\n return hasMedia && hasPayload && (block.encoding === 'base64' || block.encoding === 'text')\n case 'audio':\n case 'video':\n return hasMedia && typeof block.data === 'string'\n default:\n return false\n }\n}\n\n/**\n * Collapse malformed structured tool-result blocks to a text placeholder so a\n * downstream wire serializer never has to project an `{ type: 'image' }` with\n * no payload (or a foreign block shape). Plain strings and already-valid block\n * arrays pass through untouched (same array reference when nothing changed), so\n * this is a cheap no-op on the happy path.\n */\nexport function coerceToolResultContent(content: string | ToolResultContent[]): string | ToolResultContent[] {\n if (typeof content === 'string' || !Array.isArray(content))\n return content\n let changed = false\n const coerced = content.map((block): ToolResultContent => {\n if (isToolResultContentBlock(block))\n return block\n changed = true\n const type = block && typeof block === 'object' && typeof (block as { type?: unknown }).type === 'string'\n ? (block as { type: string }).type\n : 'unknown'\n return { type: 'text', text: `[dropped malformed ${type} tool-result block]` }\n })\n return changed ? coerced : content\n}\n\n/**\n * Approximate **text-budget size** of a tool output, in bytes.\n *\n * Structured attachment blocks count as the short markers produced by\n * `toolResultToText`, not as raw base64 payload bytes. The per-turn\n * `toolOutputBudget` is a readability/context-loading steering guard; a\n * vision/PDF-capable provider can receive attachment bytes without needing the\n * model to summarize them as if they were text output.\n *\n * @param content - Plain string or structured tool-result blocks.\n */\nexport function toolOutputBudgetByteLength(content: string | ToolResultContent[]): number {\n return Buffer.byteLength(toolResultToText(content))\n}\n\n/**\n * Approximate **wire payload size** of a tool output, in bytes.\n *\n * - Plain text: UTF-8 byte length.\n * - Structured content: text blocks contribute their UTF-8 byte length; image\n * blocks contribute their **base64 character length** — a proxy for the\n * serialized request-body footprint, NOT for tokens. Vision encoders\n * tokenize decoded pixels (geometry-dependent; e.g. Anthropic ≈ `w·h/750`,\n * OpenAI ≈ 85 + 170/tile), which has no meaningful relationship to base64\n * length.\n *\n * Used by the agent loop to populate `outputBytes` on `tool:after`,\n * `tool:transform`, `mcp:tool:after`, and `mcp:tool:transform` hooks so\n * consumers can size-budget tool output without re-counting bytes themselves.\n * Suitable for wire-size telemetry; the per-turn `toolOutputBudget` uses\n * `toolOutputBudgetByteLength` so attachments do not trigger text-output\n * summarization nudges by themselves. NOT a substitute for provider-side\n * context-window accounting — defer to server-side context management (e.g.\n * Anthropic's `context-management-*` beta) when token accuracy matters.\n *\n * @param content - Plain string or structured tool-result blocks.\n */\nexport function toolOutputByteLength(content: string | ToolResultContent[]): number {\n if (typeof content === 'string')\n return Buffer.byteLength(content)\n let total = 0\n for (const block of content) {\n if (block.type === 'text')\n total += Buffer.byteLength(block.text)\n else if (block.type === 'image')\n total += typeof block.data === 'string' ? block.data.length : block.ref.length\n else if (block.type === 'document')\n total += typeof block.data === 'string' ? block.data.length : block.ref.length\n else\n total += block.data.length\n }\n return total\n}\n\n/**\n * Route that minted a Gemini-wire thought signature: `google` (Gemini API),\n * `google-vertex` (Vertex AI), or the scope a `googleCompatible` backend\n * (an extension's Gemini-wire provider) tags its own with. Signatures replay\n * only to the scope that minted them; every other wire treats them as foreign.\n */\nexport type GeminiSignatureScope = 'google' | 'google-vertex' | (string & Record<never, never>)\n\n/**\n * Provider that minted a block's `signature`. Signatures are provider-bound\n * (Anthropic HMAC, OpenAI `encrypted_content`, Gemini thought signatures) and\n * are dropped or stripped on cross-provider hops to avoid 400s. Gemini's are\n * keyed by the route that issued them (see {@link GeminiSignatureScope}).\n */\nexport type SignatureProducer = 'anthropic' | 'openai' | GeminiSignatureScope\n\n/**\n * Marks a text block as a note from the harness to the model: a mode change,\n * an environment update, changed project instructions, a new effort level.\n * Notes ride a user turn after the user's own content and are never edited\n * afterwards, so announcing a change appends to the conversation instead of\n * rewriting its cached prefix. Anthropic models that accept mid-conversation\n * system messages receive notes as `role: \"system\"` messages; other\n * providers receive them as `<system-reminder>` text.\n */\nexport interface SystemNote {\n /** What the note is about, e.g. `'mode:plan'`, `'env'`, `'instructions'`, `'effort'`. Hosts dedupe on it. */\n kind: string\n /** One-line transcript label. The transcript shows the note text when it is absent. */\n label?: string\n /** Hash of the state the note announces, so a host can tell what the model was last told. */\n digest?: string\n /**\n * Effort the conversation runs at from this note on. Anthropic sends it as\n * a per-message `output_config.effort` where the model supports that, which\n * keeps the request's top-level effort and its prompt cache; otherwise the\n * Anthropic provider applies it as the top-level effort. Other providers\n * ignore it.\n */\n effort?: 'low' | 'medium' | 'high' | 'xhigh' | 'max'\n}\n\n/**\n * The sections the SDK appended to a run's fresh system prompt, exactly as\n * appended; a section is `undefined` when the run had none.\n */\nexport interface FrozenPromptSections {\n /** The skills catalog (`<available_skills>`). */\n skills?: string\n /** The searchable tools catalog (`<searchable_tools>`). */\n tools?: string\n /** The MCP server instructions (`# MCP Server Instructions`). */\n mcp?: string\n}\n\n/** See {@link AgentRunOptions.systemFreeze}. */\nexport interface SystemFreezeOptions {\n /**\n * The frozen system prompt, sent verbatim. As a function, it receives the\n * run's fresh prompt and the sections the SDK appended to it, and returns\n * the prompt to send, or `undefined` to send the fresh one (for instance\n * when the host's own base prompt changed and the frozen text is stale).\n */\n system?: string | ((fresh: string, sections: FrozenPromptSections) => string | undefined)\n /** Called with the run's fresh system prompt, and the sections the SDK appended to it, when the run sends the fresh prompt. */\n onCapture?: (fresh: string, sections: FrozenPromptSections) => void | Promise<void>\n /** Notes announcing how the fresh prompt (and the sections the SDK appended to it) differs from the frozen one. Called only when the run sends a frozen prompt. */\n notes?: (fresh: string, sections: FrozenPromptSections) => ReadonlyArray<SystemNote & { text: string }> | Promise<ReadonlyArray<SystemNote & { text: string }>>\n}\n\n/**\n * Content block of a session message: text, media, a tool call, a tool result, or thinking.\n *\n * `text`, `tool_call` and `thinking` blocks can carry a `signature`: opaque\n * state the producing API wants back with that block (a Gemini thought\n * signature, an Anthropic thinking HMAC, OpenAI encrypted reasoning), tagged\n * with its {@link SignatureProducer}. Only that provider's wire builder reads\n * it; the others map blocks field by field and never send it.\n */\nexport type SessionContentBlock\n = | { type: 'text', text: string, signature?: string, signatureProducer?: GeminiSignatureScope, systemNote?: SystemNote, origin?: TextOrigin }\n | { type: 'image', mediaType: string, data: string, name?: string, ref?: never }\n | { type: 'image', mediaType: string, ref: string, name?: string, data?: never }\n | { type: 'audio', mediaType: string, data: string, name?: string }\n | { type: 'video', mediaType: string, data: string, name?: string }\n | { type: 'document', mediaType: string, data: string, encoding: 'base64' | 'text', name?: string, ref?: never }\n | { type: 'document', mediaType: string, ref: string, encoding: 'base64' | 'text', name?: string, data?: never }\n | {\n type: 'tool_call'\n id: string\n name: string\n input: Record<string, unknown>\n /**\n * Gemini thought signature of this function call. Gemini 3 rejects a\n * tool-use step whose calls come back without theirs, so the Google\n * provider persists it here; other providers never send it.\n */\n signature?: string\n /** Route that minted `signature`. */\n signatureProducer?: GeminiSignatureScope\n }\n | {\n type: 'tool_result'\n callId: string\n /**\n * Tool output — either a plain string (text-only, the common case) or a structured\n * array of content blocks (text + image for multimodal tools such as screenshots).\n */\n output: string | ToolResultContent[]\n isError?: boolean\n metadata?: ToolResultMetadata\n /**\n * Calls this tool made through `ToolContext.callTool` (a codemode\n * script's `tools.*`), bounded. Never sent to providers: it is how a\n * reloaded transcript shows what the call did. See {@link NestedToolCallRecord}.\n */\n calls?: NestedToolCallRecord[]\n /**\n * Wall time of the call in milliseconds, as the loop measured it. Never\n * sent to providers: it is how a reloaded transcript shows how long the\n * call took. Absent on older sessions and in replay mode (a\n * durable run rebuilds its turns identically on replay).\n */\n durationMs?: number\n }\n | {\n type: 'thinking'\n text: string\n signature?: string\n /**\n * Provider that minted `signature` (see {@link SignatureProducer}). Unset\n * means legacy/unknown, most plausibly Anthropic output from before\n * producers were recorded.\n */\n signatureProducer?: SignatureProducer\n }\n | { type: 'redacted_thinking', data: string }\n | {\n /**\n * Opaque round-trip envelope for reasoning state minted by OpenRouter.\n * The gateway expects its own `reasoning_details` array echoed back\n * verbatim on the next turn so the upstream model can resume an\n * extended-reasoning chain across tool calls.\n *\n * Stored opaquely because the items are provider-bound (Anthropic HMAC\n * signatures, OpenAI `encrypted_content`, model-specific summary formats\n * — all flowing through the gateway's normalized envelope).\n */\n type: 'provider_reasoning'\n producer: 'openrouter'\n details: unknown[]\n /**\n * Model id that produced the details. Reasoning is bound to a specific\n * upstream route — a model switch on the next turn invalidates the\n * embedded signatures, so the sender drops the block on mismatch.\n */\n model?: string\n }\n | {\n /**\n * Plain reasoning text emitted by vLLM-style reasoning parsers\n * (`reasoning_content` / `reasoning`) and echoed back on assistant\n * messages for hosts such as Arcee Trinity-Thinking. The server re-wraps\n * the string in `<think>...</think>` at tokenization so the model resumes\n * its prior reasoning across tool calls.\n */\n type: 'provider_reasoning'\n producer: 'vllm'\n reasoningText: string\n /**\n * Model id that produced the reasoning. A model switch invalidates the\n * prior chain state, so the sender drops the block on mismatch.\n */\n model?: string\n }\n | {\n /**\n * Anthropic server-side tool invocation block (currently `web_search`).\n * Captured opaquely so the model's reasoning chain stays intact across\n * turns — Anthropic's docs require `encrypted_content` and\n * `encrypted_index` to be passed back on follow-up turns or citation\n * context is lost. Dropped on cross-provider sends.\n */\n type: 'server_tool_use'\n producer: 'anthropic'\n id: string\n name: string\n input: Record<string, unknown>\n }\n | {\n /**\n * Anthropic server-side tool result block. Pairs with `server_tool_use`\n * and carries the search hits Anthropic injected into the assistant\n * message. The full content array is preserved as `unknown[]` so\n * `encrypted_content` / `encrypted_index` survive a session round-trip\n * verbatim.\n */\n type: 'server_tool_result'\n producer: 'anthropic'\n toolUseId: string\n toolName: string\n content: unknown\n }\n | {\n /**\n * Compaction marker. Inserted by `compactConversation()` to replace a\n * prefix of turns with an LLM-generated summary.\n *\n * The marker lives in `session.turns` and renders in the transcript —\n * the user can still scroll back to see the original turns. From the\n * agent loop's wire-level perspective, every turn whose id appears in\n * `replacesTurnIds` is dropped, and this block's `summary` text is\n * sent to the model as a single user message in their place.\n *\n * The marker turn carries `role: 'user'` so it sits naturally at a\n * conversational boundary. Only the latest `compact-summary` block in\n * the session is honored — earlier markers are subsumed by later\n * ones (their `replacesTurnIds` are a strict prefix).\n */\n type: 'compact-summary'\n /** Turn ids the summary replaces, in chronological order. */\n replacesTurnIds: readonly string[]\n /** The summary text sent to the model in place of the elided turns. */\n summary: string\n /** Model id used to produce the summary. */\n model: string\n /** Token usage from the summary call. */\n usage: TurnUsage\n /** Unix-ms when compaction completed. */\n compactedAt: number\n }\n\n/** One message of a session conversation, as sent to the provider. */\nexport interface SessionMessage {\n /** Author of the message. */\n role: 'user' | 'assistant'\n /** Content blocks of the message, in order. */\n content: SessionContentBlock[]\n}\n\n/** One persisted turn of a session: a user prompt, an assistant response, or a system or command entry. */\nexport interface SessionTurn {\n /** Unique turn id. Provider-loop turns are minted from AgentClock.randomUUID(). */\n id: string\n /** Run that produced this turn (e.g. 'run_1') */\n runId?: string\n /**\n * Conversational role, plus `'command'` — a display-only turn emitted by a\n * `/` command's local output. `'command'` turns are persisted + rendered\n * but excluded from every model-message builder (they never reach the LLM),\n * mirroring how `'system'` turns are filtered from the wire.\n */\n role: 'user' | 'assistant' | 'system' | 'command'\n /** Content blocks of the turn, in order. */\n content: SessionContentBlock[]\n /** Token usage — only present on assistant turns */\n usage?: TurnUsage\n /** Unix timestamp (Date.now()) when the turn was created */\n createdAt: number\n /**\n * Display-only replacement for transcript rendering — the full `content`\n * still reaches the model unchanged. Set by command context notes\n * (`ctx.appendContext`) so a replayed session renders the same one-line\n * marker the live path showed (with the full note as a collapsed detail)\n * instead of duplicating bulky note bodies as a wall of user text.\n * Mirrors `SessionRun.origin.display`, at turn granularity, for turns\n * that belong to no run.\n */\n display?: string\n /**\n * Set on a `role: 'user'` turn the agent loop injected rather than the user\n * typed (a refusal retry, an output-limit or pause \"Please continue.\", a\n * tool-output-budget warning). The content still reaches the model; hosts\n * use the marker to keep the turn out of everything that treats user turns\n * as prompts (transcript bubbles, prompt matching, usage counts).\n */\n injected?: InjectedTurnReason\n /**\n * On a `role: 'assistant'` turn: model output a process death cut off,\n * rebuilt when its durable run resumed. Shown in the transcript, left out\n * of the model context; the request is sent again.\n */\n aborted?: true\n /**\n * On a `role: 'system'` turn: the conversation's frozen system prompt, which\n * `content` holds as one text block. The latest snapshot for a prompt key\n * wins. Recorded by hosts that pass {@link AgentRunOptions.systemFreeze}.\n */\n systemSnapshot?: SystemPromptSnapshot\n}\n\n/** Why the agent loop injected a user turn (see {@link SessionTurn.injected}). */\nexport type InjectedTurnReason = 'refusal-retry' | 'length-resume' | 'pause-resume' | 'tool-output-budget'\n\n/**\n * A frozen system prompt's bookkeeping, carried by a `role: 'system'` turn\n * (see {@link SessionTurn.systemSnapshot}).\n */\nexport interface SystemPromptSnapshot {\n /** Format version of the snapshot record. */\n v: 1\n /** Which composition the prompt belongs to (a host profile id, an agent name…). Omitted: the conversation's only one. */\n key?: string\n /** No prompt is frozen for `key` from this turn on (for every key when `key` is omitted): the next run captures a fresh one. */\n cleared?: boolean\n /** Digest of each section the host tracks, by section kind (`'skills'`, `'tools'`, `'mcp'`, plus the host's own). */\n digests: Record<string, string>\n /** Thinking level of the run that captured the prompt. */\n effort?: ThinkingLevel\n /** Host data stored with the snapshot; the SDK never reads it. */\n host?: Record<string, unknown>\n}\n\n/**\n * Whether a turn is sent to the model on the wire. `system` is carried\n * out-of-band and `command` is display-only `/`-command output — neither\n * reaches the LLM. This is the SINGLE decision point every turns→messages\n * builder must funnel through (see `turnsToMessages` in `loop-wire.ts`,\n * `toWireMessages`, the headless mappers, and compaction).\n *\n * The check is deliberately POSITIVE and its result type is pinned to\n * `SessionMessage['role']`, so a future non-conversational role is excluded\n * by default (fail-safe) — the class of bug that a `role !== 'system'` gap or\n * a lying `t is …` predicate would otherwise reintroduce.\n *\n * An `aborted` assistant turn (output a process death cut off, see\n * {@link SessionTurn.aborted}) is display-only too: the request is sent again.\n */\nexport function isWireTurn<T extends { role: SessionTurn['role'], aborted?: true }>(\n turn: T,\n): turn is T & { role: SessionMessage['role'] } {\n return (turn.role === 'user' || turn.role === 'assistant') && turn.aborted !== true\n}\n\n// ---------------------------------------------------------------------------\n// Agent run options\n// ---------------------------------------------------------------------------\n\n/**\n * Per-run hook registrations. Each entry can be a single handler or an array of handlers.\n * Keys are `AgentHooks` event names (loose-typed here to avoid a circular import; `agent-run.ts`\n * validates the event names at run time, and {@link AgentHookMap} is the strongly-typed form).\n */\nexport type RunHookMap = Record<string, ((ctx: any) => unknown) | ((ctx: any) => unknown)[]>\n\n/**\n * One entry of the run-level model fallback list — a model id optionally\n * pinned to a specific provider (referenced by `Provider.name` against the\n * agent's provider list). Entries without `provider` fan out across every\n * configured provider, in provider order.\n */\nexport interface ModelTargetSpec {\n /** Name of the provider to pin the model to. Fans out across every provider when omitted. */\n provider?: string\n /** Model id. */\n model: string\n /**\n * Context window (tokens) of this target's model, for hosts that size\n * auto-compaction from their own model catalog (the SDK core has none).\n * When a fallback advances onto this target, an explicit run-level\n * `autoCompact.contextWindow` is re-based onto this value so the\n * compaction trigger tracks the ACTIVE model, not the primary's window.\n * Omitted: the window only re-derives when it was provider-meta-sourced\n * to begin with.\n */\n contextWindow?: number\n}\n\n/**\n * A resolved provider+model pair the loop can dispatch a turn to. The run\n * resolves `AgentRunOptions.model` against the agent's providers into an\n * ordered list of these; `targets[0]` is the primary and the rest are\n * fallbacks tried in order when a stream fails terminally.\n */\nexport interface ModelTarget {\n /** Provider that serves the model. */\n provider: Provider\n /** Model id sent to the provider. */\n model: string\n /** Host-supplied window of this target's model — see {@link ModelTargetSpec.contextWindow}. */\n contextWindow?: number\n}\n\n/** Options for one {@link Agent.run} call. */\nexport interface AgentRunOptions {\n /**\n * Model selection for this run.\n *\n * - `string` — a model id. With a single provider the id runs on that\n * provider; with multiple providers the id is tried on EVERY provider in order\n * (fallback chain).\n * - `Array<string | ModelTargetSpec>` — an ordered fallback list. Object\n * entries pin a model to one provider by name; bare strings (and object\n * entries without `provider`) fan out across all providers.\n * - omitted — each provider's `meta.defaultModel`, in provider order.\n *\n * The run starts on the first resolved target and advances down the list\n * when a stream fails terminally (see the `model:fallback` hook). Once\n * advanced, the run stays on that target (run-sticky); the next `run()`\n * starts from the primary again.\n */\n model?: string | Array<string | ModelTargetSpec>\n /**\n * User prompt. Optional when resuming a session with existing turns.\n *\n * Accepts either a plain string (single text part) or an array of `PromptPart`s for\n * multimodal inputs (text, images, documents). See {@link PromptPart}.\n */\n prompt?: string | PromptPart[]\n /**\n * Notes for the model, appended to this run's prompt turn after the prompt\n * as text blocks marked with {@link SystemNote}. Use them to announce a\n * change (mode, environment, instructions, effort) without editing the\n * system prompt. Ignored when the run seeds no prompt turn.\n */\n systemNotes?: ReadonlyArray<SystemNote & { text: string }>\n /**\n * Keep the conversation's system prompt byte-stable across runs and agent\n * rebuilds, so provider prompt caches and replayed thinking stay valid. The\n * run composes its fresh prompt as usual (base, skills catalog,\n * searchable-tools catalog, MCP instructions), then:\n *\n * - with `system` set, sends that string instead and appends the notes\n * `notes(fresh, sections)` returns to this run's prompt turn, ahead of\n * {@link AgentRunOptions.systemNotes};\n * - otherwise sends the fresh prompt and hands it to `onCapture`, for the\n * host to persist as the conversation's frozen prompt.\n *\n * Both callbacks also get the sections the SDK appended\n * ({@link FrozenPromptSections}); the `zidane` root exports helpers\n * (`systemSnapshotTurn`, `latestSystemSnapshot`, `catalogChangeNotes`…) to\n * store snapshots and announce those sections' changes.\n * `system:transform` hooks still run per turn on top. Omitted: every run\n * sends its fresh prompt.\n */\n systemFreeze?: SystemFreezeOptions\n /** System prompt for this run. Falls back to {@link AgentOptions.system}, then to `'You are a helpful assistant.'`. */\n system?: string\n /**\n * Optional metadata describing which named sections the host baked into\n * `system` (e.g. the rendered AGENTS.md/CLAUDE.md \"Rules\" block). Purely\n * informational — used by `agent.getContextBreakdown()` to attribute system-\n * prompt tokens to a \"Rules\" bucket the agent can't otherwise isolate from\n * the opaque base string. Does not affect what's sent to the model.\n */\n contextSections?: {\n /** Rendered user-instructions / rules block text (a substring of `system`). */\n rulesBlock?: string\n /** Source files behind the rules block, for per-file breakdown rows. */\n rulesFiles?: { path: string, source: string }[]\n }\n /**\n * Thinking effort for this run. See {@link ThinkingLevel}.\n * @default 'off'\n */\n thinking?: ThinkingLevel\n /**\n * Enabled model-specific options for this run, keyed by option id (e.g.\n * `{ fast: true }` for Anthropic's fast mode). Options the active model\n * doesn't declare are silently ignored by the provider. Overrides\n * {@link AgentBehavior.modelOptions} when both are set.\n */\n modelOptions?: Record<string, boolean>\n /** Abort signal — when triggered, the run aborts the same way `agent.abort()` does */\n signal?: AbortSignal\n /**\n * Pause signal: when triggered, the run stops at the next turn boundary\n * (after the model call in flight and its tool results are saved), never\n * mid-turn, and returns with `stats.paused`. A later run on the same\n * session continues from those turns. A host uses it to restart (an\n * update) without losing work.\n */\n pauseSignal?: AbortSignal\n /**\n * How often a top-level checkpoint run (`zidane/durable`) renews its\n * session's lease, in ms. Default 60 s. Shorter: a run another process\n * took over notices sooner, and stops showing output its new owner now\n * shows (a channel run, whose reply lives in a shared conversation).\n */\n leaseRenewMs?: number\n /** Behavior overrides for this run (overrides agent defaults) */\n behavior?: AgentBehavior\n /**\n * Host data about this run's prompt (an extension `prompt` interceptor's\n * `metadata`). The agent never reads it: `run` interceptors see it on the\n * options they receive. Not persisted.\n */\n metadata?: Readonly<Record<string, JsonValue>>\n /** Tool overrides for this run. Pass {} for no tools. Omit to use agent tools. */\n tools?: Record<string, ToolDef>\n /**\n * Per-run hook registrations. Each hook is attached before the run starts and\n * detached in a finally block so handlers never leak across runs.\n *\n * Accepts either a single handler or an array (all handlers register).\n */\n hooks?: RunHookMap\n /**\n * Parent run id. Populated automatically by the `spawn` tool when the child\n * shares the parent's session; recorded on the resulting `SessionRun` so the\n * parent↔child run tree can be reconstructed from a persisted session.\n */\n parentRunId?: string\n /**\n * Tool-call id of the `spawn` call that created this run. The built-in\n * `spawn` tool populates it from its own `ToolContext.callId` for every\n * child it starts — no `persist` / shared session needed, unlike\n * `parentRunId`. Hosts that roll their own subagent (calling `agent.run`\n * with `depth` set from inside a tool) must pass it themselves; nothing\n * derives it, so `depth >= 1` alone does not imply it is set.\n *\n * Lands on every `ToolContext` / `ToolHookContext` / `McpToolHookContext`\n * of the run, so a host can attribute a subagent's tool call to the spawn\n * call that owns it — see {@link ToolHookContext.parentCallId}. Recorded\n * on the run (`SessionRun.parentCallId`), so a replay pairs the child\n * with its `spawn` call exactly.\n */\n parentCallId?: string\n /**\n * Resume a prior run's conversation: seed this run's turns from the\n * session turns tagged with this `runId` (filtered through\n * `filterUnresolvedToolUses` so a run killed mid-tool doesn't resume\n * with an orphan `tool_use`). Used by the spawn tool to continue a\n * stopped/failed subagent — the new run gets its own `runId`, with\n * `resumedFromRunId` recorded on the resulting `SessionRun` so the\n * chain stays reconstructible. Requires a session and a `prompt`\n * (the continuation instruction); throws when the referenced run has\n * no persisted turns.\n */\n resumeRunId?: string\n /**\n * Host-assigned display label persisted onto the resulting `SessionRun`\n * (`SessionRun.label`). The spawn tool passes its reserved `child-N`\n * label here so label → run resolution (spawn `resume`) and transcript\n * numbering read a stored fact instead of deriving it positionally.\n */\n runLabel?: string\n /**\n * The host's agent profile this run runs as, persisted onto the resulting\n * `SessionRun.agent` so a transcript can name it per run.\n */\n runAgent?: import('./session').SessionRunAgent\n /**\n * Zero-based subagent depth. 0 = top-level `agent.run()`, 1 = first-level\n * child spawned by a parent agent, and so on. Used by the spawn tool to\n * enforce `maxDepth` and to stamp `child:*` forwarded hook payloads.\n */\n depth?: number\n /**\n * Opaque trace-context carrier propagated from the parent agent's\n * tracer (typically a W3C `{ traceparent, tracestate }` map). The\n * child's `agent.run()` re-emits it on the `agent:start` hook so the\n * child's tracer can stitch its root span as a continuation of the\n * parent's spawn span. Empty / absent on top-level runs.\n *\n * Set automatically by the `spawn` tool when a parent's tracer wrote\n * into `SpawnHookContext.tracingContext` on `spawn:before`. SDK\n * consumers that drive subagents manually can populate this directly.\n */\n tracingContext?: Readonly<Record<string, string>>\n /**\n * Override the time + UUID source for this run only. Per-run wins over\n * agent-level ({@link AgentOptions.clock}); both fall back to\n * {@link DEFAULT_AGENT_CLOCK}. See {@link AgentClock}.\n */\n clock?: AgentClock\n}\n\n// ---------------------------------------------------------------------------\n// Agent stats\n// ---------------------------------------------------------------------------\n\n/**\n * Reason the provider gave for stopping the turn.\n *\n * - `'stop'` — natural turn end (`end_turn` / `stop_sequence`).\n * - `'tool-calls'` — model emitted tool_use blocks.\n * - `'length'` — `max_tokens` reached, or (Anthropic 4.6+) the response bumped\n * against the model's context window mid-stream\n * (`model_context_window_exceeded`). The partial response is preserved; the\n * loop emits this reason so consumers can prune/retry.\n * - `'content-filter'` — model refused.\n * - `'pause'` — Anthropic `pause_turn`: a server-side mid-turn pause for very\n * long thinking. The loop continues with a synthetic \"Please continue.\"\n * user message rather than terminating; consumers see the pause via this\n * finish reason on the prior assistant turn.\n * - `'error'` — provider classified the turn as failed.\n * - `'other'` — unknown / unmapped.\n */\nexport type TurnFinishReason = 'stop' | 'tool-calls' | 'length' | 'content-filter' | 'pause' | 'error' | 'other'\n\n/** Token usage and timing for one turn. */\nexport interface TurnUsage {\n /** Input tokens for the turn. Cache tokens are reported in `cacheRead` and `cacheCreation`. */\n input: number\n /** Output tokens for the turn. */\n output: number\n /** Tokens written to cache (Anthropic) */\n cacheCreation?: number\n /** Tokens read from cache (Anthropic) */\n cacheRead?: number\n /** Thinking/reasoning tokens used */\n thinking?: number\n /** Replayed thinking blocks the provider dropped before sampling this turn (Anthropic `input_transformations` entries of type `'thinking_dropped'`); absent when none. */\n thinkingDropped?: number\n /** Measured energy the provider reports for this request, in joules (Neuralwatt's `energy.energy_joules`); absent when the provider reports none. */\n energyJoules?: number\n /**\n * Cost in USD for this turn. Provider-reported when available\n * (OpenRouter, OpenAI via pi-ai); otherwise estimated from `modelId` ×\n * pi-ai's bundled price registry by `fillEstimatedCost` in\n * `src/providers/cost.ts`. Absent only when neither path could resolve\n * a price (unknown / unbundled model).\n */\n cost?: number\n /**\n * Provider key that served this turn, stamped by `fillEstimatedCost`\n * alongside the cost fill. Authoritative where present — downstream\n * spend attribution (e.g. the GUI usage dashboard) does not have to\n * GUESS the provider from the model id's shape, which mis-attributes\n * proxied/rebranded models (a Claude model via Bedrock ≠ Anthropic).\n * Absent on turns persisted before this field existed.\n */\n provider?: string\n /**\n * Why the model stopped this turn. Providers normalize native stop reasons to this union.\n * Absent when the provider did not surface a reason (e.g. mock turns).\n */\n finishReason?: TurnFinishReason\n /**\n * The model ID the provider ultimately used. May differ from the requested model when the\n * provider remaps aliases. Absent for providers that do not echo a model ID.\n */\n modelId?: string\n /**\n * Milliseconds from the moment the loop dispatched `provider.stream()`\n * (the `stream:start` hook firing) to the first observable byte of this\n * turn — earliest of `stream:text`, `stream:thinking`, or a tool_use\n * block. Captures the per-turn TTFT independently of run-relative TTFT\n * ({@link AgentStats.timeTillFirstTokenMs}, which only marks the first\n * turn).\n *\n * Useful for metrics histograms (`gen_ai.client.time_to_first_token`)\n * across long multi-turn runs where the run-level metric collapses to\n * the cold turn only. Absent for empty-stream turns and turns that\n * errored before any byte landed.\n */\n timeToFirstTokenMs?: number\n /**\n * Milliseconds from `provider.stream()` dispatch to the stream returning —\n * the whole provider round trip for this turn, retries included (same\n * span `timeToFirstTokenMs` is measured within). With `output` this gives\n * an honest generation speed: `output / (streamDurationMs −\n * timeToFirstTokenMs)` is tokens per second of actual generation, which a\n * run's wall-clock cannot provide (it includes tool execution). Absent\n * when the stream never returned.\n */\n streamDurationMs?: number\n}\n\n/** Token, cost and timing totals returned by {@link Agent.run}. */\nexport interface AgentStats {\n /** The run stopped at a turn boundary on `pauseSignal`, with work left: continue it with another run. */\n paused?: boolean\n /** `Agent.suspend` stopped the run: on a checkpoint runtime it stays resumable (`Agent.resume`). */\n suspended?: boolean\n /**\n * Cumulative input tokens across the parent agent loop **and** every\n * recursively-spawned sub-agent. Use this for billing / token-ledger\n * consumption.\n */\n totalIn: number\n /** Cumulative output tokens. Same semantics as {@link AgentStats.totalIn}. */\n totalOut: number\n /**\n * Cumulative cache-read tokens across the parent agent loop and every\n * recursively-spawned sub-agent. Surfaced at the top level (rather than\n * only per-`TurnUsage`) because Anthropic prices cache reads at a separate\n * line-item rate from regular input — billing-correct cost computation\n * needs this number directly. Always `0` for providers that don't report\n * cache usage.\n */\n totalCacheRead: number\n /**\n * Cumulative cache-creation tokens across the parent agent loop and every\n * recursively-spawned sub-agent. Same rationale as\n * {@link AgentStats.totalCacheRead} — separate Anthropic billing rate.\n * Always `0` for providers that don't report cache usage.\n */\n totalCacheCreation: number\n /**\n * Number of parent agent-loop turns. Children's turn counts live under\n * `children[].stats.turns` and are NOT folded in here — a single \"turns\"\n * number for the whole tree would conflate two different measures\n * (parent-loop iterations vs. tree-wide tool-call rounds).\n *\n * Tree-wide turn count: `flattenTurns(stats).length`.\n */\n turns: number\n /**\n * Wall-clock duration of the top-level `agent.run()` call, in milliseconds.\n * Children run during parent tool calls so this naturally subsumes child\n * wall time — sequential children inflate it, parallel children compress\n * into the parent's window.\n */\n elapsed: number\n /**\n * Per-turn usage breakdown for the **parent loop only**. Children's per-turn\n * usages live under `children[].stats.turnUsage`. Use {@link flattenTurns}\n * to walk the full tree.\n */\n turnUsage?: TurnUsage[]\n /**\n * Cumulative cost in USD — parent loop plus every recursively-spawned\n * sub-agent. Sums per-turn `TurnUsage.cost` reported by the provider.\n * Absent when neither parent nor any descendant reported a non-zero cost.\n */\n cost?: number\n /** Stats from child agents spawned during this run, in completion order. Recursive. */\n children?: ChildRunStats[]\n /** Structured output from schema enforcement (only present when behavior.schema is set) */\n output?: Record<string, unknown>\n /**\n * Milliseconds from the start of `agent.run()` to the first observable signal from the\n * provider (first `stream:text`, `stream:thinking`, or `tool:before` event).\n *\n * Absent when the run produced no observable signals (e.g. aborted before any stream event).\n */\n timeTillFirstTokenMs?: number\n}\n\n/** Stats of one spawned subagent run, passed to `spawn:complete` and listed in {@link AgentStats.children}. */\nexport interface ChildRunStats {\n /** Spawn id of the child. */\n id: string\n /** Task the child was given. */\n task: string\n /**\n * Tool-call id of the `spawn` call that produced this child — see\n * {@link SpawnHookContext.callId}. Lets a `spawn:complete` listener close\n * out the same grouping key it opened on `spawn:before`.\n */\n callId?: string\n /**\n * The child agent's full {@link AgentStats}. Cumulative for that child's\n * own subtree (child loop + its grandchildren). Do **not** sum\n * `ctx.stats.totalIn` across `spawn:complete` events to derive top-level\n * totals — `agent.run()`'s return value is the canonical cumulative root.\n */\n stats: AgentStats\n /**\n * Subagent depth when this child ran. 1 = direct child of the top-level\n * agent, 2 = grandchild, etc. Useful for telemetry that wants to group\n * runs by depth.\n */\n depth?: number\n /**\n * Terminal state of the child run. `'completed'` is the default. Exposed so\n * a parent reading `stats.children` can distinguish aborted/timed-out\n * children without re-parsing the returned string.\n */\n status?: 'completed' | 'aborted' | 'timeout' | 'error'\n /**\n * Final structured output when the child was run with `behavior.schema`.\n * Mirrors `AgentStats.output` but is surfaced here so the parent can read\n * it without peeking at the nested `stats` bag.\n */\n output?: Record<string, unknown>\n}\n\n// ---------------------------------------------------------------------------\n// Hook context types\n// ---------------------------------------------------------------------------\n\n/**\n * One call a tool made through `ToolContext.callTool`, as persisted on the\n * caller's `tool_result` (`calls`) and rebuilt into transcript rows on\n * reload. Bounded by `src/nested-call-record.ts`: `input` / `output` may be\n * cut (`inputBytes` / `outputTruncated` say so).\n */\nexport interface NestedToolCallRecord {\n /** `<caller call id>/<n>`, in the order the caller issued the calls. */\n id: string\n /** Canonical tool name. */\n name: string\n /** Arguments; absent (with {@link inputBytes}) when over the size bound. */\n input?: Record<string, unknown>\n /** JSON size of an input left out for size. */\n inputBytes?: number\n /** How the call ended. */\n status: 'ok' | 'error' | 'cancelled'\n /** Wall time of the call in milliseconds. */\n durationMs: number\n /** Start of the result text, as a transcript row shows it. */\n output?: string\n /** `true` when `output` is cut short of the full result text. */\n outputTruncated?: boolean\n /** The called tool runs a subagent (`DurableToolDef.runsNestedAgent`): its worker attaches to this call. */\n runsNestedAgent?: true\n /** Line of the calling script that made the call, when the caller tracked lines (`NestedToolCallOptions.line`). */\n line?: number\n}\n\n/**\n * Base context for tool execution hooks: the tool call a `tool:*` hook fires\n * for, with ids, names, input and the run it belongs to.\n *\n * `name` is the canonical tool identity — the spec name registered on the agent (or the\n * `mcp_{server}_{tool}` name for MCP tools). Hooks should policy-match against `name`.\n *\n * `displayName` is the outward-facing name — the alias surfaced to the LLM when\n * `AgentOptions.toolAliases` maps the canonical name; otherwise equal to `name`.\n * UI/telemetry adapters should emit `displayName`.\n *\n * Canonical vs. alias matters on session resume: `session.turns` persists canonical\n * names only, so renaming an alias cannot desync history.\n */\nexport interface ToolHookContext {\n /** Id of the turn that dispatched the call. */\n turnId: string\n /** Id of the tool call. */\n callId: string\n /** Canonical tool name (spec name). Stable across alias-map changes. */\n name: string\n /** Aliased (wire) name — equal to `name` when no alias is defined. */\n displayName: string\n /** Input the tool was called with. */\n input: Record<string, unknown>\n /**\n * The run this tool call belongs to (the `SessionRun.id`). Lets a single\n * `tool:*` listener disambiguate calls across parallel runs / subagent\n * trees without subscribing to the `child:tool:*` bubble events.\n */\n runId?: string\n /**\n * Parent run id when this tool call's agent is a subagent — i.e. the\n * `SessionRun.parentRunId` of the run that owns the call. Absent on\n * top-level runs. Useful for observability stitching: a UI grouping\n * subagent-scoped state (e.g. todowrite, edit batches) by parent\n * run can read this directly off `tool:before` / `tool:after`\n * without resolving the run row.\n */\n parentRunId?: string\n /**\n * Tool-call id of the `spawn` call that created the run this tool call\n * belongs to. Absent on top-level runs.\n *\n * Set for every child the built-in `spawn` tool starts — it comes from the\n * spawn tool's own call rather than from session-backed run bookkeeping,\n * so unlike `parentRunId` it survives `persist: false` spawns. It is NOT\n * an invariant of `depth >= 1`: a host that spawns by calling `agent.run`\n * with `depth` itself sees a subagent with no `parentCallId` unless it\n * passes `AgentRunOptions.parentCallId` too. Treat it as present-or-absent,\n * not as derivable from depth.\n *\n * Hosts mirroring subagent activity into a client protocol use it as the\n * \"parent tool use\" grouping key: absent at depth 0, and at depth >= 1 a\n * real tool-call id rather than a synthetic label. Where it *resolves*\n * depends on depth — at depth 1 it names a call the parent's own `tool:*`\n * events already carried, while at depth >= 2 it names a call in the\n * child's turn, which a top-level listener only ever saw as\n * `child:tool:*`. So it always groups correctly, but a lookup against the\n * top-level tool stream alone only hits for depth 1. This mirrors the\n * ancestry semantics of `childId` / `depth` on `child:*` events.\n */\n parentCallId?: string\n /**\n * Set when another tool issued this call through `ToolContext.callTool`\n * (the `codemode` tool): the id of that calling tool call. Nested calls\n * never appear in `session.turns` and the model never sees their result,\n * so consumers that render model-visible tool calls or coach the model on\n * them should skip these.\n */\n parentToolCallId?: string\n /**\n * On a nested call: the line of the calling script that made it\n * (`NestedToolCallOptions.line`). Absent when the caller tracks no lines.\n */\n scriptLine?: number\n /** Set when the called tool runs a subagent (`DurableToolDef.runsNestedAgent`, e.g. `spawn`). */\n runsNestedAgent?: true\n /**\n * Subagent depth for this tool call. 0 = top-level, 1 = first-level\n * child, etc. Mirrors `ToolContext.depth` so hook consumers don't\n * have to cross-reference the tool context. Omitted on top-level\n * runs (treated as 0).\n */\n depth?: number\n}\n\n/**\n * Base context for MCP tool hooks.\n *\n * `tool` is the native tool name on the MCP server. `server` is the configured server\n * name. The canonical zidane-namespaced identity is `mcp_{server}_{tool}`.\n *\n * `displayName` equals the canonical namespaced name unless the agent has aliased\n * this MCP tool via `AgentOptions.toolAliases`; in which case `displayName` is the\n * alias that the LLM sees.\n */\nexport interface McpToolHookContext {\n /** Id of the turn that dispatched the call. */\n turnId: string\n /** Id of the tool call. */\n callId: string\n /** Configured name of the MCP server. */\n server: string\n /** Tool name on the MCP server, without the `mcp_<server>_` prefix. */\n tool: string\n /** Aliased wire name for this MCP tool, or the canonical `mcp_{server}_{tool}` name. */\n displayName: string\n /** Input the tool was called with. */\n input: Record<string, unknown>\n /** Owning run id — same semantics as `ToolHookContext.runId`. */\n runId?: string\n /** Parent run id when this tool call's agent is a subagent — see `ToolHookContext.parentRunId`. */\n parentRunId?: string\n /** Spawning tool call's id when this tool call's agent is a subagent — see `ToolHookContext.parentCallId`. */\n parentCallId?: string\n /** Subagent depth — see `ToolHookContext.depth`. */\n depth?: number\n}\n\n/** Base context for session hooks */\nexport interface SessionHookContext {\n /** Id of the session. */\n sessionId: string\n}\n\n/** `spawn:queued`: a top-level `spawn` call waiting for a free slot. */\nexport interface SpawnQueuedContext {\n /** Task of the waiting `spawn` call. */\n task: string\n /** The waiting `spawn` call's id (see {@link SpawnHookContext.callId}). */\n callId?: string\n /** The depth its child will run at (1: the top-level agent's child). */\n depth: number\n /** The slots taken and the cap, when the call started waiting. */\n waitingFor: { active: number, max: number }\n}\n\n/** Base context for spawn hooks */\nexport interface SpawnHookContext {\n /** Spawn id of the child. */\n id: string\n /** Task the child was given. */\n task: string\n /**\n * Tool-call id of the `spawn` call this event belongs to — the same value\n * the child's own tool events carry as `ToolHookContext.parentCallId`.\n * Gives hosts the `childId` -> spawn-call mapping at spawn time, which is\n * the only place to learn it for a child that never dispatches a tool (and\n * the only place at all under parallel spawns, where `tool:before` for two\n * spawn calls and their `spawn:before` events interleave).\n *\n * Absent for hand-fired payloads and for spawn-style tools invoked with a\n * `ToolContext` that carries no `callId`. The child's run records it as\n * `SessionRun.parentCallId`.\n */\n callId?: string\n /**\n * Child label of the prior run this spawn continues (e.g. `\"child-2\"`),\n * set when the spawn was invoked with `resume`. Hosts render it next to\n * the spawn-start marker (“child-4 (resumes child-2)”). Absent on\n * fresh spawns.\n */\n resumesChildId?: string\n /**\n * Model id the child runs on — the resolved spawn choice (per-call\n * `model` input > subagent-type default > tool option > inherited\n * parent model > child provider default). Set on `spawn:before` /\n * `spawn:error`; hosts surface it next to the spawn-start marker so\n * cost-tiered delegation is visible. Optional for backward\n * compatibility with hand-fired hook payloads.\n */\n model?: string\n /** `Provider.name` serving the child — differs from the parent's when the spawn routed to another provider. */\n provider?: string\n /**\n * Subagent depth for the spawn. 1 = direct child of the top-level agent.\n * Present on spawn:before/complete/error, and on their nested\n * `child:spawn:*` re-emissions (where it is ≥ 2).\n */\n depth?: number\n /**\n * Mutable trace-context carrier for parent → child span linkage. Empty\n * object by default; a parent tracer mutates it on `spawn:before` (e.g.\n * `ctx.tracingContext.traceparent = '00-…-…-01'`) and the `spawn` tool\n * forwards the populated object to the child via\n * `AgentRunOptions.tracingContext`. The child's tracer re-emits it on\n * `agent:start` so it can be used as parent context when opening the\n * child's root span.\n *\n * Opaque to the harness — keys / values are tracer-defined. Standard\n * choice is W3C Trace Context (`traceparent` + optional `tracestate`),\n * but Datadog / Sentry / B3 carriers work too.\n */\n tracingContext?: Record<string, string>\n}\n\n/**\n * Identity a nested spawn lifecycle event (`child:spawn:*`) adds to the\n * spawned agent's record.\n *\n * `child:spawn:*` events describe a spawn fired BY a subagent (the spawned\n * agent sits at depth ≥ 2). Their payload is the spawned agent's own record\n * — `id` / `depth` / `task` / `callId` are the grandchild's, exactly as its\n * spawner's `spawn:*` saw them — so one handler can serve both families.\n * Unlike the other `child:*` events there is deliberately NO `childId`: on\n * those, `childId` + `depth` name the agent that originated the event, and\n * here `depth` already belongs to the spawned agent. The spawner is named\n * by `parentChildId` instead (its depth is `depth - 1`).\n */\nexport interface ChildSpawnIdentity {\n /**\n * Id of the subagent that ran the `spawn` call (the spawned agent's\n * parent). Set by the first bubble hop and forwarded verbatim through\n * deeper hops, so a root listener sees the true spawner, not its own\n * direct child.\n */\n parentChildId: string\n}\n\n/**\n * Payload of `child:spawn:before` / `child:spawn:error`: the spawned agent's\n * {@link SpawnHookContext} plus {@link ChildSpawnIdentity}, minus the\n * mutable `tracingContext` carrier. Trace propagation into a nested child\n * is owned by its spawner's own tracer (the one listening on the spawner's\n * bus, which knows the spawner's active span); a root tracer writing into\n * the carrier would re-parent the grandchild under the ROOT's ambient span\n * and race the spawner's injection — so the bubbled copy carries none.\n */\nexport type ChildSpawnHookContext = Omit<SpawnHookContext, 'tracingContext'> & ChildSpawnIdentity\n\n/** Context for stream hooks */\nexport interface StreamHookContext {\n /** Id of the turn that is streaming. */\n turnId: string\n}\n\n/**\n * Cumulative in-flight usage a provider can surface mid-stream via\n * `StreamCallbacks.onUsage`. Every field is the running total so far, never\n * a delta; the loop keeps only the latest snapshot. Anthropic reports the\n * prompt side on `message_start` and the output count only in the final\n * `message_delta`; providers without mid-stream accounting never call it.\n * Every field is optional: omit `outputTokens` when the wire hasn't counted\n * it yet and the loop keeps its character estimate moving instead of\n * freezing the counter on a placeholder.\n */\nexport interface StreamUsageSnapshot {\n /** Output tokens counted so far. */\n outputTokens?: number\n /** Input tokens counted so far. */\n inputTokens?: number\n /** Cache-read tokens counted so far. */\n cacheRead?: number\n /** Cache-creation tokens counted so far. */\n cacheCreation?: number\n /**\n * `false` when the provider knows the traffic is flat-rate (subscription\n * OAuth): the loop then reports tokens without a price, matching the\n * turn-level rule that `usage.cost` stays absent on such turns.\n */\n billable?: boolean\n}\n\n/**\n * Fired (throttled) while a parent turn streams, then once more unthrottled\n * right before `stream:end`. Drives live footer counters: tokens streamed so\n * far and a running price for the turn. `turn:after` is the settle point —\n * its `usage.cost` REPLACES `estCostUsd`, it never adds to it.\n */\nexport interface StreamProgressHookContext extends StreamHookContext {\n /** Wall-clock since the provider stream opened. */\n elapsedMs: number\n /** Output tokens so far — provider-reported when available, else chars ÷ 4. */\n outputTokens: number\n /** Prompt-side figures when the provider reported them mid-stream. */\n inputTokens?: number\n /** Cache-read tokens, when the provider reported them mid-stream. */\n cacheRead?: number\n /** Cache-creation tokens, when the provider reported them mid-stream. */\n cacheCreation?: number\n /** `true` when `outputTokens` is the character heuristic, not a provider count. */\n estimated: boolean\n /**\n * Running USD estimate for THIS turn at catalog rates. Absent when the model\n * has no rate, or the provider flagged the traffic as flat-rate. The input\n * side is the provider's mid-stream figure when it sent one, otherwise the\n * previous assistant turn's prompt split as a proxy (the first turn of a\n * session prices output only until it settles).\n */\n estCostUsd?: number\n}\n\n/**\n * Base context for the compaction hooks (`compact:start` /\n * `compact:progress` / `compact:end`). Auto-compaction fires them at a\n * turn boundary inside a run when `behavior.autoCompact` is enabled and the\n * threshold trips; `agent.compact()` fires them between runs. Never during a\n * provider stream, so it carries run/turn coordinates rather than extending\n * {@link StreamHookContext}.\n */\nexport interface CompactHookContext {\n /** Run that triggered the compaction. For `reason: 'manual'`, the agent's latest run, or `''` before its first run. */\n runId: string\n /** Bound session id, when the agent has a session. */\n sessionId?: string\n /** 0-based index of the turn after which compaction fired. For `reason: 'manual'`, the latest run's last turn, or `0` before the first run. */\n turn: number\n /** Why compaction ran: `'auto'` for the threshold trigger inside a run, `'manual'` for `agent.compact()`. */\n reason: 'auto' | 'manual'\n}\n\n/**\n * Context for the `history:repair` hook — fired at most once per provider\n * stream call, AFTER the stream settles, when the loop's canonical history\n * was scrubbed of provider-rejected reasoning state (currently: Anthropic\n * signature-400 poisoning). Deliberately deferred to the settle boundary so\n * a replay-mode durable runtime never sees it inside a recorded model\n * step, where its context methods are unavailable.\n *\n * The in-memory runtime turns and the bound `Session` object are already\n * repaired when this fires; `persisted` reports whether `session.save()`\n * succeeded. Hosts that mirror turns into an EXTERNAL durable store must\n * apply the repair there too (using `turnIds`) — otherwise reloading from\n * that store re-poisons the next run. Observational; handlers cannot cancel\n * the repair.\n */\nexport interface HistoryRepairHookContext {\n /** What was repaired. Currently only Anthropic signature poisoning. */\n reason: 'invalid-anthropic-thinking-signature'\n /** Run whose stream call detected the poison. */\n runId: string\n /** Bound session id, when the run has a session. */\n sessionId?: string\n /** Ids of the turns whose content was scrubbed (runtime and/or session). */\n turnIds: string[]\n /** Total reasoning blocks removed across runtime + session turns. */\n removedBlocks: number\n /**\n * Whether the repaired session was durably saved to its store. Also\n * `false` when the session has no store at all (in-memory sessions have\n * nothing to persist to — the in-memory repair is their final state).\n */\n persisted: boolean\n}\n\n/**\n * Payload of the `'provider:input-transformations'` hook: replayed thinking\n * blocks the provider discarded server-side before sampling. Canonical history\n * is untouched; the model simply answered this turn without that reasoning.\n */\nexport interface InputTransformationsHookContext {\n runId: string\n turnId: string\n /** Bound session id, when the run has a session. */\n sessionId?: string\n /** One entry per dropped block, as reported by the provider. */\n transformations: import('./providers').InputTransformation[]\n /** Entries whose `reason` is `prefix_binding_mismatch` — host-side prefix edits. */\n prefixMismatches: number\n /** Entries whose `reason` is `model_binding_mismatch` — a model switch, not a bug. */\n modelMismatches: number\n}\n\n/** Fired right before the summary call starts. See `compact:start`. */\nexport interface CompactStartHookContext extends CompactHookContext {\n /** Fraction of the effective window the last turn's input reached. `0` when the window is unknown. */\n usedFraction: number\n /** Effective window (raw minus output reserve) used for the ratio. `0` when the window is unknown. */\n effectiveWindow: number\n}\n\n/**\n * Fired (throttled, best-effort) while the summary call streams. Carries a\n * rough cumulative output-token estimate so hosts can paint live progress\n * (\"Compacting… ~2.1k tokens\") instead of an opaque spinner — a summary on\n * a near-window prompt legitimately streams for minutes. Zero or more per\n * `compact:start`; never fired after the paired `compact:end`.\n */\nexport interface CompactProgressHookContext extends CompactHookContext {\n /** Rough token estimate of the summary streamed so far (chars ÷ 4). */\n estOutputTokens: number\n}\n\n/**\n * Fired exactly once after a `compact:start`, on every outcome. `status`\n * discriminates success (carries the result counts + post-compact size\n * estimate hosts use to refresh their context indicator) from failure.\n */\nexport type CompactEndHookContext = CompactHookContext & (\n | {\n status: 'success'\n /** Number of turn ids the summary replaced on the wire. */\n replacedCount: number\n /** Turns dropped by `prompt_too_long` head-truncation (not summarized). */\n droppedCount: number\n /** Files re-injected by post-compact restoration. */\n restoredFiles: number\n /** Skills re-injected by post-compact restoration. */\n restoredSkills: number\n /** Model that produced the summary. */\n summaryModel: string\n /**\n * Estimated post-compact effective token size (summary output +\n * restored attachments). Hosts latch this into their context indicator\n * so it drops immediately instead of waiting for the next real turn.\n */\n effectiveTokens: number\n durationMs: number\n }\n | {\n status: 'error'\n /** The thrown value from `compactConversation` / restoration. */\n error: unknown\n durationMs: number\n }\n)\n\n/** Context for OAuth refresh hooks */\nexport interface OAuthRefreshHookContext {\n /** Name of the provider whose credentials were refreshed. */\n provider: string\n /** OAuth provider id used to look up the refresh flow. */\n providerId: string\n /** Where the credentials came from: provider params or the shared credentials file. */\n source: 'params' | 'file'\n /** Credentials before the refresh. */\n previousCredentials: Record<string, unknown> & { access: string, refresh: string, expires: number }\n /** Credentials after the refresh. */\n credentials: Record<string, unknown> & { access: string, refresh: string, expires: number }\n}\n\n/** How a run ended: `'completed'`, `'aborted'` or `'error'`. */\nexport type SessionEndStatus = 'completed' | 'aborted' | 'error'\n"],"mappings":";;;AAuDA,MAAa,sBAAkC;CAC7C,WAAW,KAAK,IAAI;CACpB,kBAAkB,OAAO,WAAW;AACtC;AAy5EA,MAAM,yCAAyC;AAC/C,MAAM,qBAAqB;;;;;;;;;;;;AAa3B,SAAgB,2BACd,OACA,WAAW,wCACuB;CAClC,MAAM,WAAqB,CAAC;CAC5B,MAAM,MAA0B,CAAC;CACjC,MAAM,uBAAO,IAAI,QAAgB;CACjC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,GAAG;EAChD,MAAM,YAAY,sBAAsB,OAAO;GAAE;GAAM;GAAU,OAAO;EAAE,CAAC;EAC3E,IAAI,cAAc,KAAA,GAChB,IAAI,OAAO;CACf;CACA,MAAM,UAAU,yBAAyB,KAAK,UAAU,QAAQ;CAChE,OAAO,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,IACjC;EAAE,UAAU;EAAS;CAAS,IAC9B,EAAE,SAAS;AACjB;AAEA,SAAS,sBACP,OACA,KACuB;CACvB,IAAI,UAAU,MACZ,OAAO;CACT,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,WAChD,OAAO;CACT,IAAI,OAAO,UAAU,UACnB,OAAO,OAAO,SAAS,KAAK,IAAI,QAAQ,KAAA;CAC1C,IAAI,OAAO,UAAU,UAAU;EAC7B,IAAI,SAAS,KAAK,mCAAmC;EACrD,OAAO,MAAM,SAAS;CACxB;CACA,IAAI,OAAO,UAAU,UACnB,OAAO,KAAA;CACT,IAAI,IAAI,SAAS,oBAAoB;EACnC,IAAI,SAAS,KAAK,mCAAmC;EACrD;CACF;CACA,IAAI,IAAI,KAAK,IAAI,KAAK,GAAG;EACvB,IAAI,SAAS,KAAK,+BAA+B;EACjD;CACF;CACA,IAAI,KAAK,IAAI,KAAK;CAClB,IAAI;EACF,IAAI,MAAM,QAAQ,KAAK,GAAG;GACxB,MAAM,MAAmB,CAAC;GAC1B,KAAK,MAAM,QAAQ,OAAO;IACxB,MAAM,OAAO,sBAAsB,MAAM;KAAE,GAAG;KAAK,OAAO,IAAI,QAAQ;IAAE,CAAC;IACzE,IAAI,SAAS,KAAA,GACX,IAAI,KAAK,IAAI;GACjB;GACA,OAAO;EACT;EACA,MAAM,MAAoC,CAAC;EAC3C,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,GAAG;GAChD,MAAM,OAAO,sBAAsB,OAAO;IAAE,GAAG;IAAK,OAAO,IAAI,QAAQ;GAAE,CAAC;GAC1E,IAAI,SAAS,KAAA,GACX,IAAI,OAAO;EACf;EACA,OAAO;CACT,UACQ;EACN,IAAI,KAAK,OAAO,KAAK;CACvB;AACF;AAEA,SAAS,yBACP,OACA,UACA,UACoB;CACpB,IAAI,OAAO,WAAW,KAAK,UAAU,KAAK,GAAG,MAAM,KAAK,UACtD,OAAO;CACT,MAAM,MAA0B,CAAC;CACjC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,GAAG;EAChD,IAAI,OAAO;EACX,IAAI,OAAO,WAAW,KAAK,UAAU,GAAG,GAAG,MAAM,IAAI,UACnD,OAAO,IAAI;CACf;CACA,SAAS,KAAK,gDAAgD;CAC9D,OAAO;AACT;;;;;;;;;;;AAqHA,SAAgB,yBAOb,OAAU,QAAuD;CAClE,IAAI,OAAO,MAAM,SAAS,UACxB;CACF,MAAM,IAAI,MAAM,0CAA0C,MAAM,OAAO,YAAY,OAAO,OAAO,EAAE;AACrG;;;;;;;;;;;;;;AAeA,SAAgB,oBACd,KACA,QACQ;CACR,MAAM,YAAY,OAAO,IAAI,SAAS,WACjC,IAAI,aAAa,WAAW,GAAG,IAAI,KAAK,OAAO,cAAc,GAAG,IAAI,KAAK,OAAO,UACjF,OAAO,IAAI;CACf,OAAO,IAAI,OAAO,IAAI,IAAI,OAAO,GAAG,IAAI,KAAK,MAAM,KAAK,IAAI,UAAU,IAAI,UAAU;AACtF;;;;;;;;;;;AAYA,SAAgB,iBAAiB,SAA+C;CAC9E,IAAI,OAAO,YAAY,UACrB,OAAO;CACT,OAAO,QACJ,KAAK,UAAU;EACd,IAAI,MAAM,SAAS,QACjB,OAAO,MAAM;EACf,IAAI,MAAM,SAAS,SACjB,OAAO,OAAO,MAAM,SAAS,WACzB,WAAW,MAAM,UAAU,IAAI,MAAM,KAAK,OAAO,eACjD,WAAW,MAAM,UAAU,QAAQ,MAAM,IAAI;EAEnD,IAAI,MAAM,SAAS,SACjB,OAAO,WAAW,MAAM,UAAU,IAAI,MAAM,KAAK,OAAO;EAC1D,IAAI,MAAM,SAAS,SACjB,OAAO,WAAW,MAAM,UAAU,IAAI,MAAM,KAAK,OAAO;EAC1D,OAAO,OAAO,MAAM,SAAS,WACzB,cAAc,MAAM,UAAU,IAAI,MAAM,KAAK,OAAO,GAAG,MAAM,aAAa,WAAW,cAAc,UAAU,MAAM,OAAO,KAAK,MAAM,SAAS,GAAG,KACjJ,cAAc,MAAM,UAAU,QAAQ,MAAM,MAAM,MAAM,OAAO,KAAK,MAAM,SAAS,GAAG;CAC5F,CAAC,CAAC,CACD,KAAK,IAAI;AACd;;;;;;;;;;AAWA,SAAgB,yBAAyB,OAA4C;CACnF,IAAI,CAAC,SAAS,OAAO,UAAU,UAC7B,OAAO;CACT,MAAM,QAAQ;CACd,MAAM,WAAW,OAAO,MAAM,cAAc;CAC5C,MAAM,aAAa,OAAO,MAAM,SAAS,YAAY,OAAO,MAAM,QAAQ;CAC1E,QAAQ,MAAM,MAAd;EACE,KAAK,QACH,OAAO,OAAO,MAAM,SAAS;EAC/B,KAAK,SACH,OAAO,YAAY;EACrB,KAAK,YACH,OAAO,YAAY,eAAe,MAAM,aAAa,YAAY,MAAM,aAAa;EACtF,KAAK;EACL,KAAK,SACH,OAAO,YAAY,OAAO,MAAM,SAAS;EAC3C,SACE,OAAO;CACX;AACF;;;;;;;;AASA,SAAgB,wBAAwB,SAAqE;CAC3G,IAAI,OAAO,YAAY,YAAY,CAAC,MAAM,QAAQ,OAAO,GACvD,OAAO;CACT,IAAI,UAAU;CACd,MAAM,UAAU,QAAQ,KAAK,UAA6B;EACxD,IAAI,yBAAyB,KAAK,GAChC,OAAO;EACT,UAAU;EAIV,OAAO;GAAE,MAAM;GAAQ,MAAM,sBAHhB,SAAS,OAAO,UAAU,YAAY,OAAQ,MAA6B,SAAS,WAC5F,MAA2B,OAC5B,UACoD;EAAqB;CAC/E,CAAC;CACD,OAAO,UAAU,UAAU;AAC7B;;;;;;;;;;;;AAaA,SAAgB,2BAA2B,SAA+C;CACxF,OAAO,OAAO,WAAW,iBAAiB,OAAO,CAAC;AACpD;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,qBAAqB,SAA+C;CAClF,IAAI,OAAO,YAAY,UACrB,OAAO,OAAO,WAAW,OAAO;CAClC,IAAI,QAAQ;CACZ,KAAK,MAAM,SAAS,SAClB,IAAI,MAAM,SAAS,QACjB,SAAS,OAAO,WAAW,MAAM,IAAI;MAClC,IAAI,MAAM,SAAS,SACtB,SAAS,OAAO,MAAM,SAAS,WAAW,MAAM,KAAK,SAAS,MAAM,IAAI;MACrE,IAAI,MAAM,SAAS,YACtB,SAAS,OAAO,MAAM,SAAS,WAAW,MAAM,KAAK,SAAS,MAAM,IAAI;MAExE,SAAS,MAAM,KAAK;CAExB,OAAO;AACT;;;;;;;;;;;;;;;;AA0UA,SAAgB,WACd,MAC8C;CAC9C,QAAQ,KAAK,SAAS,UAAU,KAAK,SAAS,gBAAgB,KAAK,YAAY;AACjF"}