autohand-cli 0.8.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (551) hide show
  1. package/README.md +173 -74
  2. package/dist/AgentRegistry-EGBDIUAK.cjs +10 -0
  3. package/dist/{AgentRegistry-7LDL5HJH.js → AgentRegistry-XPWSVO3Q.js} +3 -3
  4. package/dist/{AutomodeManager-MWLKGPZK.js → AutomodeManager-APLLPEYJ.js} +32 -18
  5. package/dist/{AutomodeManager-NYIZNODK.cjs → AutomodeManager-MPSXT2RH.cjs} +38 -24
  6. package/dist/CommunitySkillsCache-2OIUV227.cjs +8 -0
  7. package/dist/CommunitySkillsCache-ZEQWP6YM.js +8 -0
  8. package/dist/GitHubRegistryFetcher-CYJLF2XL.cjs +7 -0
  9. package/dist/{GitHubRegistryFetcher-6JQ5JEDZ.js → GitHubRegistryFetcher-J2BWPXJF.js} +2 -2
  10. package/dist/HookManager-CWLTFKWO.js +7 -0
  11. package/dist/HookManager-TMAJQU4S.cjs +7 -0
  12. package/dist/{ImportWizard-35YBJ4AM.cjs → ImportWizard-MQXEED2U.cjs} +41 -17
  13. package/dist/{ImportWizard-XH7CINCH.js → ImportWizard-QBKQEXDW.js} +35 -11
  14. package/dist/LearnAdvisor-46FG2XIP.js +9 -0
  15. package/dist/LearnAdvisor-XBRDNAGH.cjs +9 -0
  16. package/dist/McpClientManager-BNSKLHIN.cjs +8 -0
  17. package/dist/{McpClientManager-7RM6YT35.js → McpClientManager-SL35BR24.js} +2 -2
  18. package/dist/MemoryManager-34L4YOKA.cjs +8 -0
  19. package/dist/MemoryManager-WJMLPWGU.js +8 -0
  20. package/dist/NVIDIAProvider-2HR737UE.js +14 -0
  21. package/dist/NVIDIAProvider-MVTL62PR.cjs +14 -0
  22. package/dist/PermissionManager-5OOJ7FAT.cjs +11 -0
  23. package/dist/{PermissionManager-ATUV34LQ.js → PermissionManager-KMYILJ4Z.js} +4 -4
  24. package/dist/{ProjectProfiler-ZDWR2ODG.cjs → ProjectProfiler-CDAE7ECW.cjs} +2 -1
  25. package/dist/{ProjectProfiler-UMJJSOCE.js → ProjectProfiler-NZTJDRHD.js} +2 -1
  26. package/dist/ProviderFactory-C3YPXTDD.cjs +11 -0
  27. package/dist/ProviderFactory-HFPRVQ25.js +11 -0
  28. package/dist/SessionManager-PNBTJJTQ.js +10 -0
  29. package/dist/SessionManager-WAPTFMDO.cjs +10 -0
  30. package/dist/SkillsRegistry-4RRD5GMX.js +9 -0
  31. package/dist/SkillsRegistry-7AJP74GJ.cjs +9 -0
  32. package/dist/SubAgent-FDIH3DXB.js +11 -0
  33. package/dist/SubAgent-HVL2ICVZ.cjs +11 -0
  34. package/dist/{SyncApiClient-LVIO4C2S.js → SyncApiClient-LRPFNCKJ.js} +2 -2
  35. package/dist/SyncApiClient-VMBOLQ6H.cjs +11 -0
  36. package/dist/about-BW3PDZUU.js +14 -0
  37. package/dist/about-FCAX37YC.cjs +14 -0
  38. package/dist/acp-EOETGAHC.cjs +1517 -0
  39. package/dist/acp-VHEL7BOW.js +1517 -0
  40. package/dist/actionExecutor-33I47NZS.js +24 -0
  41. package/dist/actionExecutor-4YBMR3YH.cjs +24 -0
  42. package/dist/add-dir-7SS6KSH5.cjs +11 -0
  43. package/dist/add-dir-VFX7QT4E.js +11 -0
  44. package/dist/agent-DMRUFU4M.cjs +116 -0
  45. package/dist/agent-WCJEYQJT.js +116 -0
  46. package/dist/agents/builtin/code-cleaner.md +1 -1
  47. package/dist/agents/builtin/docs-writer.md +1 -1
  48. package/dist/agents/builtin/researcher.md +2 -2
  49. package/dist/agents/builtin/reviewer.md +1 -1
  50. package/dist/agents/builtin/tester.md +1 -1
  51. package/dist/agents/builtin/todo-resolver.md +1 -1
  52. package/dist/agents-CHROO3VU.cjs +17 -0
  53. package/dist/agents-UVDUL65K.js +17 -0
  54. package/dist/agents-new-AUBWSOXP.js +17 -0
  55. package/dist/agents-new-Y562WC47.cjs +17 -0
  56. package/dist/{autoSkill-6TGBTEQD.js → autoSkill-EFMK6WU6.js} +3 -3
  57. package/dist/autoSkill-YOLLFTP2.cjs +20 -0
  58. package/dist/automode-BMYSRM34.js +10 -0
  59. package/dist/automode-DBLKTTKD.cjs +10 -0
  60. package/dist/browserToolBridge-BXRQB4B4.cjs +58 -0
  61. package/dist/browserToolBridge-F5N66PE7.js +58 -0
  62. package/dist/{cc-7LEIJ3KF.js → cc-AYEP2CA3.js} +1 -1
  63. package/dist/{cc-Q5MM4AWC.cjs → cc-S2QLQFBR.cjs} +1 -1
  64. package/dist/chrome-ATC4OO5I.cjs +20 -0
  65. package/dist/chrome-NHJ44WUN.js +50 -0
  66. package/dist/chrome-O62WXV7F.js +20 -0
  67. package/dist/chrome-XHJLCO4M.cjs +50 -0
  68. package/dist/chromeSkill-53TH55PM.js +105 -0
  69. package/dist/chromeSkill-THW7N4IY.cjs +105 -0
  70. package/dist/{chunk-HOAHWIQ5.cjs → chunk-2CGE7ZV3.cjs} +26 -26
  71. package/dist/chunk-2H5O745H.js +63 -0
  72. package/dist/chunk-2HOLOHVK.js +2219 -0
  73. package/dist/{chunk-3PDTTAKJ.js → chunk-2QL6MNXG.js} +15 -8
  74. package/dist/{chunk-PGRH5Q77.cjs → chunk-2VHB43IX.cjs} +35 -6
  75. package/dist/{chunk-VG34MG2U.js → chunk-37M5UM6I.js} +9 -6
  76. package/dist/{chunk-6HYLHBQG.cjs → chunk-3DDL2E55.cjs} +20 -16
  77. package/dist/chunk-3EDDDZS2.cjs +5 -0
  78. package/dist/{chunk-6OYHF6MF.js → chunk-3GUEUCZK.js} +28 -4
  79. package/dist/{chunk-N254NRHT.cjs → chunk-3HT76LNN.cjs} +9 -2
  80. package/dist/{chunk-DVUHHH3B.cjs → chunk-3KLSNNRW.cjs} +4 -4
  81. package/dist/chunk-3LJJG5YY.cjs +387 -0
  82. package/dist/chunk-3OF56EMA.cjs +197 -0
  83. package/dist/chunk-3PXKRVCW.js +108 -0
  84. package/dist/{chunk-IKGWDOGU.cjs → chunk-3S563FNF.cjs} +160 -79
  85. package/dist/chunk-3UT7R3XV.js +663 -0
  86. package/dist/chunk-4256YRCO.cjs +80 -0
  87. package/dist/chunk-46C73ZKK.cjs +663 -0
  88. package/dist/chunk-46MTALKD.js +44 -0
  89. package/dist/{chunk-AEJH23FO.cjs → chunk-47PHDKNW.cjs} +6 -6
  90. package/dist/chunk-4LMUDS2K.js +124 -0
  91. package/dist/{chunk-HLHTG5ZU.cjs → chunk-5CMYEM3R.cjs} +14 -12
  92. package/dist/{chunk-MBBY4ZIK.js → chunk-5F6ZKSHO.js} +4 -1
  93. package/dist/chunk-5JFTY3VU.js +74 -0
  94. package/dist/chunk-5JTTM5SC.js +59 -0
  95. package/dist/{chunk-IETRBBMP.cjs → chunk-5K3CDSWZ.cjs} +108 -31
  96. package/dist/{chunk-GJH7XMSK.js → chunk-5VHP6HDQ.js} +8 -6
  97. package/dist/{chunk-6ZCULLCA.js → chunk-62RTC3XX.js} +1 -1
  98. package/dist/{chunk-MSED7RH2.cjs → chunk-6GUODJKM.cjs} +112 -41
  99. package/dist/{chunk-47CKWKEX.cjs → chunk-6HH236FV.cjs} +9 -4
  100. package/dist/{chunk-WHE2SWHU.js → chunk-6NVAK6CK.js} +2 -2
  101. package/dist/chunk-6R25D2H5.js +121 -0
  102. package/dist/{chunk-3PCTTUNW.cjs → chunk-6XVVIY54.cjs} +55 -25
  103. package/dist/{chunk-3L53OA4E.cjs → chunk-75EDROHL.cjs} +10 -10
  104. package/dist/{chunk-G4CAKI3V.js → chunk-7HPWMALN.js} +7 -2
  105. package/dist/chunk-7JLT2VNW.cjs +18963 -0
  106. package/dist/chunk-7LWQCE4Y.cjs +987 -0
  107. package/dist/{chunk-OLSBBZW6.cjs → chunk-7MU7LWF3.cjs} +5 -5
  108. package/dist/{chunk-HLQV64Y5.js → chunk-7RFJB75Y.js} +140 -59
  109. package/dist/{chunk-X2MSVKDV.js → chunk-A7HHCIDQ.js} +2 -2
  110. package/dist/{chunk-SKYG33B2.cjs → chunk-AC7DZ6SK.cjs} +3 -3
  111. package/dist/{chunk-XX2ZO7DS.js → chunk-ACDQGA4Z.js} +90 -16
  112. package/dist/{chunk-CZXGCVTR.cjs → chunk-AOKCI722.cjs} +2 -2
  113. package/dist/chunk-ARBEHFCG.js +715 -0
  114. package/dist/{chunk-OOKY3HPZ.js → chunk-AT7OWLY5.js} +50 -9
  115. package/dist/chunk-AU6JAGZJ.cjs +231 -0
  116. package/dist/chunk-AUFNACED.js +18963 -0
  117. package/dist/{chunk-CDBPBM2K.cjs → chunk-AUYSIEVV.cjs} +3 -3
  118. package/dist/chunk-B4HSNOIH.cjs +354 -0
  119. package/dist/{chunk-EGMZDTSL.js → chunk-B5CGDNMR.js} +10 -2
  120. package/dist/{chunk-34M3HWLR.js → chunk-BFQDQRDE.js} +2 -2
  121. package/dist/{chunk-2AA5MFES.js → chunk-BL2UC7HC.js} +8 -5
  122. package/dist/{chunk-LNMYK2F5.cjs → chunk-BQ22N3TX.cjs} +56 -41
  123. package/dist/chunk-BQU3HAE7.js +21 -0
  124. package/dist/chunk-BWF4VDYE.js +3897 -0
  125. package/dist/chunk-BYE7RQFZ.cjs +121 -0
  126. package/dist/{chunk-APIXPPMT.js → chunk-CBFH2J3O.js} +855 -33
  127. package/dist/chunk-CCVMREXI.js +420 -0
  128. package/dist/chunk-CFAWTLSC.js +13 -0
  129. package/dist/{chunk-G27PQQFD.js → chunk-CFFE4VA3.js} +1 -1
  130. package/dist/{chunk-ZYQMLKOK.cjs → chunk-CH2J4PVE.cjs} +212 -70
  131. package/dist/{chunk-7BTSG4ME.cjs → chunk-CHI52KFR.cjs} +855 -33
  132. package/dist/{chunk-DJDE4DTT.cjs → chunk-CWINVFRI.cjs} +25 -19
  133. package/dist/{chunk-HXGBSJL5.cjs → chunk-D2OSPLYC.cjs} +2 -2
  134. package/dist/{chunk-GBHDROGL.js → chunk-D6GZBSOX.js} +16 -4
  135. package/dist/{chunk-CNBKZEX5.cjs → chunk-DA7NBAJK.cjs} +49 -17
  136. package/dist/chunk-DEQVRSV5.cjs +866 -0
  137. package/dist/{chunk-RGR6ME5J.cjs → chunk-DI57A4BX.cjs} +25 -28
  138. package/dist/{chunk-WM5PAOTQ.cjs → chunk-DIZTWFVR.cjs} +1649 -484
  139. package/dist/{chunk-YZXUDM5X.js → chunk-DLP436GI.js} +204 -62
  140. package/dist/{chunk-OHUZKDGX.js → chunk-DMRXF5DU.js} +3 -3
  141. package/dist/{chunk-U46VYPLR.cjs → chunk-DPSIGY6L.cjs} +9 -9
  142. package/dist/{chunk-DN573ME7.cjs → chunk-DRWDEHE5.cjs} +4 -4
  143. package/dist/{chunk-SEKD5FH3.cjs → chunk-E26KKI46.cjs} +5 -2
  144. package/dist/{chunk-J4Q7XR3G.js → chunk-E46DJH5S.js} +3 -3
  145. package/dist/{chunk-Q7XSCYND.cjs → chunk-E6GFD7VR.cjs} +49 -26
  146. package/dist/chunk-ES2HJQ4N.js +37 -0
  147. package/dist/chunk-EZJHLOWP.js +80 -0
  148. package/dist/chunk-FFBDRUO5.cjs +59 -0
  149. package/dist/{chunk-AYSFIUFW.js → chunk-FGT6KK6T.js} +38 -15
  150. package/dist/{chunk-SAHBLB3E.js → chunk-FP46B4X3.js} +208 -3
  151. package/dist/{chunk-DSPQEHDT.js → chunk-FQAVGSPW.js} +2 -2
  152. package/dist/chunk-FQVG6ZHF.js +197 -0
  153. package/dist/chunk-GBMDFWJX.cjs +152 -0
  154. package/dist/{chunk-DRE2RXBZ.js → chunk-GGLKUENP.js} +1605 -440
  155. package/dist/{chunk-S52YW5ZQ.js → chunk-GNBBIAMD.js} +16 -19
  156. package/dist/{chunk-MYISNQH4.js → chunk-GWO66KBI.js} +1 -1
  157. package/dist/chunk-GWY26SUD.cjs +63 -0
  158. package/dist/{chunk-YGN4CQIP.js → chunk-GZ6DV2UG.js} +1 -1
  159. package/dist/{chunk-EZMINVLU.js → chunk-H4D2Q2AL.js} +5 -2
  160. package/dist/chunk-HDK2EHVH.cjs +570 -0
  161. package/dist/chunk-HJIXWGQZ.js +139 -0
  162. package/dist/{chunk-6RF7UKUS.js → chunk-HKYZSUQ5.js} +48 -47
  163. package/dist/{chunk-LQGVEP3E.js → chunk-HQUSEWT4.js} +45 -13
  164. package/dist/{chunk-HTLINWX6.cjs → chunk-HQWQZML5.cjs} +16 -13
  165. package/dist/{chunk-DVZOENQ7.cjs → chunk-HS4USDND.cjs} +9 -4
  166. package/dist/chunk-HSCUPEA4.cjs +3897 -0
  167. package/dist/{chunk-JCLYQ2JC.js → chunk-HXJEGMGB.js} +12 -6
  168. package/dist/{chunk-LJFUXC56.cjs → chunk-HXQREVTA.cjs} +9 -6
  169. package/dist/{chunk-SCXX4LW5.js → chunk-I24CWKKK.js} +15 -8
  170. package/dist/chunk-IAGCRVJ3.js +355 -0
  171. package/dist/chunk-IEXKKKOF.cjs +92 -0
  172. package/dist/{chunk-5IXII4HX.cjs → chunk-IGMPWJ2I.cjs} +19 -12
  173. package/dist/chunk-IKUMVBCW.cjs +33 -0
  174. package/dist/{chunk-XTB6VJVQ.cjs → chunk-J2K57QM7.cjs} +6 -6
  175. package/dist/{chunk-O4IF4NJT.cjs → chunk-J5ADZN6V.cjs} +57 -56
  176. package/dist/chunk-J5HE6CUM.cjs +124 -0
  177. package/dist/{chunk-JS7IPR7P.js → chunk-JAQO6XDB.js} +31 -2
  178. package/dist/chunk-JIMSII7R.js +987 -0
  179. package/dist/chunk-JJ4KA7HK.cjs +2219 -0
  180. package/dist/chunk-JMN3GZU6.js +570 -0
  181. package/dist/{chunk-X5VSP65C.cjs → chunk-JP3YHTQ2.cjs} +4 -4
  182. package/dist/{chunk-SKV2F3NM.js → chunk-JYKPWK5O.js} +1 -1
  183. package/dist/{chunk-NNPAM4HC.cjs → chunk-KNTUI4TZ.cjs} +12 -6
  184. package/dist/{chunk-HIVRCQS2.js → chunk-L4F7SUYL.js} +56 -25
  185. package/dist/{chunk-C5IJIM2V.cjs → chunk-LDJQ5QHG.cjs} +68 -37
  186. package/dist/{chunk-ULQ6MDSJ.cjs → chunk-LFDPTJYF.cjs} +43 -35
  187. package/dist/chunk-LJD7KR3L.cjs +44 -0
  188. package/dist/chunk-LN7D3EJZ.js +387 -0
  189. package/dist/{chunk-643VRA5S.cjs → chunk-LQBONA55.cjs} +32 -8
  190. package/dist/{chunk-NCC6ETZS.js → chunk-LUMV3DB2.js} +41 -33
  191. package/dist/chunk-M4KNC5BQ.js +78 -0
  192. package/dist/chunk-MO4KP6XS.cjs +229 -0
  193. package/dist/{chunk-BVKXEQVG.cjs → chunk-MOJ7ADW4.cjs} +22 -10
  194. package/dist/chunk-MPULXVC4.cjs +715 -0
  195. package/dist/{chunk-X5YJ34FZ.cjs → chunk-MQESBFBZ.cjs} +46 -23
  196. package/dist/chunk-MUNUUFU7.cjs +21 -0
  197. package/dist/{chunk-X3WS5LDG.js → chunk-MX75JYIY.js} +7 -5
  198. package/dist/chunk-MXVIDIC6.cjs +139 -0
  199. package/dist/{chunk-3K2ESU53.cjs → chunk-N6O3XUZ2.cjs} +2 -2
  200. package/dist/{chunk-IVM5F2AE.js → chunk-NBGOIFKP.js} +265 -18
  201. package/dist/{chunk-SLQAYV3W.js → chunk-NMQ47RCG.js} +8 -2
  202. package/dist/{chunk-JYTDYJVW.js → chunk-O64I2GYI.js} +1 -1
  203. package/dist/{chunk-IFFXSTOM.cjs → chunk-O6TQP7U7.cjs} +3 -3
  204. package/dist/{chunk-AYS2ASM7.js → chunk-OHWMWKJQ.js} +1 -1
  205. package/dist/{chunk-N23UAW4I.js → chunk-OM24WXEE.js} +7 -2
  206. package/dist/{chunk-MAKMSQMQ.cjs → chunk-OPNI6O7F.cjs} +8 -6
  207. package/dist/chunk-OR67YXQK.cjs +13 -0
  208. package/dist/{chunk-QOXPOR5D.js → chunk-OV3PVUYN.js} +115 -44
  209. package/dist/{chunk-TNZRZQ7Q.js → chunk-PB7W7R72.js} +3 -78
  210. package/dist/chunk-PCM3N3CL.cjs +37 -0
  211. package/dist/{chunk-A4IJHHV7.cjs → chunk-PDHT7LQS.cjs} +52 -11
  212. package/dist/{chunk-LA7H35XM.cjs → chunk-PHJO5YAL.cjs} +9 -9
  213. package/dist/{chunk-KPELYZ6L.js → chunk-PQLKJCIE.js} +2 -2
  214. package/dist/chunk-PUD76XQT.cjs +78 -0
  215. package/dist/chunk-Q2IL4DDI.cjs +355 -0
  216. package/dist/{chunk-WPVWQSL7.cjs → chunk-Q6AC3PHY.cjs} +16 -13
  217. package/dist/{chunk-XRZEUWKF.js → chunk-QCHIDZBA.js} +1 -1
  218. package/dist/{chunk-IQ5RXU6O.js → chunk-QEGOB6QV.js} +1 -1
  219. package/dist/{chunk-JSBRDJBE.js → chunk-QGM4M3NI.js} +8 -1
  220. package/dist/chunk-QKP772OZ.js +4291 -0
  221. package/dist/chunk-QQ7PANOP.js +59 -0
  222. package/dist/{chunk-YHD6TUIR.cjs → chunk-QW2RW2GY.cjs} +2 -0
  223. package/dist/chunk-RAKO7UN7.js +114 -0
  224. package/dist/chunk-RBHANOYY.js +33 -0
  225. package/dist/chunk-RBZ7AFX7.cjs +29 -0
  226. package/dist/{chunk-TXSDBGKX.cjs → chunk-REMGR23V.cjs} +46 -49
  227. package/dist/chunk-ROLA6EFO.cjs +85 -0
  228. package/dist/{chunk-TBEGGJNC.cjs → chunk-SOLBYSLY.cjs} +33 -16
  229. package/dist/{chunk-FKSDEWDH.js → chunk-SWCQM52V.js} +68 -25
  230. package/dist/{chunk-H5SWOLG6.js → chunk-TBOLJDUG.js} +39 -24
  231. package/dist/chunk-TH26BQJG.js +101 -0
  232. package/dist/{chunk-245KJE5Y.cjs → chunk-TJNBASFD.cjs} +14 -6
  233. package/dist/chunk-TKKN34TV.js +229 -0
  234. package/dist/chunk-TQ33WBY5.cjs +74 -0
  235. package/dist/{chunk-EGFT4PGW.js → chunk-TWQDAUZU.js} +5 -2
  236. package/dist/{chunk-OGV4WJ5L.cjs → chunk-TXPSTCG7.cjs} +74 -59
  237. package/dist/{chunk-CAMZTXV6.js → chunk-U4C3HW6P.js} +67 -52
  238. package/dist/{chunk-5P2NXKP3.js → chunk-UEIXJG45.js} +104 -76
  239. package/dist/{chunk-22D2CNTP.cjs → chunk-UGFVM77J.cjs} +5 -2
  240. package/dist/chunk-UOQECODR.js +34 -0
  241. package/dist/chunk-UR27UDTB.js +354 -0
  242. package/dist/chunk-UWFG2R2I.cjs +420 -0
  243. package/dist/{chunk-Y72HH2TF.cjs → chunk-UXLW45ZE.cjs} +102 -28
  244. package/dist/{chunk-FPHU2ES6.cjs → chunk-VBOFPU5M.cjs} +6 -0
  245. package/dist/{chunk-JX3DFKBI.js → chunk-VDWJMWDF.js} +54 -24
  246. package/dist/{chunk-LENHP55G.cjs → chunk-VGEV44V2.cjs} +1076 -158
  247. package/dist/{chunk-33RSHBDH.js → chunk-VKBIE6I6.js} +38 -7
  248. package/dist/{chunk-CWMZKFTT.js → chunk-VRMZO6TB.js} +31 -8
  249. package/dist/chunk-VUGOOGHB.js +400 -0
  250. package/dist/chunk-WBU4Q4GS.cjs +400 -0
  251. package/dist/chunk-WGLBC5AY.cjs +59 -0
  252. package/dist/{chunk-KWXVKLQ5.cjs → chunk-WGNMOVMT.cjs} +7 -82
  253. package/dist/{chunk-RKJTGGMU.cjs → chunk-WKRDBCP2.cjs} +221 -16
  254. package/dist/chunk-WRTXCQ3V.cjs +4291 -0
  255. package/dist/chunk-WTB7AFL6.cjs +101 -0
  256. package/dist/{chunk-NDMIPTV4.js → chunk-WZV6UVPY.js} +8 -3
  257. package/dist/chunk-XF2WIKHR.cjs +34 -0
  258. package/dist/{chunk-L3WAH3EM.cjs → chunk-XGMI6IYB.cjs} +46 -15
  259. package/dist/{chunk-FW774QXH.js → chunk-XIVFAD2L.js} +1048 -130
  260. package/dist/chunk-XV2HXRHX.js +85 -0
  261. package/dist/{chunk-72FKPBT5.js → chunk-Y3M2DABP.js} +16 -12
  262. package/dist/{chunk-QNGEW5TC.js → chunk-YV63VW4K.js} +1 -1
  263. package/dist/{chunk-UJ2JSM6H.js → chunk-YVQI26H4.js} +2 -0
  264. package/dist/{chunk-RD5XAJR2.cjs → chunk-YWTIXHU6.cjs} +266 -19
  265. package/dist/chunk-YZWE7XSM.js +5 -0
  266. package/dist/{chunk-I5IW3T2Y.js → chunk-Z3XSSF7B.js} +32 -15
  267. package/dist/{chunk-6UJMCWRY.js → chunk-Z4LIPMPM.js} +6 -0
  268. package/dist/chunk-ZASDSY7P.cjs +114 -0
  269. package/dist/{chunk-R33VKSH5.cjs → chunk-ZHJ7JGME.cjs} +11 -11
  270. package/dist/chunk-ZPD2AO3U.js +866 -0
  271. package/dist/{chunk-7UOUW76C.js → chunk-ZQAT5VT5.js} +101 -24
  272. package/dist/chunk-ZR46OJNZ.js +152 -0
  273. package/dist/{chunk-G3V4SFET.cjs → chunk-ZZ63NW7A.cjs} +78 -35
  274. package/dist/clear-33TWQ2ES.cjs +12 -0
  275. package/dist/clear-OFLFQ3MR.js +12 -0
  276. package/dist/{communityInstaller-6KCFN7YZ.js → communityInstaller-COB2KTOW.js} +9 -6
  277. package/dist/communityInstaller-TWMGPSYM.cjs +22 -0
  278. package/dist/completion-IIZMHXYP.cjs +17 -0
  279. package/dist/completion-PT4VM2H2.js +17 -0
  280. package/dist/config-KL6WU7R2.cjs +20 -0
  281. package/dist/{config-ZN66VXPS.js → config-R4O7GBTY.js} +7 -5
  282. package/dist/{constants-UFM5B232.js → constants-HVCHVQAF.js} +2 -2
  283. package/dist/constants-RLMJ5D5P.cjs +21 -0
  284. package/dist/{defaultHooks-RCXPHF4M.cjs → defaultHooks-2V2CQL63.cjs} +26 -11
  285. package/dist/{defaultHooks-RDRMER3Z.js → defaultHooks-TMHDU3FS.js} +26 -11
  286. package/dist/export-3PK3VFCE.js +15 -0
  287. package/dist/export-WLGNWEMJ.cjs +15 -0
  288. package/dist/{extractSessionMemories-V7K42ZHW.js → extractSessionMemories-A2JX5WJ2.js} +1 -1
  289. package/dist/{extractSessionMemories-SDW2MVBQ.cjs → extractSessionMemories-XL3MS37F.cjs} +1 -1
  290. package/dist/feedback-G635NCLJ.js +18 -0
  291. package/dist/feedback-LLMK3TZH.cjs +18 -0
  292. package/dist/fffSearchProvider-2YCNKOYD.js +412 -0
  293. package/dist/fffSearchProvider-W6627E2V.cjs +412 -0
  294. package/dist/filesystem-L6DQKGWK.js +11 -0
  295. package/dist/filesystem-PGUPCMVK.cjs +11 -0
  296. package/dist/{formatters-6K7QVWQL.cjs → formatters-53XTCNGQ.cjs} +1 -1
  297. package/dist/{formatters-DQHO5I36.js → formatters-T6KV4BPP.js} +1 -1
  298. package/dist/help-BAXLP2M2.cjs +12 -0
  299. package/dist/{help-2BLR7L43.js → help-X3OAZ3CY.js} +3 -3
  300. package/dist/history-6HOJSEMT.cjs +14 -0
  301. package/dist/{history-5FZ3M2AK.js → history-YKFPHBJN.js} +3 -3
  302. package/dist/hooks-7TA4PIIB.js +18 -0
  303. package/dist/hooks-XVFU67T2.cjs +18 -0
  304. package/dist/{i18n-K7QOWIBH.js → i18n-2KBUU7XL.js} +2 -2
  305. package/dist/i18n-MUJMKTFM.cjs +33 -0
  306. package/dist/ide-CCQ33PGC.cjs +15 -0
  307. package/dist/ide-VLVFBZ5F.js +15 -0
  308. package/dist/immediateCommandRouter-MTEHZXQX.js +15 -0
  309. package/dist/immediateCommandRouter-ROXU3MWT.cjs +15 -0
  310. package/dist/{import-UXM3VK7B.js → import-4CHYLS4K.js} +4 -4
  311. package/dist/{import-QEME3E4T.cjs → import-I7T4ZHYL.cjs} +4 -4
  312. package/dist/import-J46F54JY.cjs +10 -0
  313. package/dist/import-JWPYKXCZ.js +10 -0
  314. package/dist/index.cjs +747 -22356
  315. package/dist/index.d.cts +93 -0
  316. package/dist/index.d.ts +93 -0
  317. package/dist/index.js +786 -22395
  318. package/dist/init-262MWZV4.js +10 -0
  319. package/dist/init-EE5Y7RBL.cjs +10 -0
  320. package/dist/inkMode-VUE6ZDLD.cjs +7 -0
  321. package/dist/inkMode-WBNFOSAT.js +7 -0
  322. package/dist/inputPrompt-IIFKCX5Q.cjs +90 -0
  323. package/dist/inputPrompt-YGBHDUEP.js +90 -0
  324. package/dist/language-CVLPB7OV.js +21 -0
  325. package/dist/language-ZTWFHUSV.cjs +21 -0
  326. package/dist/learn-PON7I5QS.js +23 -0
  327. package/dist/learn-XLRSVNA3.cjs +23 -0
  328. package/dist/{lint-D5UOJWIK.cjs → lint-4NDGCSCL.cjs} +1 -1
  329. package/dist/{lint-NJPZWVN2.js → lint-FZ7ZJUB3.js} +1 -1
  330. package/dist/login-223QTGBG.cjs +26 -0
  331. package/dist/login-6IKTBUBY.js +26 -0
  332. package/dist/logout-RN2AG6SI.js +23 -0
  333. package/dist/logout-U3M4FFX7.cjs +23 -0
  334. package/dist/mcp-5O6PUL4G.js +20 -0
  335. package/dist/mcp-NUQ76QQB.cjs +20 -0
  336. package/dist/{mcp-install-VESN42PI.js → mcp-install-6XWXLFVY.js} +18 -11
  337. package/dist/{mcp-install-G27HSS3Z.cjs → mcp-install-P6DHES7V.cjs} +22 -15
  338. package/dist/memory-CYMDQ2YC.cjs +10 -0
  339. package/dist/memory-Q54CESNM.js +10 -0
  340. package/dist/{message-ZJ5AYAMT.cjs → message-4OKO775J.cjs} +1 -1
  341. package/dist/{message-JUBOK2VU.js → message-X3LOAAM7.js} +1 -1
  342. package/dist/model-6AJ77PJG.js +10 -0
  343. package/dist/model-IDRCKDML.cjs +10 -0
  344. package/dist/new-7LEWOUF2.cjs +12 -0
  345. package/dist/new-ZXHEWC2S.js +12 -0
  346. package/dist/onboarding-FXX7YHSJ.cjs +35 -0
  347. package/dist/onboarding-JYNMK6NI.js +35 -0
  348. package/dist/{patch-MOD7QC3D.cjs → patch-DJ2GPFST.cjs} +1 -1
  349. package/dist/{patch-5F6VBIT3.js → patch-NIJWIRHS.js} +1 -1
  350. package/dist/permissions-YBNSANIA.cjs +10 -0
  351. package/dist/permissions-YHJMVA6L.js +10 -0
  352. package/dist/plan-XEJMOT55.cjs +13 -0
  353. package/dist/plan-YYUAXPTL.js +13 -0
  354. package/dist/pr-review-CW6J7P62.cjs +9 -0
  355. package/dist/pr-review-YZSBQVT2.js +9 -0
  356. package/dist/quit-B43SJ6E4.cjs +10 -0
  357. package/dist/quit-GWTNHQSP.js +10 -0
  358. package/dist/rawMode-6W5AXAKI.cjs +7 -0
  359. package/dist/rawMode-GFNLXQPU.js +7 -0
  360. package/dist/{registry-KWZGYJC2.js → registry-34GL6BNJ.js} +30 -45
  361. package/dist/{registry-YN4FQPOO.cjs → registry-V24W7YK6.cjs} +66 -81
  362. package/dist/repeat-P4FAPE3Y.cjs +17 -0
  363. package/dist/repeat-RALE6AUO.js +17 -0
  364. package/dist/resume-DYVOQN5L.cjs +16 -0
  365. package/dist/resume-M25UQKOX.js +16 -0
  366. package/dist/review-QHP2KP4Q.js +9 -0
  367. package/dist/review-UWHWQHCB.cjs +9 -0
  368. package/dist/ripgrep-67SCU2BA.cjs +9 -0
  369. package/dist/ripgrep-VHJQQ55W.js +9 -0
  370. package/dist/rpc-NPS3PU4O.cjs +3730 -0
  371. package/dist/rpc-S3DGW6KV.js +3730 -0
  372. package/dist/search-CPX4PWHP.js +20 -0
  373. package/dist/search-VUF3M4N3.cjs +20 -0
  374. package/dist/{session-BSU2L5UI.cjs → session-LXKC4X5Q.cjs} +1 -1
  375. package/dist/{session-SZMRN5KG.js → session-NMMO4QOL.js} +1 -1
  376. package/dist/sessions-IYAXMK23.js +10 -0
  377. package/dist/sessions-JODKER5D.cjs +10 -0
  378. package/dist/settings-55BNW6BF.js +33 -0
  379. package/dist/settings-BKTS7OMC.cjs +33 -0
  380. package/dist/setup-IRC5HQG2.js +29 -0
  381. package/dist/setup-MMQ7TWOP.cjs +29 -0
  382. package/dist/share-6JRQECAH.cjs +17 -0
  383. package/dist/share-YM5MJKMO.js +17 -0
  384. package/dist/{skills-FYY6F2WV.cjs → skills-GGMZOVIE.cjs} +14 -11
  385. package/dist/skills-LWSOKOSH.cjs +29 -0
  386. package/dist/{skills-6OL4OSGA.js → skills-MVIZNHT4.js} +13 -10
  387. package/dist/skills-VWOMV3CR.js +29 -0
  388. package/dist/{skills-install-6CSWC24P.js → skills-install-QBA5RCV6.js} +22 -71
  389. package/dist/{skills-install-O3LZ2ETC.cjs → skills-install-VTAMCNNY.cjs} +35 -84
  390. package/dist/skills-new-GPL46YV2.js +18 -0
  391. package/dist/skills-new-L5BEZN5V.cjs +18 -0
  392. package/dist/slashCommands-FBPCIWM5.cjs +95 -0
  393. package/dist/slashCommands-FCWY5DXW.js +95 -0
  394. package/dist/status-3B5SDZPL.js +17 -0
  395. package/dist/status-TTGRF6NC.cjs +17 -0
  396. package/dist/summarizer-DGPHE5IQ.js +17 -0
  397. package/dist/summarizer-JNXLUAQG.cjs +17 -0
  398. package/dist/sync-77CXVVTK.cjs +21 -0
  399. package/dist/{sync-KWX67OUN.js → sync-7BILIQHP.js} +4 -4
  400. package/dist/sync-7OKF6636.js +21 -0
  401. package/dist/sync-EH4K5U3N.cjs +40 -0
  402. package/dist/{tasks-5FPBIFLC.js → tasks-R5MBGAQ6.js} +1 -1
  403. package/dist/{tasks-TXGKGNH6.cjs → tasks-V4WB3MFR.cjs} +1 -1
  404. package/dist/{team-5YXP3JGR.js → team-JESCHGWB.js} +1 -1
  405. package/dist/{team-IIWEZKNR.cjs → team-VQQKWGZB.cjs} +1 -1
  406. package/dist/{teammate-L6EZQ3I2.js → teammate-GZQSCIMS.js} +29 -8
  407. package/dist/{teammate-2KMKJXAM.cjs → teammate-J6PHGL23.cjs} +30 -9
  408. package/dist/theme-64BYGJ57.cjs +21 -0
  409. package/dist/theme-YJE6HEON.js +21 -0
  410. package/dist/tools-3PPTTKFV.js +9 -0
  411. package/dist/tools-THIQA7WC.cjs +9 -0
  412. package/dist/ui/questionModal.cjs +9 -6
  413. package/dist/ui/questionModal.js +8 -5
  414. package/dist/undo-PRTEGL2J.cjs +10 -0
  415. package/dist/undo-SZEHLX7X.js +10 -0
  416. package/dist/{update-TVAJMMBC.js → update-53WMKYVS.js} +1 -1
  417. package/dist/{update-Z6BIIQDC.cjs → update-NV6QRYY7.cjs} +1 -1
  418. package/dist/web-3BA2WV37.cjs +37 -0
  419. package/dist/web-6FYGBX5K.js +37 -0
  420. package/dist/workspaceSafety-MDJGHK6D.cjs +9 -0
  421. package/dist/workspaceSafety-XOUMUBVB.js +9 -0
  422. package/dist/yolo-GF2YD7ZI.js +9 -0
  423. package/dist/yolo-OGDA7HNC.cjs +9 -0
  424. package/dist/yoloMode-3DJDA75U.cjs +17 -0
  425. package/dist/yoloMode-4JOOSU26.js +17 -0
  426. package/package.json +46 -49
  427. package/dist/AgentRegistry-NQCLWABO.cjs +0 -10
  428. package/dist/CommunitySkillsCache-6QPRMTJO.js +0 -8
  429. package/dist/CommunitySkillsCache-GTQMOCCO.cjs +0 -8
  430. package/dist/GitHubRegistryFetcher-S7QFUEKV.cjs +0 -7
  431. package/dist/HookManager-Q2KYMCP4.cjs +0 -7
  432. package/dist/HookManager-TTP4Y6DC.js +0 -7
  433. package/dist/LearnAdvisor-A4Q5PPBI.js +0 -9
  434. package/dist/LearnAdvisor-GASQD7HT.cjs +0 -9
  435. package/dist/McpClientManager-RKD7C6OY.cjs +0 -8
  436. package/dist/MemoryManager-GUNLRP5S.js +0 -8
  437. package/dist/MemoryManager-TNSGKDKX.cjs +0 -8
  438. package/dist/PermissionManager-KMN53FJP.cjs +0 -11
  439. package/dist/ProviderFactory-MR5B23QJ.js +0 -9
  440. package/dist/ProviderFactory-VFGCJJX6.cjs +0 -9
  441. package/dist/SessionManager-FEUAU3ZJ.cjs +0 -10
  442. package/dist/SessionManager-IKWAK2PI.js +0 -10
  443. package/dist/SkillsRegistry-KPQFTRIT.cjs +0 -9
  444. package/dist/SkillsRegistry-XJSKPDF2.js +0 -9
  445. package/dist/SubAgent-NYH6GWQ3.js +0 -11
  446. package/dist/SubAgent-PZKBDUBA.cjs +0 -11
  447. package/dist/SyncApiClient-ZNYMT36M.cjs +0 -11
  448. package/dist/about-HHTF2YFL.js +0 -12
  449. package/dist/about-JGRVNNQC.cjs +0 -12
  450. package/dist/actionExecutor-U6IBN2TU.cjs +0 -19
  451. package/dist/actionExecutor-XT5FW3W6.js +0 -19
  452. package/dist/add-dir-247K3XRY.js +0 -10
  453. package/dist/add-dir-GS4DXKKH.cjs +0 -10
  454. package/dist/agents-R6ZEFTVR.cjs +0 -12
  455. package/dist/agents-WJPQWQF2.js +0 -12
  456. package/dist/agents-new-HKVEIBDJ.js +0 -14
  457. package/dist/agents-new-X6GTHIO6.cjs +0 -14
  458. package/dist/autoSkill-H4T6VVDA.cjs +0 -20
  459. package/dist/automode-BC6NVECO.js +0 -10
  460. package/dist/automode-WN2RSOGW.cjs +0 -10
  461. package/dist/chunk-33A755XB.cjs +0 -168
  462. package/dist/chunk-3OTU3RS3.cjs +0 -1607
  463. package/dist/chunk-4PKF7WPD.js +0 -100
  464. package/dist/chunk-ALYU6VTM.js +0 -105
  465. package/dist/chunk-AS6RTLN7.cjs +0 -203
  466. package/dist/chunk-BWN2CLLM.cjs +0 -298
  467. package/dist/chunk-HQ7YZKXE.js +0 -168
  468. package/dist/chunk-HVKOZ2VP.cjs +0 -115
  469. package/dist/chunk-J6QET7EF.cjs +0 -454
  470. package/dist/chunk-LWUJFGOZ.js +0 -115
  471. package/dist/chunk-P47WPOXN.js +0 -298
  472. package/dist/chunk-PRRCJFU3.cjs +0 -85
  473. package/dist/chunk-RO6WYEWH.js +0 -454
  474. package/dist/chunk-SYVYLZZF.cjs +0 -24
  475. package/dist/chunk-T73IDKDF.js +0 -111
  476. package/dist/chunk-YRLYSQEQ.cjs +0 -105
  477. package/dist/chunk-ZQE72E6W.cjs +0 -100
  478. package/dist/chunk-ZVY2XD6T.js +0 -1607
  479. package/dist/clear-UO4MNWZW.cjs +0 -12
  480. package/dist/clear-ZJ5NYP6E.js +0 -12
  481. package/dist/communityInstaller-PVSOFDZD.cjs +0 -19
  482. package/dist/completion-MMF2PN2H.js +0 -14
  483. package/dist/completion-UI5WKHXI.cjs +0 -14
  484. package/dist/config-E7RINK4R.cjs +0 -18
  485. package/dist/constants-6CPCJ3DY.cjs +0 -21
  486. package/dist/export-N4XIVDSL.cjs +0 -12
  487. package/dist/export-W22L4D5C.js +0 -12
  488. package/dist/feedback-DR6ADSNE.cjs +0 -15
  489. package/dist/feedback-FEEAP4QW.js +0 -15
  490. package/dist/filesystem-3SGCW2BF.js +0 -10
  491. package/dist/filesystem-MCFXJQ6R.cjs +0 -10
  492. package/dist/help-AQHGTS7P.cjs +0 -12
  493. package/dist/history-NIUDRMKA.cjs +0 -14
  494. package/dist/hooks-2EY4IPKV.js +0 -13
  495. package/dist/hooks-LJVORRIG.cjs +0 -13
  496. package/dist/i18n-ARDG2SMC.cjs +0 -33
  497. package/dist/ide-GFW6IJHD.js +0 -12
  498. package/dist/ide-N2ZNSSB3.cjs +0 -12
  499. package/dist/import-DFVN3KNZ.js +0 -10
  500. package/dist/import-ZS6DPGU5.cjs +0 -10
  501. package/dist/init-PY75HA3S.cjs +0 -10
  502. package/dist/init-QNMWLAVY.js +0 -10
  503. package/dist/language-5UE4G2BT.cjs +0 -18
  504. package/dist/language-UXMHEZUJ.js +0 -18
  505. package/dist/learn-HJ3FLNZC.cjs +0 -20
  506. package/dist/learn-MVYS3RU5.js +0 -20
  507. package/dist/localProjectPermissions-N77HA3XK.js +0 -18
  508. package/dist/localProjectPermissions-UFSMNTBJ.cjs +0 -18
  509. package/dist/login-DSE7H63A.js +0 -20
  510. package/dist/login-V3MEWPKN.cjs +0 -20
  511. package/dist/logout-BMVCLKKW.js +0 -18
  512. package/dist/logout-XEG7FHOZ.cjs +0 -18
  513. package/dist/mcp-PYUR4PHO.js +0 -18
  514. package/dist/mcp-SG6JFLGC.cjs +0 -18
  515. package/dist/memory-4ZMMEZ2Z.js +0 -10
  516. package/dist/memory-QSGMVVGH.cjs +0 -10
  517. package/dist/model-NANLBZ4Z.cjs +0 -10
  518. package/dist/model-ZXNV4AF7.js +0 -10
  519. package/dist/new-5QJY5JP2.js +0 -12
  520. package/dist/new-PMMG55UX.cjs +0 -12
  521. package/dist/permissions-LECTCJ4H.cjs +0 -13
  522. package/dist/permissions-VP5VGIBL.js +0 -13
  523. package/dist/plan-G5CEKJI4.js +0 -11
  524. package/dist/plan-QKOHE3LH.cjs +0 -11
  525. package/dist/quit-BKOOPHU5.cjs +0 -10
  526. package/dist/quit-FVFNYACP.js +0 -10
  527. package/dist/resume-EXFQSQPH.js +0 -13
  528. package/dist/resume-PP2IQM5S.cjs +0 -13
  529. package/dist/search-C56FBN67.cjs +0 -17
  530. package/dist/search-XGZDYBF4.js +0 -17
  531. package/dist/sessions-54KI3F2Q.js +0 -10
  532. package/dist/sessions-DDTSPNVW.cjs +0 -10
  533. package/dist/settings-BDO37TTO.cjs +0 -30
  534. package/dist/settings-FHRDFPLK.js +0 -30
  535. package/dist/share-IERCTBGN.cjs +0 -14
  536. package/dist/share-TGROUE6R.js +0 -14
  537. package/dist/skills-OM4IGBAA.cjs +0 -26
  538. package/dist/skills-S3GRN323.js +0 -26
  539. package/dist/skills-new-ALD2PTHN.js +0 -15
  540. package/dist/skills-new-PWLKK7GW.cjs +0 -15
  541. package/dist/slashCommands-L4ZD33LJ.js +0 -75
  542. package/dist/slashCommands-YY2VUUDF.cjs +0 -75
  543. package/dist/status-3PC5XWSS.cjs +0 -11
  544. package/dist/status-KCLVOYPD.js +0 -11
  545. package/dist/sync-6SDWG5RK.js +0 -18
  546. package/dist/sync-7JMZVEQD.cjs +0 -40
  547. package/dist/sync-WHURZL3U.cjs +0 -18
  548. package/dist/theme-BE5A4FPN.cjs +0 -18
  549. package/dist/theme-YMFCQP7J.js +0 -18
  550. package/dist/undo-KZHUUZTD.cjs +0 -10
  551. package/dist/undo-NEIEHQVX.js +0 -10
