squidclaw 3.0.3 → 3.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. package/dist/{audio-preflight-BTYxAJjy.js → audio-preflight-BKgdc7dS.js} +4 -4
  2. package/dist/{audio-preflight-Dkl6Z32z.js → audio-preflight-DpCWFB4z.js} +4 -4
  3. package/dist/{audio-transcription-runner-Gi_h5HEE.js → audio-transcription-runner-B2BdTEps.js} +1 -1
  4. package/dist/{audio-transcription-runner-DBkDgluo.js → audio-transcription-runner-BnbdYMDl.js} +1 -1
  5. package/dist/build-info.json +3 -3
  6. package/dist/bundled/boot-md/handler.js +6 -6
  7. package/dist/bundled/session-memory/handler.js +6 -6
  8. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  9. package/dist/{chrome-CAd6FQEn.js → chrome-BQDCalPp.js} +8 -8
  10. package/dist/{chrome-pBkBuWci.js → chrome-CjxCwFA9.js} +8 -8
  11. package/dist/{command-registry-CvgCFxfY.js → command-registry-CxiSVXru.js} +6 -6
  12. package/dist/{completion-cli-BGM1V6EN.js → completion-cli-s0fxD0OE.js} +2 -2
  13. package/dist/{completion-cli-OrgUDc2S.js → completion-cli-slzdOlRV.js} +1 -1
  14. package/dist/{config-cli-DbBvjvpS.js → config-cli-C0mk9eRl.js} +1 -1
  15. package/dist/{config-cli-uIP4r17f.js → config-cli-DhHEA_Nc.js} +1 -1
  16. package/dist/{configure-BGvoAfbs.js → configure-CeLdVVyh.js} +8 -1
  17. package/dist/{configure-BOsTXjBw.js → configure-FRd92XZ8.js} +8 -1
  18. package/dist/{deliver-DBXe-ZmL.js → deliver-D-f6Wa3i.js} +1 -1
  19. package/dist/{deliver-BJuiq0GS.js → deliver-aL8yOYS1.js} +1 -1
  20. package/dist/{deliver-runtime-DHKcNQzq.js → deliver-runtime-ChVR6sR3.js} +3 -3
  21. package/dist/{deliver-runtime-GlnBJNCj.js → deliver-runtime-_egya0QZ.js} +3 -3
  22. package/dist/{deps-send-whatsapp.runtime-DdxKewuy.js → deps-send-whatsapp.runtime-CZj97m5A.js} +7 -7
  23. package/dist/{deps-send-whatsapp.runtime-CslTuV47.js → deps-send-whatsapp.runtime-CiG6xd2e.js} +7 -7
  24. package/dist/{doctor-completion-42wcUATu.js → doctor-completion-I-WDZUs1.js} +1 -1
  25. package/dist/{doctor-completion-D6RGDBD5.js → doctor-completion-mnJOkoQ_.js} +1 -1
  26. package/dist/entry.js +2 -2
  27. package/dist/extensionAPI.js +6 -6
  28. package/dist/{gateway-cli-CG3mshpO.js → gateway-cli-B5rTNvd7.js} +1 -1
  29. package/dist/{gateway-cli-BbPfLLT6.js → gateway-cli-BHfkbI0u.js} +1 -1
  30. package/dist/{image-j_UomzVG.js → image-C-C7hQ26.js} +1 -1
  31. package/dist/{image-sRW3RpTY.js → image-DKZCmkET.js} +1 -1
  32. package/dist/{image-runtime-BNh3IfMj.js → image-runtime-D7n4dID4.js} +3 -3
  33. package/dist/{image-runtime-SUtf9jqh.js → image-runtime-poRypm-b.js} +3 -3
  34. package/dist/index.js +1 -1
  35. package/dist/llm-slug-generator.js +6 -6
  36. package/dist/{onboard-ChxvwUze.js → onboard-BP2Cr_Xy.js} +1 -1
  37. package/dist/{onboard-1KfKwvMR.js → onboard-CCJCvPgf.js} +1 -1
  38. package/dist/{onboarding-D6kMb3yv.js → onboarding-D5G87dvM.js} +1 -1
  39. package/dist/{onboarding-DuUMPrqA.js → onboarding-D7CIbxzd.js} +1 -1
  40. package/dist/{onboarding.finalize-KTOhO1-l.js → onboarding.finalize-B1MmYTFV.js} +4 -4
  41. package/dist/{onboarding.finalize-bphDUwZy.js → onboarding.finalize-Cxzl-fYU.js} +3 -3
  42. package/dist/{pi-embedded-BP2UlUm_.js → pi-embedded-CHzwPt6X.js} +24 -24
  43. package/dist/{pi-embedded-BGz_qdCc.js → pi-embedded-MktS4l8v.js} +24 -24
  44. package/dist/{pi-embedded-helpers-CmLnmKlb.js → pi-embedded-helpers-Bse_QhEf.js} +3 -3
  45. package/dist/{pi-embedded-helpers-BruaFB5l.js → pi-embedded-helpers-DYWYzEOC.js} +3 -3
  46. package/dist/plugin-sdk/accounts-BNuRM3rG.js +288 -0
  47. package/dist/plugin-sdk/accounts-CGTYP7Rh.js +46 -0
  48. package/dist/plugin-sdk/accounts-CcS9IAhD.js +35 -0
  49. package/dist/plugin-sdk/{accounts-YTdQYQFr.js → accounts-CxUSDHsT.js} +3 -3
  50. package/dist/plugin-sdk/{accounts-h__dTrLK.js → accounts-PSzw-z3S.js} +2 -2
  51. package/dist/plugin-sdk/{accounts-DghIDNk2.js → accounts-kr-Gz1hk.js} +2 -2
  52. package/dist/plugin-sdk/{active-listener-_PRYjtJv.js → active-listener-BQNrTcR3.js} +2 -2
  53. package/dist/plugin-sdk/active-listener-CTsLn1AX.js +50 -0
  54. package/dist/plugin-sdk/{api-key-rotation-mVDSAkKQ.js → api-key-rotation-Bhck7wki.js} +2 -2
  55. package/dist/plugin-sdk/api-key-rotation-DE4gr5YM.js +181 -0
  56. package/dist/plugin-sdk/audio-preflight-CRGLqp-g.js +69 -0
  57. package/dist/plugin-sdk/{audio-preflight-BZlQM-qX.js → audio-preflight-_xgGaeho.js} +26 -26
  58. package/dist/plugin-sdk/{audio-transcription-runner-CrYTX8py.js → audio-transcription-runner-Dwc0Eh-B.js} +11 -11
  59. package/dist/plugin-sdk/audio-transcription-runner-RXsskMMk.js +2176 -0
  60. package/dist/plugin-sdk/audit-membership-runtime-B9b-zRwg.js +58 -0
  61. package/dist/plugin-sdk/{audit-membership-runtime-Xl20kCBe.js → audit-membership-runtime-DHQDvH4u.js} +2 -2
  62. package/dist/plugin-sdk/{channel-activity-gwxRn4wF.js → channel-activity-XajEg_DL.js} +3 -3
  63. package/dist/plugin-sdk/channel-activity-gPvD1D7S.js +94 -0
  64. package/dist/plugin-sdk/{channel-web-1WF-Nabe.js → channel-web-KtqCp4mz.js} +18 -18
  65. package/dist/plugin-sdk/channel-web-LGl1zPJt.js +2256 -0
  66. package/dist/plugin-sdk/chrome-9Y_LcUg1.js +2415 -0
  67. package/dist/plugin-sdk/{chrome-BXbYwXRH.js → chrome-diV5m81I.js} +6 -6
  68. package/dist/plugin-sdk/commands-registry-CcdEPxVg.js +1125 -0
  69. package/dist/plugin-sdk/{commands-registry-0w-aZenK.js → commands-registry-DwZAJuut.js} +4 -4
  70. package/dist/plugin-sdk/{common-DBOCt6Yv.js → common-CqnO92P8.js} +2 -2
  71. package/dist/plugin-sdk/config-CrQ5bCrw.js +17912 -0
  72. package/dist/plugin-sdk/{config-pRtEoVyZ.js → config-DYbtdrsT.js} +7 -7
  73. package/dist/plugin-sdk/deliver-D3xr5AkB.js +1694 -0
  74. package/dist/plugin-sdk/{deliver-FjlJrtZk.js → deliver-DG_7Uagn.js} +10 -10
  75. package/dist/plugin-sdk/deliver-runtime-B79ZQu69.js +32 -0
  76. package/dist/plugin-sdk/deliver-runtime-BdTC7uKE.js +32 -0
  77. package/dist/plugin-sdk/deps-send-discord.runtime-BOQZIqC8.js +23 -0
  78. package/dist/plugin-sdk/deps-send-discord.runtime-CObCNMt3.js +23 -0
  79. package/dist/plugin-sdk/deps-send-imessage.runtime-CuHOc9Ka.js +22 -0
  80. package/dist/plugin-sdk/deps-send-imessage.runtime-DlWgi2DH.js +22 -0
  81. package/dist/plugin-sdk/deps-send-signal.runtime-Cz7FT8J8.js +21 -0
  82. package/dist/plugin-sdk/deps-send-signal.runtime-iPynghkE.js +21 -0
  83. package/dist/plugin-sdk/deps-send-slack.runtime-D4vDoRsg.js +19 -0
  84. package/dist/plugin-sdk/deps-send-slack.runtime-DNTbE5jS.js +19 -0
  85. package/dist/plugin-sdk/deps-send-telegram.runtime-7CR-xtCF.js +24 -0
  86. package/dist/plugin-sdk/deps-send-telegram.runtime-DjTVED_m.js +24 -0
  87. package/dist/plugin-sdk/deps-send-whatsapp.runtime-CRWOIKRC.js +57 -0
  88. package/dist/plugin-sdk/deps-send-whatsapp.runtime-bUi8kghi.js +57 -0
  89. package/dist/plugin-sdk/diagnostic-BXkLYs_9.js +319 -0
  90. package/dist/plugin-sdk/{diagnostic-Dt2i3afe.js → diagnostic-CT7v_kM2.js} +2 -2
  91. package/dist/plugin-sdk/{errors-CgRPdp3o.js → errors-9oVz7reJ.js} +1 -1
  92. package/dist/plugin-sdk/errors-B8oJXuCF.js +54 -0
  93. package/dist/plugin-sdk/fetch-guard-C55uvn27.js +156 -0
  94. package/dist/plugin-sdk/{fetch-guard-DyPZh8r2.js → fetch-guard-Or5BCq0E.js} +2 -2
  95. package/dist/plugin-sdk/{fs-safe-DqCO1D4C.js → fs-safe-DFbwq9CS.js} +3 -3
  96. package/dist/plugin-sdk/fs-safe-Dqmpk-Fr.js +352 -0
  97. package/dist/plugin-sdk/image-3xW7IJdq.js +2310 -0
  98. package/dist/plugin-sdk/image-ops-BjK2qZZn.js +584 -0
  99. package/dist/plugin-sdk/{image-ops-sw0uZ0GN.js → image-ops-CMsocOob.js} +2 -2
  100. package/dist/plugin-sdk/image-runtime-CZZJJqcW.js +25 -0
  101. package/dist/plugin-sdk/image-runtime-Cjz368oj.js +25 -0
  102. package/dist/plugin-sdk/{image-CQ9TZ9vq.js → image-rycGCqJO.js} +6 -6
  103. package/dist/plugin-sdk/index.js +50 -50
  104. package/dist/plugin-sdk/ir-CS7uuQhN.js +1296 -0
  105. package/dist/plugin-sdk/{ir-BVZ5kUMb.js → ir-DihI2SIz.js} +7 -7
  106. package/dist/plugin-sdk/{local-roots-fO3ZgW3G.js → local-roots-1xVosTZ4.js} +4 -4
  107. package/dist/plugin-sdk/local-roots-DmOKwiNW.js +186 -0
  108. package/dist/plugin-sdk/{logger-DIb2cGHp.js → logger-Bg4vIUJn.js} +2 -2
  109. package/dist/plugin-sdk/logger-DDdrdbDu.js +1163 -0
  110. package/dist/plugin-sdk/login-BSEeU27Y.js +57 -0
  111. package/dist/plugin-sdk/{login-Dg5cxB_3.js → login-YhFrVUWo.js} +4 -4
  112. package/dist/plugin-sdk/login-qr-BwWJsDSj.js +320 -0
  113. package/dist/plugin-sdk/{login-qr-C3Vn30cq.js → login-qr-SpUTuwYv.js} +5 -5
  114. package/dist/plugin-sdk/manager-DiXPCubI.js +3917 -0
  115. package/dist/plugin-sdk/{manager-BR-TwWTH.js → manager-DrzOPeMD.js} +8 -8
  116. package/dist/plugin-sdk/manager-runtime-CF55pBNe.js +15 -0
  117. package/dist/plugin-sdk/manager-runtime-Ct0m9UJC.js +15 -0
  118. package/dist/plugin-sdk/mattermost.js +3 -3
  119. package/dist/plugin-sdk/{outbound-1a3Z_QJ2.js → outbound-Cc4cUn9K.js} +5 -5
  120. package/dist/plugin-sdk/outbound-attachment-BoFx05zw.js +19 -0
  121. package/dist/plugin-sdk/{outbound-attachment-BTQjD4YE.js → outbound-attachment-Dtp3hQgc.js} +2 -2
  122. package/dist/plugin-sdk/outbound-cpqK1GFe.js +212 -0
  123. package/dist/plugin-sdk/{path-alias-guards-TnxupPQC.js → path-alias-guards-DA0MhfkG.js} +1 -1
  124. package/dist/plugin-sdk/path-alias-guards-gBhrAn14.js +43 -0
  125. package/dist/plugin-sdk/paths-C6W4VHoa.js +166 -0
  126. package/dist/plugin-sdk/{paths-B7_75Pdr.js → paths-CP67O8eN.js} +1 -1
  127. package/dist/plugin-sdk/{pi-embedded-helpers-DZRNadD8.js → pi-embedded-helpers-BDJ_4Plh.js} +16 -16
  128. package/dist/plugin-sdk/pi-embedded-helpers-C-B9B6Sp.js +9627 -0
  129. package/dist/plugin-sdk/pi-model-discovery-BGEeoPzN.js +134 -0
  130. package/dist/plugin-sdk/{pi-model-discovery-DGh6xekX.js → pi-model-discovery-Mk0GTDJl.js} +1 -1
  131. package/dist/plugin-sdk/pi-model-discovery-runtime-BHZ_Htob.js +8 -0
  132. package/dist/plugin-sdk/pi-model-discovery-runtime-BrwtJHPU.js +8 -0
  133. package/dist/plugin-sdk/pi-tools.before-tool-call.runtime-ByN_xThw.js +354 -0
  134. package/dist/plugin-sdk/{pi-tools.before-tool-call.runtime-BZ9XgG_x.js → pi-tools.before-tool-call.runtime-DV72wTDb.js} +4 -4
  135. package/dist/plugin-sdk/plugins-D5cdn70e.js +864 -0
  136. package/dist/plugin-sdk/{plugins-B8pWVYug.js → plugins-DSs2-fnK.js} +4 -4
  137. package/dist/plugin-sdk/{proxy-env-BOlkiW1-.js → proxy-env-Ib4-LUh-.js} +1 -1
  138. package/dist/plugin-sdk/{proxy-fetch-Dt5BedH8.js → proxy-fetch-Cf3IUSDw.js} +1 -1
  139. package/dist/plugin-sdk/proxy-fetch-ZPEvp58f.js +38 -0
  140. package/dist/plugin-sdk/pw-ai-C_QOIuin.js +1938 -0
  141. package/dist/plugin-sdk/{pw-ai-C17A1o4w.js → pw-ai-DIx2wpkY.js} +9 -9
  142. package/dist/plugin-sdk/qmd-manager-6bozlfFg.js +1448 -0
  143. package/dist/plugin-sdk/{qmd-manager-Bei6TaFq.js → qmd-manager-Ov9ElEfG.js} +7 -7
  144. package/dist/plugin-sdk/{query-expansion-POz2za8a.js → query-expansion-CzjwW461.js} +4 -4
  145. package/dist/plugin-sdk/query-expansion-eeVz_aEm.js +1011 -0
  146. package/dist/plugin-sdk/{redact-9WsNyb7S.js → redact-BoNEjbpF.js} +1 -1
  147. package/dist/plugin-sdk/redact-DfACyt0X.js +319 -0
  148. package/dist/plugin-sdk/reply-CQUX_haM.js +98828 -0
  149. package/dist/plugin-sdk/{reply-BFbijn6_.js → reply-CWWUd_JS.js} +73 -73
  150. package/dist/plugin-sdk/{resolve-outbound-target-B9iFEh0y.js → resolve-outbound-target-BOkvxZtM.js} +2 -2
  151. package/dist/plugin-sdk/resolve-outbound-target-Dbz0O8cR.js +40 -0
  152. package/dist/plugin-sdk/run-with-concurrency-5DMu9szx.js +1994 -0
  153. package/dist/plugin-sdk/{run-with-concurrency-DmTrN5JG.js → run-with-concurrency-kVooFCVo.js} +1 -1
  154. package/dist/plugin-sdk/runtime-whatsapp-login.runtime-DitS0I1z.js +10 -0
  155. package/dist/plugin-sdk/runtime-whatsapp-login.runtime-OthrtsLL.js +10 -0
  156. package/dist/plugin-sdk/runtime-whatsapp-outbound.runtime-CYCr6A3v.js +19 -0
  157. package/dist/plugin-sdk/runtime-whatsapp-outbound.runtime-Q2HL0zL3.js +19 -0
  158. package/dist/plugin-sdk/send-BACEu1Un.js +414 -0
  159. package/dist/plugin-sdk/{send-BGZo6HW1.js → send-BP1fSEBR.js} +5 -5
  160. package/dist/plugin-sdk/send-BU4OoR7u.js +2587 -0
  161. package/dist/plugin-sdk/{send-BqkUDZed.js → send-BeLBlAsQ.js} +13 -13
  162. package/dist/plugin-sdk/{send-BisREGBZ.js → send-D9CSOGul.js} +6 -6
  163. package/dist/plugin-sdk/{send-D6_nNvi0.js → send-DLKxJJYV.js} +8 -8
  164. package/dist/plugin-sdk/send-DbxOJ_BC.js +3135 -0
  165. package/dist/plugin-sdk/{send-Dj7XEcZN.js → send-XZ6IXCtL.js} +7 -7
  166. package/dist/plugin-sdk/send-n932vjT5.js +540 -0
  167. package/dist/plugin-sdk/send-uCPS53j8.js +503 -0
  168. package/dist/plugin-sdk/session-DenDKR_-.js +169 -0
  169. package/dist/plugin-sdk/{session-D4KDs7Hq.js → session-DtLUYWvY.js} +3 -3
  170. package/dist/plugin-sdk/skill-commands-BK1KDKmS.js +342 -0
  171. package/dist/plugin-sdk/{skill-commands-D_xeseiI.js → skill-commands-Bv7EZypt.js} +4 -4
  172. package/dist/plugin-sdk/{skills-Bs2b3JfV.js → skills-BzXN4uev.js} +6 -6
  173. package/dist/plugin-sdk/skills-D4am-zkO.js +1428 -0
  174. package/dist/plugin-sdk/slash-commands.runtime-Bx1K1iqP.js +13 -0
  175. package/dist/plugin-sdk/slash-commands.runtime-DWfFqMZw.js +13 -0
  176. package/dist/plugin-sdk/slash-dispatch.runtime-DVn338JI.js +52 -0
  177. package/dist/plugin-sdk/slash-dispatch.runtime-pnWH5AjM.js +52 -0
  178. package/dist/plugin-sdk/slash-skill-commands.runtime-Dbi_YzPO.js +16 -0
  179. package/dist/plugin-sdk/slash-skill-commands.runtime-DxvNWv_E.js +16 -0
  180. package/dist/plugin-sdk/ssrf-2WBi1Tzx.js +202 -0
  181. package/dist/plugin-sdk/store-BKDMuvyn.js +81 -0
  182. package/dist/plugin-sdk/{store-B7ESm9_L.js → store-DnJhFFW5.js} +2 -2
  183. package/dist/plugin-sdk/subagent-registry-runtime-FhP0l-Rw.js +52 -0
  184. package/dist/plugin-sdk/subagent-registry-runtime-hH9ADku1.js +52 -0
  185. package/dist/plugin-sdk/{tables-1vhBJPK_.js → tables-CpmqssLF.js} +1 -1
  186. package/dist/plugin-sdk/tables-CrDYcv_b.js +55 -0
  187. package/dist/plugin-sdk/target-errors-aOwE-MIU.js +195 -0
  188. package/dist/plugin-sdk/{thinking-DjaClmzi.js → thinking-1UCPuD9d.js} +7 -7
  189. package/dist/plugin-sdk/thinking-D41FMh9T.js +1206 -0
  190. package/dist/plugin-sdk/{tokens-CLE20fRI.js → tokens-CTIYTLWu.js} +1 -1
  191. package/dist/plugin-sdk/tokens-DAL_5WHL.js +52 -0
  192. package/dist/plugin-sdk/{tool-images-B95xcwiR.js → tool-images-CWc54lpI.js} +2 -2
  193. package/dist/plugin-sdk/tool-images-RX4QTMnt.js +274 -0
  194. package/dist/plugin-sdk/web-AtEy-48y.js +56 -0
  195. package/dist/plugin-sdk/web-DjKONHqF.js +56 -0
  196. package/dist/plugin-sdk/{whatsapp-actions-BYpcWkTN.js → whatsapp-actions-BF6ih4Gi.js} +17 -17
  197. package/dist/plugin-sdk/whatsapp-actions-DEZcm_CZ.js +80 -0
  198. package/dist/plugin-sdk/whatsapp.js +50 -50
  199. package/dist/{program-xNEHPhT8.js → program-2J-jgdfk.js} +2 -2
  200. package/dist/{program-context-J_FyEsaS.js → program-context-SjYSWx_N.js} +8 -8
  201. package/dist/{prompt-select-styled-B1LjjgQ0.js → prompt-select-styled-Baiu3mAU.js} +1 -1
  202. package/dist/{prompt-select-styled-BRiogP_P.js → prompt-select-styled-DPnVmH8f.js} +1 -1
  203. package/dist/{pw-ai-7kHgUGj0.js → pw-ai-BwRP3TWc.js} +1 -1
  204. package/dist/{pw-ai-BmGrTicP.js → pw-ai-zFPBSxaL.js} +1 -1
  205. package/dist/{register.configure-DezZ4Q1p.js → register.configure-Ao1K2uze.js} +1 -1
  206. package/dist/{register.configure-C4p9ad2q.js → register.configure-CCkfhF-7.js} +1 -1
  207. package/dist/{register.maintenance-CzMKTC2a.js → register.maintenance-DMbs8w2m.js} +4 -4
  208. package/dist/{register.maintenance-CTvFmkAm.js → register.maintenance-pLpE2oF-.js} +5 -5
  209. package/dist/{register.onboard-C39xhpv1.js → register.onboard-Cb8xLIye.js} +2 -2
  210. package/dist/{register.onboard-DZt2kSAg.js → register.onboard-IMrHOeW_.js} +2 -2
  211. package/dist/{register.setup-04L_8wfA.js → register.setup-CX8IUmew.js} +2 -2
  212. package/dist/{register.setup-DWctFmOd.js → register.setup-DU7IDzAv.js} +2 -2
  213. package/dist/{register.subclis-C3TphbCF.js → register.subclis-BIbL6FBV.js} +3 -3
  214. package/dist/{run-main-7tknx04F.js → run-main-CpDZPsC_.js} +5 -5
  215. package/dist/{slash-dispatch.runtime-BL3qA1O3.js → slash-dispatch.runtime-DkcAYuyK.js} +6 -6
  216. package/dist/{slash-dispatch.runtime-Dh2L_3Tg.js → slash-dispatch.runtime-DuJRl-LD.js} +6 -6
  217. package/dist/{subagent-registry-runtime-MtjBCcgn.js → subagent-registry-runtime-BlRAnw80.js} +6 -6
  218. package/dist/{subagent-registry-runtime-BRNDawlJ.js → subagent-registry-runtime-COygB9b1.js} +6 -6
  219. package/dist/{update-cli-0UiUaT3q.js → update-cli-BwhvSo1R.js} +5 -5
  220. package/dist/{update-cli-C-uyQcFS.js → update-cli-TQEfxhWF.js} +4 -4
  221. package/dist/{web-D1ZoRVB0.js → web-DddJa7ZT.js} +6 -6
  222. package/dist/{web-B7kbCskR.js → web-DyCuTR9b.js} +6 -6
  223. package/package.json +7 -7
  224. package/dist/plugin-sdk/deliver-runtime-DEzvpBW1.js +0 -32
  225. package/dist/plugin-sdk/deps-send-discord.runtime-Bhusa_Hi.js +0 -23
  226. package/dist/plugin-sdk/deps-send-imessage.runtime-bmakPm5f.js +0 -22
  227. package/dist/plugin-sdk/deps-send-signal.runtime-n00sfFto.js +0 -21
  228. package/dist/plugin-sdk/deps-send-slack.runtime-BvM3Z-Mr.js +0 -19
  229. package/dist/plugin-sdk/deps-send-telegram.runtime-CPuMkcmo.js +0 -24
  230. package/dist/plugin-sdk/deps-send-whatsapp.runtime-BzO6S-KX.js +0 -57
  231. package/dist/plugin-sdk/image-runtime-17_mTqsy.js +0 -25
  232. package/dist/plugin-sdk/manager-runtime-CvI9wF8N.js +0 -15
  233. package/dist/plugin-sdk/pi-model-discovery-runtime-DjjBdPYt.js +0 -8
  234. package/dist/plugin-sdk/runtime-whatsapp-login.runtime-DzhkSmLi.js +0 -10
  235. package/dist/plugin-sdk/runtime-whatsapp-outbound.runtime-DyILWezU.js +0 -19
  236. package/dist/plugin-sdk/slash-commands.runtime-CUb5sqqf.js +0 -13
  237. package/dist/plugin-sdk/slash-dispatch.runtime-DCB6bGjB.js +0 -52
  238. package/dist/plugin-sdk/slash-skill-commands.runtime-BqEweE4K.js +0 -16
  239. package/dist/plugin-sdk/subagent-registry-runtime-CCUW4SbM.js +0 -52
  240. package/dist/plugin-sdk/web-DeRmHQ4_.js +0 -56