@@ -0,0 +1,4291 @@
1
+ import {
2
+ NVIDIAProvider
3
+ } from "./chunk-CCVMREXI.js";
4
+ import {
5
+ ApiError,
6
+ classifyApiError
7
+ } from "./chunk-FQVG6ZHF.js";
8
+
9
+ // src/providers/OllamaProvider.ts
10
+ var DEFAULT_TIMEOUT = 12e4;
11
+ var DEFAULT_CHUNK_TIMEOUT = 3e4;
12
+ var DEFAULT_MAX_RETRIES = 2;
13
+ var MAX_ALLOWED_RETRIES = 5;
14
+ var DEFAULT_RETRY_DELAY = 1e3;
15
+ var AVAILABILITY_TIMEOUT = 5e3;
16
+ var OllamaProvider = class {
17
+ baseUrl;
18
+ model;
19
+ disableTools = false;
20
+ maxRetries;
21
+ retryDelay;
22
+ timeout;
23
+ chunkTimeout;
24
+ constructor(config, networkSettings) {
25
+ this.baseUrl = config.baseUrl || "http://localhost:11434";
26
+ this.model = config.model || "llama3.2:latest";
27
+ const configuredRetries = networkSettings?.maxRetries ?? DEFAULT_MAX_RETRIES;
28
+ this.maxRetries = Math.min(Math.max(0, configuredRetries), MAX_ALLOWED_RETRIES);
29
+ this.retryDelay = networkSettings?.retryDelay ?? DEFAULT_RETRY_DELAY;
30
+ this.timeout = networkSettings?.timeout ?? DEFAULT_TIMEOUT;
31
+ this.chunkTimeout = DEFAULT_CHUNK_TIMEOUT;
32
+ }
33
+ getName() {
34
+ return "ollama";
35
+ }
36
+ setModel(model) {
37
+ this.model = model;
38
+ }
39
+ async listModels() {
40
+ try {
41
+ const controller = new AbortController();
42
+ const timerId = setTimeout(() => controller.abort(), AVAILABILITY_TIMEOUT);
43
+ try {
44
+ const response = await fetch(`${this.baseUrl}/api/tags`, {
45
+ signal: controller.signal
46
+ });
47
+ if (!response.ok) {
48
+ return [];
49
+ }
50
+ const data = await response.json();
51
+ return data.models.map((m) => m.name);
52
+ } finally {
53
+ clearTimeout(timerId);
54
+ }
55
+ } catch {
56
+ return [];
57
+ }
58
+ }
59
+ async isAvailable() {
60
+ try {
61
+ const controller = new AbortController();
62
+ const timerId = setTimeout(() => controller.abort(), AVAILABILITY_TIMEOUT);
63
+ try {
64
+ const response = await fetch(`${this.baseUrl}/api/tags`, {
65
+ signal: controller.signal
66
+ });
67
+ return response.ok;
68
+ } finally {
69
+ clearTimeout(timerId);
70
+ }
71
+ } catch {
72
+ return false;
73
+ }
74
+ }
75
+ async complete(request) {
76
+ const body = {
77
+ model: request.model || this.model,
78
+ messages: this.buildMessages(request.messages, !this.disableTools),
79
+ stream: request.stream || false
80
+ };
81
+ if (request.temperature !== void 0) {
82
+ body.options = { temperature: request.temperature };
83
+ }
84
+ if (!this.disableTools && request.tools && request.tools.length > 0) {
85
+ body.tools = request.tools.map((tool) => ({
86
+ type: "function",
87
+ function: {
88
+ name: tool.name,
89
+ description: tool.description,
90
+ parameters: tool.parameters ?? { type: "object", properties: {} }
91
+ }
92
+ }));
93
+ }
94
+ let lastError = null;
95
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
96
+ try {
97
+ return await this.makeRequest(body, request);
98
+ } catch (error) {
99
+ lastError = error;
100
+ if (this.isNonRetryableError(error)) {
101
+ throw error;
102
+ }
103
+ if (attempt < this.maxRetries) {
104
+ const delay = this.retryDelay * Math.pow(2, attempt);
105
+ await this.sleep(delay);
106
+ }
107
+ }
108
+ }
109
+ throw lastError ?? new ApiError(
110
+ "Failed to communicate with Ollama. Please try again.",
111
+ "network_error",
112
+ 0,
113
+ true
114
+ );
115
+ }
116
+ async makeRequest(body, request) {
117
+ let response;
118
+ try {
119
+ const timeoutController = new AbortController();
120
+ const timerId = setTimeout(() => timeoutController.abort(), this.timeout);
121
+ const combinedSignal = request.signal ? this.combineSignals(request.signal, timeoutController.signal) : timeoutController.signal;
122
+ try {
123
+ response = await fetch(`${this.baseUrl}/api/chat`, {
124
+ method: "POST",
125
+ headers: {
126
+ "Content-Type": "application/json"
127
+ },
128
+ body: JSON.stringify(body),
129
+ signal: combinedSignal
130
+ });
131
+ } finally {
132
+ clearTimeout(timerId);
133
+ }
134
+ } catch (error) {
135
+ const err = error;
136
+ if (err.name === "AbortError" && request.signal?.aborted) {
137
+ throw new ApiError("Request cancelled.", "cancelled", 0, false);
138
+ }
139
+ if (err.name === "AbortError") {
140
+ throw new ApiError(
141
+ `Ollama request timed out after ${this.timeout / 1e3}s. Local inference can be slow \u2014 consider increasing the timeout in your config.`,
142
+ "timeout",
143
+ 0,
144
+ true
145
+ );
146
+ }
147
+ throw new ApiError(
148
+ `Cannot connect to Ollama at ${this.baseUrl}. Make sure Ollama is running (try 'ollama serve').`,
149
+ "network_error",
150
+ 0,
151
+ true
152
+ );
153
+ }
154
+ if (!response.ok) {
155
+ const apiError = await this.buildApiError(response, body);
156
+ if (apiError === null) {
157
+ return this.makeRequest(body, request);
158
+ }
159
+ throw apiError;
160
+ }
161
+ if (request.stream) {
162
+ return this.handleStreamingResponse(response);
163
+ }
164
+ const data = await response.json();
165
+ let toolCalls;
166
+ if (data.message.tool_calls && Array.isArray(data.message.tool_calls)) {
167
+ toolCalls = data.message.tool_calls.map((tc, index) => {
168
+ let argumentsStr;
169
+ try {
170
+ argumentsStr = JSON.stringify(tc.function.arguments);
171
+ } catch (error) {
172
+ console.warn("Failed to stringify tool call arguments, using fallback:", error);
173
+ argumentsStr = String(tc.function.arguments);
174
+ }
175
+ return {
176
+ id: `ollama-tool-${Date.now()}-${index}`,
177
+ type: "function",
178
+ function: {
179
+ name: tc.function.name,
180
+ arguments: argumentsStr
181
+ }
182
+ };
183
+ });
184
+ }
185
+ let usage;
186
+ if (data.prompt_eval_count !== void 0 || data.eval_count !== void 0) {
187
+ usage = {
188
+ promptTokens: data.prompt_eval_count ?? 0,
189
+ completionTokens: data.eval_count ?? 0,
190
+ totalTokens: (data.prompt_eval_count ?? 0) + (data.eval_count ?? 0)
191
+ };
192
+ }
193
+ return {
194
+ id: `ollama-${Date.now()}`,
195
+ created: Math.floor(new Date(data.created_at).getTime() / 1e3),
196
+ content: data.message.content,
197
+ toolCalls,
198
+ finishReason: toolCalls?.length ? "tool_calls" : "stop",
199
+ usage,
200
+ raw: data
201
+ };
202
+ }
203
+ /**
204
+ * Build an ApiError from a non-ok response.
205
+ *
206
+ * Returns `null` as a sentinel when the request was retried internally
207
+ * (e.g., disableTools retry). The caller must re-run makeRequest in
208
+ * that case.
209
+ */
210
+ async buildApiError(response, body) {
211
+ let errorBody = "";
212
+ try {
213
+ errorBody = await response.text();
214
+ } catch {
215
+ }
216
+ if (errorBody.includes("does not support tools") && body.tools) {
217
+ console.warn(`Model ${body.model} does not support tools. Retrying without tool support.`);
218
+ this.disableTools = true;
219
+ delete body.tools;
220
+ if (Array.isArray(body.messages)) {
221
+ body.messages = this.sanitizeMessagesForToollessMode(body.messages);
222
+ }
223
+ return null;
224
+ }
225
+ if (this.isToolParserError(errorBody) && (body.tools || this.hasToolMetadata(body.messages))) {
226
+ console.warn(`Model ${body.model} rejected tool metadata. Retrying without tool support.`);
227
+ this.disableTools = true;
228
+ delete body.tools;
229
+ if (Array.isArray(body.messages)) {
230
+ body.messages = this.sanitizeMessagesForToollessMode(body.messages);
231
+ }
232
+ return null;
233
+ }
234
+ if (response.status === 429 || this.isOllamaCloudRateLimitError(errorBody)) {
235
+ const baseError = classifyApiError(
236
+ response.status === 429 ? response.status : 429,
237
+ errorBody,
238
+ response.headers
239
+ );
240
+ return new ApiError(
241
+ "Ollama Cloud has paused this session because you hit a usage limit. This is expected on hosted Ollama plans. Wait a bit and try again, switch to another model, or upgrade your Ollama plan if you need higher limits.",
242
+ "rate_limited",
243
+ baseError.httpStatus,
244
+ true,
245
+ baseError.retryAfterMs,
246
+ errorBody
247
+ );
248
+ }
249
+ if (response.status === 400) {
250
+ const baseError = classifyApiError(response.status, errorBody, response.headers);
251
+ return new ApiError(
252
+ `Ollama rejected the request. This can happen when message content confuses the model's parser. Try simplifying your prompt or using a different model.
253
+ ${errorBody}`,
254
+ baseError.code,
255
+ baseError.httpStatus,
256
+ baseError.retryable,
257
+ baseError.retryAfterMs,
258
+ errorBody
259
+ );
260
+ }
261
+ if (response.status === 404) {
262
+ const baseError = classifyApiError(response.status, errorBody, response.headers);
263
+ return new ApiError(
264
+ `Model not found. Run 'ollama pull ${String(body.model)}' to download it.
265
+ ${errorBody}`,
266
+ baseError.code,
267
+ baseError.httpStatus,
268
+ baseError.retryable,
269
+ baseError.retryAfterMs,
270
+ errorBody
271
+ );
272
+ }
273
+ return classifyApiError(response.status, errorBody, response.headers);
274
+ }
275
+ buildMessages(messages, includeToolMetadata) {
276
+ if (!includeToolMetadata) {
277
+ return this.sanitizeMessagesForToollessMode(messages);
278
+ }
279
+ return messages.map((msg) => {
280
+ const mapped = {
281
+ role: msg.role,
282
+ content: msg.content ?? ""
283
+ };
284
+ if (msg.name) {
285
+ mapped.name = msg.name;
286
+ }
287
+ if (msg.role === "tool" && msg.tool_call_id) {
288
+ mapped.tool_call_id = msg.tool_call_id;
289
+ }
290
+ if (msg.role === "assistant" && msg.tool_calls?.length) {
291
+ mapped.tool_calls = this.normalizeToolCallsForRequest(msg.tool_calls);
292
+ }
293
+ return mapped;
294
+ });
295
+ }
296
+ normalizeToolCallsForRequest(toolCalls) {
297
+ return toolCalls.map((toolCall) => ({
298
+ function: {
299
+ name: toolCall.function.name,
300
+ arguments: this.parseToolArguments(toolCall.function.arguments)
301
+ }
302
+ }));
303
+ }
304
+ parseToolArguments(rawArguments) {
305
+ try {
306
+ const parsed = JSON.parse(rawArguments);
307
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
308
+ return parsed;
309
+ }
310
+ } catch {
311
+ }
312
+ return { __raw_arguments: rawArguments };
313
+ }
314
+ sanitizeMessagesForToollessMode(messages) {
315
+ return messages.map((msg) => {
316
+ const role = typeof msg.role === "string" ? msg.role : "user";
317
+ const content = typeof msg.content === "string" ? msg.content : "";
318
+ const name = typeof msg.name === "string" ? msg.name : void 0;
319
+ const toolCalls = Array.isArray(msg.tool_calls) ? msg.tool_calls : void 0;
320
+ if (role === "tool") {
321
+ return {
322
+ role: "user",
323
+ content: name ? `[Tool result: ${name}]
324
+ ${content}` : `[Tool result]
325
+ ${content}`
326
+ };
327
+ }
328
+ if (role === "assistant" && toolCalls?.length) {
329
+ const toolNames = toolCalls.map((call) => {
330
+ const fn = call && typeof call === "object" ? call.function : void 0;
331
+ return typeof fn?.name === "string" ? fn.name : void 0;
332
+ }).filter((value) => Boolean(value));
333
+ const toolSummary = toolNames.length > 0 ? `
334
+ [Assistant requested tools: ${toolNames.join(", ")}]` : "";
335
+ return {
336
+ role: "assistant",
337
+ content: `${content}${toolSummary}`.trim()
338
+ };
339
+ }
340
+ return {
341
+ role,
342
+ content,
343
+ ...name ? { name } : {}
344
+ };
345
+ });
346
+ }
347
+ hasToolMetadata(messages) {
348
+ if (!Array.isArray(messages)) {
349
+ return false;
350
+ }
351
+ return messages.some((msg) => {
352
+ if (!msg || typeof msg !== "object") {
353
+ return false;
354
+ }
355
+ const candidate = msg;
356
+ return candidate.role === "tool" || candidate.tool_call_id !== void 0 || candidate.tool_calls !== void 0;
357
+ });
358
+ }
359
+ isToolParserError(errorBody) {
360
+ const lower = errorBody.toLowerCase();
361
+ return lower.includes("value looks like object, but can't find closing '}' symbol") || lower.includes("value looks like object, but can't find closing") || lower.includes("tool") && lower.includes("parse") || lower.includes("function") && lower.includes("arguments") && lower.includes("closing");
362
+ }
363
+ isOllamaCloudRateLimitError(errorBody) {
364
+ const lower = errorBody.toLowerCase();
365
+ return lower.includes("session usage limit") || lower.includes("rate limit exceeded") || lower.includes("upgrade for higher limits") && lower.includes("ollama.com/upgrade");
366
+ }
367
+ async handleStreamingResponse(response) {
368
+ const reader = response.body?.getReader();
369
+ if (!reader) {
370
+ throw new Error("No response body");
371
+ }
372
+ const decoder = new TextDecoder();
373
+ let fullContent = "";
374
+ let lastData = null;
375
+ let streamEndedWithDone = false;
376
+ try {
377
+ while (true) {
378
+ const chunkResult = await this.readWithTimeout(reader, this.chunkTimeout, fullContent);
379
+ if ("timedOut" in chunkResult) {
380
+ if (fullContent) {
381
+ return {
382
+ id: `ollama-${Date.now()}`,
383
+ created: lastData ? Math.floor(new Date(lastData.created_at).getTime() / 1e3) : Math.floor(Date.now() / 1e3),
384
+ content: fullContent,
385
+ finishReason: "length",
386
+ raw: lastData
387
+ };
388
+ }
389
+ throw new ApiError(
390
+ `Ollama stream timed out after ${this.chunkTimeout / 1e3}s with no data. Make sure Ollama is running and the model is loaded.`,
391
+ "timeout",
392
+ 0,
393
+ true
394
+ );
395
+ }
396
+ const { done, value } = chunkResult;
397
+ if (done) {
398
+ break;
399
+ }
400
+ const chunk = decoder.decode(value, { stream: true });
401
+ const lines = chunk.split("\n").filter((line) => line.trim());
402
+ for (const line of lines) {
403
+ try {
404
+ const data = JSON.parse(line);
405
+ fullContent += data.message.content;
406
+ lastData = data;
407
+ if (data.done) {
408
+ streamEndedWithDone = true;
409
+ }
410
+ } catch {
411
+ }
412
+ }
413
+ if (streamEndedWithDone) {
414
+ break;
415
+ }
416
+ }
417
+ } finally {
418
+ reader.releaseLock();
419
+ }
420
+ const finishReason = streamEndedWithDone ? "stop" : "length";
421
+ return {
422
+ id: `ollama-${Date.now()}`,
423
+ created: lastData ? Math.floor(new Date(lastData.created_at).getTime() / 1e3) : Math.floor(Date.now() / 1e3),
424
+ content: fullContent,
425
+ finishReason,
426
+ raw: lastData
427
+ };
428
+ }
429
+ /**
430
+ * Read one chunk from the stream with a timeout.
431
+ * Returns `{ timedOut: true }` if the timeout fires before a chunk arrives.
432
+ */
433
+ async readWithTimeout(reader, timeoutMs, _partialContent) {
434
+ let timerId;
435
+ const timeoutPromise = new Promise((resolve) => {
436
+ timerId = setTimeout(() => resolve({ timedOut: true }), timeoutMs);
437
+ });
438
+ try {
439
+ const result = await Promise.race([
440
+ reader.read(),
441
+ timeoutPromise
442
+ ]);
443
+ if ("timedOut" in result) {
444
+ return result;
445
+ }
446
+ return { done: result.done, value: result.value || new Uint8Array() };
447
+ } finally {
448
+ clearTimeout(timerId);
449
+ }
450
+ }
451
+ isNonRetryableError(error) {
452
+ if (error instanceof ApiError) {
453
+ return !error.retryable;
454
+ }
455
+ const classified = classifyApiError(0, error.message);
456
+ return !classified.retryable;
457
+ }
458
+ combineSignals(signal1, signal2) {
459
+ const controller = new AbortController();
460
+ const abort = () => controller.abort();
461
+ signal1.addEventListener("abort", abort, { once: true });
462
+ signal2.addEventListener("abort", abort, { once: true });
463
+ if (signal1.aborted || signal2.aborted) {
464
+ controller.abort();
465
+ }
466
+ return controller.signal;
467
+ }
468
+ sleep(ms) {
469
+ return new Promise((resolve) => setTimeout(resolve, ms));
470
+ }
471
+ };
472
+
473
+ // src/providers/openaiAuth.ts
474
+ import { createHash, randomBytes } from "crypto";
475
+ import { createServer } from "http";
476
+ var OPENAI_AUTH_BASE_URL = "https://auth.openai.com";
477
+ var OPENAI_OAUTH_AUTHORIZE_URL = `${OPENAI_AUTH_BASE_URL}/oauth/authorize`;
478
+ var OPENAI_OAUTH_TOKEN_URL = `${OPENAI_AUTH_BASE_URL}/oauth/token`;
479
+ var OPENAI_DEVICE_USER_CODE_URL = `${OPENAI_AUTH_BASE_URL}/api/accounts/deviceauth/usercode`;
480
+ var OPENAI_DEVICE_TOKEN_URL = `${OPENAI_AUTH_BASE_URL}/api/accounts/deviceauth/token`;
481
+ var OPENAI_DEVICE_VERIFICATION_URL = `${OPENAI_AUTH_BASE_URL}/codex/device`;
482
+ var OPENAI_DEVICE_CALLBACK_URL = `${OPENAI_AUTH_BASE_URL}/deviceauth/callback`;
483
+ var OPENAI_CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
484
+ var OPENAI_AUTH_REQUEST_TIMEOUT_MS = 15e3;
485
+ var OPENAI_BROWSER_AUTH_TIMEOUT_MS = 5 * 6e4;
486
+ var OPENAI_BROWSER_AUTH_SCOPE = "openid profile email offline_access";
487
+ var OPENAI_BROWSER_CALLBACK_HOST = "127.0.0.1";
488
+ var OPENAI_BROWSER_CALLBACK_URL_HOST = "localhost";
489
+ var OPENAI_BROWSER_CALLBACK_PORT = 1455;
490
+ var OPENAI_BROWSER_CALLBACK_PATH = "/auth/callback";
491
+ var OPENAI_BROWSER_OAUTH_ORIGINATOR = "autohand-code";
492
+ function decodeJwtPayload(token) {
493
+ const parts = token.split(".");
494
+ if (parts.length < 2) return null;
495
+ try {
496
+ const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
497
+ const padded = base64.padEnd(base64.length + (4 - base64.length % 4) % 4, "=");
498
+ return JSON.parse(Buffer.from(padded, "base64").toString("utf8"));
499
+ } catch {
500
+ return null;
501
+ }
502
+ }
503
+ function decodeJwtExpiry(token) {
504
+ const payload = decodeJwtPayload(token);
505
+ if (!payload?.exp) return void 0;
506
+ return new Date(payload.exp * 1e3).toISOString();
507
+ }
508
+ function buildTokenBody(params) {
509
+ return new URLSearchParams(params).toString();
510
+ }
511
+ function toBase64Url(buffer) {
512
+ return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
513
+ }
514
+ function generatePkceVerifier() {
515
+ return toBase64Url(randomBytes(32));
516
+ }
517
+ function generatePkceChallenge(verifier) {
518
+ return toBase64Url(createHash("sha256").update(verifier).digest());
519
+ }
520
+ function createState() {
521
+ return randomBytes(16).toString("hex");
522
+ }
523
+ async function fetchWithTimeout(input, init, context, timeoutMs = OPENAI_AUTH_REQUEST_TIMEOUT_MS) {
524
+ const controller = new AbortController();
525
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
526
+ try {
527
+ return await fetch(input, {
528
+ ...init,
529
+ signal: controller.signal
530
+ });
531
+ } catch (error) {
532
+ if (error instanceof Error && error.name === "AbortError") {
533
+ throw new Error(`${context} timed out. Check your connection and try again.`);
534
+ }
535
+ throw error;
536
+ } finally {
537
+ clearTimeout(timer);
538
+ }
539
+ }
540
+ function extractErrorDetail(payload) {
541
+ if (!payload || typeof payload !== "object") return void 0;
542
+ const candidate = payload;
543
+ const direct = candidate.error_description ?? candidate.error ?? candidate.message ?? candidate.detail;
544
+ if (typeof direct === "string" && direct.trim()) {
545
+ return direct.trim();
546
+ }
547
+ const nestedError = candidate.error;
548
+ if (nestedError && typeof nestedError === "object") {
549
+ const nested = nestedError;
550
+ for (const key of ["message", "error_description", "detail", "code"]) {
551
+ const value = nested[key];
552
+ if (typeof value === "string" && value.trim()) {
553
+ return value.trim();
554
+ }
555
+ }
556
+ }
557
+ return void 0;
558
+ }
559
+ async function parseJsonResponse(response, context) {
560
+ const { payload, detail } = await parseResponseBody(response);
561
+ if (!response.ok) {
562
+ throw new Error(
563
+ detail ? `${context} failed with status ${response.status}: ${detail}` : `${context} failed with status ${response.status}.`
564
+ );
565
+ }
566
+ if (payload === void 0) {
567
+ throw new Error(`${context} returned an empty response.`);
568
+ }
569
+ return payload;
570
+ }
571
+ async function parseResponseBody(response) {
572
+ const rawText = await response.text();
573
+ let payload;
574
+ if (rawText.trim()) {
575
+ try {
576
+ payload = JSON.parse(rawText);
577
+ } catch {
578
+ payload = rawText;
579
+ }
580
+ }
581
+ const detail = extractErrorDetail(payload) ?? (typeof payload === "string" && payload.trim() ? payload.trim() : void 0);
582
+ return { payload, detail };
583
+ }
584
+ async function openBrowser(url) {
585
+ try {
586
+ const open = await import("open").then((mod) => mod.default);
587
+ await open(url);
588
+ return true;
589
+ } catch {
590
+ return false;
591
+ }
592
+ }
593
+ function callbackSuccessHtml() {
594
+ return "<!doctype html><html><body><h1>OpenAI sign-in complete.</h1><p>You can close this window.</p></body></html>";
595
+ }
596
+ function callbackErrorHtml(message) {
597
+ return `<!doctype html><html><body><h1>OpenAI sign-in failed.</h1><p>${message}</p></body></html>`;
598
+ }
599
+ async function listenForOAuthCallback(expectedState) {
600
+ const server = createServer((req, res) => {
601
+ try {
602
+ const url = new URL(req.url || "", `http://${OPENAI_BROWSER_CALLBACK_HOST}`);
603
+ if (url.pathname !== OPENAI_BROWSER_CALLBACK_PATH) {
604
+ res.statusCode = 404;
605
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
606
+ res.end(callbackErrorHtml("Callback route not found."));
607
+ return;
608
+ }
609
+ const error = url.searchParams.get("error");
610
+ const errorDescription = url.searchParams.get("error_description");
611
+ if (error) {
612
+ res.statusCode = 400;
613
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
614
+ res.end(callbackErrorHtml(errorDescription || error));
615
+ settle?.({
616
+ error,
617
+ errorDescription: errorDescription || void 0
618
+ });
619
+ return;
620
+ }
621
+ const state = url.searchParams.get("state");
622
+ if (state !== expectedState) {
623
+ res.statusCode = 400;
624
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
625
+ res.end(callbackErrorHtml("State mismatch."));
626
+ settle?.({
627
+ error: "state_mismatch",
628
+ errorDescription: "State mismatch."
629
+ });
630
+ return;
631
+ }
632
+ const code = url.searchParams.get("code");
633
+ if (!code) {
634
+ res.statusCode = 400;
635
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
636
+ res.end(callbackErrorHtml("Missing authorization code."));
637
+ settle?.({
638
+ error: "missing_code",
639
+ errorDescription: "Missing authorization code."
640
+ });
641
+ return;
642
+ }
643
+ res.statusCode = 200;
644
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
645
+ res.end(callbackSuccessHtml());
646
+ settle?.({ code });
647
+ } catch {
648
+ res.statusCode = 500;
649
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
650
+ res.end(callbackErrorHtml("Internal error while handling the callback."));
651
+ settle?.({
652
+ error: "callback_error",
653
+ errorDescription: "Internal error while handling the callback."
654
+ });
655
+ }
656
+ });
657
+ const timeoutId = setTimeout(() => {
658
+ settle?.({
659
+ error: "timeout",
660
+ errorDescription: "OpenAI sign-in timed out. Finish the browser sign-in and try again."
661
+ });
662
+ }, OPENAI_BROWSER_AUTH_TIMEOUT_MS);
663
+ let settle;
664
+ let settled = false;
665
+ const waitForResult = new Promise((resolve) => {
666
+ settle = (result) => {
667
+ if (settled) return;
668
+ settled = true;
669
+ if (timeoutId) clearTimeout(timeoutId);
670
+ resolve(result);
671
+ };
672
+ });
673
+ const listenOnPort = async (port) => new Promise((resolve, reject) => {
674
+ server.once("error", reject);
675
+ server.listen(port, OPENAI_BROWSER_CALLBACK_HOST, () => {
676
+ server.off("error", reject);
677
+ resolve();
678
+ });
679
+ });
680
+ try {
681
+ await listenOnPort(OPENAI_BROWSER_CALLBACK_PORT);
682
+ } catch (error) {
683
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "EADDRINUSE") {
684
+ throw error;
685
+ }
686
+ await listenOnPort(0);
687
+ }
688
+ const address = server.address();
689
+ if (!address || typeof address === "string") {
690
+ throw new Error("Failed to determine OpenAI OAuth callback address.");
691
+ }
692
+ const callbackPort = address.port;
693
+ return {
694
+ redirectUri: `http://${OPENAI_BROWSER_CALLBACK_URL_HOST}:${callbackPort}${OPENAI_BROWSER_CALLBACK_PATH}`,
695
+ waitForResult: () => waitForResult,
696
+ close: async () => {
697
+ if (timeoutId) clearTimeout(timeoutId);
698
+ await new Promise((resolve) => {
699
+ server.close(() => resolve());
700
+ });
701
+ }
702
+ };
703
+ }
704
+ function extractChatGPTAccountId(token) {
705
+ const payload = decodeJwtPayload(token);
706
+ return payload?.["https://api.openai.com/auth"]?.chatgpt_account_id;
707
+ }
708
+ function isChatGPTAuthExpired(auth, leewayMs = 6e4) {
709
+ if (!auth.expiresAt) return false;
710
+ return new Date(auth.expiresAt).getTime() <= Date.now() + leewayMs;
711
+ }
712
+ async function authenticateOpenAIChatGPT(options = {}) {
713
+ const verifier = generatePkceVerifier();
714
+ const challenge = generatePkceChallenge(verifier);
715
+ const state = createState();
716
+ const callback = await listenForOAuthCallback(state);
717
+ try {
718
+ const authorizeUrl = new URL(OPENAI_OAUTH_AUTHORIZE_URL);
719
+ authorizeUrl.searchParams.set("response_type", "code");
720
+ authorizeUrl.searchParams.set("client_id", OPENAI_CODEX_CLIENT_ID);
721
+ authorizeUrl.searchParams.set("redirect_uri", callback.redirectUri);
722
+ authorizeUrl.searchParams.set("scope", OPENAI_BROWSER_AUTH_SCOPE);
723
+ authorizeUrl.searchParams.set("code_challenge", challenge);
724
+ authorizeUrl.searchParams.set("code_challenge_method", "S256");
725
+ authorizeUrl.searchParams.set("state", state);
726
+ authorizeUrl.searchParams.set("id_token_add_organizations", "true");
727
+ authorizeUrl.searchParams.set("codex_cli_simplified_flow", "true");
728
+ authorizeUrl.searchParams.set("originator", OPENAI_BROWSER_OAUTH_ORIGINATOR);
729
+ const browserOpened = await openBrowser(authorizeUrl.toString());
730
+ await options.onPrompt?.({
731
+ authorizationUrl: authorizeUrl.toString(),
732
+ redirectUri: callback.redirectUri,
733
+ browserOpened
734
+ });
735
+ const result = await callback.waitForResult();
736
+ if (result.error) {
737
+ throw new Error(result.errorDescription || result.error);
738
+ }
739
+ if (!result.code) {
740
+ throw new Error("OpenAI sign-in did not return an authorization code.");
741
+ }
742
+ const tokenResponse = await fetchWithTimeout(OPENAI_OAUTH_TOKEN_URL, {
743
+ method: "POST",
744
+ headers: {
745
+ "Content-Type": "application/x-www-form-urlencoded"
746
+ },
747
+ body: buildTokenBody({
748
+ grant_type: "authorization_code",
749
+ client_id: OPENAI_CODEX_CLIENT_ID,
750
+ code: result.code,
751
+ redirect_uri: callback.redirectUri,
752
+ code_verifier: verifier
753
+ })
754
+ }, "OpenAI ChatGPT token exchange");
755
+ const tokenPayload = await parseJsonResponse(
756
+ tokenResponse,
757
+ "OpenAI ChatGPT token exchange"
758
+ );
759
+ const accessToken = tokenPayload.access_token;
760
+ const refreshToken = tokenPayload.refresh_token;
761
+ const idToken = tokenPayload.id_token;
762
+ const accountId = idToken && extractChatGPTAccountId(idToken) || accessToken && extractChatGPTAccountId(accessToken);
763
+ if (!accessToken || !accountId) {
764
+ throw new Error("OpenAI ChatGPT token exchange returned no usable account credentials.");
765
+ }
766
+ const expiresAt = tokenPayload.expires_in ? new Date(Date.now() + tokenPayload.expires_in * 1e3).toISOString() : idToken && decodeJwtExpiry(idToken) || decodeJwtExpiry(accessToken);
767
+ return {
768
+ accessToken,
769
+ refreshToken,
770
+ idToken,
771
+ accountId,
772
+ expiresAt,
773
+ lastRefresh: (/* @__PURE__ */ new Date()).toISOString()
774
+ };
775
+ } finally {
776
+ await callback.close();
777
+ }
778
+ }
779
+ async function refreshChatGPTAuth(auth) {
780
+ if (!auth.refreshToken) {
781
+ throw new Error("ChatGPT refresh token is missing. Sign in again.");
782
+ }
783
+ const response = await fetchWithTimeout(OPENAI_OAUTH_TOKEN_URL, {
784
+ method: "POST",
785
+ headers: {
786
+ "Content-Type": "application/x-www-form-urlencoded"
787
+ },
788
+ body: buildTokenBody({
789
+ grant_type: "refresh_token",
790
+ client_id: OPENAI_CODEX_CLIENT_ID,
791
+ refresh_token: auth.refreshToken
792
+ })
793
+ }, "ChatGPT token refresh");
794
+ const payload = await parseJsonResponse(response, "ChatGPT token refresh");
795
+ if (!payload.access_token) {
796
+ throw new Error("ChatGPT token refresh returned no access token.");
797
+ }
798
+ const accountId = auth.accountId || payload.id_token && extractChatGPTAccountId(payload.id_token) || extractChatGPTAccountId(payload.access_token);
799
+ if (!accountId) {
800
+ throw new Error("ChatGPT token refresh returned no ChatGPT account ID.");
801
+ }
802
+ const expiresAt = payload.expires_in ? new Date(Date.now() + payload.expires_in * 1e3).toISOString() : payload.id_token && decodeJwtExpiry(payload.id_token) || decodeJwtExpiry(payload.access_token);
803
+ return {
804
+ accessToken: payload.access_token,
805
+ refreshToken: payload.refresh_token ?? auth.refreshToken,
806
+ idToken: payload.id_token ?? auth.idToken,
807
+ accountId,
808
+ expiresAt,
809
+ lastRefresh: (/* @__PURE__ */ new Date()).toISOString()
810
+ };
811
+ }
812
+
813
+ // src/providers/OpenAIProvider.ts
814
+ var OPENAI_MODELS = [
815
+ "gpt-5.4",
816
+ "gpt-5.4-pro",
817
+ "gpt-5.4-mini",
818
+ "gpt-5.4-nano",
819
+ "gpt-5.3-codex",
820
+ "gpt-5.1-codex-max"
821
+ ];
822
+ var VALID_REASONING_EFFORTS = /* @__PURE__ */ new Set(["none", "low", "medium", "high", "xhigh"]);
823
+ var OPENAI_API_BASE_URL = "https://api.openai.com/v1";
824
+ var OPENAI_CODEX_BASE_URL = "https://chatgpt.com/backend-api/codex";
825
+ var DEFAULT_CODEX_INSTRUCTIONS = "You are Autohand, a coding assistant. Follow the repository instructions and help the user complete software tasks.";
826
+ var OPENAI_API_KEY_FRIENDLY_MESSAGES = {
827
+ auth_failed: "Authentication failed. Please verify your OpenAI API key in ~/.autohand/config.json.",
828
+ payment_required: "Payment required. Please check your OpenAI account balance or billing settings.",
829
+ access_denied: "Access denied. Your OpenAI API key may not have permission for this model.",
830
+ server_error: "The OpenAI service encountered an error. Please try again later.",
831
+ network_error: "Unable to connect to OpenAI. Please check your internet connection and OpenAI API configuration.",
832
+ timeout: "The request timed out. The OpenAI service may be experiencing high load."
833
+ };
834
+ var OPENAI_CHATGPT_FRIENDLY_MESSAGES = {
835
+ auth_failed: "ChatGPT authentication failed. Please sign in again.",
836
+ access_denied: "Access denied. Your ChatGPT account may not have access to this model or Codex backend.",
837
+ server_error: "The ChatGPT Codex service encountered an error. Please try again later.",
838
+ network_error: "Unable to connect to ChatGPT Codex. Please check your internet connection.",
839
+ timeout: "The request timed out. The ChatGPT Codex service may be experiencing high load."
840
+ };
841
+ var OpenAIProvider = class {
842
+ baseUrl;
843
+ apiKey;
844
+ model;
845
+ reasoningEffort;
846
+ authMode;
847
+ chatgptAuth;
848
+ constructor(config) {
849
+ this.authMode = config.authMode === "chatgpt" ? "chatgpt" : "api-key";
850
+ this.baseUrl = this.resolveBaseUrl(config.baseUrl);
851
+ this.apiKey = config.apiKey || "";
852
+ this.model = config.model || "gpt-5.4";
853
+ this.reasoningEffort = config.reasoningEffort;
854
+ this.chatgptAuth = config.chatgptAuth;
855
+ }
856
+ getName() {
857
+ return "openai";
858
+ }
859
+ setModel(model) {
860
+ this.model = model;
861
+ }
862
+ async listModels() {
863
+ return [...OPENAI_MODELS];
864
+ }
865
+ async isAvailable() {
866
+ if (this.authMode === "chatgpt") {
867
+ return !!this.chatgptAuth?.accessToken && !!this.chatgptAuth?.accountId;
868
+ }
869
+ try {
870
+ const headers = await this.buildAuthHeaders();
871
+ const response = await fetch(`${this.baseUrl}/models`, {
872
+ headers
873
+ });
874
+ return response.ok;
875
+ } catch {
876
+ return false;
877
+ }
878
+ }
879
+ async complete(request) {
880
+ if (this.authMode === "chatgpt") {
881
+ return this.completeWithResponsesApi(request);
882
+ }
883
+ const body = {
884
+ model: request.model || this.model,
885
+ messages: request.messages.map((msg) => {
886
+ const mapped = {
887
+ role: msg.role === "system" ? "system" : msg.role === "user" ? "user" : msg.role === "tool" ? "tool" : "assistant",
888
+ content: msg.content
889
+ };
890
+ if (msg.role === "assistant" && msg.tool_calls?.length) {
891
+ mapped.tool_calls = msg.tool_calls;
892
+ }
893
+ if (msg.role === "tool" && msg.tool_call_id) {
894
+ mapped.tool_call_id = msg.tool_call_id;
895
+ }
896
+ if (msg.name) {
897
+ mapped.name = msg.name;
898
+ }
899
+ return mapped;
900
+ }),
901
+ temperature: request.temperature || 0.7,
902
+ // Newer OpenAI models (gpt-5.x, o-series) require max_completion_tokens
903
+ // instead of max_tokens. Use the correct parameter based on model.
904
+ ...this.usesMaxCompletionTokens(request.model || this.model) ? { max_completion_tokens: request.maxTokens } : { max_tokens: request.maxTokens }
905
+ };
906
+ if (this.reasoningEffort && VALID_REASONING_EFFORTS.has(this.reasoningEffort)) {
907
+ body.reasoning_effort = this.reasoningEffort;
908
+ }
909
+ if (request.tools && request.tools.length > 0) {
910
+ body.tools = request.tools.map((tool) => ({
911
+ type: "function",
912
+ function: {
913
+ name: tool.name,
914
+ description: tool.description,
915
+ parameters: tool.parameters ?? { type: "object", properties: {} }
916
+ }
917
+ }));
918
+ if (request.toolChoice) {
919
+ body.tool_choice = request.toolChoice;
920
+ }
921
+ }
922
+ let response;
923
+ const headers = await this.buildAuthHeaders();
924
+ try {
925
+ response = await fetch(`${this.baseUrl}/chat/completions`, {
926
+ method: "POST",
927
+ headers: {
928
+ "Content-Type": "application/json",
929
+ ...headers
930
+ },
931
+ body: JSON.stringify(body),
932
+ signal: request.signal
933
+ });
934
+ } catch (error) {
935
+ const err = error;
936
+ if (err.name === "AbortError" && request.signal?.aborted) {
937
+ throw new ApiError("Request cancelled.", "cancelled", 0, false);
938
+ }
939
+ if (err.name === "AbortError") {
940
+ throw new ApiError(
941
+ "The request timed out. The OpenAI service may be experiencing high load.",
942
+ "timeout",
943
+ 0,
944
+ true
945
+ );
946
+ }
947
+ throw new ApiError(
948
+ `Unable to connect to ${this.baseUrl}. Please check the URL and your internet connection.`,
949
+ "network_error",
950
+ 0,
951
+ true
952
+ );
953
+ }
954
+ if (!response.ok) {
955
+ throw await this.buildApiError(response);
956
+ }
957
+ const data = await response.json();
958
+ const message = data.choices[0].message;
959
+ const finishReason = data.choices[0].finish_reason;
960
+ let toolCalls;
961
+ if (message.tool_calls && Array.isArray(message.tool_calls)) {
962
+ toolCalls = message.tool_calls.map((tc) => ({
963
+ id: tc.id,
964
+ type: "function",
965
+ function: {
966
+ name: tc.function.name,
967
+ arguments: tc.function.arguments
968
+ }
969
+ }));
970
+ }
971
+ let usage;
972
+ if (data.usage) {
973
+ usage = {
974
+ promptTokens: data.usage.prompt_tokens,
975
+ completionTokens: data.usage.completion_tokens,
976
+ totalTokens: data.usage.total_tokens
977
+ };
978
+ }
979
+ return {
980
+ id: data.id,
981
+ created: data.created,
982
+ content: message.content ?? "",
983
+ toolCalls,
984
+ finishReason,
985
+ usage,
986
+ raw: data
987
+ };
988
+ }
989
+ async completeWithResponsesApi(request) {
990
+ const instructions = this.buildCodexInstructions(request.messages);
991
+ const body = {
992
+ model: request.model || this.model,
993
+ instructions,
994
+ store: false,
995
+ stream: true,
996
+ tool_choice: "auto",
997
+ parallel_tool_calls: true,
998
+ input: request.messages.flatMap((msg) => this.toResponsesInputItems(msg))
999
+ };
1000
+ if (this.reasoningEffort && VALID_REASONING_EFFORTS.has(this.reasoningEffort)) {
1001
+ body.reasoning = {
1002
+ effort: this.reasoningEffort
1003
+ };
1004
+ body.include = ["reasoning.encrypted_content"];
1005
+ }
1006
+ if (request.tools && request.tools.length > 0) {
1007
+ body.tools = request.tools.map((tool) => ({
1008
+ type: "function",
1009
+ name: tool.name,
1010
+ description: tool.description,
1011
+ parameters: tool.parameters ?? { type: "object", properties: {} }
1012
+ }));
1013
+ if (request.toolChoice === "required") {
1014
+ body.tool_choice = "required";
1015
+ } else if (request.toolChoice === "none") {
1016
+ body.tool_choice = "none";
1017
+ } else if (request.toolChoice && typeof request.toolChoice === "object") {
1018
+ body.tool_choice = {
1019
+ type: "function",
1020
+ name: request.toolChoice.function.name
1021
+ };
1022
+ }
1023
+ }
1024
+ const headers = await this.buildAuthHeaders();
1025
+ let response;
1026
+ try {
1027
+ response = await fetch(`${this.baseUrl}/responses`, {
1028
+ method: "POST",
1029
+ headers: {
1030
+ "Content-Type": "application/json",
1031
+ ...headers
1032
+ },
1033
+ body: JSON.stringify(body),
1034
+ signal: request.signal
1035
+ });
1036
+ } catch (error) {
1037
+ const err = error;
1038
+ if (err.name === "AbortError" && request.signal?.aborted) {
1039
+ throw new ApiError("Request cancelled.", "cancelled", 0, false);
1040
+ }
1041
+ if (err.name === "AbortError") {
1042
+ throw new ApiError(
1043
+ "The request timed out. The ChatGPT Codex service may be experiencing high load.",
1044
+ "timeout",
1045
+ 0,
1046
+ true
1047
+ );
1048
+ }
1049
+ throw new ApiError(
1050
+ `Unable to connect to ${this.baseUrl}. Please check the URL and your internet connection.`,
1051
+ "network_error",
1052
+ 0,
1053
+ true
1054
+ );
1055
+ }
1056
+ if (!response.ok) {
1057
+ throw await this.buildApiError(response);
1058
+ }
1059
+ const data = await this.parseCodexStream(response);
1060
+ const toolCalls = this.extractResponsesToolCalls(data.output);
1061
+ const content = this.extractResponsesContent(data);
1062
+ const usage = data.usage ? {
1063
+ promptTokens: data.usage.input_tokens ?? 0,
1064
+ completionTokens: data.usage.output_tokens ?? 0,
1065
+ totalTokens: data.usage.total_tokens ?? (data.usage.input_tokens ?? 0) + (data.usage.output_tokens ?? 0)
1066
+ } : void 0;
1067
+ return {
1068
+ id: data.id,
1069
+ created: data.created_at ?? Math.floor(Date.now() / 1e3),
1070
+ content,
1071
+ toolCalls,
1072
+ finishReason: toolCalls.length > 0 ? "tool_calls" : data.incomplete_details?.reason === "max_output_tokens" ? "length" : "stop",
1073
+ usage,
1074
+ raw: data
1075
+ };
1076
+ }
1077
+ async buildApiError(response) {
1078
+ let errorDetail = "";
1079
+ try {
1080
+ const body = await response.json();
1081
+ const errObj = body?.error;
1082
+ errorDetail = errObj?.message ?? body?.detail ?? body?.error ?? "";
1083
+ if (typeof errorDetail === "object") {
1084
+ errorDetail = JSON.stringify(errorDetail);
1085
+ }
1086
+ } catch {
1087
+ try {
1088
+ errorDetail = await response.text();
1089
+ } catch {
1090
+ }
1091
+ }
1092
+ return this.withOpenAIMessage(classifyApiError(response.status, errorDetail, response.headers));
1093
+ }
1094
+ withOpenAIMessage(error) {
1095
+ const messages = this.authMode === "chatgpt" ? OPENAI_CHATGPT_FRIENDLY_MESSAGES : OPENAI_API_KEY_FRIENDLY_MESSAGES;
1096
+ const friendlyMessage = messages[error.code];
1097
+ if (!friendlyMessage) {
1098
+ return error;
1099
+ }
1100
+ return new ApiError(
1101
+ error.rawDetail ? `${friendlyMessage}
1102
+ ${error.rawDetail}` : friendlyMessage,
1103
+ error.code,
1104
+ error.httpStatus,
1105
+ error.retryable,
1106
+ error.retryAfterMs,
1107
+ error.rawDetail
1108
+ );
1109
+ }
1110
+ /**
1111
+ * Parse an SSE stream from the ChatGPT Codex backend and extract the
1112
+ * `response.completed` event payload as the full response object.
1113
+ */
1114
+ async parseCodexStream(response) {
1115
+ const text = await response.text();
1116
+ let currentEvent = "";
1117
+ let completedData = null;
1118
+ for (const line of text.split("\n")) {
1119
+ if (line.startsWith("event: ")) {
1120
+ currentEvent = line.slice(7).trim();
1121
+ continue;
1122
+ }
1123
+ if (line.startsWith("data: ") && currentEvent === "response.completed") {
1124
+ completedData = JSON.parse(line.slice(6));
1125
+ break;
1126
+ }
1127
+ }
1128
+ if (!completedData) {
1129
+ throw new ApiError(
1130
+ "No response.completed event found in stream. The API response may be malformed.",
1131
+ "invalid_request",
1132
+ 0,
1133
+ false
1134
+ );
1135
+ }
1136
+ return completedData;
1137
+ }
1138
+ async buildAuthHeaders() {
1139
+ if (this.authMode === "chatgpt") {
1140
+ if (!this.chatgptAuth?.accessToken || !this.chatgptAuth.accountId) {
1141
+ throw new ApiError("ChatGPT authentication is missing. Please sign in again.", "auth_failed", 401, false);
1142
+ }
1143
+ if (isChatGPTAuthExpired(this.chatgptAuth)) {
1144
+ this.chatgptAuth = await refreshChatGPTAuth(this.chatgptAuth);
1145
+ }
1146
+ return {
1147
+ Authorization: `Bearer ${this.chatgptAuth.accessToken}`,
1148
+ "chatgpt-account-id": this.chatgptAuth.accountId
1149
+ };
1150
+ }
1151
+ return {
1152
+ Authorization: `Bearer ${this.apiKey}`
1153
+ };
1154
+ }
1155
+ resolveBaseUrl(configBaseUrl) {
1156
+ if (this.authMode === "chatgpt") {
1157
+ if (!configBaseUrl || configBaseUrl === OPENAI_API_BASE_URL) {
1158
+ return OPENAI_CODEX_BASE_URL;
1159
+ }
1160
+ return configBaseUrl.replace(/\/$/, "");
1161
+ }
1162
+ return (configBaseUrl || OPENAI_API_BASE_URL).replace(/\/$/, "");
1163
+ }
1164
+ toResponsesInputItems(msg) {
1165
+ const items = [];
1166
+ if (msg.role === "system") {
1167
+ return items;
1168
+ }
1169
+ if (msg.role === "tool" && msg.tool_call_id) {
1170
+ items.push({
1171
+ type: "function_call_output",
1172
+ call_id: msg.tool_call_id,
1173
+ output: msg.content
1174
+ });
1175
+ return items;
1176
+ }
1177
+ if (msg.content) {
1178
+ items.push({
1179
+ type: "message",
1180
+ role: msg.role === "tool" ? "user" : msg.role,
1181
+ content: [{ type: "input_text", text: msg.content }]
1182
+ });
1183
+ }
1184
+ if (msg.role === "assistant" && msg.tool_calls?.length) {
1185
+ for (const toolCall of msg.tool_calls) {
1186
+ items.push({
1187
+ type: "function_call",
1188
+ call_id: toolCall.id,
1189
+ name: toolCall.function.name,
1190
+ arguments: toolCall.function.arguments
1191
+ });
1192
+ }
1193
+ }
1194
+ return items;
1195
+ }
1196
+ buildCodexInstructions(messages) {
1197
+ const systemMessages = messages.filter((msg) => msg.role === "system" && typeof msg.content === "string" && msg.content.trim()).map((msg) => msg.content.trim());
1198
+ if (systemMessages.length === 0) {
1199
+ return DEFAULT_CODEX_INSTRUCTIONS;
1200
+ }
1201
+ return [DEFAULT_CODEX_INSTRUCTIONS, ...systemMessages].join("\n\n");
1202
+ }
1203
+ extractResponsesToolCalls(output) {
1204
+ if (!Array.isArray(output)) {
1205
+ return [];
1206
+ }
1207
+ return output.filter((entry) => entry?.type === "function_call").map((toolCall, index) => ({
1208
+ id: toolCall.call_id ?? `call_${index + 1}`,
1209
+ type: "function",
1210
+ function: {
1211
+ name: toolCall.name,
1212
+ arguments: toolCall.arguments
1213
+ }
1214
+ }));
1215
+ }
1216
+ /**
1217
+ * Determine if a model requires `max_completion_tokens` instead of `max_tokens`.
1218
+ * OpenAI's newer models (gpt-5.x, o-series) reject `max_tokens` with a 400 error.
1219
+ */
1220
+ usesMaxCompletionTokens(model) {
1221
+ const lower = model.toLowerCase();
1222
+ return lower.startsWith("gpt-5") || lower.startsWith("o1") || lower.startsWith("o3") || lower.startsWith("o4");
1223
+ }
1224
+ extractResponsesContent(data) {
1225
+ if (typeof data.output_text === "string" && data.output_text.trim()) {
1226
+ return data.output_text;
1227
+ }
1228
+ if (!Array.isArray(data.output)) {
1229
+ return "";
1230
+ }
1231
+ const parts = [];
1232
+ for (const item of data.output) {
1233
+ if (item?.type !== "message" || !Array.isArray(item.content)) {
1234
+ continue;
1235
+ }
1236
+ for (const contentItem of item.content) {
1237
+ if (contentItem?.type === "output_text" && typeof contentItem.text === "string") {
1238
+ parts.push(contentItem.text);
1239
+ }
1240
+ }
1241
+ }
1242
+ return parts.join("\n").trim();
1243
+ }
1244
+ };
1245
+
1246
+ // src/providers/LlamaCppProvider.ts
1247
+ var LlamaCppProvider = class _LlamaCppProvider {
1248
+ baseUrl;
1249
+ model;
1250
+ static DEFAULT_MODEL = "local";
1251
+ constructor(config) {
1252
+ const port = config.port || 8080;
1253
+ this.baseUrl = config.baseUrl || `http://localhost:${port}`;
1254
+ this.model = config.model || _LlamaCppProvider.DEFAULT_MODEL;
1255
+ }
1256
+ getName() {
1257
+ return "llamacpp";
1258
+ }
1259
+ setModel(model) {
1260
+ this.model = model;
1261
+ }
1262
+ async listModels() {
1263
+ try {
1264
+ const response = await fetch(`${this.baseUrl}/v1/models`);
1265
+ if (!response.ok) {
1266
+ return this.model ? [this.model] : [];
1267
+ }
1268
+ const data = await response.json();
1269
+ return data.data?.map((m) => m.id) ?? [this.model];
1270
+ } catch {
1271
+ return this.model ? [this.model] : [];
1272
+ }
1273
+ }
1274
+ async isAvailable() {
1275
+ try {
1276
+ const response = await fetch(`${this.baseUrl}/health`);
1277
+ return response.ok;
1278
+ } catch {
1279
+ return false;
1280
+ }
1281
+ }
1282
+ async complete(request) {
1283
+ const body = {
1284
+ model: request.model || this.model || _LlamaCppProvider.DEFAULT_MODEL,
1285
+ messages: request.messages.map((msg) => {
1286
+ const mapped = {
1287
+ role: msg.role,
1288
+ content: msg.content
1289
+ };
1290
+ if (msg.name) mapped.name = msg.name;
1291
+ if (msg.role === "tool" && msg.tool_call_id) mapped.tool_call_id = msg.tool_call_id;
1292
+ if (msg.role === "assistant" && msg.tool_calls) mapped.tool_calls = msg.tool_calls;
1293
+ return mapped;
1294
+ }),
1295
+ temperature: request.temperature ?? 0.7,
1296
+ max_tokens: request.maxTokens ?? 4096,
1297
+ stream: false
1298
+ };
1299
+ if (request.tools && request.tools.length > 0) {
1300
+ body.tools = request.tools.map((tool) => ({
1301
+ type: "function",
1302
+ function: {
1303
+ name: tool.name,
1304
+ description: tool.description,
1305
+ parameters: tool.parameters ?? { type: "object", properties: {} }
1306
+ }
1307
+ }));
1308
+ }
1309
+ const response = await fetch(`${this.baseUrl}/v1/chat/completions`, {
1310
+ method: "POST",
1311
+ headers: {
1312
+ "Content-Type": "application/json"
1313
+ },
1314
+ body: JSON.stringify(body),
1315
+ signal: request.signal
1316
+ });
1317
+ if (!response.ok) {
1318
+ throw await this.buildApiError(response, body);
1319
+ }
1320
+ const data = await response.json();
1321
+ const choice = data.choices[0];
1322
+ let toolCalls;
1323
+ if (choice?.message.tool_calls?.length) {
1324
+ toolCalls = choice.message.tool_calls.map((tc) => ({
1325
+ id: tc.id,
1326
+ type: "function",
1327
+ function: {
1328
+ name: tc.function.name,
1329
+ arguments: tc.function.arguments
1330
+ }
1331
+ }));
1332
+ }
1333
+ let usage;
1334
+ if (data.usage) {
1335
+ usage = {
1336
+ promptTokens: data.usage.prompt_tokens,
1337
+ completionTokens: data.usage.completion_tokens,
1338
+ totalTokens: data.usage.total_tokens
1339
+ };
1340
+ }
1341
+ const finishReason = toolCalls?.length ? "tool_calls" : choice?.finish_reason === "stop" || choice?.finish_reason === "length" || choice?.finish_reason === "content_filter" ? choice.finish_reason : "stop";
1342
+ return {
1343
+ id: data.id || `llamacpp-${Date.now()}`,
1344
+ created: data.created || Math.floor(Date.now() / 1e3),
1345
+ content: choice?.message.content ?? "",
1346
+ toolCalls,
1347
+ finishReason,
1348
+ usage,
1349
+ raw: data
1350
+ };
1351
+ }
1352
+ async buildApiError(response, body) {
1353
+ let errorBody = "";
1354
+ try {
1355
+ errorBody = await response.text();
1356
+ } catch {
1357
+ }
1358
+ const lowerBody = errorBody.toLowerCase();
1359
+ if (body.tools && response.status === 400 && (lowerBody.includes("tool") || lowerBody.includes("function") || lowerBody.includes("schema"))) {
1360
+ return new ApiError(
1361
+ `llama.cpp rejected tool-enabled requests. If you want tool support, start llama-server with function-calling settings such as '--jinja -fa' and, if needed, '--chat-template chatml' or '--chat-template-file /path/to/tool_use.jinja'.
1362
+ ${errorBody}`,
1363
+ "invalid_request",
1364
+ response.status,
1365
+ false,
1366
+ void 0,
1367
+ errorBody
1368
+ );
1369
+ }
1370
+ const baseError = classifyApiError(response.status, errorBody, response.headers);
1371
+ return new ApiError(
1372
+ `llama.cpp API error: ${response.status} ${response.statusText}${errorBody ? `
1373
+ ${errorBody}` : ""}`,
1374
+ baseError.code,
1375
+ baseError.httpStatus,
1376
+ baseError.retryable,
1377
+ baseError.retryAfterMs,
1378
+ errorBody
1379
+ );
1380
+ }
1381
+ };
1382
+
1383
+ // src/providers/modelCapabilities.ts
1384
+ var OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
1385
+ var CACHE_TTL_MS = 30 * 60 * 1e3;
1386
+ var cache = null;
1387
+ function normalizeModelId(model) {
1388
+ return model.trim().toLowerCase();
1389
+ }
1390
+ function getCachedModels() {
1391
+ if (!cache) {
1392
+ return null;
1393
+ }
1394
+ if (Date.now() - cache.fetchedAt >= CACHE_TTL_MS) {
1395
+ return null;
1396
+ }
1397
+ return cache.models;
1398
+ }
1399
+ function findModelCapability(models, model) {
1400
+ const normalizedModel = normalizeModelId(model);
1401
+ const exactMatch = models.find((candidate) => {
1402
+ const candidateIds = [
1403
+ candidate.id,
1404
+ candidate.canonical_slug,
1405
+ candidate.name
1406
+ ].filter((value) => Boolean(value)).map(normalizeModelId);
1407
+ return candidateIds.includes(normalizedModel);
1408
+ });
1409
+ if (exactMatch) {
1410
+ return exactMatch;
1411
+ }
1412
+ return models.find((candidate) => {
1413
+ const candidateIds = [
1414
+ candidate.id,
1415
+ candidate.canonical_slug,
1416
+ candidate.name
1417
+ ].filter((value) => Boolean(value)).map(normalizeModelId);
1418
+ return candidateIds.some(
1419
+ (candidateId) => candidateId.includes(normalizedModel) || normalizedModel.includes(candidateId)
1420
+ );
1421
+ });
1422
+ }
1423
+ function getInputModalities(capability) {
1424
+ if (!capability) {
1425
+ return [];
1426
+ }
1427
+ if (Array.isArray(capability.input_modalities)) {
1428
+ return capability.input_modalities;
1429
+ }
1430
+ if (Array.isArray(capability.architecture?.input_modalities)) {
1431
+ return capability.architecture.input_modalities;
1432
+ }
1433
+ return [];
1434
+ }
1435
+ function getOpenRouterCapabilityContextWindow(capability) {
1436
+ const contextWindow = capability?.top_provider?.context_length ?? capability?.context_length;
1437
+ return typeof contextWindow === "number" && Number.isFinite(contextWindow) && contextWindow > 0 ? Math.floor(contextWindow) : void 0;
1438
+ }
1439
+ async function getOpenRouterModelContextWindow(model) {
1440
+ const capability = await findCapabilityForModel(model);
1441
+ return getOpenRouterCapabilityContextWindow(capability);
1442
+ }
1443
+ async function findCapabilityForModel(model) {
1444
+ const cachedModels = getCachedModels();
1445
+ if (cachedModels) {
1446
+ const cachedMatch = findModelCapability(cachedModels, model);
1447
+ if (cachedMatch) {
1448
+ return cachedMatch;
1449
+ }
1450
+ const refreshedModels = await fetchOpenRouterModelCapabilities(true);
1451
+ return findModelCapability(refreshedModels, model);
1452
+ }
1453
+ const models = await fetchOpenRouterModelCapabilities();
1454
+ return findModelCapability(models, model);
1455
+ }
1456
+ async function fetchOpenRouterModelCapabilities(forceRefresh = false) {
1457
+ if (!forceRefresh && cache && Date.now() - cache.fetchedAt < CACHE_TTL_MS) {
1458
+ return cache.models;
1459
+ }
1460
+ try {
1461
+ const response = await fetch(OPENROUTER_MODELS_URL, {
1462
+ headers: {
1463
+ "Content-Type": "application/json"
1464
+ },
1465
+ signal: AbortSignal.timeout(1e4)
1466
+ // 10s timeout
1467
+ });
1468
+ if (!response.ok) {
1469
+ throw new Error(`Failed to fetch model capabilities: ${response.status} ${response.statusText}`);
1470
+ }
1471
+ const data = await response.json();
1472
+ const models = Array.isArray(data?.data) ? data.data : [];
1473
+ cache = {
1474
+ models,
1475
+ fetchedAt: Date.now()
1476
+ };
1477
+ return models;
1478
+ } catch (error) {
1479
+ if (cache) {
1480
+ return cache.models;
1481
+ }
1482
+ throw error;
1483
+ }
1484
+ }
1485
+ async function modelSupportsImages(model) {
1486
+ const lowerModel = model.toLowerCase();
1487
+ try {
1488
+ const found = await findCapabilityForModel(model);
1489
+ const inputModalities = getInputModalities(found);
1490
+ if (inputModalities.length > 0) {
1491
+ return inputModalities.includes("image");
1492
+ }
1493
+ if (found) {
1494
+ return quickVisionCheck(found.id.toLowerCase());
1495
+ }
1496
+ return quickVisionCheck(lowerModel);
1497
+ } catch {
1498
+ return quickVisionCheck(lowerModel);
1499
+ }
1500
+ }
1501
+ function quickVisionCheck(lowerModel) {
1502
+ if (lowerModel.includes("claude-3") || lowerModel.includes("claude-4") || lowerModel.includes("claude-sonnet-4") || lowerModel.includes("claude-opus-4") || lowerModel.includes("claude-opus-4-7")) {
1503
+ return true;
1504
+ }
1505
+ if (lowerModel.includes("gpt-4o") || lowerModel.includes("gpt-4-turbo") || lowerModel.includes("gpt-4-vision") || lowerModel.includes("gpt-4.5") || lowerModel.includes("chatgpt-4o")) {
1506
+ return true;
1507
+ }
1508
+ if (lowerModel.includes("gemini") && !lowerModel.includes("gemini-pro")) {
1509
+ return true;
1510
+ }
1511
+ if (lowerModel.includes("gemini-1.5") || lowerModel.includes("gemini-2.0") || lowerModel.includes("gemini-2.5") || lowerModel.includes("gemini-pro-vision")) {
1512
+ return true;
1513
+ }
1514
+ if (lowerModel.includes("llama-3.2") || lowerModel.includes("llama-3.3") || lowerModel.includes("llama-4")) {
1515
+ if (lowerModel.includes("vision") || lowerModel.includes("multimodal")) {
1516
+ return true;
1517
+ }
1518
+ }
1519
+ if (lowerModel.includes("pixtral")) {
1520
+ return true;
1521
+ }
1522
+ if (lowerModel.includes("qwen") && lowerModel.includes("vl")) {
1523
+ return true;
1524
+ }
1525
+ if (lowerModel.includes("minicpm") && lowerModel.includes("v")) {
1526
+ return true;
1527
+ }
1528
+ if (lowerModel.includes("command-r") && lowerModel.includes("vision")) {
1529
+ return true;
1530
+ }
1531
+ if (lowerModel.includes("deepseek") && lowerModel.includes("vl")) {
1532
+ return true;
1533
+ }
1534
+ if (lowerModel.includes("vision") || lowerModel.includes("vl-") || lowerModel.includes("-vl") || lowerModel.includes("multimodal")) {
1535
+ return true;
1536
+ }
1537
+ return false;
1538
+ }
1539
+
1540
+ // src/providers/OpenRouterClient.ts
1541
+ function messageContainsImageContent(messages) {
1542
+ return messages.some(
1543
+ (msg) => Array.isArray(msg.content) && msg.content.some(
1544
+ (part) => typeof part === "object" && part !== null && "type" in part && part.type === "image_url"
1545
+ )
1546
+ );
1547
+ }
1548
+ function getTextContent(content) {
1549
+ if (typeof content === "string") {
1550
+ return content;
1551
+ }
1552
+ if (!Array.isArray(content)) {
1553
+ return "";
1554
+ }
1555
+ return content.filter(
1556
+ (part) => typeof part === "object" && part !== null && "type" in part
1557
+ ).filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text ?? "").join("\n");
1558
+ }
1559
+ function sanitizeMessages(messages, allowImageInputs) {
1560
+ return messages.map((msg) => {
1561
+ const sanitized = {
1562
+ role: msg.role,
1563
+ content: allowImageInputs || !Array.isArray(msg.content) ? msg.content : getTextContent(msg.content)
1564
+ };
1565
+ if (msg.role === "tool" && msg.tool_call_id) {
1566
+ sanitized.tool_call_id = msg.tool_call_id;
1567
+ }
1568
+ if (msg.role === "assistant" && msg.tool_calls?.length) {
1569
+ sanitized.tool_calls = msg.tool_calls;
1570
+ }
1571
+ if (msg.name) {
1572
+ sanitized.name = msg.name;
1573
+ }
1574
+ return sanitized;
1575
+ });
1576
+ }
1577
+ var DEFAULT_BASE_URL = "https://openrouter.ai/api/v1";
1578
+ var DEFAULT_MAX_RETRIES2 = 3;
1579
+ var MAX_ALLOWED_RETRIES2 = 5;
1580
+ var DEFAULT_RETRY_DELAY2 = 1e3;
1581
+ var DEFAULT_TIMEOUT2 = 3e4;
1582
+ var OPENROUTER_FRIENDLY_MESSAGES = {
1583
+ auth_failed: "Authentication failed. Please verify your OpenRouter API key in ~/.autohand/config.json.",
1584
+ payment_required: "Payment required. Please check your OpenRouter account balance or billing settings.",
1585
+ access_denied: "Access denied. Your OpenRouter API key may not have permission for this model.",
1586
+ server_error: "The OpenRouter service encountered an error. Please try again later.",
1587
+ network_error: "Unable to connect to OpenRouter. Please check your internet connection.",
1588
+ timeout: "The request timed out. The OpenRouter service may be experiencing high load."
1589
+ };
1590
+ function withOpenRouterMessage(error) {
1591
+ const friendlyMessage = OPENROUTER_FRIENDLY_MESSAGES[error.code];
1592
+ if (!friendlyMessage) {
1593
+ return error;
1594
+ }
1595
+ return new ApiError(
1596
+ error.rawDetail ? `${friendlyMessage}
1597
+ ${error.rawDetail}` : friendlyMessage,
1598
+ error.code,
1599
+ error.httpStatus,
1600
+ error.retryable,
1601
+ error.retryAfterMs,
1602
+ error.rawDetail
1603
+ );
1604
+ }
1605
+ var OpenRouterClient = class {
1606
+ apiKey;
1607
+ baseUrl;
1608
+ defaultModel;
1609
+ maxRetries;
1610
+ retryDelay;
1611
+ timeout;
1612
+ constructor(settings, networkSettings) {
1613
+ this.apiKey = settings.apiKey ?? "";
1614
+ this.baseUrl = settings.baseUrl ?? DEFAULT_BASE_URL;
1615
+ this.defaultModel = settings.model;
1616
+ const configuredRetries = networkSettings?.maxRetries ?? DEFAULT_MAX_RETRIES2;
1617
+ this.maxRetries = Math.min(
1618
+ Math.max(0, configuredRetries),
1619
+ MAX_ALLOWED_RETRIES2
1620
+ );
1621
+ this.retryDelay = networkSettings?.retryDelay ?? DEFAULT_RETRY_DELAY2;
1622
+ this.timeout = networkSettings?.timeout ?? DEFAULT_TIMEOUT2;
1623
+ }
1624
+ setDefaultModel(model) {
1625
+ this.defaultModel = model;
1626
+ }
1627
+ async complete(request) {
1628
+ const selectedModel = request.model ?? this.defaultModel;
1629
+ const allowImageInputs = messageContainsImageContent(request.messages) ? await modelSupportsImages(selectedModel) : false;
1630
+ const payload = {
1631
+ model: selectedModel,
1632
+ messages: sanitizeMessages(request.messages, allowImageInputs),
1633
+ temperature: request.temperature ?? 0.2,
1634
+ max_tokens: request.maxTokens ?? 16e3,
1635
+ // Increased from 1000 to allow large file generation
1636
+ stream: request.stream ?? false
1637
+ };
1638
+ if (request.tools && request.tools.length > 0) {
1639
+ payload.tools = request.tools.map((tool) => ({
1640
+ type: "function",
1641
+ function: {
1642
+ name: tool.name,
1643
+ description: tool.description,
1644
+ parameters: tool.parameters ?? { type: "object", properties: {} }
1645
+ }
1646
+ }));
1647
+ if (request.toolChoice) {
1648
+ payload.tool_choice = request.toolChoice;
1649
+ }
1650
+ }
1651
+ const model = selectedModel.toLowerCase();
1652
+ if (request.thinkingLevel && request.thinkingLevel !== "normal") {
1653
+ if (model.includes("o1") || model.includes("o3")) {
1654
+ if (request.thinkingLevel === "extended") {
1655
+ payload.reasoning_effort = "high";
1656
+ } else if (request.thinkingLevel === "none") {
1657
+ payload.reasoning_effort = "low";
1658
+ }
1659
+ }
1660
+ if (model.includes("claude") && request.thinkingLevel === "extended") {
1661
+ payload.provider = {
1662
+ anthropic: {
1663
+ thinking: {
1664
+ type: "enabled",
1665
+ budget_tokens: 1e4
1666
+ }
1667
+ }
1668
+ };
1669
+ }
1670
+ }
1671
+ const headers = {
1672
+ "Content-Type": "application/json",
1673
+ "HTTP-Referer": "https://autohand.dev",
1674
+ "X-OpenRouter-Title": "Autohand Code CLI",
1675
+ "X-OpenRouter-Categories": "cli-agent"
1676
+ };
1677
+ if (this.apiKey) {
1678
+ headers.Authorization = `Bearer ${this.apiKey}`;
1679
+ }
1680
+ const payloadJson = JSON.stringify(payload);
1681
+ const payloadSizeBytes = payloadJson.length;
1682
+ const maxPayloadSize = 5 * 1024 * 1024;
1683
+ if (payloadSizeBytes > maxPayloadSize) {
1684
+ const sizeMB = (payloadSizeBytes / (1024 * 1024)).toFixed(2);
1685
+ throw new ApiError(
1686
+ `Request payload too large (${sizeMB}MB). This usually happens when the conversation history grows too long. Try using /undo to remove recent turns or /new to start fresh.`,
1687
+ "context_overflow",
1688
+ 400,
1689
+ true
1690
+ );
1691
+ }
1692
+ let lastError = null;
1693
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
1694
+ try {
1695
+ const response = await this.makeRequest(
1696
+ payload,
1697
+ headers,
1698
+ request.signal,
1699
+ payloadJson
1700
+ );
1701
+ return response;
1702
+ } catch (error) {
1703
+ lastError = error;
1704
+ if (this.isNonRetryableError(error)) {
1705
+ throw error;
1706
+ }
1707
+ if (attempt < this.maxRetries) {
1708
+ const retryAfterMs = error instanceof ApiError ? error.retryAfterMs : void 0;
1709
+ const delay = Math.max(
1710
+ this.retryDelay * Math.pow(2, attempt),
1711
+ retryAfterMs ?? 0
1712
+ );
1713
+ await this.sleep(delay);
1714
+ }
1715
+ }
1716
+ }
1717
+ throw lastError ?? new ApiError("Failed to communicate with the AI service. Please try again.", "network_error", 0, true);
1718
+ }
1719
+ async makeRequest(payload, headers, signal, preSerializedBody) {
1720
+ let response;
1721
+ try {
1722
+ const timeoutController = new AbortController();
1723
+ const timeoutId = setTimeout(
1724
+ () => timeoutController.abort(),
1725
+ this.timeout
1726
+ );
1727
+ const combinedSignal = signal ? this.combineSignals(signal, timeoutController.signal) : timeoutController.signal;
1728
+ try {
1729
+ response = await fetch(`${this.baseUrl}/chat/completions`, {
1730
+ method: "POST",
1731
+ headers,
1732
+ body: preSerializedBody ?? JSON.stringify(payload),
1733
+ signal: combinedSignal
1734
+ });
1735
+ } finally {
1736
+ clearTimeout(timeoutId);
1737
+ }
1738
+ } catch (error) {
1739
+ const err = error;
1740
+ if (err.name === "AbortError" && signal?.aborted) {
1741
+ throw new ApiError("Request cancelled.", "cancelled", 0, false);
1742
+ }
1743
+ if (err.name === "AbortError") {
1744
+ throw new ApiError(
1745
+ "The request timed out. The OpenRouter service may be experiencing high load.",
1746
+ "timeout",
1747
+ 0,
1748
+ true
1749
+ );
1750
+ }
1751
+ throw new ApiError(
1752
+ "Unable to connect to OpenRouter. Please check your internet connection.",
1753
+ "network_error",
1754
+ 0,
1755
+ true
1756
+ );
1757
+ }
1758
+ if (!response.ok) {
1759
+ throw await this.buildApiError(response);
1760
+ }
1761
+ const json = await response.json();
1762
+ const message = json?.choices?.[0]?.message;
1763
+ const text = message?.content ?? "";
1764
+ const finishReason = json?.choices?.[0]?.finish_reason;
1765
+ let toolCalls;
1766
+ if (message?.tool_calls && Array.isArray(message.tool_calls)) {
1767
+ toolCalls = message.tool_calls.map((tc) => {
1768
+ const rawArgs = tc.function?.arguments;
1769
+ return {
1770
+ id: tc.id,
1771
+ type: "function",
1772
+ function: {
1773
+ name: tc.function?.name ?? "",
1774
+ arguments: rawArgs ?? "{}"
1775
+ }
1776
+ };
1777
+ });
1778
+ }
1779
+ let usage;
1780
+ if (json?.usage) {
1781
+ usage = {
1782
+ promptTokens: json.usage.prompt_tokens ?? 0,
1783
+ completionTokens: json.usage.completion_tokens ?? 0,
1784
+ totalTokens: json.usage.total_tokens ?? 0
1785
+ };
1786
+ }
1787
+ return {
1788
+ id: json.id ?? "autohand-local",
1789
+ created: json.created ?? Date.now(),
1790
+ content: text,
1791
+ toolCalls,
1792
+ finishReason,
1793
+ usage,
1794
+ raw: json
1795
+ };
1796
+ }
1797
+ async buildApiError(response) {
1798
+ const status = response.status;
1799
+ let errorDetail = "";
1800
+ try {
1801
+ const body = await response.json();
1802
+ errorDetail = body?.error?.message || body?.error || body?.message || "";
1803
+ if (typeof errorDetail === "object") {
1804
+ errorDetail = JSON.stringify(errorDetail);
1805
+ }
1806
+ } catch {
1807
+ try {
1808
+ errorDetail = await response.text();
1809
+ } catch {
1810
+ }
1811
+ }
1812
+ return withOpenRouterMessage(classifyApiError(status, errorDetail, response.headers));
1813
+ }
1814
+ isNonRetryableError(error) {
1815
+ if (error instanceof ApiError) {
1816
+ return !error.retryable;
1817
+ }
1818
+ const classified = classifyApiError(0, error.message);
1819
+ return !classified.retryable;
1820
+ }
1821
+ combineSignals(signal1, signal2) {
1822
+ const controller = new AbortController();
1823
+ const abort = () => controller.abort();
1824
+ signal1.addEventListener("abort", abort, { once: true });
1825
+ signal2.addEventListener("abort", abort, { once: true });
1826
+ if (signal1.aborted || signal2.aborted) {
1827
+ controller.abort();
1828
+ }
1829
+ return controller.signal;
1830
+ }
1831
+ sleep(ms) {
1832
+ return new Promise((resolve) => setTimeout(resolve, ms));
1833
+ }
1834
+ };
1835
+
1836
+ // src/providers/OpenRouterProvider.ts
1837
+ var OpenRouterProvider = class {
1838
+ client;
1839
+ model;
1840
+ constructor(config, networkSettings) {
1841
+ this.client = new OpenRouterClient(config, networkSettings);
1842
+ this.model = config.model;
1843
+ }
1844
+ getName() {
1845
+ return "openrouter";
1846
+ }
1847
+ setModel(model) {
1848
+ this.model = model;
1849
+ this.client.setDefaultModel(model);
1850
+ }
1851
+ async listModels() {
1852
+ try {
1853
+ const models = await fetchOpenRouterModelCapabilities();
1854
+ const ids = models.map((model) => model.id).filter((id) => Boolean(id));
1855
+ if (ids.length > 0) {
1856
+ return ids;
1857
+ }
1858
+ } catch {
1859
+ }
1860
+ return [
1861
+ "anthropic/claude-4-sonnet",
1862
+ "anthropic/claude-3-opus",
1863
+ "google/gemini-pro-1.5",
1864
+ "openai/gpt-4o",
1865
+ "x-ai/grok-2-latest",
1866
+ "meta-llama/llama-3.1-70b-instruct"
1867
+ ];
1868
+ }
1869
+ async isAvailable() {
1870
+ return true;
1871
+ }
1872
+ async complete(request) {
1873
+ return this.client.complete(request);
1874
+ }
1875
+ };
1876
+
1877
+ // src/utils/platform.ts
1878
+ function isAppleSilicon() {
1879
+ return process.platform === "darwin" && process.arch === "arm64";
1880
+ }
1881
+ function isMLXSupported() {
1882
+ return isAppleSilicon();
1883
+ }
1884
+
1885
+ // src/providers/MLXProvider.ts
1886
+ var DEFAULT_TIMEOUT3 = 6e4;
1887
+ var DEFAULT_MAX_RETRIES3 = 2;
1888
+ var MAX_ALLOWED_RETRIES3 = 5;
1889
+ var DEFAULT_RETRY_DELAY3 = 1e3;
1890
+ var AVAILABILITY_TIMEOUT2 = 5e3;
1891
+ var MLXProvider = class {
1892
+ baseUrl;
1893
+ model;
1894
+ maxRetries;
1895
+ retryDelay;
1896
+ timeout;
1897
+ constructor(config, networkSettings) {
1898
+ const port = config.port || 8080;
1899
+ this.baseUrl = config.baseUrl || `http://localhost:${port}`;
1900
+ this.model = config.model || "mlx-model";
1901
+ const configuredRetries = networkSettings?.maxRetries ?? DEFAULT_MAX_RETRIES3;
1902
+ this.maxRetries = Math.min(Math.max(0, configuredRetries), MAX_ALLOWED_RETRIES3);
1903
+ this.retryDelay = networkSettings?.retryDelay ?? DEFAULT_RETRY_DELAY3;
1904
+ this.timeout = networkSettings?.timeout ?? DEFAULT_TIMEOUT3;
1905
+ }
1906
+ getName() {
1907
+ return "mlx";
1908
+ }
1909
+ setModel(model) {
1910
+ this.model = model;
1911
+ }
1912
+ async listModels() {
1913
+ if (!isMLXSupported()) {
1914
+ return [];
1915
+ }
1916
+ try {
1917
+ const controller = new AbortController();
1918
+ const timerId = setTimeout(() => controller.abort(), AVAILABILITY_TIMEOUT2);
1919
+ try {
1920
+ const response = await fetch(`${this.baseUrl}/v1/models`, {
1921
+ signal: controller.signal
1922
+ });
1923
+ if (!response.ok) {
1924
+ return this.model ? [this.model] : [];
1925
+ }
1926
+ const data = await response.json();
1927
+ return data.data?.map((m) => m.id) ?? (this.model ? [this.model] : []);
1928
+ } finally {
1929
+ clearTimeout(timerId);
1930
+ }
1931
+ } catch {
1932
+ return this.model ? [this.model] : [];
1933
+ }
1934
+ }
1935
+ async isAvailable() {
1936
+ if (!isMLXSupported()) {
1937
+ return false;
1938
+ }
1939
+ try {
1940
+ const controller = new AbortController();
1941
+ const timerId = setTimeout(() => controller.abort(), AVAILABILITY_TIMEOUT2);
1942
+ try {
1943
+ const response = await fetch(`${this.baseUrl}/v1/models`, {
1944
+ signal: controller.signal
1945
+ });
1946
+ return response.ok;
1947
+ } finally {
1948
+ clearTimeout(timerId);
1949
+ }
1950
+ } catch {
1951
+ return false;
1952
+ }
1953
+ }
1954
+ async complete(request) {
1955
+ if (!isMLXSupported()) {
1956
+ throw new Error("MLX is only supported on macOS with Apple Silicon");
1957
+ }
1958
+ const body = {
1959
+ model: request.model || this.model,
1960
+ messages: request.messages.map((msg) => {
1961
+ const mapped = {
1962
+ role: msg.role,
1963
+ content: msg.content
1964
+ };
1965
+ if (msg.name) mapped.name = msg.name;
1966
+ if (msg.role === "tool" && msg.tool_call_id) mapped.tool_call_id = msg.tool_call_id;
1967
+ if (msg.role === "assistant" && msg.tool_calls) mapped.tool_calls = msg.tool_calls;
1968
+ return mapped;
1969
+ }),
1970
+ temperature: request.temperature ?? 0.7,
1971
+ max_tokens: request.maxTokens ?? 4096,
1972
+ stream: false
1973
+ };
1974
+ if (request.tools && request.tools.length > 0) {
1975
+ body.tools = request.tools.map((tool) => ({
1976
+ type: "function",
1977
+ function: {
1978
+ name: tool.name,
1979
+ description: tool.description,
1980
+ parameters: tool.parameters ?? { type: "object", properties: {} }
1981
+ }
1982
+ }));
1983
+ }
1984
+ let lastError = null;
1985
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
1986
+ try {
1987
+ return await this.makeRequest(body, request.signal);
1988
+ } catch (error) {
1989
+ lastError = error;
1990
+ if (this.isNonRetryableError(error)) {
1991
+ throw error;
1992
+ }
1993
+ if (attempt < this.maxRetries) {
1994
+ const delay = this.retryDelay * Math.pow(2, attempt);
1995
+ await this.sleep(delay);
1996
+ }
1997
+ }
1998
+ }
1999
+ throw lastError ?? new ApiError(
2000
+ "Failed to communicate with the MLX server. Please try again.",
2001
+ "network_error",
2002
+ 0,
2003
+ true
2004
+ );
2005
+ }
2006
+ async makeRequest(body, userSignal) {
2007
+ let response;
2008
+ try {
2009
+ const timeoutController = new AbortController();
2010
+ const timerId = setTimeout(() => timeoutController.abort(), this.timeout);
2011
+ const combinedSignal = userSignal ? this.combineSignals(userSignal, timeoutController.signal) : timeoutController.signal;
2012
+ try {
2013
+ response = await fetch(`${this.baseUrl}/v1/chat/completions`, {
2014
+ method: "POST",
2015
+ headers: {
2016
+ "Content-Type": "application/json"
2017
+ },
2018
+ body: JSON.stringify(body),
2019
+ signal: combinedSignal
2020
+ });
2021
+ } finally {
2022
+ clearTimeout(timerId);
2023
+ }
2024
+ } catch (error) {
2025
+ const err = error;
2026
+ if (err.name === "AbortError" && userSignal?.aborted) {
2027
+ throw new ApiError("Request cancelled.", "cancelled", 0, false);
2028
+ }
2029
+ if (err.name === "AbortError") {
2030
+ throw new ApiError(
2031
+ `MLX server request timed out after ${this.timeout / 1e3}s. Local inference can be slow \u2014 consider increasing the timeout in your config.`,
2032
+ "timeout",
2033
+ 0,
2034
+ true
2035
+ );
2036
+ }
2037
+ throw new ApiError(
2038
+ `Cannot connect to MLX server at ${this.baseUrl}. Make sure it is running.`,
2039
+ "network_error",
2040
+ 0,
2041
+ true
2042
+ );
2043
+ }
2044
+ if (!response.ok) {
2045
+ throw await this.buildApiError(response);
2046
+ }
2047
+ let data;
2048
+ try {
2049
+ data = await response.json();
2050
+ } catch {
2051
+ let rawBody = "";
2052
+ try {
2053
+ rawBody = await response.text();
2054
+ } catch {
2055
+ }
2056
+ throw new ApiError(
2057
+ `MLX server returned an invalid response. The model may have crashed or returned malformed output. Raw: ${rawBody.slice(0, 500)}`,
2058
+ "invalid_request",
2059
+ response.status,
2060
+ false
2061
+ );
2062
+ }
2063
+ const choice = data.choices[0];
2064
+ let toolCalls;
2065
+ if (choice?.message.tool_calls?.length) {
2066
+ toolCalls = choice.message.tool_calls.map((tc) => ({
2067
+ id: tc.id,
2068
+ type: "function",
2069
+ function: {
2070
+ name: tc.function.name,
2071
+ arguments: tc.function.arguments
2072
+ }
2073
+ }));
2074
+ }
2075
+ let usage;
2076
+ if (data.usage) {
2077
+ usage = {
2078
+ promptTokens: data.usage.prompt_tokens,
2079
+ completionTokens: data.usage.completion_tokens,
2080
+ totalTokens: data.usage.total_tokens
2081
+ };
2082
+ }
2083
+ const finishReason = toolCalls?.length ? "tool_calls" : choice?.finish_reason === "stop" || choice?.finish_reason === "length" || choice?.finish_reason === "content_filter" ? choice.finish_reason : "stop";
2084
+ return {
2085
+ id: data.id || `mlx-${Date.now()}`,
2086
+ created: data.created || Math.floor(Date.now() / 1e3),
2087
+ content: choice?.message.content ?? "",
2088
+ toolCalls,
2089
+ finishReason,
2090
+ usage,
2091
+ raw: data
2092
+ };
2093
+ }
2094
+ async buildApiError(response) {
2095
+ let errorDetail = "";
2096
+ try {
2097
+ const body = await response.json();
2098
+ const maybeError = body?.error;
2099
+ if (maybeError && typeof maybeError === "object") {
2100
+ errorDetail = maybeError?.message ?? "";
2101
+ } else if (typeof maybeError === "string") {
2102
+ errorDetail = maybeError;
2103
+ } else if (typeof body?.message === "string") {
2104
+ errorDetail = body.message;
2105
+ }
2106
+ if (typeof errorDetail === "object") {
2107
+ errorDetail = JSON.stringify(errorDetail);
2108
+ }
2109
+ } catch {
2110
+ try {
2111
+ errorDetail = await response.text();
2112
+ } catch {
2113
+ }
2114
+ }
2115
+ return classifyApiError(response.status, errorDetail, response.headers);
2116
+ }
2117
+ isNonRetryableError(error) {
2118
+ if (error instanceof ApiError) {
2119
+ return !error.retryable;
2120
+ }
2121
+ const classified = classifyApiError(0, error.message);
2122
+ return !classified.retryable;
2123
+ }
2124
+ combineSignals(signal1, signal2) {
2125
+ const controller = new AbortController();
2126
+ const abort = () => controller.abort();
2127
+ signal1.addEventListener("abort", abort, { once: true });
2128
+ signal2.addEventListener("abort", abort, { once: true });
2129
+ if (signal1.aborted || signal2.aborted) {
2130
+ controller.abort();
2131
+ }
2132
+ return controller.signal;
2133
+ }
2134
+ sleep(ms) {
2135
+ return new Promise((resolve) => setTimeout(resolve, ms));
2136
+ }
2137
+ };
2138
+
2139
+ // src/providers/LLMGatewayClient.ts
2140
+ function sanitizeMessages2(messages) {
2141
+ return messages.map((msg) => {
2142
+ const sanitized = {
2143
+ role: msg.role,
2144
+ content: msg.content
2145
+ };
2146
+ if (msg.role === "tool" && msg.tool_call_id) {
2147
+ sanitized.tool_call_id = msg.tool_call_id;
2148
+ }
2149
+ if (msg.role === "assistant" && msg.tool_calls?.length) {
2150
+ sanitized.tool_calls = msg.tool_calls;
2151
+ }
2152
+ if (msg.name) {
2153
+ sanitized.name = msg.name;
2154
+ }
2155
+ return sanitized;
2156
+ });
2157
+ }
2158
+ var DEFAULT_BASE_URL2 = "https://api.llmgateway.io/v1";
2159
+ var DEFAULT_MAX_RETRIES4 = 3;
2160
+ var MAX_ALLOWED_RETRIES4 = 5;
2161
+ var DEFAULT_RETRY_DELAY4 = 1e3;
2162
+ var DEFAULT_TIMEOUT4 = 3e4;
2163
+ var DEFAULT_ERROR_LABELS = {
2164
+ serviceName: "LLM Gateway",
2165
+ credentialName: "LLM Gateway API key",
2166
+ accountName: "LLM Gateway account"
2167
+ };
2168
+ function buildFriendlyErrors(labels) {
2169
+ return {
2170
+ 400: "The request was malformed. This often happens when the context is too long. Try /undo to remove recent turns or /new to start fresh.",
2171
+ 401: `Authentication failed. Please verify your ${labels.credentialName} in ~/.autohand/config.json.`,
2172
+ 402: `Payment required. Please check your ${labels.accountName} balance or billing settings.`,
2173
+ 403: `Access denied. Your ${labels.credentialName} may not have permission for this model.`,
2174
+ 404: "The requested model was not found. Use /model to select a different one.",
2175
+ 429: "Rate limit exceeded. Please wait a moment and try again, or choose a different model.",
2176
+ 500: `The ${labels.serviceName} service encountered an internal error. Please try again later.`,
2177
+ 502: `The ${labels.serviceName} service is temporarily unavailable. Please try again in a few moments.`,
2178
+ 503: `The ${labels.serviceName} service is currently overloaded. Please try again later.`,
2179
+ 504: `The request timed out. The ${labels.serviceName} service may be experiencing high load.`
2180
+ };
2181
+ }
2182
+ var LLMGatewayClient = class {
2183
+ apiKey;
2184
+ baseUrl;
2185
+ defaultModel;
2186
+ maxRetries;
2187
+ retryDelay;
2188
+ timeout;
2189
+ errorLabels;
2190
+ constructor(settings, networkSettings, errorLabels = DEFAULT_ERROR_LABELS) {
2191
+ this.apiKey = settings.apiKey ?? "";
2192
+ this.baseUrl = settings.baseUrl ?? DEFAULT_BASE_URL2;
2193
+ this.defaultModel = settings.model;
2194
+ this.errorLabels = errorLabels;
2195
+ const configuredRetries = networkSettings?.maxRetries ?? DEFAULT_MAX_RETRIES4;
2196
+ this.maxRetries = Math.min(
2197
+ Math.max(0, configuredRetries),
2198
+ MAX_ALLOWED_RETRIES4
2199
+ );
2200
+ this.retryDelay = networkSettings?.retryDelay ?? DEFAULT_RETRY_DELAY4;
2201
+ this.timeout = networkSettings?.timeout ?? DEFAULT_TIMEOUT4;
2202
+ }
2203
+ setDefaultModel(model) {
2204
+ this.defaultModel = model;
2205
+ }
2206
+ async complete(request) {
2207
+ const payload = this.buildPayload(request);
2208
+ if (request.tools && request.tools.length > 0) {
2209
+ payload.tools = request.tools.map((tool) => ({
2210
+ type: "function",
2211
+ function: {
2212
+ name: tool.name,
2213
+ description: tool.description,
2214
+ parameters: tool.parameters ?? { type: "object", properties: {} }
2215
+ }
2216
+ }));
2217
+ if (request.toolChoice) {
2218
+ payload.tool_choice = request.toolChoice;
2219
+ }
2220
+ }
2221
+ if (request.chatTemplateKwargs) {
2222
+ payload.extra_body = {
2223
+ chat_template_kwargs: this.buildChatTemplateKwargs(request.chatTemplateKwargs)
2224
+ };
2225
+ }
2226
+ const headers = {
2227
+ "Content-Type": "application/json",
2228
+ "x-source": "Autohand Code CLI"
2229
+ };
2230
+ if (this.apiKey) {
2231
+ headers.Authorization = `Bearer ${this.apiKey}`;
2232
+ }
2233
+ const payloadJson = JSON.stringify(payload);
2234
+ const payloadSizeBytes = payloadJson.length;
2235
+ const maxPayloadSize = 5 * 1024 * 1024;
2236
+ if (payloadSizeBytes > maxPayloadSize) {
2237
+ const sizeMB = (payloadSizeBytes / (1024 * 1024)).toFixed(2);
2238
+ throw new Error(
2239
+ `Request payload too large (${sizeMB}MB). This usually happens when the conversation history grows too long. Try using /undo to remove recent turns or /new to start fresh.`
2240
+ );
2241
+ }
2242
+ let lastError = null;
2243
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
2244
+ try {
2245
+ const response = await this.makeRequest(
2246
+ payload,
2247
+ headers,
2248
+ request.signal,
2249
+ payloadJson,
2250
+ request.stream ?? false
2251
+ );
2252
+ return response;
2253
+ } catch (error) {
2254
+ lastError = error;
2255
+ if (this.isNonRetryableError(error)) {
2256
+ throw error;
2257
+ }
2258
+ if (attempt < this.maxRetries) {
2259
+ const delay = this.retryDelay * Math.pow(2, attempt);
2260
+ await this.sleep(delay);
2261
+ }
2262
+ }
2263
+ }
2264
+ throw lastError ?? new Error("Failed to communicate with LLM Gateway. Please try again.");
2265
+ }
2266
+ buildPayload(request) {
2267
+ const payload = {
2268
+ model: request.model ?? this.defaultModel,
2269
+ messages: sanitizeMessages2(request.messages),
2270
+ temperature: request.temperature ?? 0.2,
2271
+ max_tokens: request.maxTokens ?? 16e3,
2272
+ stream: request.stream ?? false
2273
+ };
2274
+ return payload;
2275
+ }
2276
+ buildChatTemplateKwargs(kwargs) {
2277
+ const result = {};
2278
+ if (kwargs.thinking !== void 0) result.thinking = kwargs.thinking;
2279
+ if (kwargs.enable_thinking !== void 0) result.enable_thinking = kwargs.enable_thinking;
2280
+ if (kwargs.reasoning_effort !== void 0) result.reasoning_effort = kwargs.reasoning_effort;
2281
+ if (kwargs.clear_thinking !== void 0) result.clear_thinking = kwargs.clear_thinking;
2282
+ return result;
2283
+ }
2284
+ async makeRequest(payload, headers, signal, preSerializedBody, isStreaming = false) {
2285
+ let response;
2286
+ try {
2287
+ const timeoutController = new AbortController();
2288
+ const timeoutId = setTimeout(
2289
+ () => timeoutController.abort(),
2290
+ this.timeout
2291
+ );
2292
+ const combinedSignal = signal ? this.combineSignals(signal, timeoutController.signal) : timeoutController.signal;
2293
+ try {
2294
+ response = await fetch(`${this.baseUrl}/chat/completions`, {
2295
+ method: "POST",
2296
+ headers,
2297
+ body: preSerializedBody ?? JSON.stringify(payload),
2298
+ signal: combinedSignal
2299
+ });
2300
+ } finally {
2301
+ clearTimeout(timeoutId);
2302
+ }
2303
+ } catch (error) {
2304
+ const err = error;
2305
+ if (err.name === "AbortError" && signal?.aborted) {
2306
+ throw new Error("Request cancelled.");
2307
+ }
2308
+ if (err.name === "AbortError") {
2309
+ throw new Error(
2310
+ `Request timed out. The ${this.errorLabels.serviceName} service may be experiencing high load.`
2311
+ );
2312
+ }
2313
+ throw new Error(
2314
+ `Unable to connect to ${this.errorLabels.serviceName}. Please check your internet connection.`
2315
+ );
2316
+ }
2317
+ if (!response.ok) {
2318
+ throw new Error(await this.buildFriendlyError(response));
2319
+ }
2320
+ if (isStreaming) {
2321
+ return this.handleStreamingResponse(response);
2322
+ }
2323
+ const json = await response.json();
2324
+ const message = json?.choices?.[0]?.message;
2325
+ const text = message?.content ?? "";
2326
+ const finishReason = json?.choices?.[0]?.finish_reason;
2327
+ let toolCalls;
2328
+ if (message?.tool_calls && Array.isArray(message.tool_calls)) {
2329
+ toolCalls = message.tool_calls.map((tc) => {
2330
+ const rawArgs = tc.function?.arguments;
2331
+ return {
2332
+ id: tc.id,
2333
+ type: "function",
2334
+ function: {
2335
+ name: tc.function?.name ?? "",
2336
+ arguments: rawArgs ?? "{}"
2337
+ }
2338
+ };
2339
+ });
2340
+ }
2341
+ let usage;
2342
+ if (json?.usage) {
2343
+ usage = {
2344
+ promptTokens: json.usage.prompt_tokens ?? 0,
2345
+ completionTokens: json.usage.completion_tokens ?? 0,
2346
+ totalTokens: json.usage.total_tokens ?? 0
2347
+ };
2348
+ }
2349
+ return {
2350
+ id: json.id ?? "llmgateway-response",
2351
+ created: json.created ?? Date.now(),
2352
+ content: text,
2353
+ toolCalls,
2354
+ finishReason,
2355
+ usage,
2356
+ raw: json
2357
+ };
2358
+ }
2359
+ async handleStreamingResponse(response) {
2360
+ const reader = response.body?.getReader();
2361
+ if (!reader) {
2362
+ throw new Error("No response body for streaming");
2363
+ }
2364
+ const decoder = new TextDecoder();
2365
+ let fullContent = "";
2366
+ let fullReasoning = "";
2367
+ let lastChunk = null;
2368
+ let finishReason = "stop";
2369
+ try {
2370
+ while (true) {
2371
+ const { done, value } = await reader.read();
2372
+ if (done) break;
2373
+ const chunk = decoder.decode(value, { stream: true });
2374
+ const lines = chunk.split("\n").filter((line) => line.trim());
2375
+ for (const line of lines) {
2376
+ if (line.startsWith("data: ")) {
2377
+ const dataStr = line.slice(6).trim();
2378
+ if (dataStr === "[DONE]") continue;
2379
+ try {
2380
+ const data = JSON.parse(dataStr);
2381
+ lastChunk = data;
2382
+ const delta = data.choices?.[0]?.delta;
2383
+ if (!delta) continue;
2384
+ const reasoning = delta.reasoning || delta.reasoning_content;
2385
+ if (reasoning) {
2386
+ fullReasoning += reasoning;
2387
+ }
2388
+ if (delta.content) {
2389
+ fullContent += delta.content;
2390
+ }
2391
+ if (data.choices?.[0]?.finish_reason) {
2392
+ finishReason = data.choices[0].finish_reason;
2393
+ }
2394
+ } catch {
2395
+ }
2396
+ }
2397
+ }
2398
+ }
2399
+ } finally {
2400
+ reader.releaseLock();
2401
+ }
2402
+ const finalContent = fullReasoning ? `<thinking>${fullReasoning}</thinking>
2403
+
2404
+ ${fullContent}` : fullContent;
2405
+ return {
2406
+ id: lastChunk?.id ?? `llmgateway-stream-${Date.now()}`,
2407
+ created: lastChunk?.created ?? Math.floor(Date.now() / 1e3),
2408
+ content: finalContent,
2409
+ finishReason,
2410
+ raw: { content: fullContent, reasoning: fullReasoning, chunks: lastChunk }
2411
+ };
2412
+ }
2413
+ async buildFriendlyError(response) {
2414
+ const status = response.status;
2415
+ let errorDetail = "";
2416
+ try {
2417
+ const body = await response.json();
2418
+ errorDetail = body?.error?.message || body?.error || body?.message || "";
2419
+ if (typeof errorDetail === "object") {
2420
+ errorDetail = JSON.stringify(errorDetail);
2421
+ }
2422
+ } catch {
2423
+ try {
2424
+ errorDetail = await response.text();
2425
+ } catch {
2426
+ }
2427
+ }
2428
+ const friendlyMessage = buildFriendlyErrors(this.errorLabels)[status];
2429
+ if (friendlyMessage) {
2430
+ return errorDetail ? `${friendlyMessage}
2431
+ ${errorDetail}` : friendlyMessage;
2432
+ }
2433
+ if (status >= 500) {
2434
+ const base = `The ${this.errorLabels.serviceName} service is temporarily unavailable. Please try again later.`;
2435
+ return errorDetail ? `${base}
2436
+ (${status}: ${errorDetail})` : base;
2437
+ }
2438
+ if (status >= 400) {
2439
+ const base = "The request could not be processed.";
2440
+ return errorDetail ? `${base} (${status}: ${errorDetail})` : `${base} (HTTP ${status}) Please try again or adjust your prompt.`;
2441
+ }
2442
+ return errorDetail ? `An unexpected error occurred: ${errorDetail}` : "An unexpected error occurred. Please try again.";
2443
+ }
2444
+ isNonRetryableError(error) {
2445
+ const message = error.message.toLowerCase();
2446
+ if (message.includes("cancelled") || message.includes("aborted")) {
2447
+ return true;
2448
+ }
2449
+ if (message.includes("authentication") || message.includes("api key")) {
2450
+ return true;
2451
+ }
2452
+ if (message.includes("payment") || message.includes("access denied")) {
2453
+ return true;
2454
+ }
2455
+ if (message.includes("not found")) {
2456
+ return true;
2457
+ }
2458
+ return false;
2459
+ }
2460
+ combineSignals(signal1, signal2) {
2461
+ const controller = new AbortController();
2462
+ const abort = () => controller.abort();
2463
+ signal1.addEventListener("abort", abort);
2464
+ signal2.addEventListener("abort", abort);
2465
+ if (signal1.aborted || signal2.aborted) {
2466
+ controller.abort();
2467
+ }
2468
+ return controller.signal;
2469
+ }
2470
+ sleep(ms) {
2471
+ return new Promise((resolve) => setTimeout(resolve, ms));
2472
+ }
2473
+ };
2474
+
2475
+ // src/providers/LLMGatewayProvider.ts
2476
+ var LLMGatewayProvider = class {
2477
+ client;
2478
+ model;
2479
+ constructor(config, networkSettings) {
2480
+ this.client = new LLMGatewayClient(config, networkSettings);
2481
+ this.model = config.model;
2482
+ }
2483
+ getName() {
2484
+ return "llmgateway";
2485
+ }
2486
+ setModel(model) {
2487
+ this.model = model;
2488
+ this.client.setDefaultModel(model);
2489
+ }
2490
+ async listModels() {
2491
+ return [
2492
+ "gpt-4o",
2493
+ "gpt-4o-mini",
2494
+ "gpt-4-turbo",
2495
+ "claude-3-5-sonnet-20241022",
2496
+ "claude-3-5-haiku-20241022",
2497
+ "gemini-1.5-pro",
2498
+ "gemini-1.5-flash"
2499
+ ];
2500
+ }
2501
+ async isAvailable() {
2502
+ return true;
2503
+ }
2504
+ async complete(request) {
2505
+ return this.client.complete(request);
2506
+ }
2507
+ };
2508
+
2509
+ // src/providers/azure/tokenManager.ts
2510
+ var EXPIRY_BUFFER_MS = 5 * 60 * 1e3;
2511
+ var IMDS_ENDPOINT = "http://169.254.169.254/metadata/identity/oauth2/token";
2512
+ var COGNITIVE_SCOPE = "https://cognitiveservices.azure.com/.default";
2513
+ var AzureTokenManager = class {
2514
+ cache = null;
2515
+ async getToken(request) {
2516
+ if (request.authMethod === "api-key") {
2517
+ if (!request.apiKey) {
2518
+ throw new Error("API key is required for api-key authentication.");
2519
+ }
2520
+ return request.apiKey;
2521
+ }
2522
+ if (this.cache && !this.isTokenExpired()) {
2523
+ return this.cache.token;
2524
+ }
2525
+ if (request.authMethod === "entra-id") {
2526
+ return this.acquireEntraIdToken(request);
2527
+ }
2528
+ if (request.authMethod === "managed-identity") {
2529
+ return this.acquireManagedIdentityToken();
2530
+ }
2531
+ throw new Error(`Unsupported auth method: ${request.authMethod}`);
2532
+ }
2533
+ async getAuthHeaders(request) {
2534
+ if (request.authMethod === "api-key") {
2535
+ const key = await this.getToken(request);
2536
+ return { "api-key": key };
2537
+ }
2538
+ const token = await this.getToken(request);
2539
+ return { Authorization: `Bearer ${token}` };
2540
+ }
2541
+ async acquireEntraIdToken(request) {
2542
+ if (!request.tenantId) {
2543
+ throw new Error("tenantId is required for Entra ID authentication.");
2544
+ }
2545
+ if (!request.clientId) {
2546
+ throw new Error("clientId is required for Entra ID authentication.");
2547
+ }
2548
+ if (!request.clientSecret) {
2549
+ throw new Error("clientSecret is required for Entra ID authentication.");
2550
+ }
2551
+ const url = `https://login.microsoftonline.com/${request.tenantId}/oauth2/v2.0/token`;
2552
+ const body = new URLSearchParams({
2553
+ grant_type: "client_credentials",
2554
+ client_id: request.clientId,
2555
+ client_secret: request.clientSecret,
2556
+ scope: COGNITIVE_SCOPE
2557
+ });
2558
+ const response = await fetch(url, {
2559
+ method: "POST",
2560
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
2561
+ body: body.toString()
2562
+ });
2563
+ if (!response.ok) {
2564
+ const error = await response.json().catch(() => ({}));
2565
+ const description = error.error_description || error.error || `HTTP ${response.status}`;
2566
+ throw new Error(`Entra ID authentication failed: ${description}`);
2567
+ }
2568
+ const data = await response.json();
2569
+ this.cacheToken(data.access_token, data.expires_in);
2570
+ return data.access_token;
2571
+ }
2572
+ async acquireManagedIdentityToken() {
2573
+ const url = `${IMDS_ENDPOINT}?api-version=2018-02-01&resource=https://cognitiveservices.azure.com`;
2574
+ let response;
2575
+ try {
2576
+ response = await fetch(url, {
2577
+ headers: { Metadata: "true" }
2578
+ });
2579
+ } catch {
2580
+ throw new Error(
2581
+ "Managed Identity token acquisition failed. This auth method only works inside Azure VMs, App Service, or containers with managed identity enabled."
2582
+ );
2583
+ }
2584
+ if (!response.ok) {
2585
+ const error = await response.json().catch(() => ({}));
2586
+ throw new Error(
2587
+ `Managed Identity token error (${response.status}): ${error.error_description || error.error || "Unknown"}`
2588
+ );
2589
+ }
2590
+ const data = await response.json();
2591
+ this.cacheToken(data.access_token, data.expires_in);
2592
+ return data.access_token;
2593
+ }
2594
+ cacheToken(token, expiresInSeconds) {
2595
+ this.cache = {
2596
+ token,
2597
+ expiresAt: Date.now() + expiresInSeconds * 1e3
2598
+ };
2599
+ }
2600
+ isTokenExpired() {
2601
+ if (!this.cache) return true;
2602
+ return Date.now() >= this.cache.expiresAt - EXPIRY_BUFFER_MS;
2603
+ }
2604
+ };
2605
+
2606
+ // src/providers/AzureClient.ts
2607
+ function sanitizeMessages3(messages) {
2608
+ return messages.map((msg) => {
2609
+ const sanitized = {
2610
+ role: msg.role,
2611
+ content: msg.content
2612
+ };
2613
+ if (msg.role === "tool" && msg.tool_call_id) {
2614
+ sanitized.tool_call_id = msg.tool_call_id;
2615
+ }
2616
+ if (msg.role === "assistant" && msg.tool_calls?.length) {
2617
+ sanitized.tool_calls = msg.tool_calls;
2618
+ }
2619
+ if (msg.name) {
2620
+ sanitized.name = msg.name;
2621
+ }
2622
+ return sanitized;
2623
+ });
2624
+ }
2625
+ var DEFAULT_API_VERSION = "2024-10-21";
2626
+ var DEFAULT_MAX_RETRIES5 = 3;
2627
+ var MAX_ALLOWED_RETRIES5 = 5;
2628
+ var DEFAULT_RETRY_DELAY5 = 1e3;
2629
+ var DEFAULT_TIMEOUT5 = 3e4;
2630
+ var FRIENDLY_ERRORS = {
2631
+ 400: "The request was malformed. This often happens when the context is too long. Try /undo to remove recent turns or /new to start fresh.",
2632
+ 401: "Authentication failed. Please verify your Azure API key or credentials in ~/.autohand/config.json.",
2633
+ 402: "Payment required. Please check your Azure subscription and billing settings.",
2634
+ 403: "Access denied. Your credentials may not have permission for this Azure deployment.",
2635
+ 404: "The Azure deployment was not found. Verify your resourceName and deploymentName in ~/.autohand/config.json.",
2636
+ 429: "Rate limit exceeded. Please wait a moment and try again, or adjust your Azure deployment capacity.",
2637
+ 500: "Azure OpenAI encountered an internal error. Please try again later.",
2638
+ 502: "Azure OpenAI is temporarily unavailable. Please try again in a few moments.",
2639
+ 503: "Azure OpenAI is currently overloaded. Please try again later.",
2640
+ 504: "The request timed out. Azure OpenAI may be experiencing high load."
2641
+ };
2642
+ var AzureClient = class {
2643
+ tokenManager;
2644
+ options;
2645
+ defaultModel;
2646
+ maxRetries;
2647
+ retryDelay;
2648
+ timeout;
2649
+ constructor(options, networkSettings) {
2650
+ this.options = options;
2651
+ this.tokenManager = new AzureTokenManager();
2652
+ this.defaultModel = options.model;
2653
+ const configuredRetries = networkSettings?.maxRetries ?? DEFAULT_MAX_RETRIES5;
2654
+ this.maxRetries = Math.min(
2655
+ Math.max(0, configuredRetries),
2656
+ MAX_ALLOWED_RETRIES5
2657
+ );
2658
+ this.retryDelay = networkSettings?.retryDelay ?? DEFAULT_RETRY_DELAY5;
2659
+ this.timeout = networkSettings?.timeout ?? DEFAULT_TIMEOUT5;
2660
+ }
2661
+ setDefaultModel(model) {
2662
+ this.defaultModel = model;
2663
+ }
2664
+ /**
2665
+ * Build the full Azure OpenAI endpoint URL.
2666
+ *
2667
+ * If baseUrl is provided:
2668
+ * {baseUrl}/chat/completions?api-version={apiVersion}
2669
+ *
2670
+ * If resourceName is a full URL (starts with https://):
2671
+ * {origin}/openai/deployments/{deploymentName}/chat/completions?api-version={apiVersion}
2672
+ * Supports all Azure endpoint domains:
2673
+ * - *.openai.azure.com (Azure OpenAI)
2674
+ * - *.services.ai.azure.com (Microsoft Foundry)
2675
+ * - *.cognitiveservices.azure.com (Azure AI Services)
2676
+ *
2677
+ * Otherwise, from resourceName + deploymentName:
2678
+ * https://{resourceName}.openai.azure.com/openai/deployments/{deploymentName}/chat/completions?api-version={apiVersion}
2679
+ */
2680
+ buildEndpointUrl() {
2681
+ const apiVersion = this.options.apiVersion ?? DEFAULT_API_VERSION;
2682
+ if (this.options.baseUrl) {
2683
+ return `${this.options.baseUrl}/chat/completions?api-version=${apiVersion}`;
2684
+ }
2685
+ const { resourceName, deploymentName } = this.options;
2686
+ if (!resourceName || !deploymentName) {
2687
+ throw new Error(
2688
+ "Azure OpenAI requires either baseUrl or both resourceName and deploymentName in ~/.autohand/config.json."
2689
+ );
2690
+ }
2691
+ if (resourceName.startsWith("http://") || resourceName.startsWith("https://")) {
2692
+ try {
2693
+ const parsed = new URL(resourceName);
2694
+ return `${parsed.origin}/openai/deployments/${deploymentName}/chat/completions?api-version=${apiVersion}`;
2695
+ } catch {
2696
+ }
2697
+ }
2698
+ return `https://${resourceName}.openai.azure.com/openai/deployments/${deploymentName}/chat/completions?api-version=${apiVersion}`;
2699
+ }
2700
+ async complete(request) {
2701
+ const payload = {
2702
+ messages: sanitizeMessages3(request.messages),
2703
+ temperature: request.temperature ?? 0.2,
2704
+ max_tokens: request.maxTokens ?? 16e3,
2705
+ stream: request.stream ?? false
2706
+ };
2707
+ if (request.tools && request.tools.length > 0) {
2708
+ payload.tools = request.tools.map((tool) => ({
2709
+ type: "function",
2710
+ function: {
2711
+ name: tool.name,
2712
+ description: tool.description,
2713
+ parameters: tool.parameters ?? { type: "object", properties: {} }
2714
+ }
2715
+ }));
2716
+ if (request.toolChoice) {
2717
+ payload.tool_choice = request.toolChoice;
2718
+ }
2719
+ }
2720
+ const authHeaders = await this.tokenManager.getAuthHeaders({
2721
+ authMethod: this.options.authMethod,
2722
+ apiKey: this.options.apiKey,
2723
+ tenantId: this.options.tenantId,
2724
+ clientId: this.options.clientId,
2725
+ clientSecret: this.options.clientSecret
2726
+ });
2727
+ const headers = {
2728
+ "Content-Type": "application/json",
2729
+ ...authHeaders
2730
+ };
2731
+ const payloadJson = JSON.stringify(payload);
2732
+ const payloadSizeBytes = payloadJson.length;
2733
+ const maxPayloadSize = 5 * 1024 * 1024;
2734
+ if (payloadSizeBytes > maxPayloadSize) {
2735
+ const sizeMB = (payloadSizeBytes / (1024 * 1024)).toFixed(2);
2736
+ throw new Error(
2737
+ `Request payload too large (${sizeMB}MB). This usually happens when the conversation history grows too long. Try using /undo to remove recent turns or /new to start fresh.`
2738
+ );
2739
+ }
2740
+ let lastError = null;
2741
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
2742
+ try {
2743
+ const response = await this.makeRequest(
2744
+ payload,
2745
+ headers,
2746
+ request.signal,
2747
+ payloadJson
2748
+ );
2749
+ return response;
2750
+ } catch (error) {
2751
+ lastError = error;
2752
+ if (this.isNonRetryableError(error)) {
2753
+ throw error;
2754
+ }
2755
+ if (attempt < this.maxRetries) {
2756
+ const delay = this.retryDelay * Math.pow(2, attempt);
2757
+ await this.sleep(delay);
2758
+ }
2759
+ }
2760
+ }
2761
+ throw lastError ?? new Error(
2762
+ "Failed to communicate with Azure OpenAI. Please try again."
2763
+ );
2764
+ }
2765
+ async makeRequest(payload, headers, signal, preSerializedBody) {
2766
+ let response;
2767
+ const url = this.buildEndpointUrl();
2768
+ try {
2769
+ const timeoutController = new AbortController();
2770
+ const timeoutId = setTimeout(
2771
+ () => timeoutController.abort(),
2772
+ this.timeout
2773
+ );
2774
+ const combinedSignal = signal ? this.combineSignals(signal, timeoutController.signal) : timeoutController.signal;
2775
+ try {
2776
+ response = await fetch(url, {
2777
+ method: "POST",
2778
+ headers,
2779
+ body: preSerializedBody ?? JSON.stringify(payload),
2780
+ signal: combinedSignal
2781
+ });
2782
+ } finally {
2783
+ clearTimeout(timeoutId);
2784
+ }
2785
+ } catch (error) {
2786
+ const err = error;
2787
+ if (err.name === "AbortError" && signal?.aborted) {
2788
+ throw new Error("Request cancelled.");
2789
+ }
2790
+ if (err.name === "AbortError") {
2791
+ throw new Error(
2792
+ "Request timed out. Azure OpenAI may be experiencing high load."
2793
+ );
2794
+ }
2795
+ throw new Error(
2796
+ "Unable to connect to Azure OpenAI. Please check your internet connection and Azure configuration."
2797
+ );
2798
+ }
2799
+ if (!response.ok) {
2800
+ throw new Error(await this.buildFriendlyError(response));
2801
+ }
2802
+ const json = await response.json();
2803
+ const message = json?.choices?.[0]?.message;
2804
+ const text = message?.content ?? "";
2805
+ const finishReason = json?.choices?.[0]?.finish_reason;
2806
+ let toolCalls;
2807
+ if (message?.tool_calls && Array.isArray(message.tool_calls)) {
2808
+ toolCalls = message.tool_calls.map((tc) => {
2809
+ const rawArgs = tc.function?.arguments;
2810
+ return {
2811
+ id: tc.id,
2812
+ type: "function",
2813
+ function: {
2814
+ name: tc.function?.name ?? "",
2815
+ arguments: rawArgs ?? "{}"
2816
+ }
2817
+ };
2818
+ });
2819
+ }
2820
+ let usage;
2821
+ if (json?.usage) {
2822
+ usage = {
2823
+ promptTokens: json.usage.prompt_tokens ?? 0,
2824
+ completionTokens: json.usage.completion_tokens ?? 0,
2825
+ totalTokens: json.usage.total_tokens ?? 0
2826
+ };
2827
+ }
2828
+ return {
2829
+ id: json.id ?? "autohand-azure",
2830
+ created: json.created ?? Date.now(),
2831
+ content: text,
2832
+ toolCalls,
2833
+ finishReason,
2834
+ usage,
2835
+ raw: json
2836
+ };
2837
+ }
2838
+ async buildFriendlyError(response) {
2839
+ const status = response.status;
2840
+ let errorDetail = "";
2841
+ try {
2842
+ const body = await response.json();
2843
+ errorDetail = body?.error?.message || body?.error || body?.message || "";
2844
+ if (typeof errorDetail === "object") {
2845
+ errorDetail = JSON.stringify(errorDetail);
2846
+ }
2847
+ } catch {
2848
+ try {
2849
+ errorDetail = await response.text();
2850
+ } catch {
2851
+ }
2852
+ }
2853
+ const friendlyMessage = FRIENDLY_ERRORS[status];
2854
+ if (friendlyMessage) {
2855
+ return errorDetail ? `${friendlyMessage}
2856
+ ${errorDetail}` : friendlyMessage;
2857
+ }
2858
+ if (status >= 500) {
2859
+ const base = "Azure OpenAI is temporarily unavailable. Please try again later.";
2860
+ return errorDetail ? `${base}
2861
+ (${status}: ${errorDetail})` : base;
2862
+ }
2863
+ if (status >= 400) {
2864
+ const base = "The request could not be processed by Azure OpenAI.";
2865
+ return errorDetail ? `${base} (${status}: ${errorDetail})` : `${base} (HTTP ${status}) Please try again or adjust your prompt.`;
2866
+ }
2867
+ return errorDetail ? `An unexpected Azure OpenAI error occurred: ${errorDetail}` : "An unexpected Azure OpenAI error occurred. Please try again.";
2868
+ }
2869
+ isNonRetryableError(error) {
2870
+ const message = error.message.toLowerCase();
2871
+ if (message.includes("cancelled") || message.includes("aborted")) {
2872
+ return true;
2873
+ }
2874
+ if (message.includes("authentication") || message.includes("api key")) {
2875
+ return true;
2876
+ }
2877
+ if (message.includes("payment") || message.includes("access denied")) {
2878
+ return true;
2879
+ }
2880
+ if (message.includes("not found")) {
2881
+ return true;
2882
+ }
2883
+ return false;
2884
+ }
2885
+ combineSignals(signal1, signal2) {
2886
+ const controller = new AbortController();
2887
+ const abort = () => controller.abort();
2888
+ signal1.addEventListener("abort", abort);
2889
+ signal2.addEventListener("abort", abort);
2890
+ if (signal1.aborted || signal2.aborted) {
2891
+ controller.abort();
2892
+ }
2893
+ return controller.signal;
2894
+ }
2895
+ sleep(ms) {
2896
+ return new Promise((resolve) => setTimeout(resolve, ms));
2897
+ }
2898
+ };
2899
+
2900
+ // src/providers/AzureProvider.ts
2901
+ var AzureProvider = class {
2902
+ client;
2903
+ model;
2904
+ constructor(config, networkSettings) {
2905
+ this.client = new AzureClient(
2906
+ {
2907
+ model: config.model,
2908
+ resourceName: config.resourceName,
2909
+ deploymentName: config.deploymentName,
2910
+ baseUrl: config.baseUrl,
2911
+ apiVersion: config.apiVersion,
2912
+ apiKey: config.apiKey,
2913
+ authMethod: config.authMethod ?? "api-key",
2914
+ tenantId: config.tenantId,
2915
+ clientId: config.clientId,
2916
+ clientSecret: config.clientSecret
2917
+ },
2918
+ networkSettings
2919
+ );
2920
+ this.model = config.model;
2921
+ }
2922
+ getName() {
2923
+ return "azure";
2924
+ }
2925
+ setModel(model) {
2926
+ this.model = model;
2927
+ this.client.setDefaultModel(model);
2928
+ }
2929
+ async listModels() {
2930
+ return ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "gpt-4", "gpt-3.5-turbo"];
2931
+ }
2932
+ async isAvailable() {
2933
+ return true;
2934
+ }
2935
+ async complete(request) {
2936
+ return this.client.complete(request);
2937
+ }
2938
+ };
2939
+
2940
+ // src/providers/ZaiProvider.ts
2941
+ var ZAI_DEFAULT_BASE_URL = "https://api.z.ai/api/paas/v4";
2942
+ var ZAI_MODELS = [
2943
+ "glm-4.5",
2944
+ "glm-4.5v",
2945
+ "glm-4.5-air",
2946
+ "glm-4.5-prior",
2947
+ "glm-4.5-flash",
2948
+ "glm-4.5-air-2504",
2949
+ "cogview-4.5"
2950
+ ];
2951
+ var ZaiProvider = class {
2952
+ client;
2953
+ model;
2954
+ constructor(config, networkSettings) {
2955
+ const effectiveConfig = {
2956
+ ...config,
2957
+ baseUrl: config.baseUrl ?? ZAI_DEFAULT_BASE_URL
2958
+ };
2959
+ this.client = new LLMGatewayClient(effectiveConfig, networkSettings, {
2960
+ serviceName: "Z.ai",
2961
+ credentialName: "Z.ai API key",
2962
+ accountName: "Z.ai account"
2963
+ });
2964
+ this.model = config.model;
2965
+ }
2966
+ getName() {
2967
+ return "zai";
2968
+ }
2969
+ setModel(model) {
2970
+ this.model = model;
2971
+ this.client.setDefaultModel(model);
2972
+ }
2973
+ async listModels() {
2974
+ return [...ZAI_MODELS];
2975
+ }
2976
+ async isAvailable() {
2977
+ return true;
2978
+ }
2979
+ async complete(request) {
2980
+ return this.client.complete(request);
2981
+ }
2982
+ };
2983
+
2984
+ // src/utils/gcloudAuth.ts
2985
+ import { exec } from "child_process";
2986
+ import { promisify } from "util";
2987
+ var execAsync = promisify(exec);
2988
+ var tokenCache = null;
2989
+ async function isGcloudInstalled() {
2990
+ try {
2991
+ await execAsync("gcloud --version", { timeout: 5e3 });
2992
+ return true;
2993
+ } catch {
2994
+ return false;
2995
+ }
2996
+ }
2997
+ async function getGcloudProject() {
2998
+ try {
2999
+ const { stdout } = await execAsync("gcloud config get-value project", { timeout: 5e3 });
3000
+ const project = stdout.trim();
3001
+ return project && project !== "(unset)" ? project : null;
3002
+ } catch {
3003
+ return null;
3004
+ }
3005
+ }
3006
+ async function getGcloudAccessToken() {
3007
+ if (tokenCache && Date.now() < tokenCache.expiresAt) {
3008
+ return { token: tokenCache.token };
3009
+ }
3010
+ try {
3011
+ const { stdout } = await execAsync("gcloud auth print-access-token", { timeout: 1e4 });
3012
+ const token = stdout.trim();
3013
+ if (!token || token.length < 10) {
3014
+ return { token: "", error: "Failed to get access token. You may need to run: gcloud auth login" };
3015
+ }
3016
+ tokenCache = {
3017
+ token,
3018
+ expiresAt: Date.now() + 25 * 60 * 1e3
3019
+ // 25 minutes
3020
+ };
3021
+ return { token };
3022
+ } catch (error) {
3023
+ const errorMessage = error instanceof Error ? error.message : String(error);
3024
+ if (errorMessage.includes("not found") || errorMessage.includes("command not found")) {
3025
+ return {
3026
+ token: "",
3027
+ error: "gcloud CLI not found. Install it from: https://cloud.google.com/sdk/docs/install"
3028
+ };
3029
+ }
3030
+ if (errorMessage.includes("Could not determine account")) {
3031
+ return {
3032
+ token: "",
3033
+ error: "Not logged in. Run: gcloud auth login"
3034
+ };
3035
+ }
3036
+ return {
3037
+ token: "",
3038
+ error: `Failed to get access token: ${errorMessage}`
3039
+ };
3040
+ }
3041
+ }
3042
+ function clearGcloudTokenCache() {
3043
+ tokenCache = null;
3044
+ }
3045
+ async function getGcloudAccount() {
3046
+ try {
3047
+ const { stdout } = await execAsync("gcloud auth list --format=value(account)", { timeout: 5e3 });
3048
+ const account = stdout.trim().split("\n")[0];
3049
+ return account || null;
3050
+ } catch {
3051
+ return null;
3052
+ }
3053
+ }
3054
+
3055
+ // src/providers/VertexAIProvider.ts
3056
+ function sanitizeMessages4(messages) {
3057
+ return messages.map((msg) => {
3058
+ const sanitized = {
3059
+ role: msg.role,
3060
+ content: msg.content
3061
+ };
3062
+ if (msg.role === "tool" && msg.tool_call_id) {
3063
+ sanitized.tool_call_id = msg.tool_call_id;
3064
+ }
3065
+ if (msg.role === "assistant" && msg.tool_calls?.length) {
3066
+ sanitized.tool_calls = msg.tool_calls;
3067
+ }
3068
+ if (msg.name) {
3069
+ sanitized.name = msg.name;
3070
+ }
3071
+ return sanitized;
3072
+ });
3073
+ }
3074
+ var DEFAULT_ENDPOINT = "aiplatform.googleapis.com";
3075
+ var DEFAULT_REGION = "global";
3076
+ var DEFAULT_MAX_RETRIES6 = 3;
3077
+ var MAX_ALLOWED_RETRIES6 = 5;
3078
+ var DEFAULT_RETRY_DELAY6 = 1e3;
3079
+ var DEFAULT_TIMEOUT6 = 3e4;
3080
+ var VERTEX_AI_FRIENDLY_MESSAGES = {
3081
+ auth_failed: "Authentication failed. Please verify your Google Cloud Vertex AI auth token in ~/.autohand/config.json. If it came from gcloud, refresh it with `gcloud auth print-access-token`.",
3082
+ payment_required: "Payment required. Please check billing for the Google Cloud project configured for Vertex AI.",
3083
+ access_denied: "Access denied. Your Google Cloud credentials may not have permission to use Vertex AI or this model.",
3084
+ server_error: "The Google Cloud Vertex AI service encountered an error. Please try again later.",
3085
+ network_error: "Unable to connect to Google Cloud Vertex AI. Please check your internet connection and Vertex AI endpoint.",
3086
+ timeout: "The request timed out. The Google Cloud Vertex AI service may be experiencing high load."
3087
+ };
3088
+ function withVertexAIMessage(error) {
3089
+ const friendlyMessage = VERTEX_AI_FRIENDLY_MESSAGES[error.code];
3090
+ if (!friendlyMessage) {
3091
+ return error;
3092
+ }
3093
+ return new ApiError(
3094
+ error.rawDetail ? `${friendlyMessage}
3095
+ ${error.rawDetail}` : friendlyMessage,
3096
+ error.code,
3097
+ error.httpStatus,
3098
+ error.retryable,
3099
+ error.retryAfterMs,
3100
+ error.rawDetail
3101
+ );
3102
+ }
3103
+ var ANTHROPIC_MODELS = [
3104
+ "claude-3-opus",
3105
+ "claude-3-sonnet",
3106
+ "claude-3-haiku",
3107
+ "claude-3-5-sonnet",
3108
+ "claude-3-5-haiku",
3109
+ "claude-3.5-sonnet",
3110
+ "claude-3.5-haiku",
3111
+ "claude-4",
3112
+ "claude-sonnet-4",
3113
+ "claude-opus-4",
3114
+ "claude-opus-4-7",
3115
+ "claude-opus-4-6",
3116
+ "claude-opus-4.7",
3117
+ "claude-opus-4.6"
3118
+ ];
3119
+ var VERTEX_AI_CODING_MODELS = [
3120
+ // Anthropic Claude (coding-optimized)
3121
+ "anthropic/claude-opus-4-7",
3122
+ "anthropic/claude-opus-4-6",
3123
+ "anthropic/claude-opus-4",
3124
+ "anthropic/claude-sonnet-4",
3125
+ "anthropic/claude-3-5-sonnet",
3126
+ "anthropic/claude-3-opus",
3127
+ "anthropic/claude-3-haiku",
3128
+ // Google Gemini (coding-capable)
3129
+ "google/gemini-3.1-pro",
3130
+ "google/gemini-3.1-flash",
3131
+ "google/gemini-1.5-pro",
3132
+ "google/gemini-1.5-flash",
3133
+ "google/gemini-1.0-pro",
3134
+ // Z.ai models
3135
+ "zai-org/glm-5-maas"
3136
+ ];
3137
+ function isAnthropicModel(model) {
3138
+ const lowerModel = model.toLowerCase();
3139
+ return ANTHROPIC_MODELS.some((m) => lowerModel.includes(m.toLowerCase()));
3140
+ }
3141
+ function extractAnthropicModelId(model) {
3142
+ const lowerModel = model.toLowerCase();
3143
+ if (lowerModel.startsWith("anthropic/")) {
3144
+ return model.substring("anthropic/".length);
3145
+ }
3146
+ return model;
3147
+ }
3148
+ var VertexAIProvider = class {
3149
+ authToken;
3150
+ // Changed from readonly to allow refresh
3151
+ endpoint;
3152
+ region;
3153
+ projectId;
3154
+ baseUrl;
3155
+ defaultModel;
3156
+ maxRetries;
3157
+ retryDelay;
3158
+ timeout;
3159
+ useGcloudRefresh;
3160
+ // Auto-refresh via gcloud CLI
3161
+ constructor(settings, networkSettings) {
3162
+ this.authToken = settings.authToken;
3163
+ this.endpoint = settings.endpoint ?? DEFAULT_ENDPOINT;
3164
+ this.region = settings.region ?? DEFAULT_REGION;
3165
+ this.projectId = settings.projectId;
3166
+ this.defaultModel = settings.model;
3167
+ this.useGcloudRefresh = this.authToken.startsWith("ya29.") && this.authToken.length > 100;
3168
+ this.baseUrl = `https://${this.endpoint}/v1/projects/${this.projectId}/locations/${this.region}/endpoints/openapi`;
3169
+ const configuredRetries = networkSettings?.maxRetries ?? DEFAULT_MAX_RETRIES6;
3170
+ this.maxRetries = Math.min(
3171
+ Math.max(0, configuredRetries),
3172
+ MAX_ALLOWED_RETRIES6
3173
+ );
3174
+ this.retryDelay = networkSettings?.retryDelay ?? DEFAULT_RETRY_DELAY6;
3175
+ this.timeout = networkSettings?.timeout ?? DEFAULT_TIMEOUT6;
3176
+ }
3177
+ getName() {
3178
+ return "vertexai";
3179
+ }
3180
+ setModel(model) {
3181
+ this.defaultModel = model;
3182
+ }
3183
+ async listModels() {
3184
+ return [...VERTEX_AI_CODING_MODELS];
3185
+ }
3186
+ async isAvailable() {
3187
+ try {
3188
+ const token = await this.getValidToken();
3189
+ const response = await fetch(`${this.baseUrl}/models`, {
3190
+ method: "GET",
3191
+ headers: {
3192
+ Authorization: `Bearer ${token}`
3193
+ },
3194
+ signal: AbortSignal.timeout(5e3)
3195
+ });
3196
+ return response.ok;
3197
+ } catch {
3198
+ return false;
3199
+ }
3200
+ }
3201
+ /**
3202
+ * Get a valid auth token, refreshing from gcloud if needed
3203
+ */
3204
+ async getValidToken() {
3205
+ if (this.useGcloudRefresh) {
3206
+ const result = await getGcloudAccessToken();
3207
+ if (result.token) {
3208
+ this.authToken = result.token;
3209
+ return this.authToken;
3210
+ }
3211
+ }
3212
+ return this.authToken;
3213
+ }
3214
+ /**
3215
+ * Refresh the token after an auth error
3216
+ */
3217
+ async refreshToken() {
3218
+ if (!this.useGcloudRefresh) {
3219
+ return false;
3220
+ }
3221
+ clearGcloudTokenCache();
3222
+ const result = await getGcloudAccessToken();
3223
+ if (result.token) {
3224
+ this.authToken = result.token;
3225
+ return true;
3226
+ }
3227
+ return false;
3228
+ }
3229
+ async complete(request) {
3230
+ const model = request.model ?? this.defaultModel;
3231
+ const isAnthropic = isAnthropicModel(model);
3232
+ let payload;
3233
+ let url;
3234
+ if (isAnthropic) {
3235
+ const modelId = extractAnthropicModelId(model);
3236
+ url = `https://${this.endpoint}/v1/projects/${this.projectId}/locations/${this.region}/publishers/anthropic/models/${modelId}:streamRawPredict`;
3237
+ payload = {
3238
+ anthropic_version: "vertex-2023-10-16",
3239
+ messages: sanitizeMessages4(request.messages),
3240
+ max_tokens: request.maxTokens ?? 16e3,
3241
+ stream: request.stream ?? false
3242
+ };
3243
+ if (request.temperature !== void 0) {
3244
+ payload.temperature = request.temperature;
3245
+ }
3246
+ if (request.tools && request.tools.length > 0) {
3247
+ payload.tools = request.tools.map((tool) => ({
3248
+ name: tool.name,
3249
+ description: tool.description,
3250
+ input_schema: tool.parameters ?? { type: "object", properties: {} }
3251
+ }));
3252
+ }
3253
+ } else {
3254
+ payload = {
3255
+ model,
3256
+ messages: sanitizeMessages4(request.messages),
3257
+ temperature: request.temperature ?? 0.2,
3258
+ max_tokens: request.maxTokens ?? 16e3,
3259
+ stream: request.stream ?? false
3260
+ };
3261
+ if (request.tools && request.tools.length > 0) {
3262
+ payload.tools = request.tools.map((tool) => ({
3263
+ type: "function",
3264
+ function: {
3265
+ name: tool.name,
3266
+ description: tool.description,
3267
+ parameters: tool.parameters ?? { type: "object", properties: {} }
3268
+ }
3269
+ }));
3270
+ if (request.toolChoice) {
3271
+ payload.tool_choice = request.toolChoice;
3272
+ }
3273
+ }
3274
+ url = `${this.baseUrl}/chat/completions`;
3275
+ }
3276
+ const headers = {
3277
+ "Content-Type": "application/json",
3278
+ Authorization: `Bearer ${await this.getValidToken()}`
3279
+ };
3280
+ const payloadJson = JSON.stringify(payload);
3281
+ const payloadSizeBytes = payloadJson.length;
3282
+ const maxPayloadSize = 5 * 1024 * 1024;
3283
+ if (payloadSizeBytes > maxPayloadSize) {
3284
+ const sizeMB = (payloadSizeBytes / (1024 * 1024)).toFixed(2);
3285
+ throw new ApiError(
3286
+ `Request payload too large (${sizeMB}MB). This usually happens when the conversation history grows too long. Try using /undo to remove recent turns or /new to start fresh.`,
3287
+ "context_overflow",
3288
+ 400,
3289
+ false
3290
+ );
3291
+ }
3292
+ let lastError = null;
3293
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
3294
+ try {
3295
+ const response = await this.makeRequest(
3296
+ url,
3297
+ payload,
3298
+ headers,
3299
+ request.signal,
3300
+ payloadJson,
3301
+ isAnthropic
3302
+ );
3303
+ return response;
3304
+ } catch (error) {
3305
+ lastError = error;
3306
+ if (this.isAuthError(error) && await this.refreshToken()) {
3307
+ headers.Authorization = `Bearer ${this.authToken}`;
3308
+ continue;
3309
+ }
3310
+ if (this.isNonRetryableError(error)) {
3311
+ throw error;
3312
+ }
3313
+ if (attempt < this.maxRetries) {
3314
+ const delay = this.retryDelay * Math.pow(2, attempt);
3315
+ await this.sleep(delay);
3316
+ }
3317
+ }
3318
+ }
3319
+ throw lastError ?? new Error("Failed to communicate with Vertex AI. Please try again.");
3320
+ }
3321
+ async makeRequest(url, payload, headers, signal, preSerializedBody, isAnthropic = false) {
3322
+ let response;
3323
+ try {
3324
+ const timeoutController = new AbortController();
3325
+ const timeoutId = setTimeout(
3326
+ () => timeoutController.abort(),
3327
+ this.timeout
3328
+ );
3329
+ const combinedSignal = signal ? this.combineSignals(signal, timeoutController.signal) : timeoutController.signal;
3330
+ try {
3331
+ response = await fetch(url, {
3332
+ method: "POST",
3333
+ headers,
3334
+ body: preSerializedBody ?? JSON.stringify(payload),
3335
+ signal: combinedSignal
3336
+ });
3337
+ } finally {
3338
+ clearTimeout(timeoutId);
3339
+ }
3340
+ } catch (error) {
3341
+ const err = error;
3342
+ if (err.name === "AbortError" && signal?.aborted) {
3343
+ throw new ApiError("Request cancelled.", "cancelled", 0, false);
3344
+ }
3345
+ if (err.name === "AbortError") {
3346
+ throw new ApiError(
3347
+ "Request timed out. The Vertex AI service may be experiencing high load.",
3348
+ "timeout",
3349
+ 504,
3350
+ true
3351
+ );
3352
+ }
3353
+ const classified = classifyApiError(0, err.message);
3354
+ throw withVertexAIMessage(classified);
3355
+ }
3356
+ if (!response.ok) {
3357
+ throw await this.buildFriendlyError(response);
3358
+ }
3359
+ const json = await response.json();
3360
+ if (isAnthropic) {
3361
+ return this.parseAnthropicResponse(json);
3362
+ }
3363
+ const message = json?.choices?.[0]?.message;
3364
+ const text = message?.content ?? "";
3365
+ const finishReason = json?.choices?.[0]?.finish_reason;
3366
+ let toolCalls;
3367
+ if (message?.tool_calls && Array.isArray(message.tool_calls)) {
3368
+ toolCalls = message.tool_calls.map((tc) => {
3369
+ const rawArgs = tc.function?.arguments;
3370
+ return {
3371
+ id: tc.id,
3372
+ type: "function",
3373
+ function: {
3374
+ name: tc.function?.name ?? "",
3375
+ arguments: rawArgs ?? "{}"
3376
+ }
3377
+ };
3378
+ });
3379
+ }
3380
+ let usage;
3381
+ if (json?.usage) {
3382
+ usage = {
3383
+ promptTokens: json.usage.prompt_tokens ?? 0,
3384
+ completionTokens: json.usage.completion_tokens ?? 0,
3385
+ totalTokens: json.usage.total_tokens ?? 0
3386
+ };
3387
+ }
3388
+ return {
3389
+ id: json.id ?? "vertexai-response",
3390
+ created: json.created ?? Date.now(),
3391
+ content: text,
3392
+ toolCalls,
3393
+ finishReason,
3394
+ usage,
3395
+ raw: json
3396
+ };
3397
+ }
3398
+ /**
3399
+ * Parse Anthropic API response format
3400
+ */
3401
+ parseAnthropicResponse(json) {
3402
+ const contentBlocks = json?.content ?? [];
3403
+ const textBlock = contentBlocks.find((b) => b.type === "text");
3404
+ const text = textBlock?.text ?? "";
3405
+ let toolCalls;
3406
+ const toolUseBlocks = contentBlocks.filter((b) => b.type === "tool_use");
3407
+ if (toolUseBlocks.length > 0) {
3408
+ toolCalls = toolUseBlocks.map((block) => ({
3409
+ id: block.id,
3410
+ type: "function",
3411
+ function: {
3412
+ name: block.name ?? "",
3413
+ arguments: JSON.stringify(block.input ?? {})
3414
+ }
3415
+ }));
3416
+ }
3417
+ let usage;
3418
+ if (json?.usage) {
3419
+ usage = {
3420
+ promptTokens: json.usage.input_tokens ?? 0,
3421
+ completionTokens: json.usage.output_tokens ?? 0,
3422
+ totalTokens: (json.usage.input_tokens ?? 0) + (json.usage.output_tokens ?? 0)
3423
+ };
3424
+ }
3425
+ const stopReason = json?.stop_reason;
3426
+ let finishReason;
3427
+ if (stopReason === "end_turn" || stopReason === "stop_sequence") {
3428
+ finishReason = "stop";
3429
+ } else if (stopReason === "tool_use") {
3430
+ finishReason = "tool_calls";
3431
+ } else if (stopReason === "max_tokens") {
3432
+ finishReason = "length";
3433
+ }
3434
+ return {
3435
+ id: json.id ?? "vertexai-anthropic-response",
3436
+ created: Date.now(),
3437
+ content: text,
3438
+ toolCalls,
3439
+ finishReason,
3440
+ usage,
3441
+ raw: json
3442
+ };
3443
+ }
3444
+ async buildFriendlyError(response) {
3445
+ const status = response.status;
3446
+ let errorDetail = "";
3447
+ try {
3448
+ const body = await response.json();
3449
+ errorDetail = body?.error?.message || body?.error || body?.message || "";
3450
+ if (typeof errorDetail === "object") {
3451
+ errorDetail = JSON.stringify(errorDetail);
3452
+ }
3453
+ } catch {
3454
+ try {
3455
+ errorDetail = await response.text();
3456
+ } catch {
3457
+ }
3458
+ }
3459
+ const classified = classifyApiError(status, errorDetail, response.headers);
3460
+ return withVertexAIMessage(classified);
3461
+ }
3462
+ isNonRetryableError(error) {
3463
+ if (error instanceof ApiError) {
3464
+ return !error.retryable;
3465
+ }
3466
+ const message = error.message.toLowerCase();
3467
+ if (message.includes("cancelled") || message.includes("aborted")) {
3468
+ return true;
3469
+ }
3470
+ if (message.includes("authentication") || message.includes("auth token")) {
3471
+ return true;
3472
+ }
3473
+ if (message.includes("payment") || message.includes("access denied")) {
3474
+ return true;
3475
+ }
3476
+ if (message.includes("not found")) {
3477
+ return true;
3478
+ }
3479
+ return false;
3480
+ }
3481
+ /**
3482
+ * Check if error is an authentication error that can be fixed by refreshing the token
3483
+ */
3484
+ isAuthError(error) {
3485
+ if (error instanceof ApiError) {
3486
+ return error.code === "auth_failed";
3487
+ }
3488
+ const message = error.message.toLowerCase();
3489
+ if (message.includes("401") || message.includes("unauthorized") || message.includes("authentication") || message.includes("auth token") || message.includes("invalid token") || message.includes("token expired")) {
3490
+ return true;
3491
+ }
3492
+ return false;
3493
+ }
3494
+ combineSignals(signal1, signal2) {
3495
+ const controller = new AbortController();
3496
+ const abort = () => controller.abort();
3497
+ signal1.addEventListener("abort", abort);
3498
+ signal2.addEventListener("abort", abort);
3499
+ if (signal1.aborted || signal2.aborted) {
3500
+ controller.abort();
3501
+ }
3502
+ return controller.signal;
3503
+ }
3504
+ sleep(ms) {
3505
+ return new Promise((resolve) => setTimeout(resolve, ms));
3506
+ }
3507
+ };
3508
+
3509
+ // src/providers/XAIProvider.ts
3510
+ var XAI_MODELS = [
3511
+ "grok-4.20-reasoning",
3512
+ "grok-4-1-fast-reasoning-latest",
3513
+ "grok-4.20-0309-reasoning"
3514
+ ];
3515
+ var XAI_DEFAULT_MODEL = "grok-4.20-reasoning";
3516
+ var XAI_API_BASE_URL = "https://api.x.ai/v1";
3517
+ var XAI_FRIENDLY_MESSAGES = {
3518
+ auth_failed: "Authentication failed. Please verify your xAI API key in ~/.autohand/config.json.",
3519
+ payment_required: "Payment required. Please check your xAI account balance or billing settings.",
3520
+ access_denied: "Access denied. Your xAI API key may not have permission for this model.",
3521
+ server_error: "The xAI service encountered an error. Please try again later.",
3522
+ network_error: "Unable to connect to xAI. Please check your internet connection and xAI API configuration.",
3523
+ timeout: "The request timed out. The xAI service may be experiencing high load."
3524
+ };
3525
+ function withXAIMessage(error) {
3526
+ const friendlyMessage = XAI_FRIENDLY_MESSAGES[error.code];
3527
+ if (!friendlyMessage) {
3528
+ return error;
3529
+ }
3530
+ return new ApiError(
3531
+ error.rawDetail ? `${friendlyMessage}
3532
+ ${error.rawDetail}` : friendlyMessage,
3533
+ error.code,
3534
+ error.httpStatus,
3535
+ error.retryable,
3536
+ error.retryAfterMs,
3537
+ error.rawDetail
3538
+ );
3539
+ }
3540
+ var XAIProvider = class {
3541
+ baseUrl;
3542
+ apiKey;
3543
+ model;
3544
+ constructor(config) {
3545
+ this.apiKey = config.apiKey || "";
3546
+ this.baseUrl = (config.baseUrl || XAI_API_BASE_URL).replace(/\/$/, "");
3547
+ this.model = config.model || XAI_DEFAULT_MODEL;
3548
+ }
3549
+ getName() {
3550
+ return "xai";
3551
+ }
3552
+ setModel(model) {
3553
+ this.model = model;
3554
+ }
3555
+ /**
3556
+ * List available models from xAI's REST API (GET /v1/language-models),
3557
+ * falling back to the canonical static list.
3558
+ */
3559
+ async listModels() {
3560
+ try {
3561
+ const headers = await this.buildAuthHeaders();
3562
+ const response = await fetch(`${this.baseUrl}/language-models`, { headers });
3563
+ if (response.ok) {
3564
+ const data = await response.json();
3565
+ if (data?.models && Array.isArray(data.models)) {
3566
+ const ids = /* @__PURE__ */ new Set();
3567
+ for (const m of data.models) {
3568
+ if (m.id) ids.add(m.id);
3569
+ if (Array.isArray(m.aliases)) {
3570
+ for (const a of m.aliases) ids.add(a);
3571
+ }
3572
+ }
3573
+ if (ids.size > 0) {
3574
+ return [...ids];
3575
+ }
3576
+ }
3577
+ }
3578
+ } catch {
3579
+ }
3580
+ return [...XAI_MODELS];
3581
+ }
3582
+ async isAvailable() {
3583
+ if (!this.apiKey) return false;
3584
+ try {
3585
+ const headers = await this.buildAuthHeaders();
3586
+ const response = await fetch(`${this.baseUrl}/models`, { headers });
3587
+ return response.ok;
3588
+ } catch {
3589
+ return false;
3590
+ }
3591
+ }
3592
+ /**
3593
+ * Complete a chat request using the xAI Responses API.
3594
+ *
3595
+ * xAI supports server-side tools (`web_search`, `x_search`, `code_execution`)
3596
+ * in addition to standard function calling. This implementation detects
3597
+ * tool types and emits the appropriate xAI tool format.
3598
+ */
3599
+ async complete(request) {
3600
+ const body = {
3601
+ model: request.model || this.model,
3602
+ stream: true,
3603
+ tool_choice: "auto",
3604
+ input: this.toXAIInputItems(request.messages)
3605
+ };
3606
+ const tools = this.mapToXAITools(request.tools);
3607
+ if (tools.length > 0) {
3608
+ body.tools = tools;
3609
+ }
3610
+ const headers = await this.buildAuthHeaders();
3611
+ let response;
3612
+ try {
3613
+ response = await fetch(`${this.baseUrl}/responses`, {
3614
+ method: "POST",
3615
+ headers: {
3616
+ "Content-Type": "application/json",
3617
+ ...headers
3618
+ },
3619
+ body: JSON.stringify(body),
3620
+ signal: request.signal
3621
+ });
3622
+ } catch (error) {
3623
+ const err = error;
3624
+ if (err.name === "AbortError" && request.signal?.aborted) {
3625
+ throw new ApiError("Request cancelled.", "cancelled", 0, false);
3626
+ }
3627
+ if (err.name === "AbortError") {
3628
+ throw new ApiError(
3629
+ "The request timed out. The xAI service may be experiencing high load.",
3630
+ "timeout",
3631
+ 0,
3632
+ true
3633
+ );
3634
+ }
3635
+ throw new ApiError(
3636
+ `Unable to connect to ${this.baseUrl}. Please check the URL and your xAI API key.`,
3637
+ "network_error",
3638
+ 0,
3639
+ true
3640
+ );
3641
+ }
3642
+ if (!response.ok) {
3643
+ throw await this.buildApiError(response);
3644
+ }
3645
+ const data = await this.parseXAIStream(response);
3646
+ const toolCalls = this.extractXAIToolCalls(data.output);
3647
+ const content = this.extractXAIContent(data);
3648
+ const usage = this.mapXAIUsage(data.usage);
3649
+ return {
3650
+ id: data.id,
3651
+ created: data.created_at ?? Math.floor(Date.now() / 1e3),
3652
+ content,
3653
+ toolCalls,
3654
+ finishReason: toolCalls.length > 0 ? "tool_calls" : data.incomplete_details?.reason === "max_output_tokens" ? "length" : "stop",
3655
+ usage,
3656
+ raw: data
3657
+ };
3658
+ }
3659
+ // ------------------------------------------------------------------
3660
+ // Helpers
3661
+ // ------------------------------------------------------------------
3662
+ async buildAuthHeaders() {
3663
+ return {
3664
+ Authorization: `Bearer ${this.apiKey}`
3665
+ };
3666
+ }
3667
+ // Map the generic LLMRequest.tools (FunctionDefinition[]) to xAI tool payloads.
3668
+ // xAI built-in tools use a simple `{ type: "web_search" }` form.
3669
+ // If the user supplies a custom FunctionDefinition whose name matches a known
3670
+ // server-side tool we emit the server-side variant; everything else becomes a
3671
+ // standard `function` tool.
3672
+ mapToXAITools(tools) {
3673
+ if (!tools?.length) return [];
3674
+ return tools.map((tool) => {
3675
+ const name = tool.name.toLowerCase();
3676
+ if (name === "web_search" || name === "x_search" || name === "code_execution" || name === "code_interpreter") {
3677
+ return { type: name === "code_interpreter" ? "code_execution" : name };
3678
+ }
3679
+ return {
3680
+ type: "function",
3681
+ function: {
3682
+ name: tool.name,
3683
+ description: tool.description,
3684
+ parameters: tool.parameters
3685
+ }
3686
+ };
3687
+ });
3688
+ }
3689
+ // Convert the internal message format to xAI Responses API input items.
3690
+ toXAIInputItems(messages) {
3691
+ const items = [];
3692
+ for (const msg of messages) {
3693
+ if (msg.role === "system") {
3694
+ continue;
3695
+ }
3696
+ if (msg.role === "tool" && msg.tool_call_id && msg.content) {
3697
+ items.push({
3698
+ type: "function_call_output",
3699
+ call_id: msg.tool_call_id,
3700
+ output: msg.content
3701
+ });
3702
+ continue;
3703
+ }
3704
+ if (msg.role === "assistant" && msg.tool_calls?.length) {
3705
+ items.push({
3706
+ type: "message",
3707
+ role: "assistant",
3708
+ content: []
3709
+ // will have function_calls appended
3710
+ });
3711
+ for (const tc of msg.tool_calls) {
3712
+ items.push({
3713
+ type: "function_call",
3714
+ call_id: tc.id,
3715
+ name: tc.function.name,
3716
+ arguments: tc.function.arguments
3717
+ });
3718
+ }
3719
+ continue;
3720
+ }
3721
+ if (msg.content && typeof msg.content === "string" && msg.content.trim()) {
3722
+ items.push({
3723
+ type: "message",
3724
+ role: msg.role === "user" ? "user" : "user",
3725
+ content: [{ type: "input_text", text: msg.content }]
3726
+ });
3727
+ }
3728
+ }
3729
+ return items;
3730
+ }
3731
+ extractXAIToolCalls(output) {
3732
+ if (!Array.isArray(output)) return [];
3733
+ return output.filter((entry) => entry?.type === "function_call").map((toolCall, index) => ({
3734
+ id: toolCall.call_id ?? `call_${index + 1}`,
3735
+ type: "function",
3736
+ function: {
3737
+ name: toolCall.name,
3738
+ arguments: toolCall.arguments
3739
+ }
3740
+ }));
3741
+ }
3742
+ extractXAIContent(data) {
3743
+ if (typeof data.output_text === "string" && data.output_text.trim()) {
3744
+ return data.output_text;
3745
+ }
3746
+ if (!Array.isArray(data.output)) return "";
3747
+ const parts = [];
3748
+ for (const item of data.output) {
3749
+ if (item?.type !== "message" || !Array.isArray(item.content)) continue;
3750
+ for (const ci of item.content) {
3751
+ if (ci?.type === "output_text" && typeof ci.text === "string") {
3752
+ parts.push(ci.text);
3753
+ }
3754
+ }
3755
+ }
3756
+ return parts.join("\n").trim();
3757
+ }
3758
+ mapXAIUsage(usage) {
3759
+ if (!usage) return void 0;
3760
+ const input = usage.input_tokens ?? 0;
3761
+ const output = usage.output_tokens ?? 0;
3762
+ return {
3763
+ promptTokens: input,
3764
+ completionTokens: output,
3765
+ totalTokens: usage.total_tokens ?? input + output
3766
+ };
3767
+ }
3768
+ async parseXAIStream(response) {
3769
+ const text = await response.text();
3770
+ let currentEvent = "";
3771
+ let completedData = null;
3772
+ for (const line of text.split("\n")) {
3773
+ if (line.startsWith("event: ")) {
3774
+ currentEvent = line.slice(7).trim();
3775
+ continue;
3776
+ }
3777
+ if (line.startsWith("data: ") && currentEvent === "response.completed") {
3778
+ completedData = JSON.parse(line.slice(6));
3779
+ break;
3780
+ }
3781
+ }
3782
+ if (!completedData) {
3783
+ throw new ApiError(
3784
+ "No response.completed event found in stream. The API response may be malformed.",
3785
+ "invalid_request",
3786
+ 0,
3787
+ false
3788
+ );
3789
+ }
3790
+ return completedData;
3791
+ }
3792
+ async buildApiError(response) {
3793
+ let errorDetail = "";
3794
+ try {
3795
+ const body = await response.json();
3796
+ const errObj = body?.error;
3797
+ errorDetail = errObj?.message ?? body?.detail ?? body?.error ?? "";
3798
+ if (typeof errorDetail === "object") {
3799
+ errorDetail = JSON.stringify(errorDetail);
3800
+ }
3801
+ } catch {
3802
+ try {
3803
+ errorDetail = await response.text();
3804
+ } catch {
3805
+ }
3806
+ }
3807
+ return withXAIMessage(classifyApiError(response.status, errorDetail, response.headers));
3808
+ }
3809
+ };
3810
+
3811
+ // src/providers/CerebrasClient.ts
3812
+ function sanitizeMessages5(messages) {
3813
+ return messages.map((msg) => {
3814
+ const sanitized = {
3815
+ role: msg.role,
3816
+ content: msg.content
3817
+ };
3818
+ if (msg.role === "tool" && msg.tool_call_id) {
3819
+ sanitized.tool_call_id = msg.tool_call_id;
3820
+ }
3821
+ if (msg.role === "assistant" && msg.tool_calls?.length) {
3822
+ sanitized.tool_calls = msg.tool_calls;
3823
+ }
3824
+ if (msg.name) {
3825
+ sanitized.name = msg.name;
3826
+ }
3827
+ return sanitized;
3828
+ });
3829
+ }
3830
+ var DEFAULT_BASE_URL3 = "https://api.cerebras.ai/v1";
3831
+ var DEFAULT_MAX_RETRIES7 = 3;
3832
+ var MAX_ALLOWED_RETRIES7 = 5;
3833
+ var DEFAULT_RETRY_DELAY7 = 1e3;
3834
+ var DEFAULT_TIMEOUT7 = 3e4;
3835
+ var FRIENDLY_ERRORS2 = {
3836
+ 400: "The request was malformed. This often happens when the context is too long. Try /undo to remove recent turns or /new to start fresh.",
3837
+ 401: "Authentication failed. Please verify your Cerebras API key in ~/.autohand/config.json.",
3838
+ 402: "Payment required. Please check your Cerebras account balance or billing settings.",
3839
+ 403: "Access denied. Your API key may not have permission for this model.",
3840
+ 404: "The requested model was not found. Use /model to select a different one.",
3841
+ 429: "Rate limit exceeded. Please wait a moment and try again, or choose a different model.",
3842
+ 500: "The Cerebras service encountered an internal error. Please try again later.",
3843
+ 502: "The Cerebras service is temporarily unavailable. Please try again in a few moments.",
3844
+ 503: "The Cerebras service is currently overloaded. Please try again later.",
3845
+ 504: "The request timed out. The service may be experiencing high load."
3846
+ };
3847
+ var CerebrasClient = class {
3848
+ apiKey;
3849
+ baseUrl;
3850
+ defaultModel;
3851
+ maxRetries;
3852
+ retryDelay;
3853
+ timeout;
3854
+ constructor(settings, networkSettings) {
3855
+ this.apiKey = settings.apiKey;
3856
+ this.baseUrl = settings.baseUrl ?? DEFAULT_BASE_URL3;
3857
+ this.defaultModel = settings.model;
3858
+ this.maxRetries = Math.min(
3859
+ networkSettings?.maxRetries ?? DEFAULT_MAX_RETRIES7,
3860
+ MAX_ALLOWED_RETRIES7
3861
+ );
3862
+ this.retryDelay = DEFAULT_RETRY_DELAY7;
3863
+ this.timeout = networkSettings?.timeout ?? DEFAULT_TIMEOUT7;
3864
+ }
3865
+ setDefaultModel(model) {
3866
+ this.defaultModel = model;
3867
+ }
3868
+ sleep(ms) {
3869
+ return new Promise((resolve) => setTimeout(resolve, ms));
3870
+ }
3871
+ async complete(request) {
3872
+ const payload = {
3873
+ model: request.model ?? this.defaultModel,
3874
+ messages: sanitizeMessages5(request.messages),
3875
+ temperature: request.temperature ?? 0.7,
3876
+ max_tokens: request.maxTokens ?? 2e4,
3877
+ stream: request.stream ?? false
3878
+ };
3879
+ if (request.tools && request.tools.length > 0) {
3880
+ payload.tools = request.tools.map((tool) => ({
3881
+ type: "function",
3882
+ function: {
3883
+ name: tool.name,
3884
+ description: tool.description,
3885
+ parameters: tool.parameters ?? { type: "object", properties: {} }
3886
+ }
3887
+ }));
3888
+ if (request.toolChoice) {
3889
+ payload.tool_choice = request.toolChoice;
3890
+ }
3891
+ }
3892
+ const headers = {
3893
+ "Content-Type": "application/json",
3894
+ "x-source": "Autohand Code CLI"
3895
+ };
3896
+ if (this.apiKey) {
3897
+ headers.Authorization = `Bearer ${this.apiKey}`;
3898
+ }
3899
+ const payloadJson = JSON.stringify(payload);
3900
+ const payloadSizeBytes = payloadJson.length;
3901
+ const maxPayloadSize = 5 * 1024 * 1024;
3902
+ if (payloadSizeBytes > maxPayloadSize) {
3903
+ const sizeMB = (payloadSizeBytes / (1024 * 1024)).toFixed(2);
3904
+ throw new Error(
3905
+ `Request payload too large (${sizeMB}MB). This usually happens when the conversation history grows too long. Try using /undo to remove recent turns or /new to start fresh.`
3906
+ );
3907
+ }
3908
+ let lastError = null;
3909
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
3910
+ try {
3911
+ const response = await this.makeRequest(
3912
+ payload,
3913
+ headers,
3914
+ request.signal,
3915
+ payloadJson
3916
+ );
3917
+ return response;
3918
+ } catch (error) {
3919
+ lastError = error;
3920
+ if (this.isNonRetryableError(error)) {
3921
+ throw error;
3922
+ }
3923
+ if (attempt < this.maxRetries) {
3924
+ const delay = this.retryDelay * Math.pow(2, attempt);
3925
+ await this.sleep(delay);
3926
+ }
3927
+ }
3928
+ }
3929
+ throw lastError ?? new Error("Failed to communicate with Cerebras API. Please try again.");
3930
+ }
3931
+ async makeRequest(payload, headers, signal, payloadJson) {
3932
+ const timeoutController = new AbortController();
3933
+ const timerId = setTimeout(() => timeoutController.abort(), this.timeout);
3934
+ const combinedSignal = signal ? this.combineSignals(signal, timeoutController.signal) : timeoutController.signal;
3935
+ let response;
3936
+ try {
3937
+ response = await fetch(`${this.baseUrl}/chat/completions`, {
3938
+ method: "POST",
3939
+ headers,
3940
+ body: payloadJson,
3941
+ signal: combinedSignal
3942
+ });
3943
+ } finally {
3944
+ clearTimeout(timerId);
3945
+ }
3946
+ if (!response.ok) {
3947
+ throw await this.buildApiError(response, payload);
3948
+ }
3949
+ if (payload.stream) {
3950
+ return this.handleStreamingResponse(response);
3951
+ }
3952
+ const data = await response.json();
3953
+ const choice = data.choices?.[0];
3954
+ let toolCalls;
3955
+ if (choice?.message?.tool_calls?.length) {
3956
+ toolCalls = choice.message.tool_calls.map((tc) => ({
3957
+ id: tc.id,
3958
+ type: tc.type,
3959
+ function: {
3960
+ name: tc.function.name,
3961
+ arguments: tc.function.arguments
3962
+ }
3963
+ }));
3964
+ }
3965
+ let usage;
3966
+ if (data.usage) {
3967
+ usage = {
3968
+ promptTokens: data.usage.prompt_tokens,
3969
+ completionTokens: data.usage.completion_tokens,
3970
+ totalTokens: data.usage.total_tokens
3971
+ };
3972
+ }
3973
+ const finishReason = toolCalls?.length ? "tool_calls" : choice?.finish_reason === "stop" || choice?.finish_reason === "length" || choice?.finish_reason === "content_filter" ? choice.finish_reason : "stop";
3974
+ return {
3975
+ id: data.id || `cerebras-${Date.now()}`,
3976
+ created: data.created || Math.floor(Date.now() / 1e3),
3977
+ content: choice?.message?.content ?? "",
3978
+ toolCalls,
3979
+ usage,
3980
+ finishReason,
3981
+ raw: data
3982
+ };
3983
+ }
3984
+ async handleStreamingResponse(response) {
3985
+ const reader = response.body?.getReader();
3986
+ if (!reader) {
3987
+ throw new Error("No response body available for streaming");
3988
+ }
3989
+ let content = "";
3990
+ let finishReason;
3991
+ try {
3992
+ while (true) {
3993
+ const { done, value } = await reader.read();
3994
+ if (done) break;
3995
+ const chunk = new TextDecoder().decode(value);
3996
+ const lines = chunk.split("\n");
3997
+ for (const line of lines) {
3998
+ if (line.startsWith("data: ")) {
3999
+ const data = line.slice(6);
4000
+ if (data === "[DONE]") continue;
4001
+ try {
4002
+ const parsed = JSON.parse(data);
4003
+ const delta = parsed.choices?.[0]?.delta;
4004
+ if (delta?.content) {
4005
+ content += delta.content;
4006
+ }
4007
+ if (parsed.choices?.[0]?.finish_reason) {
4008
+ finishReason = parsed.choices[0].finish_reason;
4009
+ }
4010
+ } catch {
4011
+ }
4012
+ }
4013
+ }
4014
+ }
4015
+ } finally {
4016
+ reader.releaseLock();
4017
+ }
4018
+ return {
4019
+ id: `cerebras-${Date.now()}`,
4020
+ created: Math.floor(Date.now() / 1e3),
4021
+ content,
4022
+ finishReason: finishReason || "stop",
4023
+ raw: { content, finishReason }
4024
+ };
4025
+ }
4026
+ combineSignals(userSignal, timeoutSignal) {
4027
+ const controller = new AbortController();
4028
+ const onAbort = () => {
4029
+ controller.abort();
4030
+ };
4031
+ userSignal.addEventListener("abort", onAbort);
4032
+ timeoutSignal.addEventListener("abort", onAbort);
4033
+ if (userSignal.aborted || timeoutSignal.aborted) {
4034
+ controller.abort();
4035
+ }
4036
+ return controller.signal;
4037
+ }
4038
+ async buildApiError(response, _body) {
4039
+ let errorDetail = "";
4040
+ try {
4041
+ const errorData = await response.json();
4042
+ errorDetail = errorData.error?.message || JSON.stringify(errorData);
4043
+ } catch {
4044
+ try {
4045
+ errorDetail = await response.text();
4046
+ } catch {
4047
+ errorDetail = `HTTP ${response.status}`;
4048
+ }
4049
+ }
4050
+ const friendlyMessage = FRIENDLY_ERRORS2[response.status] || `Cerebras API error (${response.status}): ${errorDetail}`;
4051
+ return new Error(friendlyMessage);
4052
+ }
4053
+ isNonRetryableError(error) {
4054
+ const message = error.message.toLowerCase();
4055
+ return message.includes("authentication failed") || message.includes("access denied") || message.includes("not found") || message.includes("malformed");
4056
+ }
4057
+ };
4058
+
4059
+ // src/providers/CerebrasProvider.ts
4060
+ var CEREBRAS_DEFAULT_BASE_URL = "https://api.cerebras.ai/v1";
4061
+ var CEREBRAS_MODELS = [
4062
+ "zai-glm-4.7",
4063
+ "qwen-3-235b-a22b-instruct-2507"
4064
+ ];
4065
+ var CerebrasProvider = class {
4066
+ client;
4067
+ model;
4068
+ constructor(config, networkSettings) {
4069
+ const effectiveConfig = {
4070
+ ...config,
4071
+ baseUrl: config.baseUrl ?? CEREBRAS_DEFAULT_BASE_URL
4072
+ };
4073
+ this.client = new CerebrasClient(effectiveConfig, networkSettings);
4074
+ this.model = config.model;
4075
+ }
4076
+ getName() {
4077
+ return "cerebras";
4078
+ }
4079
+ setModel(model) {
4080
+ this.model = model;
4081
+ this.client.setDefaultModel(model);
4082
+ }
4083
+ async listModels() {
4084
+ return [...CEREBRAS_MODELS];
4085
+ }
4086
+ async isAvailable() {
4087
+ return true;
4088
+ }
4089
+ async complete(request) {
4090
+ return this.client.complete(request);
4091
+ }
4092
+ };
4093
+
4094
+ // src/providers/DeepSeekProvider.ts
4095
+ var DEEPSEEK_DEFAULT_BASE_URL = "https://api.deepseek.com";
4096
+ var DEEPSEEK_MODELS = [
4097
+ "deepseek-v4-flash",
4098
+ "deepseek-v4-pro",
4099
+ "deepseek-chat",
4100
+ "deepseek-reasoner"
4101
+ ];
4102
+ var DeepSeekProvider = class {
4103
+ client;
4104
+ model;
4105
+ constructor(config, networkSettings) {
4106
+ const effectiveConfig = {
4107
+ ...config,
4108
+ baseUrl: config.baseUrl ?? DEEPSEEK_DEFAULT_BASE_URL
4109
+ };
4110
+ this.client = new LLMGatewayClient(effectiveConfig, networkSettings, {
4111
+ serviceName: "DeepSeek",
4112
+ credentialName: "DeepSeek API key",
4113
+ accountName: "DeepSeek account"
4114
+ });
4115
+ this.model = config.model;
4116
+ }
4117
+ getName() {
4118
+ return "deepseek";
4119
+ }
4120
+ setModel(model) {
4121
+ this.model = model;
4122
+ this.client.setDefaultModel(model);
4123
+ }
4124
+ async listModels() {
4125
+ return [...DEEPSEEK_MODELS];
4126
+ }
4127
+ async isAvailable() {
4128
+ return true;
4129
+ }
4130
+ async complete(request) {
4131
+ return this.client.complete(request);
4132
+ }
4133
+ };
4134
+
4135
+ // src/providers/ProviderFactory.ts
4136
+ var ProviderNotConfiguredError = class extends Error {
4137
+ constructor(providerName) {
4138
+ super(`PROVIDER_NOT_CONFIGURED:${providerName}`);
4139
+ this.providerName = providerName;
4140
+ this.name = "ProviderNotConfiguredError";
4141
+ }
4142
+ providerName;
4143
+ };
4144
+ var UnconfiguredProvider = class {
4145
+ constructor(providerName) {
4146
+ this.providerName = providerName;
4147
+ }
4148
+ providerName;
4149
+ getName() {
4150
+ return "unconfigured";
4151
+ }
4152
+ async complete(_request) {
4153
+ throw new ProviderNotConfiguredError(this.providerName);
4154
+ }
4155
+ async listModels() {
4156
+ return [];
4157
+ }
4158
+ async isAvailable() {
4159
+ return false;
4160
+ }
4161
+ setModel(_model) {
4162
+ }
4163
+ };
4164
+ var ProviderFactory = class {
4165
+ /**
4166
+ * Create an LLM provider based on configuration.
4167
+ * Returns an UnconfiguredProvider if the selected provider is not configured,
4168
+ * allowing the agent to handle it gracefully instead of crashing.
4169
+ */
4170
+ static create(config) {
4171
+ const providerName = config.provider || "openrouter";
4172
+ switch (providerName) {
4173
+ case "ollama":
4174
+ if (!config.ollama) {
4175
+ return new UnconfiguredProvider("ollama");
4176
+ }
4177
+ return new OllamaProvider(config.ollama, config.network);
4178
+ case "openai":
4179
+ if (!config.openai) {
4180
+ return new UnconfiguredProvider("openai");
4181
+ }
4182
+ return new OpenAIProvider(config.openai);
4183
+ case "llamacpp":
4184
+ if (!config.llamacpp) {
4185
+ return new UnconfiguredProvider("llamacpp");
4186
+ }
4187
+ return new LlamaCppProvider(config.llamacpp);
4188
+ case "mlx":
4189
+ if (!config.mlx) {
4190
+ return new UnconfiguredProvider("mlx");
4191
+ }
4192
+ return new MLXProvider(config.mlx, config.network);
4193
+ case "llmgateway":
4194
+ if (!config.llmgateway) {
4195
+ return new UnconfiguredProvider("llmgateway");
4196
+ }
4197
+ return new LLMGatewayProvider(config.llmgateway, config.network);
4198
+ case "azure":
4199
+ if (!config.azure) {
4200
+ return new UnconfiguredProvider("azure");
4201
+ }
4202
+ return new AzureProvider(config.azure, config.network);
4203
+ case "zai":
4204
+ if (!config.zai) {
4205
+ return new UnconfiguredProvider("zai");
4206
+ }
4207
+ return new ZaiProvider(config.zai, config.network);
4208
+ case "vertexai":
4209
+ if (!config.vertexai) {
4210
+ return new UnconfiguredProvider("vertexai");
4211
+ }
4212
+ return new VertexAIProvider(config.vertexai, config.network);
4213
+ case "xai":
4214
+ if (!config.xai) {
4215
+ return new UnconfiguredProvider("xai");
4216
+ }
4217
+ return new XAIProvider(config.xai);
4218
+ case "cerebras":
4219
+ if (!config.cerebras) {
4220
+ return new UnconfiguredProvider("cerebras");
4221
+ }
4222
+ return new CerebrasProvider(config.cerebras, config.network);
4223
+ case "nvidia":
4224
+ if (!config.nvidia) {
4225
+ return new UnconfiguredProvider("nvidia");
4226
+ }
4227
+ return new NVIDIAProvider(config.nvidia, config.network);
4228
+ case "deepseek":
4229
+ if (!config.deepseek) {
4230
+ return new UnconfiguredProvider("deepseek");
4231
+ }
4232
+ return new DeepSeekProvider(config.deepseek, config.network);
4233
+ case "openrouter":
4234
+ default:
4235
+ if (!config.openrouter) {
4236
+ return new UnconfiguredProvider("openrouter");
4237
+ }
4238
+ return new OpenRouterProvider(config.openrouter);
4239
+ }
4240
+ }
4241
+ /**
4242
+ * Get all available provider names.
4243
+ * MLX is only included on Apple Silicon (macOS + arm64).
4244
+ */
4245
+ static getProviderNames() {
4246
+ const providers = ["zai", "xai", "vertexai", "nvidia", "openrouter", "openai", "ollama", "llmgateway", "llamacpp", "deepseek", "cerebras", "azure"];
4247
+ if (isMLXSupported()) {
4248
+ providers.push("mlx");
4249
+ }
4250
+ return providers;
4251
+ }
4252
+ /**
4253
+ * Check if a provider name is valid.
4254
+ * Note: This checks if the name is a valid provider type, not if it's available on this platform.
4255
+ * MLX is always a valid provider name, but may not be available on non-Apple Silicon systems.
4256
+ */
4257
+ static isValidProvider(name) {
4258
+ const allProviders = ["openrouter", "ollama", "openai", "llamacpp", "mlx", "llmgateway", "azure", "zai", "vertexai", "xai", "cerebras", "nvidia", "deepseek"];
4259
+ return allProviders.includes(name);
4260
+ }
4261
+ };
4262
+
4263
+ export {
4264
+ isChatGPTAuthExpired,
4265
+ authenticateOpenAIChatGPT,
4266
+ OPENAI_MODELS,
4267
+ getOpenRouterModelContextWindow,
4268
+ modelSupportsImages,
4269
+ ZAI_DEFAULT_BASE_URL,
4270
+ ZAI_MODELS,
4271
+ isGcloudInstalled,
4272
+ getGcloudProject,
4273
+ getGcloudAccessToken,
4274
+ getGcloudAccount,
4275
+ VERTEX_AI_CODING_MODELS,
4276
+ CEREBRAS_DEFAULT_BASE_URL,
4277
+ CEREBRAS_MODELS,
4278
+ DEEPSEEK_DEFAULT_BASE_URL,
4279
+ DEEPSEEK_MODELS,
4280
+ ProviderNotConfiguredError,
4281
+ ProviderFactory
4282
+ };
4283
+ /**
4284
+ * @license
4285
+ * Copyright 2025 Autohand AI LLC
4286
+ * SPDX-License-Identifier: Apache-2.0
4287
+ */
4288
+ /**
4289
+ * Google Cloud CLI authentication utilities
4290
+ * @license Apache-2.0
4291
+ */