@@ -0,0 +1,2176 @@
1
+ import { O as runExec, U as resolveAgentModelFallbackValues, W as resolveAgentModelPrimaryValue } from "./run-with-concurrency-5DMu9szx.js";
2
+ import { $n as requireApiKey, Or as resolveSquidClawAgentDir, Rn as normalizeGoogleModelId, S as resolveIMessageAttachmentRoots, b as isInboundPathAllowed, er as resolveApiKeyForProvider, jn as normalizeProviderId, n as loadConfig, x as mergeInboundPathRoots, y as DEFAULT_IMESSAGE_ATTACHMENT_ROOTS } from "./config-CrQ5bCrw.js";
3
+ import { L as logVerbose, X as resolvePreferredSquidClawTmpDir, a as createSubsystemLogger, z as shouldLogVerbose } from "./logger-DDdrdbDu.js";
4
+ import { c as detectMime, f as isAudioFileName, m as kindFromMime, u as getFileExtension } from "./image-ops-BjK2qZZn.js";
5
+ import { W as normalizeChatType } from "./plugins-D5cdn70e.js";
6
+ import { n as fetchWithTimeout } from "./fetch-timeout-D455O27U.js";
7
+ import { t as fetchWithSsrFGuard } from "./fetch-guard-C55uvn27.js";
8
+ import { i as fetchRemoteMedia, n as getDefaultMediaLocalRoots, r as MediaFetchError } from "./local-roots-DmOKwiNW.js";
9
+ import { C as ensureSquidClawModelsJson, t as describeImageWithModel } from "./image-3xW7IJdq.js";
10
+ import { n as executeWithApiKeyRotation, r as parseGeminiAuth, t as collectProviderApiKeysForExecution } from "./api-key-rotation-Bhck7wki.js";
11
+ import { n as resolveProxyFetchFromEnv } from "./proxy-fetch-ZPEvp58f.js";
12
+ import { constants } from "node:fs";
13
+ import path from "node:path";
14
+ import os from "node:os";
15
+ import fs$1, { mkdtemp, rm } from "node:fs/promises";
16
+ import crypto from "node:crypto";
17
+ import process$1 from "node:process";
18
+ import { fileURLToPath } from "node:url";
19
+
20
+ //#region src/plugin-sdk/temp-path.ts
21
+ function sanitizePrefix(prefix) {
22
+ return prefix.replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "tmp";
23
+ }
24
+ function sanitizeExtension(extension) {
25
+ if (!extension) return "";
26
+ const token = ((extension.startsWith(".") ? extension : `.${extension}`).match(/[a-zA-Z0-9._-]+$/)?.[0] ?? "").replace(/^[._-]+/, "");
27
+ if (!token) return "";
28
+ return `.${token}`;
29
+ }
30
+ function sanitizeFileName(fileName) {
31
+ return path.basename(fileName).replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "download.bin";
32
+ }
33
+ function resolveTempRoot(tmpDir) {
34
+ return tmpDir ?? resolvePreferredSquidClawTmpDir();
35
+ }
36
+ function isNodeErrorWithCode(err, code) {
37
+ return typeof err === "object" && err !== null && "code" in err && err.code === code;
38
+ }
39
+ function buildRandomTempFilePath(params) {
40
+ const prefix = sanitizePrefix(params.prefix);
41
+ const extension = sanitizeExtension(params.extension);
42
+ const nowCandidate = params.now;
43
+ const now = typeof nowCandidate === "number" && Number.isFinite(nowCandidate) ? Math.trunc(nowCandidate) : Date.now();
44
+ const uuid = params.uuid?.trim() || crypto.randomUUID();
45
+ return path.join(resolveTempRoot(params.tmpDir), `${prefix}-${now}-${uuid}${extension}`);
46
+ }
47
+ async function withTempDownloadPath(params, fn) {
48
+ const tempRoot = resolveTempRoot(params.tmpDir);
49
+ const prefix = `${sanitizePrefix(params.prefix)}-`;
50
+ const dir = await mkdtemp(path.join(tempRoot, prefix));
51
+ const tmpPath = path.join(dir, sanitizeFileName(params.fileName ?? "download.bin"));
52
+ try {
53
+ return await fn(tmpPath);
54
+ } finally {
55
+ try {
56
+ await rm(dir, {
57
+ recursive: true,
58
+ force: true
59
+ });
60
+ } catch (err) {
61
+ if (!isNodeErrorWithCode(err, "ENOENT")) console.warn(`temp-path cleanup failed for ${dir}: ${String(err)}`);
62
+ }
63
+ }
64
+ }
65
+
66
+ //#endregion
67
+ //#region src/auto-reply/templating.ts
68
+ function formatTemplateValue(value) {
69
+ if (value == null) return "";
70
+ if (typeof value === "string") return value;
71
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value);
72
+ if (typeof value === "symbol" || typeof value === "function") return value.toString();
73
+ if (Array.isArray(value)) return value.flatMap((entry) => {
74
+ if (entry == null) return [];
75
+ if (typeof entry === "string") return [entry];
76
+ if (typeof entry === "number" || typeof entry === "boolean" || typeof entry === "bigint") return [String(entry)];
77
+ return [];
78
+ }).join(",");
79
+ if (typeof value === "object") return "";
80
+ return "";
81
+ }
82
+ function applyTemplate(str, ctx) {
83
+ if (!str) return "";
84
+ return str.replace(/{{\s*(\w+)\s*}}/g, (_, key) => {
85
+ const value = ctx[key];
86
+ return formatTemplateValue(value);
87
+ });
88
+ }
89
+
90
+ //#endregion
91
+ //#region src/media-understanding/defaults.ts
92
+ const MB = 1024 * 1024;
93
+ const DEFAULT_MAX_CHARS = 500;
94
+ const DEFAULT_MAX_CHARS_BY_CAPABILITY = {
95
+ image: DEFAULT_MAX_CHARS,
96
+ audio: void 0,
97
+ video: DEFAULT_MAX_CHARS
98
+ };
99
+ const DEFAULT_MAX_BYTES = {
100
+ image: 10 * MB,
101
+ audio: 20 * MB,
102
+ video: 50 * MB
103
+ };
104
+ const DEFAULT_TIMEOUT_SECONDS = {
105
+ image: 60,
106
+ audio: 60,
107
+ video: 120
108
+ };
109
+ const DEFAULT_PROMPT = {
110
+ image: "Describe the image.",
111
+ audio: "Transcribe the audio.",
112
+ video: "Describe the video."
113
+ };
114
+ const DEFAULT_VIDEO_MAX_BASE64_BYTES = 70 * MB;
115
+ const DEFAULT_AUDIO_MODELS = {
116
+ groq: "whisper-large-v3-turbo",
117
+ openai: "gpt-4o-mini-transcribe",
118
+ deepgram: "nova-3",
119
+ mistral: "voxtral-mini-latest"
120
+ };
121
+ const AUTO_AUDIO_KEY_PROVIDERS = [
122
+ "openai",
123
+ "groq",
124
+ "deepgram",
125
+ "google",
126
+ "mistral"
127
+ ];
128
+ const AUTO_IMAGE_KEY_PROVIDERS = [
129
+ "openai",
130
+ "anthropic",
131
+ "google",
132
+ "minimax",
133
+ "zai"
134
+ ];
135
+ const AUTO_VIDEO_KEY_PROVIDERS = ["google", "moonshot"];
136
+ const DEFAULT_IMAGE_MODELS = {
137
+ openai: "gpt-5-mini",
138
+ anthropic: "claude-opus-4-6",
139
+ google: "gemini-3-flash-preview",
140
+ minimax: "MiniMax-VL-01",
141
+ zai: "glm-4.6v"
142
+ };
143
+ const CLI_OUTPUT_MAX_BUFFER = 5 * MB;
144
+ const DEFAULT_MEDIA_CONCURRENCY = 2;
145
+ /**
146
+ * Minimum audio file size in bytes below which transcription is skipped.
147
+ * Files smaller than this threshold are almost certainly empty or corrupt
148
+ * and would cause unhelpful API errors from Whisper/transcription providers.
149
+ */
150
+ const MIN_AUDIO_FILE_BYTES = 1024;
151
+
152
+ //#endregion
153
+ //#region src/media-understanding/providers/anthropic/index.ts
154
+ const anthropicProvider = {
155
+ id: "anthropic",
156
+ capabilities: ["image"],
157
+ describeImage: describeImageWithModel
158
+ };
159
+
160
+ //#endregion
161
+ //#region src/media-understanding/providers/shared.ts
162
+ const MAX_ERROR_CHARS = 300;
163
+ function normalizeBaseUrl(baseUrl, fallback) {
164
+ return (baseUrl?.trim() || fallback).replace(/\/+$/, "");
165
+ }
166
+ async function fetchWithTimeoutGuarded(url, init, timeoutMs, fetchFn, options) {
167
+ return await fetchWithSsrFGuard({
168
+ url,
169
+ fetchImpl: fetchFn,
170
+ init,
171
+ timeoutMs,
172
+ policy: options?.ssrfPolicy,
173
+ lookupFn: options?.lookupFn,
174
+ pinDns: options?.pinDns
175
+ });
176
+ }
177
+ async function postTranscriptionRequest(params) {
178
+ return fetchWithTimeoutGuarded(params.url, {
179
+ method: "POST",
180
+ headers: params.headers,
181
+ body: params.body
182
+ }, params.timeoutMs, params.fetchFn, params.allowPrivateNetwork ? { ssrfPolicy: { allowPrivateNetwork: true } } : void 0);
183
+ }
184
+ async function postJsonRequest(params) {
185
+ return fetchWithTimeoutGuarded(params.url, {
186
+ method: "POST",
187
+ headers: params.headers,
188
+ body: JSON.stringify(params.body)
189
+ }, params.timeoutMs, params.fetchFn, params.allowPrivateNetwork ? { ssrfPolicy: { allowPrivateNetwork: true } } : void 0);
190
+ }
191
+ async function readErrorResponse(res) {
192
+ try {
193
+ const collapsed = (await res.text()).replace(/\s+/g, " ").trim();
194
+ if (!collapsed) return;
195
+ if (collapsed.length <= MAX_ERROR_CHARS) return collapsed;
196
+ return `${collapsed.slice(0, MAX_ERROR_CHARS)}…`;
197
+ } catch {
198
+ return;
199
+ }
200
+ }
201
+ async function assertOkOrThrowHttpError(res, label) {
202
+ if (res.ok) return;
203
+ const detail = await readErrorResponse(res);
204
+ const suffix = detail ? `: ${detail}` : "";
205
+ throw new Error(`${label} (HTTP ${res.status})${suffix}`);
206
+ }
207
+ function requireTranscriptionText(value, missingMessage) {
208
+ const text = value?.trim();
209
+ if (!text) throw new Error(missingMessage);
210
+ return text;
211
+ }
212
+
213
+ //#endregion
214
+ //#region src/media-understanding/providers/deepgram/audio.ts
215
+ const DEFAULT_DEEPGRAM_AUDIO_BASE_URL = "https://api.deepgram.com/v1";
216
+ const DEFAULT_DEEPGRAM_AUDIO_MODEL = "nova-3";
217
+ function resolveModel$2(model) {
218
+ return model?.trim() || DEFAULT_DEEPGRAM_AUDIO_MODEL;
219
+ }
220
+ async function transcribeDeepgramAudio(params) {
221
+ const fetchFn = params.fetchFn ?? fetch;
222
+ const baseUrl = normalizeBaseUrl(params.baseUrl, DEFAULT_DEEPGRAM_AUDIO_BASE_URL);
223
+ const allowPrivate = Boolean(params.baseUrl?.trim());
224
+ const model = resolveModel$2(params.model);
225
+ const url = new URL(`${baseUrl}/listen`);
226
+ url.searchParams.set("model", model);
227
+ if (params.language?.trim()) url.searchParams.set("language", params.language.trim());
228
+ if (params.query) for (const [key, value] of Object.entries(params.query)) {
229
+ if (value === void 0) continue;
230
+ url.searchParams.set(key, String(value));
231
+ }
232
+ const headers = new Headers(params.headers);
233
+ if (!headers.has("authorization")) headers.set("authorization", `Token ${params.apiKey}`);
234
+ if (!headers.has("content-type")) headers.set("content-type", params.mime ?? "application/octet-stream");
235
+ const body = new Uint8Array(params.buffer);
236
+ const { response: res, release } = await postTranscriptionRequest({
237
+ url: url.toString(),
238
+ headers,
239
+ body,
240
+ timeoutMs: params.timeoutMs,
241
+ fetchFn,
242
+ allowPrivateNetwork: allowPrivate
243
+ });
244
+ try {
245
+ await assertOkOrThrowHttpError(res, "Audio transcription failed");
246
+ return {
247
+ text: requireTranscriptionText((await res.json()).results?.channels?.[0]?.alternatives?.[0]?.transcript, "Audio transcription response missing transcript"),
248
+ model
249
+ };
250
+ } finally {
251
+ await release();
252
+ }
253
+ }
254
+
255
+ //#endregion
256
+ //#region src/media-understanding/providers/deepgram/index.ts
257
+ const deepgramProvider = {
258
+ id: "deepgram",
259
+ capabilities: ["audio"],
260
+ transcribeAudio: transcribeDeepgramAudio
261
+ };
262
+
263
+ //#endregion
264
+ //#region src/media-understanding/providers/google/inline-data.ts
265
+ async function generateGeminiInlineDataText(params) {
266
+ const fetchFn = params.fetchFn ?? fetch;
267
+ const baseUrl = normalizeBaseUrl(params.baseUrl, params.defaultBaseUrl);
268
+ const allowPrivate = Boolean(params.baseUrl?.trim());
269
+ const model = (() => {
270
+ const trimmed = params.model?.trim();
271
+ if (!trimmed) return params.defaultModel;
272
+ return normalizeGoogleModelId(trimmed);
273
+ })();
274
+ const url = `${baseUrl}/models/${model}:generateContent`;
275
+ const authHeaders = parseGeminiAuth(params.apiKey);
276
+ const headers = new Headers(params.headers);
277
+ for (const [key, value] of Object.entries(authHeaders.headers)) if (!headers.has(key)) headers.set(key, value);
278
+ const { response: res, release } = await postJsonRequest({
279
+ url,
280
+ headers,
281
+ body: { contents: [{
282
+ role: "user",
283
+ parts: [{ text: params.prompt?.trim() || params.defaultPrompt }, { inline_data: {
284
+ mime_type: params.mime ?? params.defaultMime,
285
+ data: params.buffer.toString("base64")
286
+ } }]
287
+ }] },
288
+ timeoutMs: params.timeoutMs,
289
+ fetchFn,
290
+ allowPrivateNetwork: allowPrivate
291
+ });
292
+ try {
293
+ await assertOkOrThrowHttpError(res, params.httpErrorLabel);
294
+ const text = ((await res.json()).candidates?.[0]?.content?.parts ?? []).map((part) => part?.text?.trim()).filter(Boolean).join("\n");
295
+ if (!text) throw new Error(params.missingTextError);
296
+ return {
297
+ text,
298
+ model
299
+ };
300
+ } finally {
301
+ await release();
302
+ }
303
+ }
304
+
305
+ //#endregion
306
+ //#region src/media-understanding/providers/google/audio.ts
307
+ const DEFAULT_GOOGLE_AUDIO_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
308
+ const DEFAULT_GOOGLE_AUDIO_MODEL = "gemini-3-flash-preview";
309
+ const DEFAULT_GOOGLE_AUDIO_PROMPT = "Transcribe the audio.";
310
+ async function transcribeGeminiAudio(params) {
311
+ const { text, model } = await generateGeminiInlineDataText({
312
+ ...params,
313
+ defaultBaseUrl: DEFAULT_GOOGLE_AUDIO_BASE_URL,
314
+ defaultModel: DEFAULT_GOOGLE_AUDIO_MODEL,
315
+ defaultPrompt: DEFAULT_GOOGLE_AUDIO_PROMPT,
316
+ defaultMime: "audio/wav",
317
+ httpErrorLabel: "Audio transcription failed",
318
+ missingTextError: "Audio transcription response missing text"
319
+ });
320
+ return {
321
+ text,
322
+ model
323
+ };
324
+ }
325
+
326
+ //#endregion
327
+ //#region src/media-understanding/providers/google/video.ts
328
+ const DEFAULT_GOOGLE_VIDEO_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
329
+ const DEFAULT_GOOGLE_VIDEO_MODEL = "gemini-3-flash-preview";
330
+ const DEFAULT_GOOGLE_VIDEO_PROMPT = "Describe the video.";
331
+ async function describeGeminiVideo(params) {
332
+ const { text, model } = await generateGeminiInlineDataText({
333
+ ...params,
334
+ defaultBaseUrl: DEFAULT_GOOGLE_VIDEO_BASE_URL,
335
+ defaultModel: DEFAULT_GOOGLE_VIDEO_MODEL,
336
+ defaultPrompt: DEFAULT_GOOGLE_VIDEO_PROMPT,
337
+ defaultMime: "video/mp4",
338
+ httpErrorLabel: "Video description failed",
339
+ missingTextError: "Video description response missing text"
340
+ });
341
+ return {
342
+ text,
343
+ model
344
+ };
345
+ }
346
+
347
+ //#endregion
348
+ //#region src/media-understanding/providers/google/index.ts
349
+ const googleProvider = {
350
+ id: "google",
351
+ capabilities: [
352
+ "image",
353
+ "audio",
354
+ "video"
355
+ ],
356
+ describeImage: describeImageWithModel,
357
+ transcribeAudio: transcribeGeminiAudio,
358
+ describeVideo: describeGeminiVideo
359
+ };
360
+
361
+ //#endregion
362
+ //#region src/media-understanding/providers/openai/audio.ts
363
+ const DEFAULT_OPENAI_AUDIO_BASE_URL = "https://api.openai.com/v1";
364
+ const DEFAULT_OPENAI_AUDIO_MODEL = "gpt-4o-mini-transcribe";
365
+ function resolveModel$1(model) {
366
+ return model?.trim() || DEFAULT_OPENAI_AUDIO_MODEL;
367
+ }
368
+ async function transcribeOpenAiCompatibleAudio(params) {
369
+ const fetchFn = params.fetchFn ?? fetch;
370
+ const baseUrl = normalizeBaseUrl(params.baseUrl, DEFAULT_OPENAI_AUDIO_BASE_URL);
371
+ const allowPrivate = Boolean(params.baseUrl?.trim());
372
+ const url = `${baseUrl}/audio/transcriptions`;
373
+ const model = resolveModel$1(params.model);
374
+ const form = new FormData();
375
+ const fileName = params.fileName?.trim() || path.basename(params.fileName) || "audio";
376
+ const bytes = new Uint8Array(params.buffer);
377
+ const blob = new Blob([bytes], { type: params.mime ?? "application/octet-stream" });
378
+ form.append("file", blob, fileName);
379
+ form.append("model", model);
380
+ if (params.language?.trim()) form.append("language", params.language.trim());
381
+ if (params.prompt?.trim()) form.append("prompt", params.prompt.trim());
382
+ const headers = new Headers(params.headers);
383
+ if (!headers.has("authorization")) headers.set("authorization", `Bearer ${params.apiKey}`);
384
+ const { response: res, release } = await postTranscriptionRequest({
385
+ url,
386
+ headers,
387
+ body: form,
388
+ timeoutMs: params.timeoutMs,
389
+ fetchFn,
390
+ allowPrivateNetwork: allowPrivate
391
+ });
392
+ try {
393
+ await assertOkOrThrowHttpError(res, "Audio transcription failed");
394
+ return {
395
+ text: requireTranscriptionText((await res.json()).text, "Audio transcription response missing text"),
396
+ model
397
+ };
398
+ } finally {
399
+ await release();
400
+ }
401
+ }
402
+
403
+ //#endregion
404
+ //#region src/media-understanding/providers/groq/index.ts
405
+ const DEFAULT_GROQ_AUDIO_BASE_URL = "https://api.groq.com/openai/v1";
406
+ const groqProvider = {
407
+ id: "groq",
408
+ capabilities: ["audio"],
409
+ transcribeAudio: (req) => transcribeOpenAiCompatibleAudio({
410
+ ...req,
411
+ baseUrl: req.baseUrl ?? DEFAULT_GROQ_AUDIO_BASE_URL
412
+ })
413
+ };
414
+
415
+ //#endregion
416
+ //#region src/media-understanding/providers/minimax/index.ts
417
+ const minimaxProvider = {
418
+ id: "minimax",
419
+ capabilities: ["image"],
420
+ describeImage: describeImageWithModel
421
+ };
422
+
423
+ //#endregion
424
+ //#region src/media-understanding/providers/mistral/index.ts
425
+ const DEFAULT_MISTRAL_AUDIO_BASE_URL = "https://api.mistral.ai/v1";
426
+ const mistralProvider = {
427
+ id: "mistral",
428
+ capabilities: ["audio"],
429
+ transcribeAudio: (req) => transcribeOpenAiCompatibleAudio({
430
+ ...req,
431
+ baseUrl: req.baseUrl ?? DEFAULT_MISTRAL_AUDIO_BASE_URL
432
+ })
433
+ };
434
+
435
+ //#endregion
436
+ //#region src/media-understanding/providers/moonshot/video.ts
437
+ const DEFAULT_MOONSHOT_VIDEO_BASE_URL = "https://api.moonshot.ai/v1";
438
+ const DEFAULT_MOONSHOT_VIDEO_MODEL = "kimi-k2.5";
439
+ const DEFAULT_MOONSHOT_VIDEO_PROMPT = "Describe the video.";
440
+ function resolveModel(model) {
441
+ return model?.trim() || DEFAULT_MOONSHOT_VIDEO_MODEL;
442
+ }
443
+ function resolvePrompt$1(prompt) {
444
+ return prompt?.trim() || DEFAULT_MOONSHOT_VIDEO_PROMPT;
445
+ }
446
+ function coerceMoonshotText(payload) {
447
+ const message = payload.choices?.[0]?.message;
448
+ if (!message) return null;
449
+ if (typeof message.content === "string" && message.content.trim()) return message.content.trim();
450
+ if (Array.isArray(message.content)) {
451
+ const text = message.content.map((part) => typeof part.text === "string" ? part.text.trim() : "").filter(Boolean).join("\n").trim();
452
+ if (text) return text;
453
+ }
454
+ if (typeof message.reasoning_content === "string" && message.reasoning_content.trim()) return message.reasoning_content.trim();
455
+ return null;
456
+ }
457
+ async function describeMoonshotVideo(params) {
458
+ const fetchFn = params.fetchFn ?? fetch;
459
+ const baseUrl = normalizeBaseUrl(params.baseUrl, DEFAULT_MOONSHOT_VIDEO_BASE_URL);
460
+ const model = resolveModel(params.model);
461
+ const mime = params.mime ?? "video/mp4";
462
+ const prompt = resolvePrompt$1(params.prompt);
463
+ const url = `${baseUrl}/chat/completions`;
464
+ const headers = new Headers(params.headers);
465
+ if (!headers.has("content-type")) headers.set("content-type", "application/json");
466
+ if (!headers.has("authorization")) headers.set("authorization", `Bearer ${params.apiKey}`);
467
+ const { response: res, release } = await postJsonRequest({
468
+ url,
469
+ headers,
470
+ body: {
471
+ model,
472
+ messages: [{
473
+ role: "user",
474
+ content: [{
475
+ type: "text",
476
+ text: prompt
477
+ }, {
478
+ type: "video_url",
479
+ video_url: { url: `data:${mime};base64,${params.buffer.toString("base64")}` }
480
+ }]
481
+ }]
482
+ },
483
+ timeoutMs: params.timeoutMs,
484
+ fetchFn
485
+ });
486
+ try {
487
+ await assertOkOrThrowHttpError(res, "Moonshot video description failed");
488
+ const text = coerceMoonshotText(await res.json());
489
+ if (!text) throw new Error("Moonshot video description response missing content");
490
+ return {
491
+ text,
492
+ model
493
+ };
494
+ } finally {
495
+ await release();
496
+ }
497
+ }
498
+
499
+ //#endregion
500
+ //#region src/media-understanding/providers/moonshot/index.ts
501
+ const moonshotProvider = {
502
+ id: "moonshot",
503
+ capabilities: ["image", "video"],
504
+ describeImage: describeImageWithModel,
505
+ describeVideo: describeMoonshotVideo
506
+ };
507
+
508
+ //#endregion
509
+ //#region src/media-understanding/providers/openai/index.ts
510
+ const openaiProvider = {
511
+ id: "openai",
512
+ capabilities: ["image", "audio"],
513
+ describeImage: describeImageWithModel,
514
+ transcribeAudio: transcribeOpenAiCompatibleAudio
515
+ };
516
+
517
+ //#endregion
518
+ //#region src/media-understanding/providers/zai/index.ts
519
+ const zaiProvider = {
520
+ id: "zai",
521
+ capabilities: ["image"],
522
+ describeImage: describeImageWithModel
523
+ };
524
+
525
+ //#endregion
526
+ //#region src/media-understanding/providers/index.ts
527
+ const PROVIDERS = [
528
+ groqProvider,
529
+ openaiProvider,
530
+ googleProvider,
531
+ anthropicProvider,
532
+ minimaxProvider,
533
+ moonshotProvider,
534
+ mistralProvider,
535
+ zaiProvider,
536
+ deepgramProvider
537
+ ];
538
+ function normalizeMediaProviderId(id) {
539
+ const normalized = normalizeProviderId(id);
540
+ if (normalized === "gemini") return "google";
541
+ return normalized;
542
+ }
543
+ function buildMediaUnderstandingRegistry(overrides) {
544
+ const registry = /* @__PURE__ */ new Map();
545
+ for (const provider of PROVIDERS) registry.set(normalizeMediaProviderId(provider.id), provider);
546
+ if (overrides) for (const [key, provider] of Object.entries(overrides)) {
547
+ const normalizedKey = normalizeMediaProviderId(key);
548
+ const existing = registry.get(normalizedKey);
549
+ const merged = existing ? {
550
+ ...existing,
551
+ ...provider,
552
+ capabilities: provider.capabilities ?? existing.capabilities
553
+ } : provider;
554
+ registry.set(normalizedKey, merged);
555
+ }
556
+ return registry;
557
+ }
558
+ function getMediaUnderstandingProvider(id, registry) {
559
+ return registry.get(normalizeMediaProviderId(id));
560
+ }
561
+
562
+ //#endregion
563
+ //#region src/media-understanding/scope.ts
564
+ function normalizeDecision(value) {
565
+ const normalized = value?.trim().toLowerCase();
566
+ if (normalized === "allow") return "allow";
567
+ if (normalized === "deny") return "deny";
568
+ }
569
+ function normalizeMatch(value) {
570
+ return value?.trim().toLowerCase() || void 0;
571
+ }
572
+ function normalizeMediaUnderstandingChatType(raw) {
573
+ return normalizeChatType(raw ?? void 0);
574
+ }
575
+ function resolveMediaUnderstandingScope(params) {
576
+ const scope = params.scope;
577
+ if (!scope) return "allow";
578
+ const channel = normalizeMatch(params.channel);
579
+ const chatType = normalizeMediaUnderstandingChatType(params.chatType);
580
+ const sessionKey = normalizeMatch(params.sessionKey) ?? "";
581
+ for (const rule of scope.rules ?? []) {
582
+ if (!rule) continue;
583
+ const action = normalizeDecision(rule.action) ?? "allow";
584
+ const match = rule.match ?? {};
585
+ const matchChannel = normalizeMatch(match.channel);
586
+ const matchChatType = normalizeMediaUnderstandingChatType(match.chatType);
587
+ const matchPrefix = normalizeMatch(match.keyPrefix);
588
+ if (matchChannel && matchChannel !== channel) continue;
589
+ if (matchChatType && matchChatType !== chatType) continue;
590
+ if (matchPrefix && !sessionKey.startsWith(matchPrefix)) continue;
591
+ return action;
592
+ }
593
+ return normalizeDecision(scope.default) ?? "allow";
594
+ }
595
+
596
+ //#endregion
597
+ //#region src/media-understanding/resolve.ts
598
+ function resolveTimeoutMs(seconds, fallbackSeconds) {
599
+ const value = typeof seconds === "number" && Number.isFinite(seconds) ? seconds : fallbackSeconds;
600
+ return Math.max(1e3, Math.floor(value * 1e3));
601
+ }
602
+ function resolvePrompt(capability, prompt, maxChars) {
603
+ const base = prompt?.trim() || DEFAULT_PROMPT[capability];
604
+ if (!maxChars || capability === "audio") return base;
605
+ return `${base} Respond in at most ${maxChars} characters.`;
606
+ }
607
+ function resolveMaxChars(params) {
608
+ const { capability, entry, cfg } = params;
609
+ const configured = entry.maxChars ?? params.config?.maxChars ?? cfg.tools?.media?.[capability]?.maxChars;
610
+ if (typeof configured === "number") return configured;
611
+ return DEFAULT_MAX_CHARS_BY_CAPABILITY[capability];
612
+ }
613
+ function resolveMaxBytes(params) {
614
+ const configured = params.entry.maxBytes ?? params.config?.maxBytes ?? params.cfg.tools?.media?.[params.capability]?.maxBytes;
615
+ if (typeof configured === "number") return configured;
616
+ return DEFAULT_MAX_BYTES[params.capability];
617
+ }
618
+ function resolveScopeDecision(params) {
619
+ return resolveMediaUnderstandingScope({
620
+ scope: params.scope,
621
+ sessionKey: params.ctx.SessionKey,
622
+ channel: params.ctx.Surface ?? params.ctx.Provider,
623
+ chatType: normalizeMediaUnderstandingChatType(params.ctx.ChatType)
624
+ });
625
+ }
626
+ function resolveEntryCapabilities(params) {
627
+ if ((params.entry.type ?? (params.entry.command ? "cli" : "provider")) === "cli") return;
628
+ const providerId = normalizeMediaProviderId(params.entry.provider ?? "");
629
+ if (!providerId) return;
630
+ return params.providerRegistry.get(providerId)?.capabilities;
631
+ }
632
+ function resolveModelEntries(params) {
633
+ const { cfg, capability, config } = params;
634
+ const sharedModels = cfg.tools?.media?.models ?? [];
635
+ const entries = [...(config?.models ?? []).map((entry) => ({
636
+ entry,
637
+ source: "capability"
638
+ })), ...sharedModels.map((entry) => ({
639
+ entry,
640
+ source: "shared"
641
+ }))];
642
+ if (entries.length === 0) return [];
643
+ return entries.filter(({ entry, source }) => {
644
+ const caps = entry.capabilities && entry.capabilities.length > 0 ? entry.capabilities : source === "shared" ? resolveEntryCapabilities({
645
+ entry,
646
+ providerRegistry: params.providerRegistry
647
+ }) : void 0;
648
+ if (!caps || caps.length === 0) {
649
+ if (source === "shared") {
650
+ if (shouldLogVerbose()) logVerbose(`Skipping shared media model without capabilities: ${entry.provider ?? entry.command ?? "unknown"}`);
651
+ return false;
652
+ }
653
+ return true;
654
+ }
655
+ return caps.includes(capability);
656
+ }).map(({ entry }) => entry);
657
+ }
658
+ function resolveConcurrency(cfg) {
659
+ const configured = cfg.tools?.media?.concurrency;
660
+ if (typeof configured === "number" && Number.isFinite(configured) && configured > 0) return Math.floor(configured);
661
+ return DEFAULT_MEDIA_CONCURRENCY;
662
+ }
663
+
664
+ //#endregion
665
+ //#region src/media-understanding/attachments.normalize.ts
666
+ function normalizeAttachmentPath(raw) {
667
+ const value = raw?.trim();
668
+ if (!value) return;
669
+ if (value.startsWith("file://")) try {
670
+ return fileURLToPath(value);
671
+ } catch {
672
+ return;
673
+ }
674
+ return value;
675
+ }
676
+ function normalizeAttachments(ctx) {
677
+ const pathsFromArray = Array.isArray(ctx.MediaPaths) ? ctx.MediaPaths : void 0;
678
+ const urlsFromArray = Array.isArray(ctx.MediaUrls) ? ctx.MediaUrls : void 0;
679
+ const typesFromArray = Array.isArray(ctx.MediaTypes) ? ctx.MediaTypes : void 0;
680
+ const resolveMime = (count, index) => {
681
+ const typeHint = typesFromArray?.[index];
682
+ const trimmed = typeof typeHint === "string" ? typeHint.trim() : "";
683
+ if (trimmed) return trimmed;
684
+ return count === 1 ? ctx.MediaType : void 0;
685
+ };
686
+ if (pathsFromArray && pathsFromArray.length > 0) {
687
+ const count = pathsFromArray.length;
688
+ const urls = urlsFromArray && urlsFromArray.length > 0 ? urlsFromArray : void 0;
689
+ return pathsFromArray.map((value, index) => ({
690
+ path: value?.trim() || void 0,
691
+ url: urls?.[index] ?? ctx.MediaUrl,
692
+ mime: resolveMime(count, index),
693
+ index
694
+ })).filter((entry) => Boolean(entry.path?.trim() || entry.url?.trim()));
695
+ }
696
+ if (urlsFromArray && urlsFromArray.length > 0) {
697
+ const count = urlsFromArray.length;
698
+ return urlsFromArray.map((value, index) => ({
699
+ path: void 0,
700
+ url: value?.trim() || void 0,
701
+ mime: resolveMime(count, index),
702
+ index
703
+ })).filter((entry) => Boolean(entry.url?.trim()));
704
+ }
705
+ const pathValue = ctx.MediaPath?.trim();
706
+ const url = ctx.MediaUrl?.trim();
707
+ if (!pathValue && !url) return [];
708
+ return [{
709
+ path: pathValue || void 0,
710
+ url: url || void 0,
711
+ mime: ctx.MediaType,
712
+ index: 0
713
+ }];
714
+ }
715
+ function resolveAttachmentKind(attachment) {
716
+ const kind = kindFromMime(attachment.mime);
717
+ if (kind === "image" || kind === "audio" || kind === "video") return kind;
718
+ const ext = getFileExtension(attachment.path ?? attachment.url);
719
+ if (!ext) return "unknown";
720
+ if ([
721
+ ".mp4",
722
+ ".mov",
723
+ ".mkv",
724
+ ".webm",
725
+ ".avi",
726
+ ".m4v"
727
+ ].includes(ext)) return "video";
728
+ if (isAudioFileName(attachment.path ?? attachment.url)) return "audio";
729
+ if ([
730
+ ".png",
731
+ ".jpg",
732
+ ".jpeg",
733
+ ".webp",
734
+ ".gif",
735
+ ".bmp",
736
+ ".tiff",
737
+ ".tif"
738
+ ].includes(ext)) return "image";
739
+ return "unknown";
740
+ }
741
+ function isVideoAttachment(attachment) {
742
+ return resolveAttachmentKind(attachment) === "video";
743
+ }
744
+ function isAudioAttachment(attachment) {
745
+ return resolveAttachmentKind(attachment) === "audio";
746
+ }
747
+ function isImageAttachment(attachment) {
748
+ return resolveAttachmentKind(attachment) === "image";
749
+ }
750
+
751
+ //#endregion
752
+ //#region src/media-understanding/attachments.select.ts
753
+ const DEFAULT_MAX_ATTACHMENTS = 1;
754
+ function orderAttachments(attachments, prefer) {
755
+ const list = Array.isArray(attachments) ? attachments.filter(isAttachmentRecord) : [];
756
+ if (!prefer || prefer === "first") return list;
757
+ if (prefer === "last") return [...list].toReversed();
758
+ if (prefer === "path") {
759
+ const withPath = list.filter((item) => item.path);
760
+ const withoutPath = list.filter((item) => !item.path);
761
+ return [...withPath, ...withoutPath];
762
+ }
763
+ if (prefer === "url") {
764
+ const withUrl = list.filter((item) => item.url);
765
+ const withoutUrl = list.filter((item) => !item.url);
766
+ return [...withUrl, ...withoutUrl];
767
+ }
768
+ return list;
769
+ }
770
+ function isAttachmentRecord(value) {
771
+ if (!value || typeof value !== "object") return false;
772
+ const entry = value;
773
+ if (typeof entry.index !== "number") return false;
774
+ if (entry.path !== void 0 && typeof entry.path !== "string") return false;
775
+ if (entry.url !== void 0 && typeof entry.url !== "string") return false;
776
+ if (entry.mime !== void 0 && typeof entry.mime !== "string") return false;
777
+ if (entry.alreadyTranscribed !== void 0 && typeof entry.alreadyTranscribed !== "boolean") return false;
778
+ return true;
779
+ }
780
+ function selectAttachments(params) {
781
+ const { capability, attachments, policy } = params;
782
+ const matches = (Array.isArray(attachments) ? attachments.filter(isAttachmentRecord) : []).filter((item) => {
783
+ if (capability === "audio" && item.alreadyTranscribed) return false;
784
+ if (capability === "image") return isImageAttachment(item);
785
+ if (capability === "audio") return isAudioAttachment(item);
786
+ return isVideoAttachment(item);
787
+ });
788
+ if (matches.length === 0) return [];
789
+ const ordered = orderAttachments(matches, policy?.prefer);
790
+ const mode = policy?.mode ?? "first";
791
+ const maxAttachments = policy?.maxAttachments ?? DEFAULT_MAX_ATTACHMENTS;
792
+ if (mode === "all") return ordered.slice(0, Math.max(1, maxAttachments));
793
+ return ordered.slice(0, 1);
794
+ }
795
+
796
+ //#endregion
797
+ //#region src/infra/unhandled-rejections.ts
798
+ const handlers = /* @__PURE__ */ new Set();
799
+ /**
800
+ * Checks if an error is an AbortError.
801
+ * These are typically intentional cancellations (e.g., during shutdown) and shouldn't crash.
802
+ */
803
+ function isAbortError(err) {
804
+ if (!err || typeof err !== "object") return false;
805
+ if (("name" in err ? String(err.name) : "") === "AbortError") return true;
806
+ if (("message" in err && typeof err.message === "string" ? err.message : "") === "This operation was aborted") return true;
807
+ return false;
808
+ }
809
+ function registerUnhandledRejectionHandler(handler) {
810
+ handlers.add(handler);
811
+ return () => {
812
+ handlers.delete(handler);
813
+ };
814
+ }
815
+
816
+ //#endregion
817
+ //#region src/media-understanding/errors.ts
818
+ var MediaUnderstandingSkipError = class extends Error {
819
+ constructor(reason, message) {
820
+ super(message);
821
+ this.reason = reason;
822
+ this.name = "MediaUnderstandingSkipError";
823
+ }
824
+ };
825
+ function isMediaUnderstandingSkipError(err) {
826
+ return err instanceof MediaUnderstandingSkipError;
827
+ }
828
+
829
+ //#endregion
830
+ //#region src/media-understanding/attachments.cache.ts
831
+ const DEFAULT_LOCAL_PATH_ROOTS = mergeInboundPathRoots(getDefaultMediaLocalRoots(), DEFAULT_IMESSAGE_ATTACHMENT_ROOTS);
832
+ function resolveRequestUrl(input) {
833
+ if (typeof input === "string") return input;
834
+ if (input instanceof URL) return input.toString();
835
+ return input.url;
836
+ }
837
+ var MediaAttachmentCache = class {
838
+ constructor(attachments, options) {
839
+ this.entries = /* @__PURE__ */ new Map();
840
+ this.attachments = attachments;
841
+ this.localPathRoots = mergeInboundPathRoots(options?.localPathRoots, DEFAULT_LOCAL_PATH_ROOTS);
842
+ for (const attachment of attachments) this.entries.set(attachment.index, { attachment });
843
+ }
844
+ async getBuffer(params) {
845
+ const entry = await this.ensureEntry(params.attachmentIndex);
846
+ if (entry.buffer) {
847
+ if (entry.buffer.length > params.maxBytes) throw new MediaUnderstandingSkipError("maxBytes", `Attachment ${params.attachmentIndex + 1} exceeds maxBytes ${params.maxBytes}`);
848
+ return {
849
+ buffer: entry.buffer,
850
+ mime: entry.bufferMime,
851
+ fileName: entry.bufferFileName ?? `media-${params.attachmentIndex + 1}`,
852
+ size: entry.buffer.length
853
+ };
854
+ }
855
+ if (entry.resolvedPath) {
856
+ const size = await this.ensureLocalStat(entry);
857
+ if (entry.resolvedPath) {
858
+ if (size !== void 0 && size > params.maxBytes) throw new MediaUnderstandingSkipError("maxBytes", `Attachment ${params.attachmentIndex + 1} exceeds maxBytes ${params.maxBytes}`);
859
+ const buffer = await fs$1.readFile(entry.resolvedPath);
860
+ entry.buffer = buffer;
861
+ entry.bufferMime = entry.bufferMime ?? entry.attachment.mime ?? await detectMime({
862
+ buffer,
863
+ filePath: entry.resolvedPath
864
+ });
865
+ entry.bufferFileName = path.basename(entry.resolvedPath) || `media-${params.attachmentIndex + 1}`;
866
+ return {
867
+ buffer,
868
+ mime: entry.bufferMime,
869
+ fileName: entry.bufferFileName,
870
+ size: buffer.length
871
+ };
872
+ }
873
+ }
874
+ const url = entry.attachment.url?.trim();
875
+ if (!url) throw new MediaUnderstandingSkipError("empty", `Attachment ${params.attachmentIndex + 1} has no path or URL.`);
876
+ try {
877
+ const fetchImpl = (input, init) => fetchWithTimeout(resolveRequestUrl(input), init ?? {}, params.timeoutMs, fetch);
878
+ const fetched = await fetchRemoteMedia({
879
+ url,
880
+ fetchImpl,
881
+ maxBytes: params.maxBytes
882
+ });
883
+ entry.buffer = fetched.buffer;
884
+ entry.bufferMime = entry.attachment.mime ?? fetched.contentType ?? await detectMime({
885
+ buffer: fetched.buffer,
886
+ filePath: fetched.fileName ?? url
887
+ });
888
+ entry.bufferFileName = fetched.fileName ?? `media-${params.attachmentIndex + 1}`;
889
+ return {
890
+ buffer: fetched.buffer,
891
+ mime: entry.bufferMime,
892
+ fileName: entry.bufferFileName,
893
+ size: fetched.buffer.length
894
+ };
895
+ } catch (err) {
896
+ if (err instanceof MediaFetchError && err.code === "max_bytes") throw new MediaUnderstandingSkipError("maxBytes", `Attachment ${params.attachmentIndex + 1} exceeds maxBytes ${params.maxBytes}`);
897
+ if (isAbortError(err)) throw new MediaUnderstandingSkipError("timeout", `Attachment ${params.attachmentIndex + 1} timed out while fetching.`);
898
+ throw err;
899
+ }
900
+ }
901
+ async getPath(params) {
902
+ const entry = await this.ensureEntry(params.attachmentIndex);
903
+ if (entry.resolvedPath) {
904
+ if (params.maxBytes) {
905
+ const size = await this.ensureLocalStat(entry);
906
+ if (entry.resolvedPath) {
907
+ if (size !== void 0 && size > params.maxBytes) throw new MediaUnderstandingSkipError("maxBytes", `Attachment ${params.attachmentIndex + 1} exceeds maxBytes ${params.maxBytes}`);
908
+ }
909
+ }
910
+ if (entry.resolvedPath) return { path: entry.resolvedPath };
911
+ }
912
+ if (entry.tempPath) {
913
+ if (params.maxBytes && entry.buffer && entry.buffer.length > params.maxBytes) throw new MediaUnderstandingSkipError("maxBytes", `Attachment ${params.attachmentIndex + 1} exceeds maxBytes ${params.maxBytes}`);
914
+ return {
915
+ path: entry.tempPath,
916
+ cleanup: entry.tempCleanup
917
+ };
918
+ }
919
+ const maxBytes = params.maxBytes ?? Number.POSITIVE_INFINITY;
920
+ const bufferResult = await this.getBuffer({
921
+ attachmentIndex: params.attachmentIndex,
922
+ maxBytes,
923
+ timeoutMs: params.timeoutMs
924
+ });
925
+ const tmpPath = buildRandomTempFilePath({
926
+ prefix: "squidclaw-media",
927
+ extension: path.extname(bufferResult.fileName || "") || ""
928
+ });
929
+ await fs$1.writeFile(tmpPath, bufferResult.buffer);
930
+ entry.tempPath = tmpPath;
931
+ entry.tempCleanup = async () => {
932
+ await fs$1.unlink(tmpPath).catch(() => {});
933
+ };
934
+ return {
935
+ path: tmpPath,
936
+ cleanup: entry.tempCleanup
937
+ };
938
+ }
939
+ async cleanup() {
940
+ const cleanups = [];
941
+ for (const entry of this.entries.values()) if (entry.tempCleanup) {
942
+ cleanups.push(Promise.resolve(entry.tempCleanup()));
943
+ entry.tempCleanup = void 0;
944
+ }
945
+ await Promise.all(cleanups);
946
+ }
947
+ async ensureEntry(attachmentIndex) {
948
+ const existing = this.entries.get(attachmentIndex);
949
+ if (existing) {
950
+ if (!existing.resolvedPath) existing.resolvedPath = this.resolveLocalPath(existing.attachment);
951
+ return existing;
952
+ }
953
+ const attachment = this.attachments.find((item) => item.index === attachmentIndex) ?? { index: attachmentIndex };
954
+ const entry = {
955
+ attachment,
956
+ resolvedPath: this.resolveLocalPath(attachment)
957
+ };
958
+ this.entries.set(attachmentIndex, entry);
959
+ return entry;
960
+ }
961
+ resolveLocalPath(attachment) {
962
+ const rawPath = normalizeAttachmentPath(attachment.path);
963
+ if (!rawPath) return;
964
+ return path.isAbsolute(rawPath) ? rawPath : path.resolve(rawPath);
965
+ }
966
+ async ensureLocalStat(entry) {
967
+ if (!entry.resolvedPath) return;
968
+ if (!isInboundPathAllowed({
969
+ filePath: entry.resolvedPath,
970
+ roots: this.localPathRoots
971
+ })) {
972
+ entry.resolvedPath = void 0;
973
+ if (shouldLogVerbose()) logVerbose(`Blocked attachment path outside allowed roots: ${entry.attachment.path ?? entry.attachment.url ?? "(unknown)"}`);
974
+ return;
975
+ }
976
+ if (entry.statSize !== void 0) return entry.statSize;
977
+ try {
978
+ const currentPath = entry.resolvedPath;
979
+ const stat = await fs$1.stat(currentPath);
980
+ if (!stat.isFile()) {
981
+ entry.resolvedPath = void 0;
982
+ return;
983
+ }
984
+ const canonicalPath = await fs$1.realpath(currentPath).catch(() => currentPath);
985
+ if (!isInboundPathAllowed({
986
+ filePath: canonicalPath,
987
+ roots: await this.getCanonicalLocalPathRoots()
988
+ })) {
989
+ entry.resolvedPath = void 0;
990
+ if (shouldLogVerbose()) logVerbose(`Blocked canonicalized attachment path outside allowed roots: ${canonicalPath}`);
991
+ return;
992
+ }
993
+ entry.resolvedPath = canonicalPath;
994
+ entry.statSize = stat.size;
995
+ return stat.size;
996
+ } catch (err) {
997
+ entry.resolvedPath = void 0;
998
+ if (shouldLogVerbose()) logVerbose(`Failed to read attachment ${entry.attachment.index + 1}: ${String(err)}`);
999
+ return;
1000
+ }
1001
+ }
1002
+ async getCanonicalLocalPathRoots() {
1003
+ if (this.canonicalLocalPathRoots) return await this.canonicalLocalPathRoots;
1004
+ this.canonicalLocalPathRoots = (async () => mergeInboundPathRoots(this.localPathRoots, await Promise.all(this.localPathRoots.map(async (root) => {
1005
+ if (root.includes("*")) return root;
1006
+ return await fs$1.realpath(root).catch(() => root);
1007
+ }))))();
1008
+ return await this.canonicalLocalPathRoots;
1009
+ }
1010
+ };
1011
+
1012
+ //#endregion
1013
+ //#region src/agents/model-catalog.ts
1014
+ const log = createSubsystemLogger("model-catalog");
1015
+ let modelCatalogPromise = null;
1016
+ let hasLoggedModelCatalogError = false;
1017
+ const defaultImportPiSdk = () => import("./pi-model-discovery-BGEeoPzN.js").then((n) => n.r);
1018
+ let importPiSdk = defaultImportPiSdk;
1019
+ const CODEX_PROVIDER = "openai-codex";
1020
+ const OPENAI_CODEX_GPT53_MODEL_ID = "gpt-5.3-codex";
1021
+ const OPENAI_CODEX_GPT53_SPARK_MODEL_ID = "gpt-5.3-codex-spark";
1022
+ const NON_PI_NATIVE_MODEL_PROVIDERS = new Set(["kilocode"]);
1023
+ function applyOpenAICodexSparkFallback(models) {
1024
+ if (models.some((entry) => entry.provider === CODEX_PROVIDER && entry.id.toLowerCase() === OPENAI_CODEX_GPT53_SPARK_MODEL_ID)) return;
1025
+ const baseModel = models.find((entry) => entry.provider === CODEX_PROVIDER && entry.id.toLowerCase() === OPENAI_CODEX_GPT53_MODEL_ID);
1026
+ if (!baseModel) return;
1027
+ models.push({
1028
+ ...baseModel,
1029
+ id: OPENAI_CODEX_GPT53_SPARK_MODEL_ID,
1030
+ name: OPENAI_CODEX_GPT53_SPARK_MODEL_ID
1031
+ });
1032
+ }
1033
+ function normalizeConfiguredModelInput(input) {
1034
+ if (!Array.isArray(input)) return;
1035
+ const normalized = input.filter((item) => item === "text" || item === "image" || item === "document");
1036
+ return normalized.length > 0 ? normalized : void 0;
1037
+ }
1038
+ function readConfiguredOptInProviderModels(config) {
1039
+ const providers = config.models?.providers;
1040
+ if (!providers || typeof providers !== "object") return [];
1041
+ const out = [];
1042
+ for (const [providerRaw, providerValue] of Object.entries(providers)) {
1043
+ const provider = providerRaw.toLowerCase().trim();
1044
+ if (!NON_PI_NATIVE_MODEL_PROVIDERS.has(provider)) continue;
1045
+ if (!providerValue || typeof providerValue !== "object") continue;
1046
+ const configuredModels = providerValue.models;
1047
+ if (!Array.isArray(configuredModels)) continue;
1048
+ for (const configuredModel of configuredModels) {
1049
+ if (!configuredModel || typeof configuredModel !== "object") continue;
1050
+ const idRaw = configuredModel.id;
1051
+ if (typeof idRaw !== "string") continue;
1052
+ const id = idRaw.trim();
1053
+ if (!id) continue;
1054
+ const rawName = configuredModel.name;
1055
+ const name = (typeof rawName === "string" ? rawName : id).trim() || id;
1056
+ const contextWindowRaw = configuredModel.contextWindow;
1057
+ const contextWindow = typeof contextWindowRaw === "number" && contextWindowRaw > 0 ? contextWindowRaw : void 0;
1058
+ const reasoningRaw = configuredModel.reasoning;
1059
+ const reasoning = typeof reasoningRaw === "boolean" ? reasoningRaw : void 0;
1060
+ const input = normalizeConfiguredModelInput(configuredModel.input);
1061
+ out.push({
1062
+ id,
1063
+ name,
1064
+ provider,
1065
+ contextWindow,
1066
+ reasoning,
1067
+ input
1068
+ });
1069
+ }
1070
+ }
1071
+ return out;
1072
+ }
1073
+ function mergeConfiguredOptInProviderModels(params) {
1074
+ const configured = readConfiguredOptInProviderModels(params.config);
1075
+ if (configured.length === 0) return;
1076
+ const seen = new Set(params.models.map((entry) => `${entry.provider.toLowerCase().trim()}::${entry.id.toLowerCase().trim()}`));
1077
+ for (const entry of configured) {
1078
+ const key = `${entry.provider.toLowerCase().trim()}::${entry.id.toLowerCase().trim()}`;
1079
+ if (seen.has(key)) continue;
1080
+ params.models.push(entry);
1081
+ seen.add(key);
1082
+ }
1083
+ }
1084
+ async function loadModelCatalog(params) {
1085
+ if (params?.useCache === false) modelCatalogPromise = null;
1086
+ if (modelCatalogPromise) return modelCatalogPromise;
1087
+ modelCatalogPromise = (async () => {
1088
+ const models = [];
1089
+ const sortModels = (entries) => entries.sort((a, b) => {
1090
+ const p = a.provider.localeCompare(b.provider);
1091
+ if (p !== 0) return p;
1092
+ return a.name.localeCompare(b.name);
1093
+ });
1094
+ try {
1095
+ const cfg = params?.config ?? loadConfig();
1096
+ await ensureSquidClawModelsJson(cfg);
1097
+ const piSdk = await importPiSdk();
1098
+ const agentDir = resolveSquidClawAgentDir();
1099
+ const { join } = await import("node:path");
1100
+ const authStorage = piSdk.discoverAuthStorage(agentDir);
1101
+ const registry = new piSdk.ModelRegistry(authStorage, join(agentDir, "models.json"));
1102
+ const entries = Array.isArray(registry) ? registry : registry.getAll();
1103
+ for (const entry of entries) {
1104
+ const id = String(entry?.id ?? "").trim();
1105
+ if (!id) continue;
1106
+ const provider = String(entry?.provider ?? "").trim();
1107
+ if (!provider) continue;
1108
+ const name = String(entry?.name ?? id).trim() || id;
1109
+ const contextWindow = typeof entry?.contextWindow === "number" && entry.contextWindow > 0 ? entry.contextWindow : void 0;
1110
+ const reasoning = typeof entry?.reasoning === "boolean" ? entry.reasoning : void 0;
1111
+ const input = Array.isArray(entry?.input) ? entry.input : void 0;
1112
+ models.push({
1113
+ id,
1114
+ name,
1115
+ provider,
1116
+ contextWindow,
1117
+ reasoning,
1118
+ input
1119
+ });
1120
+ }
1121
+ mergeConfiguredOptInProviderModels({
1122
+ config: cfg,
1123
+ models
1124
+ });
1125
+ applyOpenAICodexSparkFallback(models);
1126
+ if (models.length === 0) modelCatalogPromise = null;
1127
+ return sortModels(models);
1128
+ } catch (error) {
1129
+ if (!hasLoggedModelCatalogError) {
1130
+ hasLoggedModelCatalogError = true;
1131
+ log.warn(`Failed to load model catalog: ${String(error)}`);
1132
+ }
1133
+ modelCatalogPromise = null;
1134
+ if (models.length > 0) return sortModels(models);
1135
+ return [];
1136
+ }
1137
+ })();
1138
+ return modelCatalogPromise;
1139
+ }
1140
+ /**
1141
+ * Check if a model supports image input based on its catalog entry.
1142
+ */
1143
+ function modelSupportsVision(entry) {
1144
+ return entry?.input?.includes("image") ?? false;
1145
+ }
1146
+ /**
1147
+ * Find a model in the catalog by provider and model ID.
1148
+ */
1149
+ function findModelInCatalog(catalog, provider, modelId) {
1150
+ const normalizedProvider = provider.toLowerCase().trim();
1151
+ const normalizedModelId = modelId.toLowerCase().trim();
1152
+ return catalog.find((entry) => entry.provider.toLowerCase() === normalizedProvider && entry.id.toLowerCase() === normalizedModelId);
1153
+ }
1154
+
1155
+ //#endregion
1156
+ //#region src/media-understanding/fs.ts
1157
+ async function fileExists(filePath) {
1158
+ if (!filePath) return false;
1159
+ try {
1160
+ await fs$1.stat(filePath);
1161
+ return true;
1162
+ } catch {
1163
+ return false;
1164
+ }
1165
+ }
1166
+
1167
+ //#endregion
1168
+ //#region src/media-understanding/output-extract.ts
1169
+ function extractLastJsonObject(raw) {
1170
+ const trimmed = raw.trim();
1171
+ const start = trimmed.lastIndexOf("{");
1172
+ if (start === -1) return null;
1173
+ const slice = trimmed.slice(start);
1174
+ try {
1175
+ return JSON.parse(slice);
1176
+ } catch {
1177
+ return null;
1178
+ }
1179
+ }
1180
+ function extractGeminiResponse(raw) {
1181
+ const payload = extractLastJsonObject(raw);
1182
+ if (!payload || typeof payload !== "object") return null;
1183
+ const response = payload.response;
1184
+ if (typeof response !== "string") return null;
1185
+ return response.trim() || null;
1186
+ }
1187
+
1188
+ //#endregion
1189
+ //#region src/media-understanding/video.ts
1190
+ function estimateBase64Size(bytes) {
1191
+ return Math.ceil(bytes / 3) * 4;
1192
+ }
1193
+ function resolveVideoMaxBase64Bytes(maxBytes) {
1194
+ const expanded = Math.floor(maxBytes * (4 / 3));
1195
+ return Math.min(expanded, DEFAULT_VIDEO_MAX_BASE64_BYTES);
1196
+ }
1197
+
1198
+ //#endregion
1199
+ //#region src/media-understanding/runner.entries.ts
1200
+ function trimOutput(text, maxChars) {
1201
+ const trimmed = text.trim();
1202
+ if (!maxChars || trimmed.length <= maxChars) return trimmed;
1203
+ return trimmed.slice(0, maxChars).trim();
1204
+ }
1205
+ function extractSherpaOnnxText(raw) {
1206
+ const tryParse = (value) => {
1207
+ const trimmed = value.trim();
1208
+ if (!trimmed) return null;
1209
+ const head = trimmed[0];
1210
+ if (head !== "{" && head !== "\"") return null;
1211
+ try {
1212
+ const parsed = JSON.parse(trimmed);
1213
+ if (typeof parsed === "string") return tryParse(parsed);
1214
+ if (parsed && typeof parsed === "object") {
1215
+ const text = parsed.text;
1216
+ if (typeof text === "string" && text.trim()) return text.trim();
1217
+ }
1218
+ } catch {}
1219
+ return null;
1220
+ };
1221
+ const direct = tryParse(raw);
1222
+ if (direct) return direct;
1223
+ const lines = raw.split("\n").map((line) => line.trim()).filter(Boolean);
1224
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
1225
+ const parsed = tryParse(lines[i] ?? "");
1226
+ if (parsed) return parsed;
1227
+ }
1228
+ return null;
1229
+ }
1230
+ function commandBase(command) {
1231
+ return path.parse(command).name;
1232
+ }
1233
+ function findArgValue(args, keys) {
1234
+ for (let i = 0; i < args.length; i += 1) if (keys.includes(args[i] ?? "")) {
1235
+ const value = args[i + 1];
1236
+ if (value) return value;
1237
+ }
1238
+ }
1239
+ function hasArg(args, keys) {
1240
+ return args.some((arg) => keys.includes(arg));
1241
+ }
1242
+ function resolveWhisperOutputPath(args, mediaPath) {
1243
+ const outputDir = findArgValue(args, ["--output_dir", "-o"]);
1244
+ const outputFormat = findArgValue(args, ["--output_format"]);
1245
+ if (!outputDir || !outputFormat) return null;
1246
+ if (!outputFormat.split(",").map((value) => value.trim()).includes("txt")) return null;
1247
+ const base = path.parse(mediaPath).name;
1248
+ return path.join(outputDir, `${base}.txt`);
1249
+ }
1250
+ function resolveWhisperCppOutputPath(args) {
1251
+ if (!hasArg(args, ["-otxt", "--output-txt"])) return null;
1252
+ const outputBase = findArgValue(args, ["-of", "--output-file"]);
1253
+ if (!outputBase) return null;
1254
+ return `${outputBase}.txt`;
1255
+ }
1256
+ function resolveParakeetOutputPath(args, mediaPath) {
1257
+ const outputDir = findArgValue(args, ["--output-dir"]);
1258
+ const outputFormat = findArgValue(args, ["--output-format"]);
1259
+ if (!outputDir) return null;
1260
+ if (outputFormat && outputFormat !== "txt") return null;
1261
+ const base = path.parse(mediaPath).name;
1262
+ return path.join(outputDir, `${base}.txt`);
1263
+ }
1264
+ async function resolveCliOutput(params) {
1265
+ const commandId = commandBase(params.command);
1266
+ const fileOutput = commandId === "whisper-cli" ? resolveWhisperCppOutputPath(params.args) : commandId === "whisper" ? resolveWhisperOutputPath(params.args, params.mediaPath) : commandId === "parakeet-mlx" ? resolveParakeetOutputPath(params.args, params.mediaPath) : null;
1267
+ if (fileOutput && await fileExists(fileOutput)) try {
1268
+ const content = await fs$1.readFile(fileOutput, "utf8");
1269
+ if (content.trim()) return content.trim();
1270
+ } catch {}
1271
+ if (commandId === "gemini") {
1272
+ const response = extractGeminiResponse(params.stdout);
1273
+ if (response) return response;
1274
+ }
1275
+ if (commandId === "sherpa-onnx-offline") {
1276
+ const response = extractSherpaOnnxText(params.stdout);
1277
+ if (response) return response;
1278
+ }
1279
+ return params.stdout.trim();
1280
+ }
1281
+ function normalizeProviderQuery(options) {
1282
+ if (!options) return;
1283
+ const query = {};
1284
+ for (const [key, value] of Object.entries(options)) {
1285
+ if (value === void 0) continue;
1286
+ query[key] = value;
1287
+ }
1288
+ return Object.keys(query).length > 0 ? query : void 0;
1289
+ }
1290
+ function buildDeepgramCompatQuery(options) {
1291
+ if (!options) return;
1292
+ const query = {};
1293
+ if (typeof options.detectLanguage === "boolean") query.detect_language = options.detectLanguage;
1294
+ if (typeof options.punctuate === "boolean") query.punctuate = options.punctuate;
1295
+ if (typeof options.smartFormat === "boolean") query.smart_format = options.smartFormat;
1296
+ return Object.keys(query).length > 0 ? query : void 0;
1297
+ }
1298
+ function normalizeDeepgramQueryKeys(query) {
1299
+ const normalized = { ...query };
1300
+ if ("detectLanguage" in normalized) {
1301
+ normalized.detect_language = normalized.detectLanguage;
1302
+ delete normalized.detectLanguage;
1303
+ }
1304
+ if ("smartFormat" in normalized) {
1305
+ normalized.smart_format = normalized.smartFormat;
1306
+ delete normalized.smartFormat;
1307
+ }
1308
+ return normalized;
1309
+ }
1310
+ function resolveProviderQuery(params) {
1311
+ const { providerId, config, entry } = params;
1312
+ const mergedOptions = normalizeProviderQuery({
1313
+ ...config?.providerOptions?.[providerId],
1314
+ ...entry.providerOptions?.[providerId]
1315
+ });
1316
+ if (providerId !== "deepgram") return mergedOptions;
1317
+ const query = normalizeDeepgramQueryKeys(mergedOptions ?? {});
1318
+ const compat = buildDeepgramCompatQuery({
1319
+ ...config?.deepgram,
1320
+ ...entry.deepgram
1321
+ });
1322
+ for (const [key, value] of Object.entries(compat ?? {})) if (query[key] === void 0) query[key] = value;
1323
+ return Object.keys(query).length > 0 ? query : void 0;
1324
+ }
1325
+ function buildModelDecision(params) {
1326
+ if (params.entryType === "cli") {
1327
+ const command = params.entry.command?.trim();
1328
+ return {
1329
+ type: "cli",
1330
+ provider: command ?? "cli",
1331
+ model: params.entry.model ?? command,
1332
+ outcome: params.outcome,
1333
+ reason: params.reason
1334
+ };
1335
+ }
1336
+ const providerIdRaw = params.entry.provider?.trim();
1337
+ return {
1338
+ type: "provider",
1339
+ provider: (providerIdRaw ? normalizeMediaProviderId(providerIdRaw) : void 0) ?? providerIdRaw,
1340
+ model: params.entry.model,
1341
+ outcome: params.outcome,
1342
+ reason: params.reason
1343
+ };
1344
+ }
1345
+ function resolveEntryRunOptions(params) {
1346
+ const { capability, entry, cfg } = params;
1347
+ const maxBytes = resolveMaxBytes({
1348
+ capability,
1349
+ entry,
1350
+ cfg,
1351
+ config: params.config
1352
+ });
1353
+ const maxChars = resolveMaxChars({
1354
+ capability,
1355
+ entry,
1356
+ cfg,
1357
+ config: params.config
1358
+ });
1359
+ return {
1360
+ maxBytes,
1361
+ maxChars,
1362
+ timeoutMs: resolveTimeoutMs(entry.timeoutSeconds ?? params.config?.timeoutSeconds ?? cfg.tools?.media?.[capability]?.timeoutSeconds, DEFAULT_TIMEOUT_SECONDS[capability]),
1363
+ prompt: resolvePrompt(capability, entry.prompt ?? params.config?.prompt ?? cfg.tools?.media?.[capability]?.prompt, maxChars)
1364
+ };
1365
+ }
1366
+ async function resolveProviderExecutionAuth(params) {
1367
+ const auth = await resolveApiKeyForProvider({
1368
+ provider: params.providerId,
1369
+ cfg: params.cfg,
1370
+ profileId: params.entry.profile,
1371
+ preferredProfile: params.entry.preferredProfile,
1372
+ agentDir: params.agentDir
1373
+ });
1374
+ return {
1375
+ apiKeys: collectProviderApiKeysForExecution({
1376
+ provider: params.providerId,
1377
+ primaryApiKey: requireApiKey(auth, params.providerId)
1378
+ }),
1379
+ providerConfig: params.cfg.models?.providers?.[params.providerId]
1380
+ };
1381
+ }
1382
+ async function resolveProviderExecutionContext(params) {
1383
+ const { apiKeys, providerConfig } = await resolveProviderExecutionAuth({
1384
+ providerId: params.providerId,
1385
+ cfg: params.cfg,
1386
+ entry: params.entry,
1387
+ agentDir: params.agentDir
1388
+ });
1389
+ const baseUrl = params.entry.baseUrl ?? params.config?.baseUrl ?? providerConfig?.baseUrl;
1390
+ const mergedHeaders = {
1391
+ ...providerConfig?.headers,
1392
+ ...params.config?.headers,
1393
+ ...params.entry.headers
1394
+ };
1395
+ return {
1396
+ apiKeys,
1397
+ baseUrl,
1398
+ headers: Object.keys(mergedHeaders).length > 0 ? mergedHeaders : void 0
1399
+ };
1400
+ }
1401
+ function formatDecisionSummary(decision) {
1402
+ const attachments = Array.isArray(decision.attachments) ? decision.attachments : [];
1403
+ const total = attachments.length;
1404
+ const success = attachments.filter((entry) => entry?.chosen?.outcome === "success").length;
1405
+ const chosen = attachments.find((entry) => entry?.chosen)?.chosen;
1406
+ const provider = typeof chosen?.provider === "string" ? chosen.provider.trim() : void 0;
1407
+ const model = typeof chosen?.model === "string" ? chosen.model.trim() : void 0;
1408
+ const modelLabel = provider ? model ? `${provider}/${model}` : provider : void 0;
1409
+ const reason = attachments.flatMap((entry) => {
1410
+ return (Array.isArray(entry?.attempts) ? entry.attempts : []).map((attempt) => typeof attempt?.reason === "string" ? attempt.reason : void 0).filter((value) => Boolean(value));
1411
+ }).find((value) => value.trim().length > 0);
1412
+ const shortReason = reason ? reason.split(":")[0]?.trim() : void 0;
1413
+ const countLabel = total > 0 ? ` (${success}/${total})` : "";
1414
+ const viaLabel = modelLabel ? ` via ${modelLabel}` : "";
1415
+ const reasonLabel = shortReason ? ` reason=${shortReason}` : "";
1416
+ return `${decision.capability}: ${decision.outcome}${countLabel}${viaLabel}${reasonLabel}`;
1417
+ }
1418
+ function assertMinAudioSize(params) {
1419
+ if (params.size >= MIN_AUDIO_FILE_BYTES) return;
1420
+ throw new MediaUnderstandingSkipError("tooSmall", `Audio attachment ${params.attachmentIndex + 1} is too small (${params.size} bytes, minimum ${MIN_AUDIO_FILE_BYTES})`);
1421
+ }
1422
+ async function runProviderEntry(params) {
1423
+ const { entry, capability, cfg } = params;
1424
+ const providerIdRaw = entry.provider?.trim();
1425
+ if (!providerIdRaw) throw new Error(`Provider entry missing provider for ${capability}`);
1426
+ const providerId = normalizeMediaProviderId(providerIdRaw);
1427
+ const { maxBytes, maxChars, timeoutMs, prompt } = resolveEntryRunOptions({
1428
+ capability,
1429
+ entry,
1430
+ cfg,
1431
+ config: params.config
1432
+ });
1433
+ if (capability === "image") {
1434
+ if (!params.agentDir) throw new Error("Image understanding requires agentDir");
1435
+ const modelId = entry.model?.trim();
1436
+ if (!modelId) throw new Error("Image understanding requires model id");
1437
+ const media = await params.cache.getBuffer({
1438
+ attachmentIndex: params.attachmentIndex,
1439
+ maxBytes,
1440
+ timeoutMs
1441
+ });
1442
+ const provider = getMediaUnderstandingProvider(providerId, params.providerRegistry);
1443
+ const imageInput = {
1444
+ buffer: media.buffer,
1445
+ fileName: media.fileName,
1446
+ mime: media.mime,
1447
+ model: modelId,
1448
+ provider: providerId,
1449
+ prompt,
1450
+ timeoutMs,
1451
+ profile: entry.profile,
1452
+ preferredProfile: entry.preferredProfile,
1453
+ agentDir: params.agentDir,
1454
+ cfg: params.cfg
1455
+ };
1456
+ const result = await (provider?.describeImage ?? describeImageWithModel)(imageInput);
1457
+ return {
1458
+ kind: "image.description",
1459
+ attachmentIndex: params.attachmentIndex,
1460
+ text: trimOutput(result.text, maxChars),
1461
+ provider: providerId,
1462
+ model: result.model ?? modelId
1463
+ };
1464
+ }
1465
+ const provider = getMediaUnderstandingProvider(providerId, params.providerRegistry);
1466
+ if (!provider) throw new Error(`Media provider not available: ${providerId}`);
1467
+ const fetchFn = resolveProxyFetchFromEnv();
1468
+ if (capability === "audio") {
1469
+ if (!provider.transcribeAudio) throw new Error(`Audio transcription provider "${providerId}" not available.`);
1470
+ const transcribeAudio = provider.transcribeAudio;
1471
+ const media = await params.cache.getBuffer({
1472
+ attachmentIndex: params.attachmentIndex,
1473
+ maxBytes,
1474
+ timeoutMs
1475
+ });
1476
+ assertMinAudioSize({
1477
+ size: media.size,
1478
+ attachmentIndex: params.attachmentIndex
1479
+ });
1480
+ const { apiKeys, baseUrl, headers } = await resolveProviderExecutionContext({
1481
+ providerId,
1482
+ cfg,
1483
+ entry,
1484
+ config: params.config,
1485
+ agentDir: params.agentDir
1486
+ });
1487
+ const providerQuery = resolveProviderQuery({
1488
+ providerId,
1489
+ config: params.config,
1490
+ entry
1491
+ });
1492
+ const model = entry.model?.trim() || DEFAULT_AUDIO_MODELS[providerId] || entry.model;
1493
+ const result = await executeWithApiKeyRotation({
1494
+ provider: providerId,
1495
+ apiKeys,
1496
+ execute: async (apiKey) => transcribeAudio({
1497
+ buffer: media.buffer,
1498
+ fileName: media.fileName,
1499
+ mime: media.mime,
1500
+ apiKey,
1501
+ baseUrl,
1502
+ headers,
1503
+ model,
1504
+ language: entry.language ?? params.config?.language ?? cfg.tools?.media?.audio?.language,
1505
+ prompt,
1506
+ query: providerQuery,
1507
+ timeoutMs,
1508
+ fetchFn
1509
+ })
1510
+ });
1511
+ return {
1512
+ kind: "audio.transcription",
1513
+ attachmentIndex: params.attachmentIndex,
1514
+ text: trimOutput(result.text, maxChars),
1515
+ provider: providerId,
1516
+ model: result.model ?? model
1517
+ };
1518
+ }
1519
+ if (!provider.describeVideo) throw new Error(`Video understanding provider "${providerId}" not available.`);
1520
+ const describeVideo = provider.describeVideo;
1521
+ const media = await params.cache.getBuffer({
1522
+ attachmentIndex: params.attachmentIndex,
1523
+ maxBytes,
1524
+ timeoutMs
1525
+ });
1526
+ const estimatedBase64Bytes = estimateBase64Size(media.size);
1527
+ const maxBase64Bytes = resolveVideoMaxBase64Bytes(maxBytes);
1528
+ if (estimatedBase64Bytes > maxBase64Bytes) throw new MediaUnderstandingSkipError("maxBytes", `Video attachment ${params.attachmentIndex + 1} base64 payload ${estimatedBase64Bytes} exceeds ${maxBase64Bytes}`);
1529
+ const { apiKeys, baseUrl, headers } = await resolveProviderExecutionContext({
1530
+ providerId,
1531
+ cfg,
1532
+ entry,
1533
+ config: params.config,
1534
+ agentDir: params.agentDir
1535
+ });
1536
+ const result = await executeWithApiKeyRotation({
1537
+ provider: providerId,
1538
+ apiKeys,
1539
+ execute: (apiKey) => describeVideo({
1540
+ buffer: media.buffer,
1541
+ fileName: media.fileName,
1542
+ mime: media.mime,
1543
+ apiKey,
1544
+ baseUrl,
1545
+ headers,
1546
+ model: entry.model,
1547
+ prompt,
1548
+ timeoutMs,
1549
+ fetchFn
1550
+ })
1551
+ });
1552
+ return {
1553
+ kind: "video.description",
1554
+ attachmentIndex: params.attachmentIndex,
1555
+ text: trimOutput(result.text, maxChars),
1556
+ provider: providerId,
1557
+ model: result.model ?? entry.model
1558
+ };
1559
+ }
1560
+ async function runCliEntry(params) {
1561
+ const { entry, capability, cfg, ctx } = params;
1562
+ const command = entry.command?.trim();
1563
+ const args = entry.args ?? [];
1564
+ if (!command) throw new Error(`CLI entry missing command for ${capability}`);
1565
+ const { maxBytes, maxChars, timeoutMs, prompt } = resolveEntryRunOptions({
1566
+ capability,
1567
+ entry,
1568
+ cfg,
1569
+ config: params.config
1570
+ });
1571
+ const pathResult = await params.cache.getPath({
1572
+ attachmentIndex: params.attachmentIndex,
1573
+ maxBytes,
1574
+ timeoutMs
1575
+ });
1576
+ if (capability === "audio") assertMinAudioSize({
1577
+ size: (await fs$1.stat(pathResult.path)).size,
1578
+ attachmentIndex: params.attachmentIndex
1579
+ });
1580
+ const outputDir = await fs$1.mkdtemp(path.join(resolvePreferredSquidClawTmpDir(), "squidclaw-media-cli-"));
1581
+ const mediaPath = pathResult.path;
1582
+ const outputBase = path.join(outputDir, path.parse(mediaPath).name);
1583
+ const templCtx = {
1584
+ ...ctx,
1585
+ MediaPath: mediaPath,
1586
+ MediaDir: path.dirname(mediaPath),
1587
+ OutputDir: outputDir,
1588
+ OutputBase: outputBase,
1589
+ Prompt: prompt,
1590
+ MaxChars: maxChars
1591
+ };
1592
+ const argv = [command, ...args].map((part, index) => index === 0 ? part : applyTemplate(part, templCtx));
1593
+ try {
1594
+ if (shouldLogVerbose()) logVerbose(`Media understanding via CLI: ${argv.join(" ")}`);
1595
+ const { stdout } = await runExec(argv[0], argv.slice(1), {
1596
+ timeoutMs,
1597
+ maxBuffer: CLI_OUTPUT_MAX_BUFFER
1598
+ });
1599
+ const text = trimOutput(await resolveCliOutput({
1600
+ command,
1601
+ args: argv.slice(1),
1602
+ stdout,
1603
+ mediaPath
1604
+ }), maxChars);
1605
+ if (!text) return null;
1606
+ return {
1607
+ kind: capability === "audio" ? "audio.transcription" : `${capability}.description`,
1608
+ attachmentIndex: params.attachmentIndex,
1609
+ text,
1610
+ provider: "cli",
1611
+ model: command
1612
+ };
1613
+ } finally {
1614
+ await fs$1.rm(outputDir, {
1615
+ recursive: true,
1616
+ force: true
1617
+ }).catch(() => {});
1618
+ }
1619
+ }
1620
+
1621
+ //#endregion
1622
+ //#region src/media-understanding/runner.ts
1623
+ function buildProviderRegistry(overrides) {
1624
+ return buildMediaUnderstandingRegistry(overrides);
1625
+ }
1626
+ function normalizeMediaAttachments(ctx) {
1627
+ return normalizeAttachments(ctx);
1628
+ }
1629
+ function resolveMediaAttachmentLocalRoots(params) {
1630
+ return mergeInboundPathRoots(getDefaultMediaLocalRoots(), resolveIMessageAttachmentRoots({
1631
+ cfg: params.cfg,
1632
+ accountId: params.ctx.AccountId
1633
+ }));
1634
+ }
1635
+ function createMediaAttachmentCache(attachments, options) {
1636
+ return new MediaAttachmentCache(attachments, options);
1637
+ }
1638
+ const binaryCache = /* @__PURE__ */ new Map();
1639
+ const geminiProbeCache = /* @__PURE__ */ new Map();
1640
+ function expandHomeDir(value) {
1641
+ if (!value.startsWith("~")) return value;
1642
+ const home = os.homedir();
1643
+ if (value === "~") return home;
1644
+ if (value.startsWith("~/")) return path.join(home, value.slice(2));
1645
+ return value;
1646
+ }
1647
+ function hasPathSeparator(value) {
1648
+ return value.includes("/") || value.includes("\\");
1649
+ }
1650
+ function candidateBinaryNames(name) {
1651
+ if (process.platform !== "win32") return [name];
1652
+ if (path.extname(name)) return [name];
1653
+ const pathext = (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").map((item) => item.trim()).filter(Boolean).map((item) => item.startsWith(".") ? item : `.${item}`);
1654
+ return [name, ...Array.from(new Set(pathext)).map((item) => `${name}${item}`)];
1655
+ }
1656
+ async function isExecutable(filePath) {
1657
+ try {
1658
+ if (!(await fs$1.stat(filePath)).isFile()) return false;
1659
+ if (process.platform === "win32") return true;
1660
+ await fs$1.access(filePath, constants.X_OK);
1661
+ return true;
1662
+ } catch {
1663
+ return false;
1664
+ }
1665
+ }
1666
+ async function findBinary(name) {
1667
+ const cached = binaryCache.get(name);
1668
+ if (cached) return cached;
1669
+ const resolved = (async () => {
1670
+ const direct = expandHomeDir(name.trim());
1671
+ if (direct && hasPathSeparator(direct)) {
1672
+ for (const candidate of candidateBinaryNames(direct)) if (await isExecutable(candidate)) return candidate;
1673
+ }
1674
+ const searchName = name.trim();
1675
+ if (!searchName) return null;
1676
+ const pathEntries = (process.env.PATH ?? "").split(path.delimiter);
1677
+ const candidates = candidateBinaryNames(searchName);
1678
+ for (const entryRaw of pathEntries) {
1679
+ const entry = expandHomeDir(entryRaw.trim().replace(/^"(.*)"$/, "$1"));
1680
+ if (!entry) continue;
1681
+ for (const candidate of candidates) {
1682
+ const fullPath = path.join(entry, candidate);
1683
+ if (await isExecutable(fullPath)) return fullPath;
1684
+ }
1685
+ }
1686
+ return null;
1687
+ })();
1688
+ binaryCache.set(name, resolved);
1689
+ return resolved;
1690
+ }
1691
+ async function hasBinary(name) {
1692
+ return Boolean(await findBinary(name));
1693
+ }
1694
+ async function probeGeminiCli() {
1695
+ const cached = geminiProbeCache.get("gemini");
1696
+ if (cached) return cached;
1697
+ const resolved = (async () => {
1698
+ if (!await hasBinary("gemini")) return false;
1699
+ try {
1700
+ const { stdout } = await runExec("gemini", [
1701
+ "--output-format",
1702
+ "json",
1703
+ "ok"
1704
+ ], { timeoutMs: 8e3 });
1705
+ return Boolean(extractGeminiResponse(stdout) ?? stdout.toLowerCase().includes("ok"));
1706
+ } catch {
1707
+ return false;
1708
+ }
1709
+ })();
1710
+ geminiProbeCache.set("gemini", resolved);
1711
+ return resolved;
1712
+ }
1713
+ async function resolveLocalWhisperCppEntry() {
1714
+ if (!await hasBinary("whisper-cli")) return null;
1715
+ const envModel = process.env.WHISPER_CPP_MODEL?.trim();
1716
+ const modelPath = envModel && await fileExists(envModel) ? envModel : "/opt/homebrew/share/whisper-cpp/for-tests-ggml-tiny.bin";
1717
+ if (!await fileExists(modelPath)) return null;
1718
+ return {
1719
+ type: "cli",
1720
+ command: "whisper-cli",
1721
+ args: [
1722
+ "-m",
1723
+ modelPath,
1724
+ "-otxt",
1725
+ "-of",
1726
+ "{{OutputBase}}",
1727
+ "-np",
1728
+ "-nt",
1729
+ "{{MediaPath}}"
1730
+ ]
1731
+ };
1732
+ }
1733
+ async function resolveLocalWhisperEntry() {
1734
+ if (!await hasBinary("whisper")) return null;
1735
+ return {
1736
+ type: "cli",
1737
+ command: "whisper",
1738
+ args: [
1739
+ "--model",
1740
+ "turbo",
1741
+ "--output_format",
1742
+ "txt",
1743
+ "--output_dir",
1744
+ "{{OutputDir}}",
1745
+ "--verbose",
1746
+ "False",
1747
+ "{{MediaPath}}"
1748
+ ]
1749
+ };
1750
+ }
1751
+ async function resolveSherpaOnnxEntry() {
1752
+ if (!await hasBinary("sherpa-onnx-offline")) return null;
1753
+ const modelDir = process.env.SHERPA_ONNX_MODEL_DIR?.trim();
1754
+ if (!modelDir) return null;
1755
+ const tokens = path.join(modelDir, "tokens.txt");
1756
+ const encoder = path.join(modelDir, "encoder.onnx");
1757
+ const decoder = path.join(modelDir, "decoder.onnx");
1758
+ const joiner = path.join(modelDir, "joiner.onnx");
1759
+ if (!await fileExists(tokens)) return null;
1760
+ if (!await fileExists(encoder)) return null;
1761
+ if (!await fileExists(decoder)) return null;
1762
+ if (!await fileExists(joiner)) return null;
1763
+ return {
1764
+ type: "cli",
1765
+ command: "sherpa-onnx-offline",
1766
+ args: [
1767
+ `--tokens=${tokens}`,
1768
+ `--encoder=${encoder}`,
1769
+ `--decoder=${decoder}`,
1770
+ `--joiner=${joiner}`,
1771
+ "{{MediaPath}}"
1772
+ ]
1773
+ };
1774
+ }
1775
+ async function resolveLocalAudioEntry() {
1776
+ const sherpa = await resolveSherpaOnnxEntry();
1777
+ if (sherpa) return sherpa;
1778
+ const whisperCpp = await resolveLocalWhisperCppEntry();
1779
+ if (whisperCpp) return whisperCpp;
1780
+ return await resolveLocalWhisperEntry();
1781
+ }
1782
+ async function resolveGeminiCliEntry(_capability) {
1783
+ if (!await probeGeminiCli()) return null;
1784
+ return {
1785
+ type: "cli",
1786
+ command: "gemini",
1787
+ args: [
1788
+ "--output-format",
1789
+ "json",
1790
+ "--allowed-tools",
1791
+ "read_many_files",
1792
+ "--include-directories",
1793
+ "{{MediaDir}}",
1794
+ "{{Prompt}}",
1795
+ "Use read_many_files to read {{MediaPath}} and respond with only the text output."
1796
+ ]
1797
+ };
1798
+ }
1799
+ async function resolveKeyEntry(params) {
1800
+ const { cfg, agentDir, providerRegistry, capability } = params;
1801
+ const checkProvider = async (providerId, model) => {
1802
+ const provider = getMediaUnderstandingProvider(providerId, providerRegistry);
1803
+ if (!provider) return null;
1804
+ if (capability === "audio" && !provider.transcribeAudio) return null;
1805
+ if (capability === "image" && !provider.describeImage) return null;
1806
+ if (capability === "video" && !provider.describeVideo) return null;
1807
+ try {
1808
+ await resolveApiKeyForProvider({
1809
+ provider: providerId,
1810
+ cfg,
1811
+ agentDir
1812
+ });
1813
+ return {
1814
+ type: "provider",
1815
+ provider: providerId,
1816
+ model
1817
+ };
1818
+ } catch {
1819
+ return null;
1820
+ }
1821
+ };
1822
+ if (capability === "image") {
1823
+ const activeProvider = params.activeModel?.provider?.trim();
1824
+ if (activeProvider) {
1825
+ const activeEntry = await checkProvider(activeProvider, params.activeModel?.model);
1826
+ if (activeEntry) return activeEntry;
1827
+ }
1828
+ for (const providerId of AUTO_IMAGE_KEY_PROVIDERS) {
1829
+ const model = DEFAULT_IMAGE_MODELS[providerId];
1830
+ const entry = await checkProvider(providerId, model);
1831
+ if (entry) return entry;
1832
+ }
1833
+ return null;
1834
+ }
1835
+ if (capability === "video") {
1836
+ const activeProvider = params.activeModel?.provider?.trim();
1837
+ if (activeProvider) {
1838
+ const activeEntry = await checkProvider(activeProvider, params.activeModel?.model);
1839
+ if (activeEntry) return activeEntry;
1840
+ }
1841
+ for (const providerId of AUTO_VIDEO_KEY_PROVIDERS) {
1842
+ const entry = await checkProvider(providerId, void 0);
1843
+ if (entry) return entry;
1844
+ }
1845
+ return null;
1846
+ }
1847
+ const activeProvider = params.activeModel?.provider?.trim();
1848
+ if (activeProvider) {
1849
+ const activeEntry = await checkProvider(activeProvider, params.activeModel?.model);
1850
+ if (activeEntry) return activeEntry;
1851
+ }
1852
+ for (const providerId of AUTO_AUDIO_KEY_PROVIDERS) {
1853
+ const entry = await checkProvider(providerId, void 0);
1854
+ if (entry) return entry;
1855
+ }
1856
+ return null;
1857
+ }
1858
+ function resolveImageModelFromAgentDefaults(cfg) {
1859
+ const refs = [];
1860
+ const primary = resolveAgentModelPrimaryValue(cfg.agents?.defaults?.imageModel);
1861
+ if (primary?.trim()) refs.push(primary.trim());
1862
+ for (const fb of resolveAgentModelFallbackValues(cfg.agents?.defaults?.imageModel)) if (fb?.trim()) refs.push(fb.trim());
1863
+ if (refs.length === 0) return [];
1864
+ const entries = [];
1865
+ for (const ref of refs) {
1866
+ const slashIdx = ref.indexOf("/");
1867
+ if (slashIdx <= 0 || slashIdx >= ref.length - 1) continue;
1868
+ entries.push({
1869
+ type: "provider",
1870
+ provider: ref.slice(0, slashIdx),
1871
+ model: ref.slice(slashIdx + 1)
1872
+ });
1873
+ }
1874
+ return entries;
1875
+ }
1876
+ async function resolveAutoEntries(params) {
1877
+ const activeEntry = await resolveActiveModelEntry(params);
1878
+ if (activeEntry) return [activeEntry];
1879
+ if (params.capability === "audio") {
1880
+ const localAudio = await resolveLocalAudioEntry();
1881
+ if (localAudio) return [localAudio];
1882
+ }
1883
+ if (params.capability === "image") {
1884
+ const imageModelEntries = resolveImageModelFromAgentDefaults(params.cfg);
1885
+ if (imageModelEntries.length > 0) return imageModelEntries;
1886
+ }
1887
+ const gemini = await resolveGeminiCliEntry(params.capability);
1888
+ if (gemini) return [gemini];
1889
+ const keys = await resolveKeyEntry(params);
1890
+ if (keys) return [keys];
1891
+ return [];
1892
+ }
1893
+ async function resolveAutoImageModel(params) {
1894
+ const providerRegistry = buildProviderRegistry();
1895
+ const toActive = (entry) => {
1896
+ if (!entry || entry.type === "cli") return null;
1897
+ const provider = entry.provider;
1898
+ if (!provider) return null;
1899
+ const model = entry.model ?? DEFAULT_IMAGE_MODELS[provider];
1900
+ if (!model) return null;
1901
+ return {
1902
+ provider,
1903
+ model
1904
+ };
1905
+ };
1906
+ const resolvedActive = toActive(await resolveActiveModelEntry({
1907
+ cfg: params.cfg,
1908
+ agentDir: params.agentDir,
1909
+ providerRegistry,
1910
+ capability: "image",
1911
+ activeModel: params.activeModel
1912
+ }));
1913
+ if (resolvedActive) return resolvedActive;
1914
+ return toActive(await resolveKeyEntry({
1915
+ cfg: params.cfg,
1916
+ agentDir: params.agentDir,
1917
+ providerRegistry,
1918
+ capability: "image",
1919
+ activeModel: params.activeModel
1920
+ }));
1921
+ }
1922
+ async function resolveActiveModelEntry(params) {
1923
+ const activeProviderRaw = params.activeModel?.provider?.trim();
1924
+ if (!activeProviderRaw) return null;
1925
+ const providerId = normalizeMediaProviderId(activeProviderRaw);
1926
+ if (!providerId) return null;
1927
+ const provider = getMediaUnderstandingProvider(providerId, params.providerRegistry);
1928
+ if (!provider) return null;
1929
+ if (params.capability === "audio" && !provider.transcribeAudio) return null;
1930
+ if (params.capability === "image" && !provider.describeImage) return null;
1931
+ if (params.capability === "video" && !provider.describeVideo) return null;
1932
+ try {
1933
+ await resolveApiKeyForProvider({
1934
+ provider: providerId,
1935
+ cfg: params.cfg,
1936
+ agentDir: params.agentDir
1937
+ });
1938
+ } catch {
1939
+ return null;
1940
+ }
1941
+ return {
1942
+ type: "provider",
1943
+ provider: providerId,
1944
+ model: params.activeModel?.model
1945
+ };
1946
+ }
1947
+ async function runAttachmentEntries(params) {
1948
+ const { entries, capability } = params;
1949
+ const attempts = [];
1950
+ for (const entry of entries) {
1951
+ const entryType = entry.type ?? (entry.command ? "cli" : "provider");
1952
+ try {
1953
+ const result = entryType === "cli" ? await runCliEntry({
1954
+ capability,
1955
+ entry,
1956
+ cfg: params.cfg,
1957
+ ctx: params.ctx,
1958
+ attachmentIndex: params.attachmentIndex,
1959
+ cache: params.cache,
1960
+ config: params.config
1961
+ }) : await runProviderEntry({
1962
+ capability,
1963
+ entry,
1964
+ cfg: params.cfg,
1965
+ ctx: params.ctx,
1966
+ attachmentIndex: params.attachmentIndex,
1967
+ cache: params.cache,
1968
+ agentDir: params.agentDir,
1969
+ providerRegistry: params.providerRegistry,
1970
+ config: params.config
1971
+ });
1972
+ if (result) {
1973
+ const decision = buildModelDecision({
1974
+ entry,
1975
+ entryType,
1976
+ outcome: "success"
1977
+ });
1978
+ if (result.provider) decision.provider = result.provider;
1979
+ if (result.model) decision.model = result.model;
1980
+ attempts.push(decision);
1981
+ return {
1982
+ output: result,
1983
+ attempts
1984
+ };
1985
+ }
1986
+ attempts.push(buildModelDecision({
1987
+ entry,
1988
+ entryType,
1989
+ outcome: "skipped",
1990
+ reason: "empty output"
1991
+ }));
1992
+ } catch (err) {
1993
+ if (isMediaUnderstandingSkipError(err)) {
1994
+ attempts.push(buildModelDecision({
1995
+ entry,
1996
+ entryType,
1997
+ outcome: "skipped",
1998
+ reason: `${err.reason}: ${err.message}`
1999
+ }));
2000
+ if (shouldLogVerbose()) logVerbose(`Skipping ${capability} model due to ${err.reason}: ${err.message}`);
2001
+ continue;
2002
+ }
2003
+ attempts.push(buildModelDecision({
2004
+ entry,
2005
+ entryType,
2006
+ outcome: "failed",
2007
+ reason: String(err)
2008
+ }));
2009
+ if (shouldLogVerbose()) logVerbose(`${capability} understanding failed: ${String(err)}`);
2010
+ }
2011
+ }
2012
+ return {
2013
+ output: null,
2014
+ attempts
2015
+ };
2016
+ }
2017
+ async function runCapability(params) {
2018
+ const { capability, cfg, ctx } = params;
2019
+ const config = params.config ?? cfg.tools?.media?.[capability];
2020
+ if (config?.enabled === false) return {
2021
+ outputs: [],
2022
+ decision: {
2023
+ capability,
2024
+ outcome: "disabled",
2025
+ attachments: []
2026
+ }
2027
+ };
2028
+ const attachmentPolicy = config?.attachments;
2029
+ const selected = selectAttachments({
2030
+ capability,
2031
+ attachments: params.media,
2032
+ policy: attachmentPolicy
2033
+ });
2034
+ if (selected.length === 0) return {
2035
+ outputs: [],
2036
+ decision: {
2037
+ capability,
2038
+ outcome: "no-attachment",
2039
+ attachments: []
2040
+ }
2041
+ };
2042
+ if (resolveScopeDecision({
2043
+ scope: config?.scope,
2044
+ ctx
2045
+ }) === "deny") {
2046
+ if (shouldLogVerbose()) logVerbose(`${capability} understanding disabled by scope policy.`);
2047
+ return {
2048
+ outputs: [],
2049
+ decision: {
2050
+ capability,
2051
+ outcome: "scope-deny",
2052
+ attachments: selected.map((item) => ({
2053
+ attachmentIndex: item.index,
2054
+ attempts: []
2055
+ }))
2056
+ }
2057
+ };
2058
+ }
2059
+ const activeProvider = params.activeModel?.provider?.trim();
2060
+ if (capability === "image" && activeProvider) {
2061
+ if (modelSupportsVision(findModelInCatalog(await loadModelCatalog({ config: cfg }), activeProvider, params.activeModel?.model ?? ""))) {
2062
+ if (shouldLogVerbose()) logVerbose("Skipping image understanding: primary model supports vision natively");
2063
+ const model = params.activeModel?.model?.trim();
2064
+ const reason = "primary model supports vision natively";
2065
+ return {
2066
+ outputs: [],
2067
+ decision: {
2068
+ capability,
2069
+ outcome: "skipped",
2070
+ attachments: selected.map((item) => {
2071
+ const attempt = {
2072
+ type: "provider",
2073
+ provider: activeProvider,
2074
+ model: model || void 0,
2075
+ outcome: "skipped",
2076
+ reason
2077
+ };
2078
+ return {
2079
+ attachmentIndex: item.index,
2080
+ attempts: [attempt],
2081
+ chosen: attempt
2082
+ };
2083
+ })
2084
+ }
2085
+ };
2086
+ }
2087
+ }
2088
+ let resolvedEntries = resolveModelEntries({
2089
+ cfg,
2090
+ capability,
2091
+ config,
2092
+ providerRegistry: params.providerRegistry
2093
+ });
2094
+ if (resolvedEntries.length === 0) resolvedEntries = await resolveAutoEntries({
2095
+ cfg,
2096
+ agentDir: params.agentDir,
2097
+ providerRegistry: params.providerRegistry,
2098
+ capability,
2099
+ activeModel: params.activeModel
2100
+ });
2101
+ if (resolvedEntries.length === 0) return {
2102
+ outputs: [],
2103
+ decision: {
2104
+ capability,
2105
+ outcome: "skipped",
2106
+ attachments: selected.map((item) => ({
2107
+ attachmentIndex: item.index,
2108
+ attempts: []
2109
+ }))
2110
+ }
2111
+ };
2112
+ const outputs = [];
2113
+ const attachmentDecisions = [];
2114
+ for (const attachment of selected) {
2115
+ const { output, attempts } = await runAttachmentEntries({
2116
+ capability,
2117
+ cfg,
2118
+ ctx,
2119
+ attachmentIndex: attachment.index,
2120
+ agentDir: params.agentDir,
2121
+ providerRegistry: params.providerRegistry,
2122
+ cache: params.attachments,
2123
+ entries: resolvedEntries,
2124
+ config
2125
+ });
2126
+ if (output) outputs.push(output);
2127
+ attachmentDecisions.push({
2128
+ attachmentIndex: attachment.index,
2129
+ attempts,
2130
+ chosen: attempts.find((attempt) => attempt.outcome === "success")
2131
+ });
2132
+ }
2133
+ const decision = {
2134
+ capability,
2135
+ outcome: outputs.length > 0 ? "success" : "skipped",
2136
+ attachments: attachmentDecisions
2137
+ };
2138
+ if (shouldLogVerbose()) logVerbose(`Media understanding ${formatDecisionSummary(decision)}`);
2139
+ return {
2140
+ outputs,
2141
+ decision
2142
+ };
2143
+ }
2144
+
2145
+ //#endregion
2146
+ //#region src/media-understanding/audio-transcription-runner.ts
2147
+ async function runAudioTranscription(params) {
2148
+ const attachments = params.attachments ?? normalizeMediaAttachments(params.ctx);
2149
+ if (attachments.length === 0) return {
2150
+ transcript: void 0,
2151
+ attachments
2152
+ };
2153
+ const providerRegistry = buildProviderRegistry(params.providers);
2154
+ const cache = createMediaAttachmentCache(attachments, params.localPathRoots ? { localPathRoots: params.localPathRoots } : void 0);
2155
+ try {
2156
+ return {
2157
+ transcript: (await runCapability({
2158
+ capability: "audio",
2159
+ cfg: params.cfg,
2160
+ ctx: params.ctx,
2161
+ attachments: cache,
2162
+ media: attachments,
2163
+ agentDir: params.agentDir,
2164
+ providerRegistry,
2165
+ config: params.cfg.tools?.media?.audio,
2166
+ activeModel: params.activeModel
2167
+ })).outputs.find((entry) => entry.kind === "audio.transcription")?.text?.trim() || void 0,
2168
+ attachments
2169
+ };
2170
+ } finally {
2171
+ await cache.cleanup();
2172
+ }
2173
+ }
2174
+
2175
+ //#endregion
2176
+ export { resolveMediaUnderstandingScope as _, resolveAutoImageModel as a, buildRandomTempFilePath as b, findModelInCatalog as c, registerUnhandledRejectionHandler as d, isAudioAttachment as f, normalizeMediaUnderstandingChatType as g, resolveTimeoutMs as h, normalizeMediaAttachments as i, loadModelCatalog as l, resolveConcurrency as m, buildProviderRegistry as n, resolveMediaAttachmentLocalRoots as o, resolveAttachmentKind as p, createMediaAttachmentCache as r, runCapability as s, runAudioTranscription as t, modelSupportsVision as u, CLI_OUTPUT_MAX_BUFFER as v, withTempDownloadPath as x, applyTemplate as y };