autohand-cli 0.8.3 → 0.9.1

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 (546) hide show
  1. package/README.md +191 -74
  2. package/dist/{AgentRegistry-ODDXPAFR.js → AgentRegistry-GOV7FN2O.js} +2 -2
  3. package/dist/AgentRegistry-J2C6JBZ3.cjs +10 -0
  4. package/dist/{AutomodeManager-EOVHGRPP.js → AutomodeManager-APLLPEYJ.js} +31 -17
  5. package/dist/{AutomodeManager-6ATBE7Q5.cjs → AutomodeManager-MPSXT2RH.cjs} +38 -24
  6. package/dist/CommunitySkillsCache-22BB3J6H.cjs +8 -0
  7. package/dist/{CommunitySkillsCache-QLE57BN5.js → CommunitySkillsCache-ZACDPD4J.js} +2 -2
  8. package/dist/GitHubRegistryFetcher-CYJLF2XL.cjs +7 -0
  9. package/dist/{GitHubRegistryFetcher-NT5GFZXS.js → GitHubRegistryFetcher-J2BWPXJF.js} +1 -1
  10. package/dist/{HookManager-B2F35M27.js → HookManager-2S6AHOC3.js} +1 -1
  11. package/dist/{HookManager-PFAFE3FK.cjs → HookManager-TVEQI5ZU.cjs} +2 -2
  12. package/dist/{ImportWizard-OHRKBANZ.js → ImportWizard-AEBMAKXE.js} +33 -9
  13. package/dist/{ImportWizard-CJRZPPHL.cjs → ImportWizard-AF2XL6DS.cjs} +37 -13
  14. package/dist/LearnAdvisor-KKEQ5QCV.js +9 -0
  15. package/dist/LearnAdvisor-Q7RAYLPF.cjs +9 -0
  16. package/dist/{McpClientManager-NQ3EW2IF.cjs → McpClientManager-BNSKLHIN.cjs} +2 -2
  17. package/dist/{McpClientManager-VBIMGKXU.js → McpClientManager-SL35BR24.js} +1 -1
  18. package/dist/MemoryManager-PMNAEZMB.cjs +8 -0
  19. package/dist/{MemoryManager-2LAT7IHS.js → MemoryManager-QZRLTUB5.js} +2 -2
  20. package/dist/NVIDIAProvider-C3FWQOBZ.cjs +14 -0
  21. package/dist/NVIDIAProvider-SJPY3ONR.js +14 -0
  22. package/dist/{PermissionManager-7NQHRCHY.js → PermissionManager-MT7KBNNR.js} +3 -3
  23. package/dist/PermissionManager-URCNP5EC.cjs +11 -0
  24. package/dist/{ProjectProfiler-UG42W6WD.cjs → ProjectProfiler-CDAE7ECW.cjs} +1 -0
  25. package/dist/{ProjectProfiler-P4QJQWWA.js → ProjectProfiler-NZTJDRHD.js} +1 -0
  26. package/dist/ProviderFactory-56N3ECE4.cjs +12 -0
  27. package/dist/{ProviderFactory-2IYJ5OPW.js → ProviderFactory-RB4RVAF6.js} +4 -1
  28. package/dist/SessionManager-2IZGWASQ.cjs +10 -0
  29. package/dist/{SessionManager-4U4JFQ3C.js → SessionManager-554PHVPC.js} +2 -2
  30. package/dist/SkillsRegistry-GMRVONOQ.cjs +9 -0
  31. package/dist/{SkillsRegistry-RFEINXRT.js → SkillsRegistry-NHHMIUYJ.js} +2 -2
  32. package/dist/SubAgent-4GOR2FIJ.js +13 -0
  33. package/dist/SubAgent-ZJMDH4FI.cjs +13 -0
  34. package/dist/{SyncApiClient-B5RT2ECG.js → SyncApiClient-G3FM65IV.js} +1 -1
  35. package/dist/SyncApiClient-ODBDECOG.cjs +11 -0
  36. package/dist/about-3USFSGOH.js +15 -0
  37. package/dist/about-QR52U5DD.cjs +15 -0
  38. package/dist/acp-5Y2W6OZP.js +1556 -0
  39. package/dist/acp-GSYE75OB.cjs +1556 -0
  40. package/dist/actionExecutor-KHUU3Z3Q.cjs +28 -0
  41. package/dist/actionExecutor-PLQJZLZV.js +28 -0
  42. package/dist/add-dir-BTAD5OZS.cjs +11 -0
  43. package/dist/{add-dir-TUJM3PG5.js → add-dir-HCERJDBR.js} +3 -2
  44. package/dist/agent-B3EVT7NN.cjs +126 -0
  45. package/dist/agent-SATQ3WZ3.js +126 -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-44CRJGM7.cjs +18 -0
  53. package/dist/agents-NN4ZAJ7R.js +18 -0
  54. package/dist/agents-new-6D6G4QS6.cjs +17 -0
  55. package/dist/agents-new-7GAQ25EK.js +17 -0
  56. package/dist/{autoSkill-LQEVQFIH.js → autoSkill-66PKCYTW.js} +2 -2
  57. package/dist/autoSkill-VQLGBELN.cjs +20 -0
  58. package/dist/automode-45DWV4TN.cjs +10 -0
  59. package/dist/{automode-R6P3VHLS.js → automode-BUF3VGXU.js} +2 -2
  60. package/dist/browserToolBridge-BXRQB4B4.cjs +58 -0
  61. package/dist/browserToolBridge-F5N66PE7.js +58 -0
  62. package/dist/chrome-Q2U73HZB.cjs +50 -0
  63. package/dist/chrome-Q54OALOK.cjs +21 -0
  64. package/dist/chrome-Y3T7EIHG.js +50 -0
  65. package/dist/chrome-ZNJ46OYW.js +21 -0
  66. package/dist/chromeSkill-53TH55PM.js +105 -0
  67. package/dist/chromeSkill-THW7N4IY.cjs +105 -0
  68. package/dist/{chunk-HXGBSJL5.cjs → chunk-22SCR3EV.cjs} +2 -2
  69. package/dist/chunk-26G7YGOW.js +367 -0
  70. package/dist/{chunk-3L53OA4E.cjs → chunk-2ESQR35E.cjs} +10 -10
  71. package/dist/chunk-2H5O745H.js +63 -0
  72. package/dist/{chunk-ULDM2SNB.js → chunk-2IW5YFJ4.js} +48 -47
  73. package/dist/{chunk-U55TWFJI.cjs → chunk-2VHB43IX.cjs} +24 -6
  74. package/dist/chunk-3EDDDZS2.cjs +5 -0
  75. package/dist/{chunk-X5VSP65C.cjs → chunk-3ICORF7M.cjs} +4 -4
  76. package/dist/chunk-3OF56EMA.cjs +197 -0
  77. package/dist/chunk-3UC6DMFI.cjs +93 -0
  78. package/dist/{chunk-64H4FRM3.cjs → chunk-3UHRCHKB.cjs} +32 -34
  79. package/dist/{chunk-JCLYQ2JC.js → chunk-3ZOOH44N.js} +12 -6
  80. package/dist/{chunk-TUD3Z3BD.js → chunk-44ZWOBTI.js} +136 -55
  81. package/dist/chunk-4567QNRB.js +444 -0
  82. package/dist/chunk-45CHNQM6.cjs +517 -0
  83. package/dist/chunk-46MTALKD.js +44 -0
  84. package/dist/{chunk-WHE2SWHU.js → chunk-4E7SHPNQ.js} +2 -2
  85. package/dist/chunk-4LMUDS2K.js +124 -0
  86. package/dist/{chunk-W7RYQJLO.cjs → chunk-4XFHP2SN.cjs} +46 -15
  87. package/dist/chunk-4XVLNOFA.js +360 -0
  88. package/dist/{chunk-GBHDROGL.js → chunk-575PXZX7.js} +16 -4
  89. package/dist/chunk-5A7ZL64N.js +139 -0
  90. package/dist/chunk-5GCQ5UMV.cjs +85 -0
  91. package/dist/chunk-5JTTM5SC.js +59 -0
  92. package/dist/{chunk-IETRBBMP.cjs → chunk-5K3CDSWZ.cjs} +108 -31
  93. package/dist/{chunk-KLWJIPU2.cjs → chunk-5SFV2NCY.cjs} +20 -16
  94. package/dist/chunk-5ZNYX4VH.cjs +71 -0
  95. package/dist/{chunk-A4IJHHV7.cjs → chunk-63ANVAOJ.cjs} +52 -11
  96. package/dist/{chunk-MYISNQH4.js → chunk-65LRPW5W.js} +1 -1
  97. package/dist/chunk-6AYJCDTA.js +360 -0
  98. package/dist/{chunk-X2MSVKDV.js → chunk-6HF6XSCC.js} +2 -2
  99. package/dist/{chunk-AEJH23FO.cjs → chunk-6JWCGQJU.cjs} +6 -6
  100. package/dist/chunk-6OECOAK5.cjs +360 -0
  101. package/dist/chunk-6R25D2H5.js +121 -0
  102. package/dist/chunk-7DJJGKM6.js +77 -0
  103. package/dist/{chunk-VG34MG2U.js → chunk-7IRQ7JEH.js} +9 -6
  104. package/dist/chunk-7MY4JHER.js +27 -0
  105. package/dist/chunk-7PJKTSRN.js +152 -0
  106. package/dist/{chunk-SKYG33B2.cjs → chunk-7PT646AL.cjs} +3 -3
  107. package/dist/chunk-7WAUCLLQ.js +229 -0
  108. package/dist/{chunk-U46VYPLR.cjs → chunk-7XTUL7ZD.cjs} +9 -9
  109. package/dist/chunk-AE2ILW25.cjs +152 -0
  110. package/dist/{chunk-T2M64VHA.cjs → chunk-AOKXYGBQ.cjs} +61 -41
  111. package/dist/chunk-AQ5PVHOC.cjs +113 -0
  112. package/dist/{chunk-N2BLVUPM.cjs → chunk-AZ47LKD5.cjs} +3 -3
  113. package/dist/chunk-B4HSNOIH.cjs +354 -0
  114. package/dist/chunk-B76FJKEJ.cjs +184 -0
  115. package/dist/chunk-BP37FVBM.js +85 -0
  116. package/dist/chunk-BQU3HAE7.js +21 -0
  117. package/dist/chunk-BYE7RQFZ.cjs +121 -0
  118. package/dist/chunk-C46B72VK.js +1034 -0
  119. package/dist/{chunk-HIVRCQS2.js → chunk-C5QIQQ37.js} +56 -25
  120. package/dist/chunk-CCIDL4SA.js +19931 -0
  121. package/dist/{chunk-XVUHNWMX.js → chunk-CECH6QQP.js} +17 -19
  122. package/dist/chunk-CFAWTLSC.js +13 -0
  123. package/dist/{chunk-ZYQMLKOK.cjs → chunk-CK4HAHVY.cjs} +212 -70
  124. package/dist/chunk-CO2AADYU.cjs +360 -0
  125. package/dist/chunk-CRTCZ4WL.cjs +19931 -0
  126. package/dist/chunk-CSTUTDTH.js +75 -0
  127. package/dist/{chunk-K2C56QGS.cjs → chunk-D546G7DA.cjs} +1650 -479
  128. package/dist/{chunk-CNBKZEX5.cjs → chunk-DA7NBAJK.cjs} +49 -17
  129. package/dist/{chunk-G4CAKI3V.js → chunk-DGBPWODD.js} +7 -2
  130. package/dist/chunk-DHUQZ53B.cjs +29 -0
  131. package/dist/{chunk-RGR6ME5J.cjs → chunk-DI57A4BX.cjs} +25 -28
  132. package/dist/{chunk-5K6NGAVM.js → chunk-DNSD7DVJ.js} +67 -52
  133. package/dist/{chunk-RDF37HKB.cjs → chunk-DSSCERHI.cjs} +3 -2
  134. package/dist/{chunk-NAJ4IZKD.cjs → chunk-EA2MS25U.cjs} +8 -6
  135. package/dist/{chunk-SJAVBCOA.js → chunk-EEHWALXA.js} +94 -3
  136. package/dist/chunk-ES2HJQ4N.js +37 -0
  137. package/dist/{chunk-G27PQQFD.js → chunk-EYHKL4QC.js} +1 -1
  138. package/dist/{chunk-SV2WA57F.js → chunk-FBQJZKFX.js} +38 -15
  139. package/dist/chunk-FFBDRUO5.cjs +59 -0
  140. package/dist/{chunk-N7LI55V4.js → chunk-FFHSCM5M.js} +15 -8
  141. package/dist/{chunk-SAHBLB3E.js → chunk-FP46B4X3.js} +208 -3
  142. package/dist/chunk-FQVG6ZHF.js +197 -0
  143. package/dist/{chunk-J2GSFVUV.cjs → chunk-GAOPJ5S3.cjs} +74 -59
  144. package/dist/{chunk-S52YW5ZQ.js → chunk-GNBBIAMD.js} +16 -19
  145. package/dist/chunk-GSDV75A5.js +715 -0
  146. package/dist/chunk-GWY26SUD.cjs +63 -0
  147. package/dist/{chunk-TUAITHWL.js → chunk-H426KOGY.js} +2 -1
  148. package/dist/{chunk-EZMINVLU.js → chunk-H4D2Q2AL.js} +5 -2
  149. package/dist/chunk-HC2Q6A3E.cjs +4967 -0
  150. package/dist/{chunk-3WVJEL7K.cjs → chunk-HDK2EHVH.cjs} +3 -1
  151. package/dist/{chunk-5IXII4HX.cjs → chunk-HG3ZASBH.cjs} +23 -16
  152. package/dist/chunk-HK5A54AV.cjs +280 -0
  153. package/dist/{chunk-LQGVEP3E.js → chunk-HQUSEWT4.js} +45 -13
  154. package/dist/chunk-HSWWM6AM.js +2340 -0
  155. package/dist/chunk-HTU4W3GB.js +183 -0
  156. package/dist/{chunk-HLHTG5ZU.cjs → chunk-HVCIB4SW.cjs} +14 -12
  157. package/dist/chunk-HXHE2KAO.js +385 -0
  158. package/dist/{chunk-LJFUXC56.cjs → chunk-HXQREVTA.cjs} +9 -6
  159. package/dist/{chunk-SLQAYV3W.js → chunk-HZWU6IO3.js} +11 -2
  160. package/dist/chunk-I2ZK3AOD.js +866 -0
  161. package/dist/chunk-IEPLQ47J.js +719 -0
  162. package/dist/chunk-J5HE6CUM.cjs +124 -0
  163. package/dist/{chunk-LIEXWM2M.js → chunk-JAQO6XDB.js} +19 -1
  164. package/dist/chunk-JB3JCYBJ.js +113 -0
  165. package/dist/chunk-JCUHCH37.cjs +247 -0
  166. package/dist/{chunk-FPHU2ES6.cjs → chunk-JM3ZTQUK.cjs} +19 -9
  167. package/dist/{chunk-NDMIPTV4.js → chunk-JMJRFSNJ.js} +8 -3
  168. package/dist/{chunk-ZLSGXMGQ.js → chunk-JMN3GZU6.js} +2 -0
  169. package/dist/chunk-JPFRI4L4.js +4967 -0
  170. package/dist/{chunk-BVKXEQVG.cjs → chunk-JV5HVCHU.cjs} +22 -10
  171. package/dist/chunk-KA2U6MY2.js +388 -0
  172. package/dist/{chunk-SEKD5FH3.cjs → chunk-KASXKDF2.cjs} +8 -3
  173. package/dist/chunk-KJ26T7GM.cjs +367 -0
  174. package/dist/chunk-KM7R5YPS.cjs +385 -0
  175. package/dist/{chunk-MBBY4ZIK.js → chunk-KMP6Y3LJ.js} +4 -1
  176. package/dist/chunk-KWXGDSYT.js +184 -0
  177. package/dist/{chunk-C5IJIM2V.cjs → chunk-L46OUKW5.cjs} +68 -37
  178. package/dist/{chunk-6UJMCWRY.js → chunk-L6OH44HL.js} +19 -9
  179. package/dist/chunk-LANGD5CO.js +280 -0
  180. package/dist/{chunk-APIXPPMT.js → chunk-LEZEWKPA.js} +1004 -39
  181. package/dist/{chunk-ULQ6MDSJ.cjs → chunk-LFDPTJYF.cjs} +43 -35
  182. package/dist/chunk-LJD7KR3L.cjs +44 -0
  183. package/dist/{chunk-NCC6ETZS.js → chunk-LUMV3DB2.js} +41 -33
  184. package/dist/{chunk-QXH5RCFI.js → chunk-M5DK524Z.js} +2 -2
  185. package/dist/chunk-M7B7RRBK.cjs +1034 -0
  186. package/dist/chunk-MANJ67D3.js +3897 -0
  187. package/dist/{chunk-HPHJ73GU.cjs → chunk-MGNXABSU.cjs} +9 -9
  188. package/dist/{chunk-6ZCULLCA.js → chunk-MMKZSJUN.js} +1 -1
  189. package/dist/{chunk-DVUHHH3B.cjs → chunk-MQQH4XQH.cjs} +4 -4
  190. package/dist/chunk-MUNUUFU7.cjs +21 -0
  191. package/dist/{chunk-DVZOENQ7.cjs → chunk-MZN6JBRZ.cjs} +9 -4
  192. package/dist/{chunk-OOKY3HPZ.js → chunk-NB6M6ESM.js} +50 -9
  193. package/dist/{chunk-BYONM7UM.js → chunk-NBGOIFKP.js} +265 -18
  194. package/dist/chunk-NHWHEW7M.cjs +75 -0
  195. package/dist/chunk-NQZH5CR2.cjs +468 -0
  196. package/dist/{chunk-EGPXJERY.cjs → chunk-NV6LCD2I.cjs} +119 -28
  197. package/dist/chunk-NVATU4V6.cjs +44 -0
  198. package/dist/{chunk-YZXUDM5X.js → chunk-NZ3ZWLUW.js} +204 -62
  199. package/dist/chunk-NZ7GCK7Y.cjs +92 -0
  200. package/dist/{chunk-VEKDGU2Q.cjs → chunk-NZQFKXM3.cjs} +49 -25
  201. package/dist/chunk-O5GI6SR3.js +59 -0
  202. package/dist/{chunk-56SWIDEL.cjs → chunk-OCATAKXV.cjs} +57 -56
  203. package/dist/{chunk-SKV2F3NM.js → chunk-OIGYWOL6.js} +1 -1
  204. package/dist/{chunk-IFFXSTOM.cjs → chunk-ONVYXTUT.cjs} +3 -3
  205. package/dist/chunk-OR67YXQK.cjs +13 -0
  206. package/dist/chunk-OV3T5MUH.cjs +715 -0
  207. package/dist/{chunk-OHUZKDGX.js → chunk-P4FUTSVK.js} +3 -3
  208. package/dist/chunk-P6SLT2F4.cjs +139 -0
  209. package/dist/{chunk-TNZRZQ7Q.js → chunk-PB7W7R72.js} +3 -78
  210. package/dist/chunk-PCM3N3CL.cjs +37 -0
  211. package/dist/{chunk-2AA5MFES.js → chunk-PGFLPURU.js} +8 -5
  212. package/dist/{chunk-DJDE4DTT.cjs → chunk-PIXQ2AVM.cjs} +25 -19
  213. package/dist/chunk-PNIAIOMZ.cjs +229 -0
  214. package/dist/{chunk-HTLINWX6.cjs → chunk-PQBYFEBL.cjs} +16 -13
  215. package/dist/{chunk-22D2CNTP.cjs → chunk-PSAH4ZQB.cjs} +5 -2
  216. package/dist/{chunk-3VTAFAL2.js → chunk-PTTZI4QZ.js} +16 -12
  217. package/dist/{chunk-WPVWQSL7.cjs → chunk-PY73W5MQ.cjs} +16 -13
  218. package/dist/{chunk-EGMZDTSL.js → chunk-PZZMIYII.js} +10 -2
  219. package/dist/{chunk-HBZU3RBZ.js → chunk-Q3BS6FPM.js} +34 -10
  220. package/dist/chunk-QBYGMMDD.js +517 -0
  221. package/dist/chunk-QLZOMZO5.cjs +388 -0
  222. package/dist/{chunk-ADUFCS4Q.cjs → chunk-QQ64HEHP.cjs} +160 -79
  223. package/dist/{chunk-L3TWPROA.js → chunk-QRQF3556.js} +44 -24
  224. package/dist/{chunk-YHD6TUIR.cjs → chunk-QW2RW2GY.cjs} +2 -0
  225. package/dist/chunk-RAKO7UN7.js +114 -0
  226. package/dist/{chunk-7TQH3CL4.cjs → chunk-RBXBH6EB.cjs} +78 -35
  227. package/dist/chunk-RFUKZIJF.cjs +78 -0
  228. package/dist/chunk-RS2E32YB.js +71 -0
  229. package/dist/{chunk-OLSBBZW6.cjs → chunk-S5JNQIGL.cjs} +5 -5
  230. package/dist/{chunk-FTYY5JJD.js → chunk-S5QKRA2V.js} +2 -2
  231. package/dist/chunk-SD3NTC7D.cjs +77 -0
  232. package/dist/{chunk-KPELYZ6L.js → chunk-SDLY4X3G.js} +2 -2
  233. package/dist/{chunk-IDFF5J2E.js → chunk-SFFEKZGC.js} +38 -7
  234. package/dist/{chunk-EGFT4PGW.js → chunk-SHRLFX6F.js} +8 -3
  235. package/dist/{chunk-3WICOC33.js → chunk-SNDDZG5H.js} +124 -84
  236. package/dist/{chunk-CZXGCVTR.cjs → chunk-SOVR7S3T.cjs} +2 -2
  237. package/dist/chunk-SPQ7QIQ6.js +78 -0
  238. package/dist/{chunk-XRZEUWKF.js → chunk-SRMGWMQO.js} +1 -1
  239. package/dist/{chunk-R33VKSH5.cjs → chunk-SVVP4UUZ.cjs} +11 -11
  240. package/dist/{chunk-7BTSG4ME.cjs → chunk-SY2P3Z5W.cjs} +1004 -39
  241. package/dist/chunk-SZD45IDG.js +468 -0
  242. package/dist/{chunk-3O3MOK5C.cjs → chunk-TE4SYTWR.cjs} +1114 -142
  243. package/dist/chunk-TH26BQJG.js +101 -0
  244. package/dist/{chunk-QNGEW5TC.js → chunk-TR6NECEZ.js} +1 -1
  245. package/dist/{chunk-YGN4CQIP.js → chunk-TYBPTKFT.js} +1 -1
  246. package/dist/{chunk-GJH7XMSK.js → chunk-U2QUMKCB.js} +8 -6
  247. package/dist/{chunk-47CKWKEX.cjs → chunk-U6PLSPMD.cjs} +9 -4
  248. package/dist/chunk-UOQECODR.js +34 -0
  249. package/dist/chunk-UR27UDTB.js +354 -0
  250. package/dist/chunk-UWZ4G3SQ.js +93 -0
  251. package/dist/{chunk-Y72HH2TF.cjs → chunk-V3SZLWEQ.cjs} +33 -24
  252. package/dist/chunk-V4HGZSKQ.cjs +183 -0
  253. package/dist/{chunk-3PDTTAKJ.js → chunk-V5ELP2XE.js} +19 -12
  254. package/dist/{chunk-3K2ESU53.cjs → chunk-VKOXFT4L.cjs} +2 -2
  255. package/dist/{chunk-XTB6VJVQ.cjs → chunk-VLGGMQUN.cjs} +6 -6
  256. package/dist/chunk-VQHSYAPZ.cjs +3897 -0
  257. package/dist/chunk-VUGOOGHB.js +400 -0
  258. package/dist/chunk-W5QLA6WP.cjs +866 -0
  259. package/dist/chunk-WBU4Q4GS.cjs +400 -0
  260. package/dist/{chunk-KWXVKLQ5.cjs → chunk-WGNMOVMT.cjs} +7 -82
  261. package/dist/chunk-WJYFLQ7G.cjs +27 -0
  262. package/dist/{chunk-RKJTGGMU.cjs → chunk-WKRDBCP2.cjs} +221 -16
  263. package/dist/{chunk-ZSPXQYG2.js → chunk-WNJXIACY.js} +7 -5
  264. package/dist/{chunk-AYS2ASM7.js → chunk-WPE2XHVX.js} +1 -1
  265. package/dist/chunk-WTB7AFL6.cjs +101 -0
  266. package/dist/{chunk-JYTDYJVW.js → chunk-X3TI5TJJ.js} +1 -1
  267. package/dist/chunk-X5P6QQOB.cjs +719 -0
  268. package/dist/{chunk-245KJE5Y.cjs → chunk-X7XMITIL.cjs} +14 -6
  269. package/dist/chunk-XF2WIKHR.cjs +34 -0
  270. package/dist/{chunk-DV2ZHK7B.cjs → chunk-XNIMSVS6.cjs} +49 -26
  271. package/dist/chunk-XW3XCK4E.cjs +59 -0
  272. package/dist/{chunk-N23UAW4I.js → chunk-XZVDYC5U.js} +7 -2
  273. package/dist/chunk-Y5BDE24P.cjs +444 -0
  274. package/dist/{chunk-NAGQ2PDC.js → chunk-YC5OBZQU.js} +1604 -433
  275. package/dist/chunk-YISPVAXO.cjs +2340 -0
  276. package/dist/{chunk-KQGPTCQJ.js → chunk-YQDI753V.js} +68 -25
  277. package/dist/{chunk-UJ2JSM6H.js → chunk-YVQI26H4.js} +2 -0
  278. package/dist/{chunk-NNPAM4HC.cjs → chunk-YWH55BWK.cjs} +15 -6
  279. package/dist/{chunk-X2YOZQIP.cjs → chunk-YWTIXHU6.cjs} +266 -19
  280. package/dist/{chunk-XX2ZO7DS.js → chunk-YXM6SA7P.js} +25 -16
  281. package/dist/chunk-YZWE7XSM.js +5 -0
  282. package/dist/chunk-ZASDSY7P.cjs +114 -0
  283. package/dist/{chunk-2UC22DJU.js → chunk-ZNGRLCFQ.js} +30 -4
  284. package/dist/{chunk-KANW6OYC.cjs → chunk-ZNYZB7XY.cjs} +34 -8
  285. package/dist/{chunk-NA6WQDYW.js → chunk-ZPLCAXJW.js} +1088 -116
  286. package/dist/{chunk-7UOUW76C.js → chunk-ZQAT5VT5.js} +101 -24
  287. package/dist/chunk-ZUYYHKQA.js +44 -0
  288. package/dist/clear-QJXU25IF.cjs +12 -0
  289. package/dist/{clear-A3N4GK2S.js → clear-Y5TO3RZA.js} +3 -3
  290. package/dist/communityInstaller-LXMVKVAV.cjs +22 -0
  291. package/dist/{communityInstaller-LOP2EDH5.js → communityInstaller-RVL4STPS.js} +8 -5
  292. package/dist/completion-JX4T2ONW.cjs +17 -0
  293. package/dist/completion-SE3XYG74.js +17 -0
  294. package/dist/config-3224PRW4.cjs +21 -0
  295. package/dist/{config-HPJPKTO6.js → config-7KPHKFTP.js} +7 -4
  296. package/dist/{constants-LISJW3DD.js → constants-EJFAWJQI.js} +1 -1
  297. package/dist/constants-MCCGUU5F.cjs +21 -0
  298. package/dist/{defaultHooks-IHSJR2AX.cjs → defaultHooks-2V2CQL63.cjs} +25 -10
  299. package/dist/{defaultHooks-KUKHK3AG.js → defaultHooks-TMHDU3FS.js} +25 -10
  300. package/dist/export-BJVIDZC4.js +15 -0
  301. package/dist/export-KU4557HK.cjs +15 -0
  302. package/dist/feature-K3LG5IJP.cjs +14 -0
  303. package/dist/feature-V4JF5TNJ.js +14 -0
  304. package/dist/features-5OJTLKS7.js +23 -0
  305. package/dist/features-VCBJQXCX.cjs +23 -0
  306. package/dist/feedback-D4437VE4.js +18 -0
  307. package/dist/feedback-VZPEHGFY.cjs +18 -0
  308. package/dist/fffSearchProvider-2YCNKOYD.js +412 -0
  309. package/dist/fffSearchProvider-W6627E2V.cjs +412 -0
  310. package/dist/{filesystem-Z7BWAWMZ.js → filesystem-L6DQKGWK.js} +3 -2
  311. package/dist/filesystem-PGUPCMVK.cjs +11 -0
  312. package/dist/go-X4E6BCD6.js +12 -0
  313. package/dist/go-XREVFS5I.cjs +12 -0
  314. package/dist/goal-4M3J6JS2.cjs +16 -0
  315. package/dist/goal-EI66BV7W.js +16 -0
  316. package/dist/help-GGFS7WHY.cjs +12 -0
  317. package/dist/{help-GFQXNZOK.js → help-MLIROWKM.js} +2 -2
  318. package/dist/{history-E3N6BJP7.js → history-GQJ6RZD6.js} +2 -2
  319. package/dist/history-J4LUIOSI.cjs +14 -0
  320. package/dist/hooks-FSRIUS6A.cjs +18 -0
  321. package/dist/hooks-PPFHCF7T.js +18 -0
  322. package/dist/i18n-CI6VFXL5.cjs +33 -0
  323. package/dist/{i18n-SY7QRM22.js → i18n-Q7UZJRPL.js} +1 -1
  324. package/dist/ide-BUSVE54P.js +15 -0
  325. package/dist/ide-YR27BPGM.cjs +15 -0
  326. package/dist/immediateCommandRouter-MTEHZXQX.js +15 -0
  327. package/dist/immediateCommandRouter-ROXU3MWT.cjs +15 -0
  328. package/dist/{import-W7SVLSTC.js → import-3VBKI6BN.js} +3 -3
  329. package/dist/{import-ADI37ZUR.cjs → import-C7S6UJMW.cjs} +3 -3
  330. package/dist/import-DBK4OCDF.cjs +10 -0
  331. package/dist/{import-KGKKZ3B7.js → import-U47DXCA7.js} +2 -2
  332. package/dist/index.cjs +837 -23011
  333. package/dist/index.d.cts +95 -0
  334. package/dist/index.d.ts +95 -0
  335. package/dist/index.js +864 -23038
  336. package/dist/init-OXTYS72D.cjs +10 -0
  337. package/dist/{init-7MFK626E.js → init-Q2O4R42R.js} +2 -2
  338. package/dist/inkMode-VUE6ZDLD.cjs +7 -0
  339. package/dist/inkMode-WBNFOSAT.js +7 -0
  340. package/dist/inputPrompt-3CFZDUBH.js +90 -0
  341. package/dist/inputPrompt-JMACL4EB.cjs +90 -0
  342. package/dist/language-EK3M6UBV.cjs +22 -0
  343. package/dist/language-Q3RUGGOW.js +22 -0
  344. package/dist/learn-3DSX7DYG.cjs +23 -0
  345. package/dist/learn-OPOH5L43.js +23 -0
  346. package/dist/login-GST7MWXJ.cjs +27 -0
  347. package/dist/login-Q7DZKAX4.js +27 -0
  348. package/dist/logout-2G7OUK7I.cjs +24 -0
  349. package/dist/logout-FNBBL2UI.js +24 -0
  350. package/dist/mcp-AH3MMUBU.js +21 -0
  351. package/dist/mcp-DW7R63IB.cjs +21 -0
  352. package/dist/{mcp-install-2FEROZTL.js → mcp-install-222PCKSW.js} +18 -10
  353. package/dist/{mcp-install-WM6BQRI5.cjs → mcp-install-IA4ZS2SV.cjs} +22 -14
  354. package/dist/memory-6NX3DAIY.cjs +10 -0
  355. package/dist/{memory-J73WZH2I.js → memory-VB46T5H3.js} +2 -2
  356. package/dist/model-64NAELFS.cjs +10 -0
  357. package/dist/{model-AES267IN.js → model-SJJG64AM.js} +2 -2
  358. package/dist/new-DU5SOOOY.cjs +12 -0
  359. package/dist/{new-5CLF3MKH.js → new-ERZ5C6CN.js} +3 -3
  360. package/dist/onboarding-2U2BV2KE.cjs +36 -0
  361. package/dist/onboarding-ZQXMPSMJ.js +36 -0
  362. package/dist/permissions-7ACNFK7A.js +10 -0
  363. package/dist/permissions-GSNNV7RJ.cjs +10 -0
  364. package/dist/plan-XEJMOT55.cjs +13 -0
  365. package/dist/{plan-65HMS5HQ.js → plan-YYUAXPTL.js} +3 -1
  366. package/dist/pr-review-CW6J7P62.cjs +9 -0
  367. package/dist/pr-review-YZSBQVT2.js +9 -0
  368. package/dist/{quit-XDZYRSPU.js → quit-WY6T267G.js} +2 -2
  369. package/dist/quit-XIRE2KRE.cjs +10 -0
  370. package/dist/rawMode-6W5AXAKI.cjs +7 -0
  371. package/dist/rawMode-GFNLXQPU.js +7 -0
  372. package/dist/{registry-PTHWERKC.js → registry-4DXUDKJN.js} +29 -44
  373. package/dist/{registry-IVT4G2RT.cjs → registry-UEO3ETZ7.cjs} +65 -80
  374. package/dist/repeat-P4FAPE3Y.cjs +17 -0
  375. package/dist/{repeat-EVCWUL6Z.js → repeat-RALE6AUO.js} +7 -3
  376. package/dist/resume-RQZ3WXBP.cjs +17 -0
  377. package/dist/resume-XSXZMDMD.js +17 -0
  378. package/dist/review-QHP2KP4Q.js +9 -0
  379. package/dist/review-UWHWQHCB.cjs +9 -0
  380. package/dist/ripgrep-67SCU2BA.cjs +9 -0
  381. package/dist/ripgrep-VHJQQ55W.js +9 -0
  382. package/dist/rpc-XH7QLN4H.js +3874 -0
  383. package/dist/rpc-YF54T7JU.cjs +3874 -0
  384. package/dist/search-VXXFGZWU.cjs +21 -0
  385. package/dist/search-YAWGX7P7.js +21 -0
  386. package/dist/{sessions-6GWEBMKS.js → sessions-NJYHJOEL.js} +2 -2
  387. package/dist/sessions-OSG3UJEZ.cjs +10 -0
  388. package/dist/settings-SO2UIGWV.cjs +42 -0
  389. package/dist/settings-ZXUQH3DO.js +42 -0
  390. package/dist/setup-KG7EGKF5.js +30 -0
  391. package/dist/setup-RSOPCQ57.cjs +30 -0
  392. package/dist/share-WFAGZ5PY.js +17 -0
  393. package/dist/share-ZU3SGACF.cjs +17 -0
  394. package/dist/skills-KBVAQAD2.cjs +29 -0
  395. package/dist/skills-NBTNDVAY.js +29 -0
  396. package/dist/{skills-ZZCIAS7C.js → skills-OB6RDW7D.js} +10 -7
  397. package/dist/{skills-PG542VEB.cjs → skills-ZROBG3RZ.cjs} +13 -10
  398. package/dist/{skills-install-SRC3Z2MS.js → skills-install-BHTIEMKH.js} +21 -70
  399. package/dist/{skills-install-67DOBPJC.cjs → skills-install-ILX6QVEF.cjs} +34 -83
  400. package/dist/skills-new-B45VQ2PP.cjs +18 -0
  401. package/dist/skills-new-YMRP2HNO.js +18 -0
  402. package/dist/slashCommands-53VYIBJU.js +105 -0
  403. package/dist/slashCommands-BG2RGGZ6.cjs +105 -0
  404. package/dist/status-CQ2IUOVK.cjs +24 -0
  405. package/dist/status-E7IGNVPC.js +24 -0
  406. package/dist/summarizer-DGPHE5IQ.js +17 -0
  407. package/dist/summarizer-JNXLUAQG.cjs +17 -0
  408. package/dist/sync-7C25MOT2.js +22 -0
  409. package/dist/{sync-VU2NSJ4O.js → sync-OCJN4ZSO.js} +3 -3
  410. package/dist/sync-XRP46IVG.cjs +40 -0
  411. package/dist/sync-ZMFVE7T4.cjs +22 -0
  412. package/dist/{teammate-SXRVXNQV.cjs → teammate-D77B6QRT.cjs} +31 -9
  413. package/dist/{teammate-SD26GR37.js → teammate-EZCMHOIL.js} +30 -8
  414. package/dist/templates-ARG2VRWW.cjs +11 -0
  415. package/dist/templates-UGVZV3KJ.js +11 -0
  416. package/dist/theme-KKRDE6P7.cjs +22 -0
  417. package/dist/theme-XF7XIWBQ.js +22 -0
  418. package/dist/tools-3PPTTKFV.js +9 -0
  419. package/dist/tools-THIQA7WC.cjs +9 -0
  420. package/dist/ui/questionModal.cjs +8 -5
  421. package/dist/ui/questionModal.js +7 -4
  422. package/dist/{undo-OL2EDBRY.js → undo-GNUTFXCW.js} +2 -2
  423. package/dist/undo-U4KN7QQM.cjs +10 -0
  424. package/dist/usage-QSTNSDAO.js +24 -0
  425. package/dist/usage-YDEMQBNQ.cjs +24 -0
  426. package/dist/web-3BA2WV37.cjs +37 -0
  427. package/dist/web-6FYGBX5K.js +37 -0
  428. package/dist/workspaceSafety-MDJGHK6D.cjs +9 -0
  429. package/dist/workspaceSafety-XOUMUBVB.js +9 -0
  430. package/dist/yolo-GF2YD7ZI.js +9 -0
  431. package/dist/yolo-OGDA7HNC.cjs +9 -0
  432. package/dist/yoloMode-3DJDA75U.cjs +17 -0
  433. package/dist/yoloMode-4JOOSU26.js +17 -0
  434. package/package.json +51 -49
  435. package/dist/AgentRegistry-HRPN6ZOF.cjs +0 -10
  436. package/dist/CommunitySkillsCache-KE435RAR.cjs +0 -8
  437. package/dist/GitHubRegistryFetcher-I45SESIL.cjs +0 -7
  438. package/dist/LearnAdvisor-FLBA6FDD.js +0 -9
  439. package/dist/LearnAdvisor-GG3CXQF3.cjs +0 -9
  440. package/dist/MemoryManager-2LQPIYVE.cjs +0 -8
  441. package/dist/PermissionManager-X57BXHJ6.cjs +0 -11
  442. package/dist/ProviderFactory-KPJOGQWF.cjs +0 -9
  443. package/dist/SessionManager-YBJAZXNO.cjs +0 -10
  444. package/dist/SkillsRegistry-7O72A6TZ.cjs +0 -9
  445. package/dist/SubAgent-JT4HZHN7.js +0 -11
  446. package/dist/SubAgent-VPNYDWAU.cjs +0 -11
  447. package/dist/SyncApiClient-HQXJL5BT.cjs +0 -11
  448. package/dist/about-4DB5KTHW.js +0 -12
  449. package/dist/about-LXAOXZFT.cjs +0 -12
  450. package/dist/actionExecutor-23JB2WUC.js +0 -19
  451. package/dist/actionExecutor-X5UEZSKH.cjs +0 -19
  452. package/dist/add-dir-YC37DMSF.cjs +0 -10
  453. package/dist/agents-YF3BSUU5.js +0 -12
  454. package/dist/agents-ZOUHPMYR.cjs +0 -12
  455. package/dist/agents-new-HSH4GQPG.js +0 -14
  456. package/dist/agents-new-VYUDOCE7.cjs +0 -14
  457. package/dist/autoSkill-X5W52WOE.cjs +0 -20
  458. package/dist/automode-SJGM7VEI.cjs +0 -10
  459. package/dist/chunk-ALYU6VTM.js +0 -105
  460. package/dist/chunk-B53A2NM2.js +0 -2030
  461. package/dist/chunk-BJXSNT46.js +0 -100
  462. package/dist/chunk-CB4E2T5N.cjs +0 -312
  463. package/dist/chunk-DNUOXBHL.js +0 -539
  464. package/dist/chunk-EFX2QSZX.cjs +0 -33
  465. package/dist/chunk-GCXYXLRA.cjs +0 -111
  466. package/dist/chunk-H2ZRHQQV.js +0 -33
  467. package/dist/chunk-HNRPK5MY.cjs +0 -85
  468. package/dist/chunk-HVKOZ2VP.cjs +0 -115
  469. package/dist/chunk-JJLYWH5Y.cjs +0 -100
  470. package/dist/chunk-LWUJFGOZ.js +0 -115
  471. package/dist/chunk-MERYP6AM.cjs +0 -539
  472. package/dist/chunk-MZAPWNAC.cjs +0 -207
  473. package/dist/chunk-NTSDP2WB.js +0 -226
  474. package/dist/chunk-OUZQXMHL.cjs +0 -226
  475. package/dist/chunk-PGESAU2W.cjs +0 -2030
  476. package/dist/chunk-SYVYLZZF.cjs +0 -24
  477. package/dist/chunk-SZOLA6FR.js +0 -111
  478. package/dist/chunk-VWDHR4HV.js +0 -168
  479. package/dist/chunk-Y45G6ZO5.cjs +0 -168
  480. package/dist/chunk-YRLYSQEQ.cjs +0 -105
  481. package/dist/chunk-ZYVS43MU.js +0 -312
  482. package/dist/clear-GK4IEUUS.cjs +0 -12
  483. package/dist/communityInstaller-XXC7RLE4.cjs +0 -19
  484. package/dist/completion-HWABSAEL.js +0 -14
  485. package/dist/completion-IUUVQG4D.cjs +0 -14
  486. package/dist/config-HF7WOLZF.cjs +0 -18
  487. package/dist/constants-PEO3P2SJ.cjs +0 -21
  488. package/dist/export-QJAV2FCZ.js +0 -12
  489. package/dist/export-XSRFXGWU.cjs +0 -12
  490. package/dist/feedback-4TCIL3ML.cjs +0 -15
  491. package/dist/feedback-SJ6VVEY3.js +0 -15
  492. package/dist/filesystem-W56QZUJF.cjs +0 -10
  493. package/dist/help-ISBVQL3S.cjs +0 -12
  494. package/dist/history-XQ4GTSFU.cjs +0 -14
  495. package/dist/hooks-CJNKJ5PF.js +0 -13
  496. package/dist/hooks-UTMBTAXT.cjs +0 -13
  497. package/dist/i18n-N7QQ7A5M.cjs +0 -33
  498. package/dist/ide-RTA4UJV4.js +0 -12
  499. package/dist/ide-VWVOLIFF.cjs +0 -12
  500. package/dist/immediateCommandRouter-BW34JNXL.js +0 -9
  501. package/dist/immediateCommandRouter-SHOVNB5X.cjs +0 -9
  502. package/dist/import-ZLJVONXH.cjs +0 -10
  503. package/dist/init-TBKAB4LZ.cjs +0 -10
  504. package/dist/language-MDSHEXHB.cjs +0 -18
  505. package/dist/language-PXTQSHIG.js +0 -18
  506. package/dist/learn-623TW5EK.cjs +0 -20
  507. package/dist/learn-BCPV7GM2.js +0 -20
  508. package/dist/localProjectPermissions-BHQXEWZJ.cjs +0 -18
  509. package/dist/localProjectPermissions-GMOUYQM6.js +0 -18
  510. package/dist/login-QMVEETWJ.js +0 -20
  511. package/dist/login-QYMXAL3K.cjs +0 -20
  512. package/dist/logout-2CMTDAOJ.js +0 -18
  513. package/dist/logout-ZOHVZAUK.cjs +0 -18
  514. package/dist/mcp-IUVKK65S.js +0 -18
  515. package/dist/mcp-TXC7PYG3.cjs +0 -18
  516. package/dist/memory-WRIHDEPK.cjs +0 -10
  517. package/dist/model-RLP75SF5.cjs +0 -10
  518. package/dist/new-HLSFL6A4.cjs +0 -12
  519. package/dist/permissions-GP6FTGZ2.js +0 -13
  520. package/dist/permissions-O6EKKPOG.cjs +0 -13
  521. package/dist/plan-MCAXDIM2.cjs +0 -11
  522. package/dist/quit-TQX6GXA5.cjs +0 -10
  523. package/dist/repeat-BSPS5TWD.cjs +0 -13
  524. package/dist/resume-2GOPDLL4.cjs +0 -13
  525. package/dist/resume-37IUVDA6.js +0 -13
  526. package/dist/search-7KUSHIBL.cjs +0 -17
  527. package/dist/search-OJGDRIMA.js +0 -17
  528. package/dist/sessions-CYYCHSQG.cjs +0 -10
  529. package/dist/settings-2D7CAO66.cjs +0 -30
  530. package/dist/settings-BXR6SBJP.js +0 -30
  531. package/dist/share-BXQY5IQU.js +0 -14
  532. package/dist/share-OSFXZBGS.cjs +0 -14
  533. package/dist/skills-FL6O6AOM.cjs +0 -26
  534. package/dist/skills-PNKQZRNK.js +0 -26
  535. package/dist/skills-new-XFMEHHIF.cjs +0 -15
  536. package/dist/skills-new-ZNZPHUKS.js +0 -15
  537. package/dist/slashCommands-LLCNPK3X.js +0 -76
  538. package/dist/slashCommands-RXZZS6RE.cjs +0 -76
  539. package/dist/status-BCECUJXT.cjs +0 -11
  540. package/dist/status-EFO7MQU3.js +0 -11
  541. package/dist/sync-IJYJ6KKM.js +0 -18
  542. package/dist/sync-LFT6SBPF.cjs +0 -18
  543. package/dist/sync-U7SDPBNZ.cjs +0 -40
  544. package/dist/theme-AWBHSG7J.cjs +0 -18
  545. package/dist/theme-TQLBPJ2E.js +0 -18
  546. package/dist/undo-IBBGP7A2.cjs +0 -10
@@ -0,0 +1,4967 @@
1
+ import {
2
+ NVIDIAProvider,
3
+ normalizeLLMUsage
4
+ } from "./chunk-4567QNRB.js";
5
+ import {
6
+ ApiError,
7
+ classifyApiError
8
+ } from "./chunk-FQVG6ZHF.js";
9
+ import {
10
+ isAwsBedrockProviderEnabled
11
+ } from "./chunk-LANGD5CO.js";
12
+
13
+ // src/providers/OllamaProvider.ts
14
+ var DEFAULT_TIMEOUT = 12e4;
15
+ var DEFAULT_CHUNK_TIMEOUT = 3e4;
16
+ var DEFAULT_MAX_RETRIES = 2;
17
+ var MAX_ALLOWED_RETRIES = 5;
18
+ var DEFAULT_RETRY_DELAY = 1e3;
19
+ var AVAILABILITY_TIMEOUT = 5e3;
20
+ var OllamaProvider = class {
21
+ baseUrl;
22
+ model;
23
+ disableTools = false;
24
+ maxRetries;
25
+ retryDelay;
26
+ timeout;
27
+ chunkTimeout;
28
+ constructor(config, networkSettings) {
29
+ this.baseUrl = config.baseUrl || "http://localhost:11434";
30
+ this.model = config.model || "llama3.2:latest";
31
+ const configuredRetries = networkSettings?.maxRetries ?? DEFAULT_MAX_RETRIES;
32
+ this.maxRetries = Math.min(Math.max(0, configuredRetries), MAX_ALLOWED_RETRIES);
33
+ this.retryDelay = networkSettings?.retryDelay ?? DEFAULT_RETRY_DELAY;
34
+ this.timeout = networkSettings?.timeout ?? DEFAULT_TIMEOUT;
35
+ this.chunkTimeout = DEFAULT_CHUNK_TIMEOUT;
36
+ }
37
+ getName() {
38
+ return "ollama";
39
+ }
40
+ setModel(model) {
41
+ this.model = model;
42
+ }
43
+ async listModels() {
44
+ try {
45
+ const controller = new AbortController();
46
+ const timerId = setTimeout(() => controller.abort(), AVAILABILITY_TIMEOUT);
47
+ try {
48
+ const response = await fetch(`${this.baseUrl}/api/tags`, {
49
+ signal: controller.signal
50
+ });
51
+ if (!response.ok) {
52
+ return [];
53
+ }
54
+ const data = await response.json();
55
+ return data.models.map((m) => m.name);
56
+ } finally {
57
+ clearTimeout(timerId);
58
+ }
59
+ } catch {
60
+ return [];
61
+ }
62
+ }
63
+ async isAvailable() {
64
+ try {
65
+ const controller = new AbortController();
66
+ const timerId = setTimeout(() => controller.abort(), AVAILABILITY_TIMEOUT);
67
+ try {
68
+ const response = await fetch(`${this.baseUrl}/api/tags`, {
69
+ signal: controller.signal
70
+ });
71
+ return response.ok;
72
+ } finally {
73
+ clearTimeout(timerId);
74
+ }
75
+ } catch {
76
+ return false;
77
+ }
78
+ }
79
+ async complete(request) {
80
+ const body = {
81
+ model: request.model || this.model,
82
+ messages: this.buildMessages(request.messages, !this.disableTools),
83
+ stream: request.stream || false
84
+ };
85
+ if (request.temperature !== void 0) {
86
+ body.options = { temperature: request.temperature };
87
+ }
88
+ if (!this.disableTools && request.tools && request.tools.length > 0) {
89
+ body.tools = request.tools.map((tool) => ({
90
+ type: "function",
91
+ function: {
92
+ name: tool.name,
93
+ description: tool.description,
94
+ parameters: tool.parameters ?? { type: "object", properties: {} }
95
+ }
96
+ }));
97
+ }
98
+ let lastError = null;
99
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
100
+ try {
101
+ return await this.makeRequest(body, request);
102
+ } catch (error) {
103
+ lastError = error;
104
+ if (this.isNonRetryableError(error)) {
105
+ throw error;
106
+ }
107
+ if (attempt < this.maxRetries) {
108
+ const delay = this.retryDelay * Math.pow(2, attempt);
109
+ await this.sleep(delay);
110
+ }
111
+ }
112
+ }
113
+ throw lastError ?? new ApiError(
114
+ "Failed to communicate with Ollama. Please try again.",
115
+ "network_error",
116
+ 0,
117
+ true
118
+ );
119
+ }
120
+ async makeRequest(body, request) {
121
+ let response;
122
+ try {
123
+ const timeoutController = new AbortController();
124
+ const timerId = setTimeout(() => timeoutController.abort(), this.timeout);
125
+ const combinedSignal = request.signal ? this.combineSignals(request.signal, timeoutController.signal) : timeoutController.signal;
126
+ try {
127
+ response = await fetch(`${this.baseUrl}/api/chat`, {
128
+ method: "POST",
129
+ headers: {
130
+ "Content-Type": "application/json"
131
+ },
132
+ body: JSON.stringify(body),
133
+ signal: combinedSignal
134
+ });
135
+ } finally {
136
+ clearTimeout(timerId);
137
+ }
138
+ } catch (error) {
139
+ const err = error;
140
+ if (err.name === "AbortError" && request.signal?.aborted) {
141
+ throw new ApiError("Request cancelled.", "cancelled", 0, false);
142
+ }
143
+ if (err.name === "AbortError") {
144
+ throw new ApiError(
145
+ `Ollama request timed out after ${this.timeout / 1e3}s. Local inference can be slow \u2014 consider increasing the timeout in your config.`,
146
+ "timeout",
147
+ 0,
148
+ true
149
+ );
150
+ }
151
+ throw new ApiError(
152
+ `Cannot connect to Ollama at ${this.baseUrl}. Make sure Ollama is running (try 'ollama serve').`,
153
+ "network_error",
154
+ 0,
155
+ true
156
+ );
157
+ }
158
+ if (!response.ok) {
159
+ const apiError = await this.buildApiError(response, body);
160
+ if (apiError === null) {
161
+ return this.makeRequest(body, request);
162
+ }
163
+ throw apiError;
164
+ }
165
+ if (request.stream) {
166
+ return this.handleStreamingResponse(response);
167
+ }
168
+ const data = await response.json();
169
+ let toolCalls;
170
+ if (data.message.tool_calls && Array.isArray(data.message.tool_calls)) {
171
+ toolCalls = data.message.tool_calls.map((tc, index) => {
172
+ let argumentsStr;
173
+ try {
174
+ argumentsStr = JSON.stringify(tc.function.arguments);
175
+ } catch (error) {
176
+ console.warn("Failed to stringify tool call arguments, using fallback:", error);
177
+ argumentsStr = String(tc.function.arguments);
178
+ }
179
+ return {
180
+ id: `ollama-tool-${Date.now()}-${index}`,
181
+ type: "function",
182
+ function: {
183
+ name: tc.function.name,
184
+ arguments: argumentsStr
185
+ }
186
+ };
187
+ });
188
+ }
189
+ const usage = normalizeLLMUsage({
190
+ prompt_tokens: data.prompt_eval_count,
191
+ completion_tokens: data.eval_count
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.5",
816
+ "gpt-5.5-pro",
817
+ "gpt-5.4",
818
+ "gpt-5.4-pro",
819
+ "gpt-5.4-mini",
820
+ "gpt-5.4-nano",
821
+ "gpt-5.3-codex",
822
+ "gpt-5.1-codex-max"
823
+ ];
824
+ var VALID_REASONING_EFFORTS = /* @__PURE__ */ new Set(["none", "low", "medium", "high", "xhigh"]);
825
+ var OPENAI_API_BASE_URL = "https://api.openai.com/v1";
826
+ var OPENAI_CODEX_BASE_URL = "https://chatgpt.com/backend-api/codex";
827
+ var DEFAULT_CODEX_INSTRUCTIONS = "You are Autohand, a coding assistant. Follow the repository instructions and help the user complete software tasks.";
828
+ var OPENAI_API_KEY_FRIENDLY_MESSAGES = {
829
+ auth_failed: "Authentication failed. Please verify your OpenAI API key in ~/.autohand/config.json.",
830
+ payment_required: "Payment required. Please check your OpenAI account balance or billing settings.",
831
+ access_denied: "Access denied. Your OpenAI API key may not have permission for this model.",
832
+ server_error: "The OpenAI service encountered an error. Please try again later.",
833
+ network_error: "Unable to connect to OpenAI. Please check your internet connection and OpenAI API configuration.",
834
+ timeout: "The request timed out. The OpenAI service may be experiencing high load."
835
+ };
836
+ var OPENAI_CHATGPT_FRIENDLY_MESSAGES = {
837
+ auth_failed: "ChatGPT authentication failed. Please sign in again.",
838
+ access_denied: "Access denied. Your ChatGPT account may not have access to this model or Codex backend.",
839
+ server_error: "The ChatGPT Codex service encountered an error. Please try again later.",
840
+ network_error: "Unable to connect to ChatGPT Codex. Please check your internet connection.",
841
+ timeout: "The request timed out. The ChatGPT Codex service may be experiencing high load."
842
+ };
843
+ var OpenAIProvider = class {
844
+ baseUrl;
845
+ apiKey;
846
+ model;
847
+ reasoningEffort;
848
+ authMode;
849
+ chatgptAuth;
850
+ constructor(config) {
851
+ this.authMode = config.authMode === "chatgpt" ? "chatgpt" : "api-key";
852
+ this.baseUrl = this.resolveBaseUrl(config.baseUrl);
853
+ this.apiKey = config.apiKey || "";
854
+ this.model = config.model || "gpt-5.4";
855
+ this.reasoningEffort = config.reasoningEffort;
856
+ this.chatgptAuth = config.chatgptAuth;
857
+ }
858
+ getName() {
859
+ return "openai";
860
+ }
861
+ setModel(model) {
862
+ this.model = model;
863
+ }
864
+ getCapabilities() {
865
+ return {
866
+ nativeToolCalling: true
867
+ };
868
+ }
869
+ async listModels() {
870
+ return [...OPENAI_MODELS];
871
+ }
872
+ async isAvailable() {
873
+ if (this.authMode === "chatgpt") {
874
+ return !!this.chatgptAuth?.accessToken && !!this.chatgptAuth?.accountId;
875
+ }
876
+ try {
877
+ const headers = await this.buildAuthHeaders();
878
+ const response = await fetch(`${this.baseUrl}/models`, {
879
+ headers
880
+ });
881
+ return response.ok;
882
+ } catch {
883
+ return false;
884
+ }
885
+ }
886
+ async complete(request) {
887
+ if (this.authMode === "chatgpt") {
888
+ return this.completeWithResponsesApi(request);
889
+ }
890
+ const body = {
891
+ model: request.model || this.model,
892
+ messages: request.messages.map((msg) => {
893
+ const mapped = {
894
+ role: msg.role === "system" ? "system" : msg.role === "user" ? "user" : msg.role === "tool" ? "tool" : "assistant",
895
+ content: this.toChatCompletionContent(msg.content)
896
+ };
897
+ if (msg.role === "assistant" && msg.tool_calls?.length) {
898
+ mapped.tool_calls = msg.tool_calls;
899
+ }
900
+ if (msg.role === "tool" && msg.tool_call_id) {
901
+ mapped.tool_call_id = msg.tool_call_id;
902
+ }
903
+ if (msg.name) {
904
+ mapped.name = msg.name;
905
+ }
906
+ return mapped;
907
+ }),
908
+ temperature: request.temperature || 0.7,
909
+ // Newer OpenAI models (gpt-5.x, o-series) require max_completion_tokens
910
+ // instead of max_tokens. Use the correct parameter based on model.
911
+ ...this.usesMaxCompletionTokens(request.model || this.model) ? { max_completion_tokens: request.maxTokens } : { max_tokens: request.maxTokens }
912
+ };
913
+ if (this.reasoningEffort && VALID_REASONING_EFFORTS.has(this.reasoningEffort)) {
914
+ body.reasoning_effort = this.reasoningEffort;
915
+ }
916
+ if (request.tools && request.tools.length > 0) {
917
+ body.tools = request.tools.map((tool) => ({
918
+ type: "function",
919
+ function: {
920
+ name: tool.name,
921
+ description: tool.description,
922
+ parameters: tool.parameters ?? { type: "object", properties: {} }
923
+ }
924
+ }));
925
+ if (request.toolChoice) {
926
+ body.tool_choice = request.toolChoice;
927
+ }
928
+ }
929
+ let response;
930
+ const headers = await this.buildAuthHeaders();
931
+ try {
932
+ response = await fetch(`${this.baseUrl}/chat/completions`, {
933
+ method: "POST",
934
+ headers: {
935
+ "Content-Type": "application/json",
936
+ ...headers
937
+ },
938
+ body: JSON.stringify(body),
939
+ signal: request.signal
940
+ });
941
+ } catch (error) {
942
+ const err = error;
943
+ if (err.name === "AbortError" && request.signal?.aborted) {
944
+ throw new ApiError("Request cancelled.", "cancelled", 0, false);
945
+ }
946
+ if (err.name === "AbortError") {
947
+ throw new ApiError(
948
+ "The request timed out. The OpenAI service may be experiencing high load.",
949
+ "timeout",
950
+ 0,
951
+ true
952
+ );
953
+ }
954
+ throw new ApiError(
955
+ `Unable to connect to ${this.baseUrl}. Please check the URL and your internet connection.`,
956
+ "network_error",
957
+ 0,
958
+ true
959
+ );
960
+ }
961
+ if (!response.ok) {
962
+ throw await this.buildApiError(response);
963
+ }
964
+ const data = await response.json();
965
+ const message = data.choices[0].message;
966
+ const finishReason = data.choices[0].finish_reason;
967
+ let toolCalls;
968
+ if (message.tool_calls && Array.isArray(message.tool_calls)) {
969
+ toolCalls = message.tool_calls.map((tc) => ({
970
+ id: tc.id,
971
+ type: "function",
972
+ function: {
973
+ name: tc.function.name,
974
+ arguments: tc.function.arguments
975
+ }
976
+ }));
977
+ }
978
+ const usage = normalizeLLMUsage(data.usage);
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: this.toResponsesInputItems(request.messages)
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 = normalizeLLMUsage(data.usage);
1063
+ return {
1064
+ id: data.id,
1065
+ created: data.created_at ?? Math.floor(Date.now() / 1e3),
1066
+ content,
1067
+ toolCalls,
1068
+ finishReason: toolCalls.length > 0 ? "tool_calls" : data.incomplete_details?.reason === "max_output_tokens" ? "length" : "stop",
1069
+ usage,
1070
+ raw: data
1071
+ };
1072
+ }
1073
+ async buildApiError(response) {
1074
+ let errorDetail = "";
1075
+ try {
1076
+ const body = await response.json();
1077
+ const errObj = body?.error;
1078
+ errorDetail = errObj?.message ?? body?.detail ?? body?.error ?? "";
1079
+ if (typeof errorDetail === "object") {
1080
+ errorDetail = JSON.stringify(errorDetail);
1081
+ }
1082
+ } catch {
1083
+ try {
1084
+ errorDetail = await response.text();
1085
+ } catch {
1086
+ }
1087
+ }
1088
+ return this.withOpenAIMessage(classifyApiError(response.status, errorDetail, response.headers));
1089
+ }
1090
+ withOpenAIMessage(error) {
1091
+ const messages = this.authMode === "chatgpt" ? OPENAI_CHATGPT_FRIENDLY_MESSAGES : OPENAI_API_KEY_FRIENDLY_MESSAGES;
1092
+ const friendlyMessage = messages[error.code];
1093
+ if (!friendlyMessage) {
1094
+ return error;
1095
+ }
1096
+ return new ApiError(
1097
+ error.rawDetail ? `${friendlyMessage}
1098
+ ${error.rawDetail}` : friendlyMessage,
1099
+ error.code,
1100
+ error.httpStatus,
1101
+ error.retryable,
1102
+ error.retryAfterMs,
1103
+ error.rawDetail
1104
+ );
1105
+ }
1106
+ /**
1107
+ * Parse an SSE stream from the ChatGPT Codex backend and extract the
1108
+ * `response.completed` event payload as the full response object.
1109
+ */
1110
+ async parseCodexStream(response) {
1111
+ const text = await response.text();
1112
+ let currentEvent = "";
1113
+ let completedData = null;
1114
+ let streamedOutputText = "";
1115
+ const streamedOutputItems = /* @__PURE__ */ new Map();
1116
+ for (const line of text.split("\n")) {
1117
+ if (line.startsWith("event: ")) {
1118
+ currentEvent = line.slice(7).trim();
1119
+ continue;
1120
+ }
1121
+ if (!line.startsWith("data: ")) {
1122
+ continue;
1123
+ }
1124
+ const dataLine = line.slice(6);
1125
+ const eventData = JSON.parse(dataLine);
1126
+ const eventType = this.getCodexStreamEventType(currentEvent, eventData);
1127
+ const eventRecord = eventData && typeof eventData === "object" ? eventData : {};
1128
+ if (eventType === "response.output_text.delta") {
1129
+ if (typeof eventRecord.delta === "string") {
1130
+ streamedOutputText += eventRecord.delta;
1131
+ }
1132
+ continue;
1133
+ }
1134
+ if (eventType === "response.output_text.done") {
1135
+ if (typeof eventRecord.text === "string" && eventRecord.text.trim()) {
1136
+ streamedOutputText = eventRecord.text;
1137
+ }
1138
+ continue;
1139
+ }
1140
+ if (eventType === "response.output_item.added" || eventType === "response.output_item.done") {
1141
+ this.captureStreamedOutputItem(eventData, streamedOutputItems);
1142
+ continue;
1143
+ }
1144
+ if (eventType === "response.function_call_arguments.done") {
1145
+ this.captureStreamedFunctionCallArguments(eventData, streamedOutputItems);
1146
+ continue;
1147
+ }
1148
+ if (eventType === "response.completed") {
1149
+ completedData = this.extractCompletedResponse(eventData);
1150
+ break;
1151
+ }
1152
+ }
1153
+ if (!completedData) {
1154
+ throw new ApiError(
1155
+ "No response.completed event found in stream. The API response may be malformed.",
1156
+ "invalid_request",
1157
+ 0,
1158
+ false
1159
+ );
1160
+ }
1161
+ if ((!Array.isArray(completedData.output) || completedData.output.length === 0) && streamedOutputItems.size > 0) {
1162
+ completedData.output = [...streamedOutputItems.entries()].sort(([a], [b]) => a - b).map(([, item]) => item);
1163
+ }
1164
+ if (!this.extractResponsesContent(completedData) && streamedOutputText.trim()) {
1165
+ completedData.output_text = streamedOutputText;
1166
+ }
1167
+ return completedData;
1168
+ }
1169
+ getCodexStreamEventType(currentEvent, eventData) {
1170
+ if (currentEvent) {
1171
+ return currentEvent;
1172
+ }
1173
+ if (eventData && typeof eventData === "object" && "type" in eventData) {
1174
+ const type = eventData.type;
1175
+ return typeof type === "string" ? type : "";
1176
+ }
1177
+ return "";
1178
+ }
1179
+ captureStreamedOutputItem(eventData, outputItems) {
1180
+ if (!eventData || typeof eventData !== "object") {
1181
+ return;
1182
+ }
1183
+ const event = eventData;
1184
+ if (!event.item || typeof event.output_index !== "number") {
1185
+ return;
1186
+ }
1187
+ const existing = outputItems.get(event.output_index);
1188
+ if (existing?.type === "function_call" && event.item.type === "function_call") {
1189
+ outputItems.set(event.output_index, {
1190
+ ...existing,
1191
+ ...event.item,
1192
+ arguments: event.item.arguments || existing.arguments
1193
+ });
1194
+ return;
1195
+ }
1196
+ outputItems.set(event.output_index, event.item);
1197
+ }
1198
+ captureStreamedFunctionCallArguments(eventData, outputItems) {
1199
+ if (!eventData || typeof eventData !== "object") {
1200
+ return;
1201
+ }
1202
+ const event = eventData;
1203
+ if (typeof event.output_index !== "number" || typeof event.name !== "string" || typeof event.arguments !== "string") {
1204
+ return;
1205
+ }
1206
+ const existing = outputItems.get(event.output_index);
1207
+ if (existing?.type === "function_call") {
1208
+ outputItems.set(event.output_index, {
1209
+ ...existing,
1210
+ name: event.name,
1211
+ arguments: event.arguments
1212
+ });
1213
+ return;
1214
+ }
1215
+ outputItems.set(event.output_index, {
1216
+ type: "function_call",
1217
+ id: event.item_id,
1218
+ call_id: event.item_id,
1219
+ name: event.name,
1220
+ arguments: event.arguments
1221
+ });
1222
+ }
1223
+ extractCompletedResponse(eventData) {
1224
+ if (eventData && typeof eventData === "object" && "response" in eventData && eventData.response) {
1225
+ return eventData.response;
1226
+ }
1227
+ return eventData;
1228
+ }
1229
+ async buildAuthHeaders() {
1230
+ if (this.authMode === "chatgpt") {
1231
+ if (!this.chatgptAuth?.accessToken || !this.chatgptAuth.accountId) {
1232
+ throw new ApiError("ChatGPT authentication is missing. Please sign in again.", "auth_failed", 401, false);
1233
+ }
1234
+ if (isChatGPTAuthExpired(this.chatgptAuth)) {
1235
+ this.chatgptAuth = await refreshChatGPTAuth(this.chatgptAuth);
1236
+ }
1237
+ return {
1238
+ Authorization: `Bearer ${this.chatgptAuth.accessToken}`,
1239
+ "chatgpt-account-id": this.chatgptAuth.accountId
1240
+ };
1241
+ }
1242
+ return {
1243
+ Authorization: `Bearer ${this.apiKey}`
1244
+ };
1245
+ }
1246
+ resolveBaseUrl(configBaseUrl) {
1247
+ if (this.authMode === "chatgpt") {
1248
+ if (!configBaseUrl || configBaseUrl === OPENAI_API_BASE_URL) {
1249
+ return OPENAI_CODEX_BASE_URL;
1250
+ }
1251
+ return configBaseUrl.replace(/\/$/, "");
1252
+ }
1253
+ return (configBaseUrl || OPENAI_API_BASE_URL).replace(/\/$/, "");
1254
+ }
1255
+ isContentPartsArray(content) {
1256
+ return Array.isArray(content);
1257
+ }
1258
+ toChatCompletionContent(content) {
1259
+ if (!this.isContentPartsArray(content)) {
1260
+ return content;
1261
+ }
1262
+ return content.map((part) => {
1263
+ if (part.type === "text") {
1264
+ return { type: "text", text: part.text };
1265
+ }
1266
+ if (part.type === "image_url") {
1267
+ return {
1268
+ type: "image_url",
1269
+ image_url: part.image_url
1270
+ };
1271
+ }
1272
+ return null;
1273
+ }).filter((part) => part !== null);
1274
+ }
1275
+ toResponsesMessageContent(role, content) {
1276
+ const textType = role === "assistant" ? "output_text" : "input_text";
1277
+ if (!this.isContentPartsArray(content)) {
1278
+ return [{ type: textType, text: content }];
1279
+ }
1280
+ const parts = [];
1281
+ for (const part of content) {
1282
+ if (part.type === "text") {
1283
+ parts.push({ type: textType, text: part.text });
1284
+ continue;
1285
+ }
1286
+ if (part.type === "image_url" && role !== "assistant") {
1287
+ parts.push({
1288
+ type: "input_image",
1289
+ image_url: part.image_url.url
1290
+ });
1291
+ }
1292
+ }
1293
+ return parts;
1294
+ }
1295
+ toResponsesInputItems(messages) {
1296
+ const assistantToolCallIds = /* @__PURE__ */ new Set();
1297
+ const toolOutputIds = /* @__PURE__ */ new Set();
1298
+ for (const msg of messages) {
1299
+ if (msg.role === "assistant" && msg.tool_calls?.length) {
1300
+ for (const toolCall of msg.tool_calls) {
1301
+ assistantToolCallIds.add(toolCall.id);
1302
+ }
1303
+ }
1304
+ if (msg.role === "tool" && msg.tool_call_id) {
1305
+ toolOutputIds.add(msg.tool_call_id);
1306
+ }
1307
+ }
1308
+ const matchedToolCallIds = new Set(
1309
+ [...assistantToolCallIds].filter((id) => toolOutputIds.has(id))
1310
+ );
1311
+ return messages.flatMap((msg) => this.toResponsesInputItemsForMessage(msg, matchedToolCallIds));
1312
+ }
1313
+ toResponsesInputItemsForMessage(msg, matchedToolCallIds) {
1314
+ const items = [];
1315
+ if (msg.role === "system") {
1316
+ return items;
1317
+ }
1318
+ if (msg.role === "tool" && msg.tool_call_id) {
1319
+ if (!matchedToolCallIds.has(msg.tool_call_id)) {
1320
+ return items;
1321
+ }
1322
+ items.push({
1323
+ type: "function_call_output",
1324
+ call_id: msg.tool_call_id,
1325
+ output: msg.content
1326
+ });
1327
+ return items;
1328
+ }
1329
+ if (msg.content) {
1330
+ const content = this.toResponsesMessageContent(msg.role, msg.content);
1331
+ items.push({
1332
+ type: "message",
1333
+ role: msg.role === "tool" ? "user" : msg.role,
1334
+ content
1335
+ });
1336
+ }
1337
+ if (msg.role === "assistant" && msg.tool_calls?.length) {
1338
+ for (const toolCall of msg.tool_calls) {
1339
+ if (!matchedToolCallIds.has(toolCall.id)) {
1340
+ continue;
1341
+ }
1342
+ items.push({
1343
+ type: "function_call",
1344
+ call_id: toolCall.id,
1345
+ name: toolCall.function.name,
1346
+ arguments: toolCall.function.arguments
1347
+ });
1348
+ }
1349
+ }
1350
+ return items;
1351
+ }
1352
+ buildCodexInstructions(messages) {
1353
+ const systemMessages = messages.filter((msg) => msg.role === "system" && typeof msg.content === "string" && msg.content.trim()).map((msg) => msg.content.trim());
1354
+ if (systemMessages.length === 0) {
1355
+ return DEFAULT_CODEX_INSTRUCTIONS;
1356
+ }
1357
+ return [DEFAULT_CODEX_INSTRUCTIONS, ...systemMessages].join("\n\n");
1358
+ }
1359
+ extractResponsesToolCalls(output) {
1360
+ if (!Array.isArray(output)) {
1361
+ return [];
1362
+ }
1363
+ return output.filter((entry) => entry?.type === "function_call").map((toolCall, index) => ({
1364
+ id: toolCall.call_id ?? `call_${index + 1}`,
1365
+ type: "function",
1366
+ function: {
1367
+ name: toolCall.name,
1368
+ arguments: toolCall.arguments
1369
+ }
1370
+ }));
1371
+ }
1372
+ /**
1373
+ * Determine if a model requires `max_completion_tokens` instead of `max_tokens`.
1374
+ * OpenAI's newer models (gpt-5.x, o-series) reject `max_tokens` with a 400 error.
1375
+ */
1376
+ usesMaxCompletionTokens(model) {
1377
+ const lower = model.toLowerCase();
1378
+ return lower.startsWith("gpt-5") || lower.startsWith("o1") || lower.startsWith("o3") || lower.startsWith("o4");
1379
+ }
1380
+ extractResponsesContent(data) {
1381
+ if (typeof data.output_text === "string" && data.output_text.trim()) {
1382
+ return data.output_text;
1383
+ }
1384
+ if (!Array.isArray(data.output)) {
1385
+ return "";
1386
+ }
1387
+ const parts = [];
1388
+ for (const item of data.output) {
1389
+ if (item?.type !== "message" || !Array.isArray(item.content)) {
1390
+ continue;
1391
+ }
1392
+ for (const contentItem of item.content) {
1393
+ if (contentItem?.type === "output_text" && typeof contentItem.text === "string") {
1394
+ parts.push(contentItem.text);
1395
+ }
1396
+ }
1397
+ }
1398
+ return parts.join("\n").trim();
1399
+ }
1400
+ };
1401
+
1402
+ // src/providers/LlamaCppProvider.ts
1403
+ var LlamaCppProvider = class _LlamaCppProvider {
1404
+ baseUrl;
1405
+ model;
1406
+ static DEFAULT_MODEL = "local";
1407
+ constructor(config) {
1408
+ const port = config.port || 8080;
1409
+ this.baseUrl = config.baseUrl || `http://localhost:${port}`;
1410
+ this.model = config.model || _LlamaCppProvider.DEFAULT_MODEL;
1411
+ }
1412
+ getName() {
1413
+ return "llamacpp";
1414
+ }
1415
+ setModel(model) {
1416
+ this.model = model;
1417
+ }
1418
+ async listModels() {
1419
+ try {
1420
+ const response = await fetch(`${this.baseUrl}/v1/models`);
1421
+ if (!response.ok) {
1422
+ return this.model ? [this.model] : [];
1423
+ }
1424
+ const data = await response.json();
1425
+ return data.data?.map((m) => m.id) ?? [this.model];
1426
+ } catch {
1427
+ return this.model ? [this.model] : [];
1428
+ }
1429
+ }
1430
+ async isAvailable() {
1431
+ try {
1432
+ const response = await fetch(`${this.baseUrl}/health`);
1433
+ return response.ok;
1434
+ } catch {
1435
+ return false;
1436
+ }
1437
+ }
1438
+ async complete(request) {
1439
+ const body = {
1440
+ model: request.model || this.model || _LlamaCppProvider.DEFAULT_MODEL,
1441
+ messages: request.messages.map((msg) => {
1442
+ const mapped = {
1443
+ role: msg.role,
1444
+ content: msg.content
1445
+ };
1446
+ if (msg.name) mapped.name = msg.name;
1447
+ if (msg.role === "tool" && msg.tool_call_id) mapped.tool_call_id = msg.tool_call_id;
1448
+ if (msg.role === "assistant" && msg.tool_calls) mapped.tool_calls = msg.tool_calls;
1449
+ return mapped;
1450
+ }),
1451
+ temperature: request.temperature ?? 0.7,
1452
+ max_tokens: request.maxTokens ?? 4096,
1453
+ stream: false
1454
+ };
1455
+ if (request.tools && request.tools.length > 0) {
1456
+ body.tools = request.tools.map((tool) => ({
1457
+ type: "function",
1458
+ function: {
1459
+ name: tool.name,
1460
+ description: tool.description,
1461
+ parameters: tool.parameters ?? { type: "object", properties: {} }
1462
+ }
1463
+ }));
1464
+ }
1465
+ const response = await fetch(`${this.baseUrl}/v1/chat/completions`, {
1466
+ method: "POST",
1467
+ headers: {
1468
+ "Content-Type": "application/json"
1469
+ },
1470
+ body: JSON.stringify(body),
1471
+ signal: request.signal
1472
+ });
1473
+ if (!response.ok) {
1474
+ throw await this.buildApiError(response, body);
1475
+ }
1476
+ const data = await response.json();
1477
+ const choice = data.choices[0];
1478
+ let toolCalls;
1479
+ if (choice?.message.tool_calls?.length) {
1480
+ toolCalls = choice.message.tool_calls.map((tc) => ({
1481
+ id: tc.id,
1482
+ type: "function",
1483
+ function: {
1484
+ name: tc.function.name,
1485
+ arguments: tc.function.arguments
1486
+ }
1487
+ }));
1488
+ }
1489
+ let usage;
1490
+ if (data.usage) {
1491
+ usage = {
1492
+ promptTokens: data.usage.prompt_tokens,
1493
+ completionTokens: data.usage.completion_tokens,
1494
+ totalTokens: data.usage.total_tokens
1495
+ };
1496
+ }
1497
+ const finishReason = toolCalls?.length ? "tool_calls" : choice?.finish_reason === "stop" || choice?.finish_reason === "length" || choice?.finish_reason === "content_filter" ? choice.finish_reason : "stop";
1498
+ return {
1499
+ id: data.id || `llamacpp-${Date.now()}`,
1500
+ created: data.created || Math.floor(Date.now() / 1e3),
1501
+ content: choice?.message.content ?? "",
1502
+ toolCalls,
1503
+ finishReason,
1504
+ usage,
1505
+ raw: data
1506
+ };
1507
+ }
1508
+ async buildApiError(response, body) {
1509
+ let errorBody = "";
1510
+ try {
1511
+ errorBody = await response.text();
1512
+ } catch {
1513
+ }
1514
+ const lowerBody = errorBody.toLowerCase();
1515
+ if (body.tools && response.status === 400 && (lowerBody.includes("tool") || lowerBody.includes("function") || lowerBody.includes("schema"))) {
1516
+ return new ApiError(
1517
+ `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'.
1518
+ ${errorBody}`,
1519
+ "invalid_request",
1520
+ response.status,
1521
+ false,
1522
+ void 0,
1523
+ errorBody
1524
+ );
1525
+ }
1526
+ const baseError = classifyApiError(response.status, errorBody, response.headers);
1527
+ return new ApiError(
1528
+ `llama.cpp API error: ${response.status} ${response.statusText}${errorBody ? `
1529
+ ${errorBody}` : ""}`,
1530
+ baseError.code,
1531
+ baseError.httpStatus,
1532
+ baseError.retryable,
1533
+ baseError.retryAfterMs,
1534
+ errorBody
1535
+ );
1536
+ }
1537
+ };
1538
+
1539
+ // src/providers/modelCapabilities.ts
1540
+ var OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
1541
+ var CACHE_TTL_MS = 30 * 60 * 1e3;
1542
+ var cache = null;
1543
+ function normalizeModelId(model) {
1544
+ return model.trim().toLowerCase();
1545
+ }
1546
+ function getCachedModels() {
1547
+ if (!cache) {
1548
+ return null;
1549
+ }
1550
+ if (Date.now() - cache.fetchedAt >= CACHE_TTL_MS) {
1551
+ return null;
1552
+ }
1553
+ return cache.models;
1554
+ }
1555
+ function findModelCapability(models, model) {
1556
+ const normalizedModel = normalizeModelId(model);
1557
+ const exactMatch = models.find((candidate) => {
1558
+ const candidateIds = [
1559
+ candidate.id,
1560
+ candidate.canonical_slug,
1561
+ candidate.name
1562
+ ].filter((value) => Boolean(value)).map(normalizeModelId);
1563
+ return candidateIds.includes(normalizedModel);
1564
+ });
1565
+ if (exactMatch) {
1566
+ return exactMatch;
1567
+ }
1568
+ return models.find((candidate) => {
1569
+ const candidateIds = [
1570
+ candidate.id,
1571
+ candidate.canonical_slug,
1572
+ candidate.name
1573
+ ].filter((value) => Boolean(value)).map(normalizeModelId);
1574
+ return candidateIds.some(
1575
+ (candidateId) => candidateId.includes(normalizedModel) || normalizedModel.includes(candidateId)
1576
+ );
1577
+ });
1578
+ }
1579
+ function getInputModalities(capability) {
1580
+ if (!capability) {
1581
+ return [];
1582
+ }
1583
+ if (Array.isArray(capability.input_modalities)) {
1584
+ return capability.input_modalities;
1585
+ }
1586
+ if (Array.isArray(capability.architecture?.input_modalities)) {
1587
+ return capability.architecture.input_modalities;
1588
+ }
1589
+ return [];
1590
+ }
1591
+ function getOpenRouterCapabilityContextWindow(capability) {
1592
+ const contextWindow = capability?.top_provider?.context_length ?? capability?.context_length;
1593
+ return typeof contextWindow === "number" && Number.isFinite(contextWindow) && contextWindow > 0 ? Math.floor(contextWindow) : void 0;
1594
+ }
1595
+ async function getOpenRouterModelContextWindow(model) {
1596
+ const capability = await findCapabilityForModel(model);
1597
+ return getOpenRouterCapabilityContextWindow(capability);
1598
+ }
1599
+ async function findCapabilityForModel(model) {
1600
+ const cachedModels = getCachedModels();
1601
+ if (cachedModels) {
1602
+ const cachedMatch = findModelCapability(cachedModels, model);
1603
+ if (cachedMatch) {
1604
+ return cachedMatch;
1605
+ }
1606
+ const refreshedModels = await fetchOpenRouterModelCapabilities(true);
1607
+ return findModelCapability(refreshedModels, model);
1608
+ }
1609
+ const models = await fetchOpenRouterModelCapabilities();
1610
+ return findModelCapability(models, model);
1611
+ }
1612
+ async function fetchOpenRouterModelCapabilities(forceRefresh = false) {
1613
+ if (!forceRefresh && cache && Date.now() - cache.fetchedAt < CACHE_TTL_MS) {
1614
+ return cache.models;
1615
+ }
1616
+ try {
1617
+ const response = await fetch(OPENROUTER_MODELS_URL, {
1618
+ headers: {
1619
+ "Content-Type": "application/json"
1620
+ },
1621
+ signal: AbortSignal.timeout(1e4)
1622
+ // 10s timeout
1623
+ });
1624
+ if (!response.ok) {
1625
+ throw new Error(`Failed to fetch model capabilities: ${response.status} ${response.statusText}`);
1626
+ }
1627
+ const data = await response.json();
1628
+ const models = Array.isArray(data?.data) ? data.data : [];
1629
+ cache = {
1630
+ models,
1631
+ fetchedAt: Date.now()
1632
+ };
1633
+ return models;
1634
+ } catch (error) {
1635
+ if (cache) {
1636
+ return cache.models;
1637
+ }
1638
+ throw error;
1639
+ }
1640
+ }
1641
+ async function modelSupportsImages(model) {
1642
+ const lowerModel = model.toLowerCase();
1643
+ try {
1644
+ const found = await findCapabilityForModel(model);
1645
+ const inputModalities = getInputModalities(found);
1646
+ if (inputModalities.length > 0) {
1647
+ return inputModalities.includes("image");
1648
+ }
1649
+ if (found) {
1650
+ return quickVisionCheck(found.id.toLowerCase());
1651
+ }
1652
+ return quickVisionCheck(lowerModel);
1653
+ } catch {
1654
+ return quickVisionCheck(lowerModel);
1655
+ }
1656
+ }
1657
+ function quickVisionCheck(lowerModel) {
1658
+ 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")) {
1659
+ return true;
1660
+ }
1661
+ if (lowerModel.includes("gpt-4o") || lowerModel.includes("gpt-4-turbo") || lowerModel.includes("gpt-4-vision") || lowerModel.includes("gpt-4.5") || lowerModel.includes("chatgpt-4o")) {
1662
+ return true;
1663
+ }
1664
+ if (lowerModel.includes("gemini") && !lowerModel.includes("gemini-pro")) {
1665
+ return true;
1666
+ }
1667
+ if (lowerModel.includes("gemini-1.5") || lowerModel.includes("gemini-2.0") || lowerModel.includes("gemini-2.5") || lowerModel.includes("gemini-pro-vision")) {
1668
+ return true;
1669
+ }
1670
+ if (lowerModel.includes("llama-3.2") || lowerModel.includes("llama-3.3") || lowerModel.includes("llama-4")) {
1671
+ if (lowerModel.includes("vision") || lowerModel.includes("multimodal")) {
1672
+ return true;
1673
+ }
1674
+ }
1675
+ if (lowerModel.includes("pixtral")) {
1676
+ return true;
1677
+ }
1678
+ if (lowerModel.includes("qwen") && lowerModel.includes("vl")) {
1679
+ return true;
1680
+ }
1681
+ if (lowerModel.includes("minicpm") && lowerModel.includes("v")) {
1682
+ return true;
1683
+ }
1684
+ if (lowerModel.includes("command-r") && lowerModel.includes("vision")) {
1685
+ return true;
1686
+ }
1687
+ if (lowerModel.includes("deepseek") && lowerModel.includes("vl")) {
1688
+ return true;
1689
+ }
1690
+ if (lowerModel.includes("vision") || lowerModel.includes("vl-") || lowerModel.includes("-vl") || lowerModel.includes("multimodal")) {
1691
+ return true;
1692
+ }
1693
+ return false;
1694
+ }
1695
+
1696
+ // src/providers/OpenRouterClient.ts
1697
+ function messageContainsImageContent(messages) {
1698
+ return messages.some(
1699
+ (msg) => Array.isArray(msg.content) && msg.content.some(
1700
+ (part) => typeof part === "object" && part !== null && "type" in part && part.type === "image_url"
1701
+ )
1702
+ );
1703
+ }
1704
+ function getTextContent(content) {
1705
+ if (typeof content === "string") {
1706
+ return content;
1707
+ }
1708
+ if (!Array.isArray(content)) {
1709
+ return "";
1710
+ }
1711
+ return content.filter(
1712
+ (part) => typeof part === "object" && part !== null && "type" in part
1713
+ ).filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text ?? "").join("\n");
1714
+ }
1715
+ function sanitizeMessages(messages, allowImageInputs) {
1716
+ return messages.map((msg) => {
1717
+ const sanitized = {
1718
+ role: msg.role,
1719
+ content: allowImageInputs || !Array.isArray(msg.content) ? msg.content : getTextContent(msg.content)
1720
+ };
1721
+ if (msg.role === "tool" && msg.tool_call_id) {
1722
+ sanitized.tool_call_id = msg.tool_call_id;
1723
+ }
1724
+ if (msg.role === "assistant" && msg.tool_calls?.length) {
1725
+ sanitized.tool_calls = msg.tool_calls;
1726
+ }
1727
+ if (msg.name) {
1728
+ sanitized.name = msg.name;
1729
+ }
1730
+ return sanitized;
1731
+ });
1732
+ }
1733
+ var DEFAULT_BASE_URL = "https://openrouter.ai/api/v1";
1734
+ var DEFAULT_MAX_RETRIES2 = 3;
1735
+ var MAX_ALLOWED_RETRIES2 = 5;
1736
+ var DEFAULT_RETRY_DELAY2 = 1e3;
1737
+ var DEFAULT_TIMEOUT2 = 3e4;
1738
+ var OPENROUTER_FRIENDLY_MESSAGES = {
1739
+ auth_failed: "Authentication failed. Please verify your OpenRouter API key in ~/.autohand/config.json.",
1740
+ payment_required: "Payment required. Please check your OpenRouter account balance or billing settings.",
1741
+ access_denied: "Access denied. Your OpenRouter API key may not have permission for this model.",
1742
+ server_error: "The OpenRouter service encountered an error. Please try again later.",
1743
+ network_error: "Unable to connect to OpenRouter. Please check your internet connection.",
1744
+ timeout: "The request timed out. The OpenRouter service may be experiencing high load."
1745
+ };
1746
+ function withOpenRouterMessage(error) {
1747
+ const friendlyMessage = OPENROUTER_FRIENDLY_MESSAGES[error.code];
1748
+ if (!friendlyMessage) {
1749
+ return error;
1750
+ }
1751
+ return new ApiError(
1752
+ error.rawDetail ? `${friendlyMessage}
1753
+ ${error.rawDetail}` : friendlyMessage,
1754
+ error.code,
1755
+ error.httpStatus,
1756
+ error.retryable,
1757
+ error.retryAfterMs,
1758
+ error.rawDetail
1759
+ );
1760
+ }
1761
+ var OpenRouterClient = class {
1762
+ apiKey;
1763
+ baseUrl;
1764
+ defaultModel;
1765
+ maxRetries;
1766
+ retryDelay;
1767
+ timeout;
1768
+ constructor(settings, networkSettings) {
1769
+ this.apiKey = settings.apiKey ?? "";
1770
+ this.baseUrl = settings.baseUrl ?? DEFAULT_BASE_URL;
1771
+ this.defaultModel = settings.model;
1772
+ const configuredRetries = networkSettings?.maxRetries ?? DEFAULT_MAX_RETRIES2;
1773
+ this.maxRetries = Math.min(
1774
+ Math.max(0, configuredRetries),
1775
+ MAX_ALLOWED_RETRIES2
1776
+ );
1777
+ this.retryDelay = networkSettings?.retryDelay ?? DEFAULT_RETRY_DELAY2;
1778
+ this.timeout = networkSettings?.timeout ?? DEFAULT_TIMEOUT2;
1779
+ }
1780
+ setDefaultModel(model) {
1781
+ this.defaultModel = model;
1782
+ }
1783
+ async complete(request) {
1784
+ const selectedModel = request.model ?? this.defaultModel;
1785
+ const allowImageInputs = messageContainsImageContent(request.messages) ? await modelSupportsImages(selectedModel) : false;
1786
+ const payload = {
1787
+ model: selectedModel,
1788
+ messages: sanitizeMessages(request.messages, allowImageInputs),
1789
+ temperature: request.temperature ?? 0.2,
1790
+ max_tokens: request.maxTokens ?? 16e3,
1791
+ // Increased from 1000 to allow large file generation
1792
+ stream: request.stream ?? false
1793
+ };
1794
+ if (request.tools && request.tools.length > 0) {
1795
+ payload.tools = request.tools.map((tool) => ({
1796
+ type: "function",
1797
+ function: {
1798
+ name: tool.name,
1799
+ description: tool.description,
1800
+ parameters: tool.parameters ?? { type: "object", properties: {} }
1801
+ }
1802
+ }));
1803
+ if (request.toolChoice) {
1804
+ payload.tool_choice = request.toolChoice;
1805
+ }
1806
+ }
1807
+ const model = selectedModel.toLowerCase();
1808
+ if (request.thinkingLevel && request.thinkingLevel !== "normal") {
1809
+ if (model.includes("o1") || model.includes("o3")) {
1810
+ if (request.thinkingLevel === "extended") {
1811
+ payload.reasoning_effort = "high";
1812
+ } else if (request.thinkingLevel === "none") {
1813
+ payload.reasoning_effort = "low";
1814
+ }
1815
+ }
1816
+ if (model.includes("claude") && request.thinkingLevel === "extended") {
1817
+ payload.provider = {
1818
+ anthropic: {
1819
+ thinking: {
1820
+ type: "enabled",
1821
+ budget_tokens: 1e4
1822
+ }
1823
+ }
1824
+ };
1825
+ }
1826
+ }
1827
+ const headers = {
1828
+ "Content-Type": "application/json",
1829
+ "HTTP-Referer": "https://autohand.dev",
1830
+ "X-OpenRouter-Title": "Autohand Code CLI",
1831
+ "X-OpenRouter-Categories": "cli-agent"
1832
+ };
1833
+ if (this.apiKey) {
1834
+ headers.Authorization = `Bearer ${this.apiKey}`;
1835
+ }
1836
+ const payloadJson = JSON.stringify(payload);
1837
+ const payloadSizeBytes = payloadJson.length;
1838
+ const maxPayloadSize = 5 * 1024 * 1024;
1839
+ if (payloadSizeBytes > maxPayloadSize) {
1840
+ const sizeMB = (payloadSizeBytes / (1024 * 1024)).toFixed(2);
1841
+ throw new ApiError(
1842
+ `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.`,
1843
+ "context_overflow",
1844
+ 400,
1845
+ true
1846
+ );
1847
+ }
1848
+ let lastError = null;
1849
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
1850
+ try {
1851
+ const response = await this.makeRequest(
1852
+ payload,
1853
+ headers,
1854
+ request.signal,
1855
+ payloadJson
1856
+ );
1857
+ return response;
1858
+ } catch (error) {
1859
+ lastError = error;
1860
+ if (this.isNonRetryableError(error)) {
1861
+ throw error;
1862
+ }
1863
+ if (attempt < this.maxRetries) {
1864
+ const retryAfterMs = error instanceof ApiError ? error.retryAfterMs : void 0;
1865
+ const delay = Math.max(
1866
+ this.retryDelay * Math.pow(2, attempt),
1867
+ retryAfterMs ?? 0
1868
+ );
1869
+ await this.sleep(delay);
1870
+ }
1871
+ }
1872
+ }
1873
+ throw lastError ?? new ApiError("Failed to communicate with the AI service. Please try again.", "network_error", 0, true);
1874
+ }
1875
+ async makeRequest(payload, headers, signal, preSerializedBody) {
1876
+ let response;
1877
+ try {
1878
+ const timeoutController = new AbortController();
1879
+ const timeoutId = setTimeout(
1880
+ () => timeoutController.abort(),
1881
+ this.timeout
1882
+ );
1883
+ const combinedSignal = signal ? this.combineSignals(signal, timeoutController.signal) : timeoutController.signal;
1884
+ try {
1885
+ response = await fetch(`${this.baseUrl}/chat/completions`, {
1886
+ method: "POST",
1887
+ headers,
1888
+ body: preSerializedBody ?? JSON.stringify(payload),
1889
+ signal: combinedSignal
1890
+ });
1891
+ } finally {
1892
+ clearTimeout(timeoutId);
1893
+ }
1894
+ } catch (error) {
1895
+ const err = error;
1896
+ if (err.name === "AbortError" && signal?.aborted) {
1897
+ throw new ApiError("Request cancelled.", "cancelled", 0, false);
1898
+ }
1899
+ if (err.name === "AbortError") {
1900
+ throw new ApiError(
1901
+ "The request timed out. The OpenRouter service may be experiencing high load.",
1902
+ "timeout",
1903
+ 0,
1904
+ true
1905
+ );
1906
+ }
1907
+ throw new ApiError(
1908
+ "Unable to connect to OpenRouter. Please check your internet connection.",
1909
+ "network_error",
1910
+ 0,
1911
+ true
1912
+ );
1913
+ }
1914
+ if (!response.ok) {
1915
+ throw await this.buildApiError(response);
1916
+ }
1917
+ const json = await response.json();
1918
+ const message = json?.choices?.[0]?.message;
1919
+ const text = message?.content ?? "";
1920
+ const finishReason = json?.choices?.[0]?.finish_reason;
1921
+ let toolCalls;
1922
+ if (message?.tool_calls && Array.isArray(message.tool_calls)) {
1923
+ toolCalls = message.tool_calls.map((tc) => {
1924
+ const rawArgs = tc.function?.arguments;
1925
+ return {
1926
+ id: tc.id,
1927
+ type: "function",
1928
+ function: {
1929
+ name: tc.function?.name ?? "",
1930
+ arguments: rawArgs ?? "{}"
1931
+ }
1932
+ };
1933
+ });
1934
+ }
1935
+ const usage = normalizeLLMUsage(json?.usage);
1936
+ return {
1937
+ id: json.id ?? "autohand-local",
1938
+ created: json.created ?? Date.now(),
1939
+ content: text,
1940
+ toolCalls,
1941
+ finishReason,
1942
+ usage,
1943
+ raw: json
1944
+ };
1945
+ }
1946
+ async buildApiError(response) {
1947
+ const status = response.status;
1948
+ let errorDetail = "";
1949
+ try {
1950
+ const body = await response.json();
1951
+ errorDetail = body?.error?.message || body?.error || body?.message || "";
1952
+ if (typeof errorDetail === "object") {
1953
+ errorDetail = JSON.stringify(errorDetail);
1954
+ }
1955
+ } catch {
1956
+ try {
1957
+ errorDetail = await response.text();
1958
+ } catch {
1959
+ }
1960
+ }
1961
+ return withOpenRouterMessage(classifyApiError(status, errorDetail, response.headers));
1962
+ }
1963
+ isNonRetryableError(error) {
1964
+ if (error instanceof ApiError) {
1965
+ return !error.retryable;
1966
+ }
1967
+ const classified = classifyApiError(0, error.message);
1968
+ return !classified.retryable;
1969
+ }
1970
+ combineSignals(signal1, signal2) {
1971
+ const controller = new AbortController();
1972
+ const abort = () => controller.abort();
1973
+ signal1.addEventListener("abort", abort, { once: true });
1974
+ signal2.addEventListener("abort", abort, { once: true });
1975
+ if (signal1.aborted || signal2.aborted) {
1976
+ controller.abort();
1977
+ }
1978
+ return controller.signal;
1979
+ }
1980
+ sleep(ms) {
1981
+ return new Promise((resolve) => setTimeout(resolve, ms));
1982
+ }
1983
+ };
1984
+
1985
+ // src/providers/OpenRouterProvider.ts
1986
+ var OpenRouterProvider = class {
1987
+ client;
1988
+ model;
1989
+ constructor(config, networkSettings) {
1990
+ this.client = new OpenRouterClient(config, networkSettings);
1991
+ this.model = config.model;
1992
+ }
1993
+ getName() {
1994
+ return "openrouter";
1995
+ }
1996
+ setModel(model) {
1997
+ this.model = model;
1998
+ this.client.setDefaultModel(model);
1999
+ }
2000
+ async listModels() {
2001
+ try {
2002
+ const models = await fetchOpenRouterModelCapabilities();
2003
+ const ids = models.map((model) => model.id).filter((id) => Boolean(id));
2004
+ if (ids.length > 0) {
2005
+ return ids;
2006
+ }
2007
+ } catch {
2008
+ }
2009
+ return [
2010
+ "anthropic/claude-4-sonnet",
2011
+ "anthropic/claude-3-opus",
2012
+ "google/gemini-pro-1.5",
2013
+ "openai/gpt-4o",
2014
+ "x-ai/grok-2-latest",
2015
+ "meta-llama/llama-3.1-70b-instruct"
2016
+ ];
2017
+ }
2018
+ async isAvailable() {
2019
+ return true;
2020
+ }
2021
+ async complete(request) {
2022
+ return this.client.complete(request);
2023
+ }
2024
+ };
2025
+
2026
+ // src/utils/platform.ts
2027
+ function isAppleSilicon() {
2028
+ return process.platform === "darwin" && process.arch === "arm64";
2029
+ }
2030
+ function isMLXSupported() {
2031
+ return isAppleSilicon();
2032
+ }
2033
+
2034
+ // src/providers/MLXProvider.ts
2035
+ var DEFAULT_TIMEOUT3 = 6e4;
2036
+ var DEFAULT_MAX_RETRIES3 = 2;
2037
+ var MAX_ALLOWED_RETRIES3 = 5;
2038
+ var DEFAULT_RETRY_DELAY3 = 1e3;
2039
+ var AVAILABILITY_TIMEOUT2 = 5e3;
2040
+ var MLXProvider = class {
2041
+ baseUrl;
2042
+ model;
2043
+ maxRetries;
2044
+ retryDelay;
2045
+ timeout;
2046
+ constructor(config, networkSettings) {
2047
+ const port = config.port || 8080;
2048
+ this.baseUrl = config.baseUrl || `http://localhost:${port}`;
2049
+ this.model = config.model || "mlx-model";
2050
+ const configuredRetries = networkSettings?.maxRetries ?? DEFAULT_MAX_RETRIES3;
2051
+ this.maxRetries = Math.min(Math.max(0, configuredRetries), MAX_ALLOWED_RETRIES3);
2052
+ this.retryDelay = networkSettings?.retryDelay ?? DEFAULT_RETRY_DELAY3;
2053
+ this.timeout = networkSettings?.timeout ?? DEFAULT_TIMEOUT3;
2054
+ }
2055
+ getName() {
2056
+ return "mlx";
2057
+ }
2058
+ setModel(model) {
2059
+ this.model = model;
2060
+ }
2061
+ async listModels() {
2062
+ if (!isMLXSupported()) {
2063
+ return [];
2064
+ }
2065
+ try {
2066
+ const controller = new AbortController();
2067
+ const timerId = setTimeout(() => controller.abort(), AVAILABILITY_TIMEOUT2);
2068
+ try {
2069
+ const response = await fetch(`${this.baseUrl}/v1/models`, {
2070
+ signal: controller.signal
2071
+ });
2072
+ if (!response.ok) {
2073
+ return this.model ? [this.model] : [];
2074
+ }
2075
+ const data = await response.json();
2076
+ return data.data?.map((m) => m.id) ?? (this.model ? [this.model] : []);
2077
+ } finally {
2078
+ clearTimeout(timerId);
2079
+ }
2080
+ } catch {
2081
+ return this.model ? [this.model] : [];
2082
+ }
2083
+ }
2084
+ async isAvailable() {
2085
+ if (!isMLXSupported()) {
2086
+ return false;
2087
+ }
2088
+ try {
2089
+ const controller = new AbortController();
2090
+ const timerId = setTimeout(() => controller.abort(), AVAILABILITY_TIMEOUT2);
2091
+ try {
2092
+ const response = await fetch(`${this.baseUrl}/v1/models`, {
2093
+ signal: controller.signal
2094
+ });
2095
+ return response.ok;
2096
+ } finally {
2097
+ clearTimeout(timerId);
2098
+ }
2099
+ } catch {
2100
+ return false;
2101
+ }
2102
+ }
2103
+ async complete(request) {
2104
+ if (!isMLXSupported()) {
2105
+ throw new Error("MLX is only supported on macOS with Apple Silicon");
2106
+ }
2107
+ const body = {
2108
+ model: request.model || this.model,
2109
+ messages: request.messages.map((msg) => {
2110
+ const mapped = {
2111
+ role: msg.role,
2112
+ content: msg.content
2113
+ };
2114
+ if (msg.name) mapped.name = msg.name;
2115
+ if (msg.role === "tool" && msg.tool_call_id) mapped.tool_call_id = msg.tool_call_id;
2116
+ if (msg.role === "assistant" && msg.tool_calls) mapped.tool_calls = msg.tool_calls;
2117
+ return mapped;
2118
+ }),
2119
+ temperature: request.temperature ?? 0.7,
2120
+ max_tokens: request.maxTokens ?? 4096,
2121
+ stream: false
2122
+ };
2123
+ if (request.tools && request.tools.length > 0) {
2124
+ body.tools = request.tools.map((tool) => ({
2125
+ type: "function",
2126
+ function: {
2127
+ name: tool.name,
2128
+ description: tool.description,
2129
+ parameters: tool.parameters ?? { type: "object", properties: {} }
2130
+ }
2131
+ }));
2132
+ }
2133
+ let lastError = null;
2134
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
2135
+ try {
2136
+ return await this.makeRequest(body, request.signal);
2137
+ } catch (error) {
2138
+ lastError = error;
2139
+ if (this.isNonRetryableError(error)) {
2140
+ throw error;
2141
+ }
2142
+ if (attempt < this.maxRetries) {
2143
+ const delay = this.retryDelay * Math.pow(2, attempt);
2144
+ await this.sleep(delay);
2145
+ }
2146
+ }
2147
+ }
2148
+ throw lastError ?? new ApiError(
2149
+ "Failed to communicate with the MLX server. Please try again.",
2150
+ "network_error",
2151
+ 0,
2152
+ true
2153
+ );
2154
+ }
2155
+ async makeRequest(body, userSignal) {
2156
+ let response;
2157
+ try {
2158
+ const timeoutController = new AbortController();
2159
+ const timerId = setTimeout(() => timeoutController.abort(), this.timeout);
2160
+ const combinedSignal = userSignal ? this.combineSignals(userSignal, timeoutController.signal) : timeoutController.signal;
2161
+ try {
2162
+ response = await fetch(`${this.baseUrl}/v1/chat/completions`, {
2163
+ method: "POST",
2164
+ headers: {
2165
+ "Content-Type": "application/json"
2166
+ },
2167
+ body: JSON.stringify(body),
2168
+ signal: combinedSignal
2169
+ });
2170
+ } finally {
2171
+ clearTimeout(timerId);
2172
+ }
2173
+ } catch (error) {
2174
+ const err = error;
2175
+ if (err.name === "AbortError" && userSignal?.aborted) {
2176
+ throw new ApiError("Request cancelled.", "cancelled", 0, false);
2177
+ }
2178
+ if (err.name === "AbortError") {
2179
+ throw new ApiError(
2180
+ `MLX server request timed out after ${this.timeout / 1e3}s. Local inference can be slow \u2014 consider increasing the timeout in your config.`,
2181
+ "timeout",
2182
+ 0,
2183
+ true
2184
+ );
2185
+ }
2186
+ throw new ApiError(
2187
+ `Cannot connect to MLX server at ${this.baseUrl}. Make sure it is running.`,
2188
+ "network_error",
2189
+ 0,
2190
+ true
2191
+ );
2192
+ }
2193
+ if (!response.ok) {
2194
+ throw await this.buildApiError(response);
2195
+ }
2196
+ let data;
2197
+ try {
2198
+ data = await response.json();
2199
+ } catch {
2200
+ let rawBody = "";
2201
+ try {
2202
+ rawBody = await response.text();
2203
+ } catch {
2204
+ }
2205
+ throw new ApiError(
2206
+ `MLX server returned an invalid response. The model may have crashed or returned malformed output. Raw: ${rawBody.slice(0, 500)}`,
2207
+ "invalid_request",
2208
+ response.status,
2209
+ false
2210
+ );
2211
+ }
2212
+ const choice = data.choices[0];
2213
+ let toolCalls;
2214
+ if (choice?.message.tool_calls?.length) {
2215
+ toolCalls = choice.message.tool_calls.map((tc) => ({
2216
+ id: tc.id,
2217
+ type: "function",
2218
+ function: {
2219
+ name: tc.function.name,
2220
+ arguments: tc.function.arguments
2221
+ }
2222
+ }));
2223
+ }
2224
+ let usage;
2225
+ if (data.usage) {
2226
+ usage = {
2227
+ promptTokens: data.usage.prompt_tokens,
2228
+ completionTokens: data.usage.completion_tokens,
2229
+ totalTokens: data.usage.total_tokens
2230
+ };
2231
+ }
2232
+ const finishReason = toolCalls?.length ? "tool_calls" : choice?.finish_reason === "stop" || choice?.finish_reason === "length" || choice?.finish_reason === "content_filter" ? choice.finish_reason : "stop";
2233
+ return {
2234
+ id: data.id || `mlx-${Date.now()}`,
2235
+ created: data.created || Math.floor(Date.now() / 1e3),
2236
+ content: choice?.message.content ?? "",
2237
+ toolCalls,
2238
+ finishReason,
2239
+ usage,
2240
+ raw: data
2241
+ };
2242
+ }
2243
+ async buildApiError(response) {
2244
+ let errorDetail = "";
2245
+ try {
2246
+ const body = await response.json();
2247
+ const maybeError = body?.error;
2248
+ if (maybeError && typeof maybeError === "object") {
2249
+ errorDetail = maybeError?.message ?? "";
2250
+ } else if (typeof maybeError === "string") {
2251
+ errorDetail = maybeError;
2252
+ } else if (typeof body?.message === "string") {
2253
+ errorDetail = body.message;
2254
+ }
2255
+ if (typeof errorDetail === "object") {
2256
+ errorDetail = JSON.stringify(errorDetail);
2257
+ }
2258
+ } catch {
2259
+ try {
2260
+ errorDetail = await response.text();
2261
+ } catch {
2262
+ }
2263
+ }
2264
+ return classifyApiError(response.status, errorDetail, response.headers);
2265
+ }
2266
+ isNonRetryableError(error) {
2267
+ if (error instanceof ApiError) {
2268
+ return !error.retryable;
2269
+ }
2270
+ const classified = classifyApiError(0, error.message);
2271
+ return !classified.retryable;
2272
+ }
2273
+ combineSignals(signal1, signal2) {
2274
+ const controller = new AbortController();
2275
+ const abort = () => controller.abort();
2276
+ signal1.addEventListener("abort", abort, { once: true });
2277
+ signal2.addEventListener("abort", abort, { once: true });
2278
+ if (signal1.aborted || signal2.aborted) {
2279
+ controller.abort();
2280
+ }
2281
+ return controller.signal;
2282
+ }
2283
+ sleep(ms) {
2284
+ return new Promise((resolve) => setTimeout(resolve, ms));
2285
+ }
2286
+ };
2287
+
2288
+ // src/providers/LLMGatewayClient.ts
2289
+ function sanitizeMessages2(messages) {
2290
+ return messages.map((msg) => {
2291
+ const sanitized = {
2292
+ role: msg.role,
2293
+ content: msg.content
2294
+ };
2295
+ if (msg.role === "tool" && msg.tool_call_id) {
2296
+ sanitized.tool_call_id = msg.tool_call_id;
2297
+ }
2298
+ if (msg.role === "assistant" && msg.tool_calls?.length) {
2299
+ sanitized.tool_calls = msg.tool_calls;
2300
+ }
2301
+ if (msg.name) {
2302
+ sanitized.name = msg.name;
2303
+ }
2304
+ return sanitized;
2305
+ });
2306
+ }
2307
+ var DEFAULT_BASE_URL2 = "https://api.llmgateway.io/v1";
2308
+ var DEFAULT_MAX_RETRIES4 = 3;
2309
+ var MAX_ALLOWED_RETRIES4 = 5;
2310
+ var DEFAULT_RETRY_DELAY4 = 1e3;
2311
+ var DEFAULT_TIMEOUT4 = 3e4;
2312
+ var DEFAULT_ERROR_LABELS = {
2313
+ serviceName: "LLM Gateway",
2314
+ credentialName: "LLM Gateway API key",
2315
+ accountName: "LLM Gateway account"
2316
+ };
2317
+ function buildFriendlyErrors(labels) {
2318
+ return {
2319
+ 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.",
2320
+ 401: `Authentication failed. Please verify your ${labels.credentialName} in ~/.autohand/config.json.`,
2321
+ 402: `Payment required. Please check your ${labels.accountName} balance or billing settings.`,
2322
+ 403: `Access denied. Your ${labels.credentialName} may not have permission for this model.`,
2323
+ 404: "The requested model was not found. Use /model to select a different one.",
2324
+ 429: "Rate limit exceeded. Please wait a moment and try again, or choose a different model.",
2325
+ 500: `The ${labels.serviceName} service encountered an internal error. Please try again later.`,
2326
+ 502: `The ${labels.serviceName} service is temporarily unavailable. Please try again in a few moments.`,
2327
+ 503: `The ${labels.serviceName} service is currently overloaded. Please try again later.`,
2328
+ 504: `The request timed out. The ${labels.serviceName} service may be experiencing high load.`
2329
+ };
2330
+ }
2331
+ var LLMGatewayClient = class {
2332
+ apiKey;
2333
+ baseUrl;
2334
+ defaultModel;
2335
+ maxRetries;
2336
+ retryDelay;
2337
+ timeout;
2338
+ errorLabels;
2339
+ constructor(settings, networkSettings, errorLabels = DEFAULT_ERROR_LABELS) {
2340
+ this.apiKey = settings.apiKey ?? "";
2341
+ this.baseUrl = settings.baseUrl ?? DEFAULT_BASE_URL2;
2342
+ this.defaultModel = settings.model;
2343
+ this.errorLabels = errorLabels;
2344
+ const configuredRetries = networkSettings?.maxRetries ?? DEFAULT_MAX_RETRIES4;
2345
+ this.maxRetries = Math.min(
2346
+ Math.max(0, configuredRetries),
2347
+ MAX_ALLOWED_RETRIES4
2348
+ );
2349
+ this.retryDelay = networkSettings?.retryDelay ?? DEFAULT_RETRY_DELAY4;
2350
+ this.timeout = networkSettings?.timeout ?? DEFAULT_TIMEOUT4;
2351
+ }
2352
+ setDefaultModel(model) {
2353
+ this.defaultModel = model;
2354
+ }
2355
+ async complete(request) {
2356
+ const payload = this.buildPayload(request);
2357
+ if (request.tools && request.tools.length > 0) {
2358
+ payload.tools = request.tools.map((tool) => ({
2359
+ type: "function",
2360
+ function: {
2361
+ name: tool.name,
2362
+ description: tool.description,
2363
+ parameters: tool.parameters ?? { type: "object", properties: {} }
2364
+ }
2365
+ }));
2366
+ if (request.toolChoice) {
2367
+ payload.tool_choice = request.toolChoice;
2368
+ }
2369
+ }
2370
+ if (request.chatTemplateKwargs) {
2371
+ payload.extra_body = {
2372
+ chat_template_kwargs: this.buildChatTemplateKwargs(request.chatTemplateKwargs)
2373
+ };
2374
+ }
2375
+ const headers = {
2376
+ "Content-Type": "application/json",
2377
+ "x-source": "Autohand Code CLI"
2378
+ };
2379
+ if (this.apiKey) {
2380
+ headers.Authorization = `Bearer ${this.apiKey}`;
2381
+ }
2382
+ const payloadJson = JSON.stringify(payload);
2383
+ const payloadSizeBytes = payloadJson.length;
2384
+ const maxPayloadSize = 5 * 1024 * 1024;
2385
+ if (payloadSizeBytes > maxPayloadSize) {
2386
+ const sizeMB = (payloadSizeBytes / (1024 * 1024)).toFixed(2);
2387
+ throw new Error(
2388
+ `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.`
2389
+ );
2390
+ }
2391
+ let lastError = null;
2392
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
2393
+ try {
2394
+ const response = await this.makeRequest(
2395
+ payload,
2396
+ headers,
2397
+ request.signal,
2398
+ payloadJson,
2399
+ request.stream ?? false
2400
+ );
2401
+ return response;
2402
+ } catch (error) {
2403
+ lastError = error;
2404
+ if (this.isNonRetryableError(error)) {
2405
+ throw error;
2406
+ }
2407
+ if (attempt < this.maxRetries) {
2408
+ const delay = this.retryDelay * Math.pow(2, attempt);
2409
+ await this.sleep(delay);
2410
+ }
2411
+ }
2412
+ }
2413
+ throw lastError ?? new Error("Failed to communicate with LLM Gateway. Please try again.");
2414
+ }
2415
+ buildPayload(request) {
2416
+ const payload = {
2417
+ model: request.model ?? this.defaultModel,
2418
+ messages: sanitizeMessages2(request.messages),
2419
+ temperature: request.temperature ?? 0.2,
2420
+ max_tokens: request.maxTokens ?? 16e3,
2421
+ stream: request.stream ?? false
2422
+ };
2423
+ return payload;
2424
+ }
2425
+ buildChatTemplateKwargs(kwargs) {
2426
+ const result = {};
2427
+ if (kwargs.thinking !== void 0) result.thinking = kwargs.thinking;
2428
+ if (kwargs.enable_thinking !== void 0) result.enable_thinking = kwargs.enable_thinking;
2429
+ if (kwargs.reasoning_effort !== void 0) result.reasoning_effort = kwargs.reasoning_effort;
2430
+ if (kwargs.clear_thinking !== void 0) result.clear_thinking = kwargs.clear_thinking;
2431
+ return result;
2432
+ }
2433
+ async makeRequest(payload, headers, signal, preSerializedBody, isStreaming = false) {
2434
+ let response;
2435
+ try {
2436
+ const timeoutController = new AbortController();
2437
+ const timeoutId = setTimeout(
2438
+ () => timeoutController.abort(),
2439
+ this.timeout
2440
+ );
2441
+ const combinedSignal = signal ? this.combineSignals(signal, timeoutController.signal) : timeoutController.signal;
2442
+ try {
2443
+ response = await fetch(`${this.baseUrl}/chat/completions`, {
2444
+ method: "POST",
2445
+ headers,
2446
+ body: preSerializedBody ?? JSON.stringify(payload),
2447
+ signal: combinedSignal
2448
+ });
2449
+ } finally {
2450
+ clearTimeout(timeoutId);
2451
+ }
2452
+ } catch (error) {
2453
+ const err = error;
2454
+ if (err.name === "AbortError" && signal?.aborted) {
2455
+ throw new Error("Request cancelled.");
2456
+ }
2457
+ if (err.name === "AbortError") {
2458
+ throw new Error(
2459
+ `Request timed out. The ${this.errorLabels.serviceName} service may be experiencing high load.`
2460
+ );
2461
+ }
2462
+ throw new Error(
2463
+ `Unable to connect to ${this.errorLabels.serviceName}. Please check your internet connection.`
2464
+ );
2465
+ }
2466
+ if (!response.ok) {
2467
+ throw new Error(await this.buildFriendlyError(response));
2468
+ }
2469
+ if (isStreaming) {
2470
+ return this.handleStreamingResponse(response);
2471
+ }
2472
+ const json = await response.json();
2473
+ const message = json?.choices?.[0]?.message;
2474
+ const text = message?.content ?? "";
2475
+ const finishReason = json?.choices?.[0]?.finish_reason;
2476
+ let toolCalls;
2477
+ if (message?.tool_calls && Array.isArray(message.tool_calls)) {
2478
+ toolCalls = message.tool_calls.map((tc) => {
2479
+ const rawArgs = tc.function?.arguments;
2480
+ return {
2481
+ id: tc.id,
2482
+ type: "function",
2483
+ function: {
2484
+ name: tc.function?.name ?? "",
2485
+ arguments: rawArgs ?? "{}"
2486
+ }
2487
+ };
2488
+ });
2489
+ }
2490
+ const usage = normalizeLLMUsage(json?.usage);
2491
+ return {
2492
+ id: json.id ?? "llmgateway-response",
2493
+ created: json.created ?? Date.now(),
2494
+ content: text,
2495
+ toolCalls,
2496
+ finishReason,
2497
+ usage,
2498
+ raw: json
2499
+ };
2500
+ }
2501
+ async handleStreamingResponse(response) {
2502
+ const reader = response.body?.getReader();
2503
+ if (!reader) {
2504
+ throw new Error("No response body for streaming");
2505
+ }
2506
+ const decoder = new TextDecoder();
2507
+ let fullContent = "";
2508
+ let fullReasoning = "";
2509
+ let lastChunk = null;
2510
+ let finishReason = "stop";
2511
+ try {
2512
+ while (true) {
2513
+ const { done, value } = await reader.read();
2514
+ if (done) break;
2515
+ const chunk = decoder.decode(value, { stream: true });
2516
+ const lines = chunk.split("\n").filter((line) => line.trim());
2517
+ for (const line of lines) {
2518
+ if (line.startsWith("data: ")) {
2519
+ const dataStr = line.slice(6).trim();
2520
+ if (dataStr === "[DONE]") continue;
2521
+ try {
2522
+ const data = JSON.parse(dataStr);
2523
+ lastChunk = data;
2524
+ const delta = data.choices?.[0]?.delta;
2525
+ if (!delta) continue;
2526
+ const reasoning = delta.reasoning || delta.reasoning_content;
2527
+ if (reasoning) {
2528
+ fullReasoning += reasoning;
2529
+ }
2530
+ if (delta.content) {
2531
+ fullContent += delta.content;
2532
+ }
2533
+ if (data.choices?.[0]?.finish_reason) {
2534
+ finishReason = data.choices[0].finish_reason;
2535
+ }
2536
+ } catch {
2537
+ }
2538
+ }
2539
+ }
2540
+ }
2541
+ } finally {
2542
+ reader.releaseLock();
2543
+ }
2544
+ const finalContent = fullReasoning ? `<thinking>${fullReasoning}</thinking>
2545
+
2546
+ ${fullContent}` : fullContent;
2547
+ return {
2548
+ id: lastChunk?.id ?? `llmgateway-stream-${Date.now()}`,
2549
+ created: lastChunk?.created ?? Math.floor(Date.now() / 1e3),
2550
+ content: finalContent,
2551
+ finishReason,
2552
+ raw: { content: fullContent, reasoning: fullReasoning, chunks: lastChunk }
2553
+ };
2554
+ }
2555
+ async buildFriendlyError(response) {
2556
+ const status = response.status;
2557
+ let errorDetail = "";
2558
+ try {
2559
+ const body = await response.json();
2560
+ errorDetail = body?.error?.message || body?.error || body?.message || "";
2561
+ if (typeof errorDetail === "object") {
2562
+ errorDetail = JSON.stringify(errorDetail);
2563
+ }
2564
+ } catch {
2565
+ try {
2566
+ errorDetail = await response.text();
2567
+ } catch {
2568
+ }
2569
+ }
2570
+ const friendlyMessage = buildFriendlyErrors(this.errorLabels)[status];
2571
+ if (friendlyMessage) {
2572
+ return errorDetail ? `${friendlyMessage}
2573
+ ${errorDetail}` : friendlyMessage;
2574
+ }
2575
+ if (status >= 500) {
2576
+ const base = `The ${this.errorLabels.serviceName} service is temporarily unavailable. Please try again later.`;
2577
+ return errorDetail ? `${base}
2578
+ (${status}: ${errorDetail})` : base;
2579
+ }
2580
+ if (status >= 400) {
2581
+ const base = "The request could not be processed.";
2582
+ return errorDetail ? `${base} (${status}: ${errorDetail})` : `${base} (HTTP ${status}) Please try again or adjust your prompt.`;
2583
+ }
2584
+ return errorDetail ? `An unexpected error occurred: ${errorDetail}` : "An unexpected error occurred. Please try again.";
2585
+ }
2586
+ isNonRetryableError(error) {
2587
+ const message = error.message.toLowerCase();
2588
+ if (message.includes("cancelled") || message.includes("aborted")) {
2589
+ return true;
2590
+ }
2591
+ if (message.includes("authentication") || message.includes("api key")) {
2592
+ return true;
2593
+ }
2594
+ if (message.includes("payment") || message.includes("access denied")) {
2595
+ return true;
2596
+ }
2597
+ if (message.includes("not found")) {
2598
+ return true;
2599
+ }
2600
+ return false;
2601
+ }
2602
+ combineSignals(signal1, signal2) {
2603
+ const controller = new AbortController();
2604
+ const abort = () => controller.abort();
2605
+ signal1.addEventListener("abort", abort);
2606
+ signal2.addEventListener("abort", abort);
2607
+ if (signal1.aborted || signal2.aborted) {
2608
+ controller.abort();
2609
+ }
2610
+ return controller.signal;
2611
+ }
2612
+ sleep(ms) {
2613
+ return new Promise((resolve) => setTimeout(resolve, ms));
2614
+ }
2615
+ };
2616
+
2617
+ // src/providers/LLMGatewayProvider.ts
2618
+ var LLMGatewayProvider = class {
2619
+ client;
2620
+ model;
2621
+ constructor(config, networkSettings) {
2622
+ this.client = new LLMGatewayClient(config, networkSettings);
2623
+ this.model = config.model;
2624
+ }
2625
+ getName() {
2626
+ return "llmgateway";
2627
+ }
2628
+ setModel(model) {
2629
+ this.model = model;
2630
+ this.client.setDefaultModel(model);
2631
+ }
2632
+ async listModels() {
2633
+ return [
2634
+ "gpt-4o",
2635
+ "gpt-4o-mini",
2636
+ "gpt-4-turbo",
2637
+ "claude-3-5-sonnet-20241022",
2638
+ "claude-3-5-haiku-20241022",
2639
+ "gemini-1.5-pro",
2640
+ "gemini-1.5-flash"
2641
+ ];
2642
+ }
2643
+ async isAvailable() {
2644
+ return true;
2645
+ }
2646
+ async complete(request) {
2647
+ return this.client.complete(request);
2648
+ }
2649
+ };
2650
+
2651
+ // src/providers/azure/tokenManager.ts
2652
+ var EXPIRY_BUFFER_MS = 5 * 60 * 1e3;
2653
+ var IMDS_ENDPOINT = "http://169.254.169.254/metadata/identity/oauth2/token";
2654
+ var COGNITIVE_SCOPE = "https://cognitiveservices.azure.com/.default";
2655
+ var AzureTokenManager = class {
2656
+ cache = null;
2657
+ async getToken(request) {
2658
+ if (request.authMethod === "api-key") {
2659
+ if (!request.apiKey) {
2660
+ throw new Error("API key is required for api-key authentication.");
2661
+ }
2662
+ return request.apiKey;
2663
+ }
2664
+ if (this.cache && !this.isTokenExpired()) {
2665
+ return this.cache.token;
2666
+ }
2667
+ if (request.authMethod === "entra-id") {
2668
+ return this.acquireEntraIdToken(request);
2669
+ }
2670
+ if (request.authMethod === "managed-identity") {
2671
+ return this.acquireManagedIdentityToken();
2672
+ }
2673
+ throw new Error(`Unsupported auth method: ${request.authMethod}`);
2674
+ }
2675
+ async getAuthHeaders(request) {
2676
+ if (request.authMethod === "api-key") {
2677
+ const key = await this.getToken(request);
2678
+ return { "api-key": key };
2679
+ }
2680
+ const token = await this.getToken(request);
2681
+ return { Authorization: `Bearer ${token}` };
2682
+ }
2683
+ async acquireEntraIdToken(request) {
2684
+ if (!request.tenantId) {
2685
+ throw new Error("tenantId is required for Entra ID authentication.");
2686
+ }
2687
+ if (!request.clientId) {
2688
+ throw new Error("clientId is required for Entra ID authentication.");
2689
+ }
2690
+ if (!request.clientSecret) {
2691
+ throw new Error("clientSecret is required for Entra ID authentication.");
2692
+ }
2693
+ const url = `https://login.microsoftonline.com/${request.tenantId}/oauth2/v2.0/token`;
2694
+ const body = new URLSearchParams({
2695
+ grant_type: "client_credentials",
2696
+ client_id: request.clientId,
2697
+ client_secret: request.clientSecret,
2698
+ scope: COGNITIVE_SCOPE
2699
+ });
2700
+ const response = await fetch(url, {
2701
+ method: "POST",
2702
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
2703
+ body: body.toString()
2704
+ });
2705
+ if (!response.ok) {
2706
+ const error = await response.json().catch(() => ({}));
2707
+ const description = error.error_description || error.error || `HTTP ${response.status}`;
2708
+ throw new Error(`Entra ID authentication failed: ${description}`);
2709
+ }
2710
+ const data = await response.json();
2711
+ this.cacheToken(data.access_token, data.expires_in);
2712
+ return data.access_token;
2713
+ }
2714
+ async acquireManagedIdentityToken() {
2715
+ const url = `${IMDS_ENDPOINT}?api-version=2018-02-01&resource=https://cognitiveservices.azure.com`;
2716
+ let response;
2717
+ try {
2718
+ response = await fetch(url, {
2719
+ headers: { Metadata: "true" }
2720
+ });
2721
+ } catch {
2722
+ throw new Error(
2723
+ "Managed Identity token acquisition failed. This auth method only works inside Azure VMs, App Service, or containers with managed identity enabled."
2724
+ );
2725
+ }
2726
+ if (!response.ok) {
2727
+ const error = await response.json().catch(() => ({}));
2728
+ throw new Error(
2729
+ `Managed Identity token error (${response.status}): ${error.error_description || error.error || "Unknown"}`
2730
+ );
2731
+ }
2732
+ const data = await response.json();
2733
+ this.cacheToken(data.access_token, data.expires_in);
2734
+ return data.access_token;
2735
+ }
2736
+ cacheToken(token, expiresInSeconds) {
2737
+ this.cache = {
2738
+ token,
2739
+ expiresAt: Date.now() + expiresInSeconds * 1e3
2740
+ };
2741
+ }
2742
+ isTokenExpired() {
2743
+ if (!this.cache) return true;
2744
+ return Date.now() >= this.cache.expiresAt - EXPIRY_BUFFER_MS;
2745
+ }
2746
+ };
2747
+
2748
+ // src/providers/AzureClient.ts
2749
+ function sanitizeMessages3(messages) {
2750
+ return messages.map((msg) => {
2751
+ const sanitized = {
2752
+ role: msg.role,
2753
+ content: msg.content
2754
+ };
2755
+ if (msg.role === "tool" && msg.tool_call_id) {
2756
+ sanitized.tool_call_id = msg.tool_call_id;
2757
+ }
2758
+ if (msg.role === "assistant" && msg.tool_calls?.length) {
2759
+ sanitized.tool_calls = msg.tool_calls;
2760
+ }
2761
+ if (msg.name) {
2762
+ sanitized.name = msg.name;
2763
+ }
2764
+ return sanitized;
2765
+ });
2766
+ }
2767
+ var DEFAULT_API_VERSION = "2024-10-21";
2768
+ var DEFAULT_MAX_RETRIES5 = 3;
2769
+ var MAX_ALLOWED_RETRIES5 = 5;
2770
+ var DEFAULT_RETRY_DELAY5 = 1e3;
2771
+ var DEFAULT_TIMEOUT5 = 3e4;
2772
+ var FRIENDLY_ERRORS = {
2773
+ 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.",
2774
+ 401: "Authentication failed. Please verify your Azure API key or credentials in ~/.autohand/config.json.",
2775
+ 402: "Payment required. Please check your Azure subscription and billing settings.",
2776
+ 403: "Access denied. Your credentials may not have permission for this Azure deployment.",
2777
+ 404: "The Azure deployment was not found. Verify your resourceName and deploymentName in ~/.autohand/config.json.",
2778
+ 429: "Rate limit exceeded. Please wait a moment and try again, or adjust your Azure deployment capacity.",
2779
+ 500: "Azure OpenAI encountered an internal error. Please try again later.",
2780
+ 502: "Azure OpenAI is temporarily unavailable. Please try again in a few moments.",
2781
+ 503: "Azure OpenAI is currently overloaded. Please try again later.",
2782
+ 504: "The request timed out. Azure OpenAI may be experiencing high load."
2783
+ };
2784
+ var AzureClient = class {
2785
+ tokenManager;
2786
+ options;
2787
+ defaultModel;
2788
+ maxRetries;
2789
+ retryDelay;
2790
+ timeout;
2791
+ constructor(options, networkSettings) {
2792
+ this.options = options;
2793
+ this.tokenManager = new AzureTokenManager();
2794
+ this.defaultModel = options.model;
2795
+ const configuredRetries = networkSettings?.maxRetries ?? DEFAULT_MAX_RETRIES5;
2796
+ this.maxRetries = Math.min(
2797
+ Math.max(0, configuredRetries),
2798
+ MAX_ALLOWED_RETRIES5
2799
+ );
2800
+ this.retryDelay = networkSettings?.retryDelay ?? DEFAULT_RETRY_DELAY5;
2801
+ this.timeout = networkSettings?.timeout ?? DEFAULT_TIMEOUT5;
2802
+ }
2803
+ setDefaultModel(model) {
2804
+ this.defaultModel = model;
2805
+ }
2806
+ /**
2807
+ * Build the full Azure OpenAI endpoint URL.
2808
+ *
2809
+ * If baseUrl is provided:
2810
+ * {baseUrl}/chat/completions?api-version={apiVersion}
2811
+ *
2812
+ * If resourceName is a full URL (starts with https://):
2813
+ * {origin}/openai/deployments/{deploymentName}/chat/completions?api-version={apiVersion}
2814
+ * Supports all Azure endpoint domains:
2815
+ * - *.openai.azure.com (Azure OpenAI)
2816
+ * - *.services.ai.azure.com (Microsoft Foundry)
2817
+ * - *.cognitiveservices.azure.com (Azure AI Services)
2818
+ *
2819
+ * Otherwise, from resourceName + deploymentName:
2820
+ * https://{resourceName}.openai.azure.com/openai/deployments/{deploymentName}/chat/completions?api-version={apiVersion}
2821
+ */
2822
+ buildEndpointUrl() {
2823
+ const apiVersion = this.options.apiVersion ?? DEFAULT_API_VERSION;
2824
+ if (this.options.baseUrl) {
2825
+ return `${this.options.baseUrl}/chat/completions?api-version=${apiVersion}`;
2826
+ }
2827
+ const { resourceName, deploymentName } = this.options;
2828
+ if (!resourceName || !deploymentName) {
2829
+ throw new Error(
2830
+ "Azure OpenAI requires either baseUrl or both resourceName and deploymentName in ~/.autohand/config.json."
2831
+ );
2832
+ }
2833
+ if (resourceName.startsWith("http://") || resourceName.startsWith("https://")) {
2834
+ try {
2835
+ const parsed = new URL(resourceName);
2836
+ return `${parsed.origin}/openai/deployments/${deploymentName}/chat/completions?api-version=${apiVersion}`;
2837
+ } catch {
2838
+ }
2839
+ }
2840
+ return `https://${resourceName}.openai.azure.com/openai/deployments/${deploymentName}/chat/completions?api-version=${apiVersion}`;
2841
+ }
2842
+ async complete(request) {
2843
+ const payload = {
2844
+ messages: sanitizeMessages3(request.messages),
2845
+ temperature: request.temperature ?? 0.2,
2846
+ max_tokens: request.maxTokens ?? 16e3,
2847
+ stream: request.stream ?? false
2848
+ };
2849
+ if (request.tools && request.tools.length > 0) {
2850
+ payload.tools = request.tools.map((tool) => ({
2851
+ type: "function",
2852
+ function: {
2853
+ name: tool.name,
2854
+ description: tool.description,
2855
+ parameters: tool.parameters ?? { type: "object", properties: {} }
2856
+ }
2857
+ }));
2858
+ if (request.toolChoice) {
2859
+ payload.tool_choice = request.toolChoice;
2860
+ }
2861
+ }
2862
+ const authHeaders = await this.tokenManager.getAuthHeaders({
2863
+ authMethod: this.options.authMethod,
2864
+ apiKey: this.options.apiKey,
2865
+ tenantId: this.options.tenantId,
2866
+ clientId: this.options.clientId,
2867
+ clientSecret: this.options.clientSecret
2868
+ });
2869
+ const headers = {
2870
+ "Content-Type": "application/json",
2871
+ ...authHeaders
2872
+ };
2873
+ const payloadJson = JSON.stringify(payload);
2874
+ const payloadSizeBytes = payloadJson.length;
2875
+ const maxPayloadSize = 5 * 1024 * 1024;
2876
+ if (payloadSizeBytes > maxPayloadSize) {
2877
+ const sizeMB = (payloadSizeBytes / (1024 * 1024)).toFixed(2);
2878
+ throw new Error(
2879
+ `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.`
2880
+ );
2881
+ }
2882
+ let lastError = null;
2883
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
2884
+ try {
2885
+ const response = await this.makeRequest(
2886
+ payload,
2887
+ headers,
2888
+ request.signal,
2889
+ payloadJson
2890
+ );
2891
+ return response;
2892
+ } catch (error) {
2893
+ lastError = error;
2894
+ if (this.isNonRetryableError(error)) {
2895
+ throw error;
2896
+ }
2897
+ if (attempt < this.maxRetries) {
2898
+ const delay = this.retryDelay * Math.pow(2, attempt);
2899
+ await this.sleep(delay);
2900
+ }
2901
+ }
2902
+ }
2903
+ throw lastError ?? new Error(
2904
+ "Failed to communicate with Azure OpenAI. Please try again."
2905
+ );
2906
+ }
2907
+ async makeRequest(payload, headers, signal, preSerializedBody) {
2908
+ let response;
2909
+ const url = this.buildEndpointUrl();
2910
+ try {
2911
+ const timeoutController = new AbortController();
2912
+ const timeoutId = setTimeout(
2913
+ () => timeoutController.abort(),
2914
+ this.timeout
2915
+ );
2916
+ const combinedSignal = signal ? this.combineSignals(signal, timeoutController.signal) : timeoutController.signal;
2917
+ try {
2918
+ response = await fetch(url, {
2919
+ method: "POST",
2920
+ headers,
2921
+ body: preSerializedBody ?? JSON.stringify(payload),
2922
+ signal: combinedSignal
2923
+ });
2924
+ } finally {
2925
+ clearTimeout(timeoutId);
2926
+ }
2927
+ } catch (error) {
2928
+ const err = error;
2929
+ if (err.name === "AbortError" && signal?.aborted) {
2930
+ throw new Error("Request cancelled.");
2931
+ }
2932
+ if (err.name === "AbortError") {
2933
+ throw new Error(
2934
+ "Request timed out. Azure OpenAI may be experiencing high load."
2935
+ );
2936
+ }
2937
+ throw new Error(
2938
+ "Unable to connect to Azure OpenAI. Please check your internet connection and Azure configuration."
2939
+ );
2940
+ }
2941
+ if (!response.ok) {
2942
+ throw new Error(await this.buildFriendlyError(response));
2943
+ }
2944
+ const json = await response.json();
2945
+ const message = json?.choices?.[0]?.message;
2946
+ const text = message?.content ?? "";
2947
+ const finishReason = json?.choices?.[0]?.finish_reason;
2948
+ let toolCalls;
2949
+ if (message?.tool_calls && Array.isArray(message.tool_calls)) {
2950
+ toolCalls = message.tool_calls.map((tc) => {
2951
+ const rawArgs = tc.function?.arguments;
2952
+ return {
2953
+ id: tc.id,
2954
+ type: "function",
2955
+ function: {
2956
+ name: tc.function?.name ?? "",
2957
+ arguments: rawArgs ?? "{}"
2958
+ }
2959
+ };
2960
+ });
2961
+ }
2962
+ const usage = normalizeLLMUsage(json?.usage);
2963
+ return {
2964
+ id: json.id ?? "autohand-azure",
2965
+ created: json.created ?? Date.now(),
2966
+ content: text,
2967
+ toolCalls,
2968
+ finishReason,
2969
+ usage,
2970
+ raw: json
2971
+ };
2972
+ }
2973
+ async buildFriendlyError(response) {
2974
+ const status = response.status;
2975
+ let errorDetail = "";
2976
+ try {
2977
+ const body = await response.json();
2978
+ errorDetail = body?.error?.message || body?.error || body?.message || "";
2979
+ if (typeof errorDetail === "object") {
2980
+ errorDetail = JSON.stringify(errorDetail);
2981
+ }
2982
+ } catch {
2983
+ try {
2984
+ errorDetail = await response.text();
2985
+ } catch {
2986
+ }
2987
+ }
2988
+ const friendlyMessage = FRIENDLY_ERRORS[status];
2989
+ if (friendlyMessage) {
2990
+ return errorDetail ? `${friendlyMessage}
2991
+ ${errorDetail}` : friendlyMessage;
2992
+ }
2993
+ if (status >= 500) {
2994
+ const base = "Azure OpenAI is temporarily unavailable. Please try again later.";
2995
+ return errorDetail ? `${base}
2996
+ (${status}: ${errorDetail})` : base;
2997
+ }
2998
+ if (status >= 400) {
2999
+ const base = "The request could not be processed by Azure OpenAI.";
3000
+ return errorDetail ? `${base} (${status}: ${errorDetail})` : `${base} (HTTP ${status}) Please try again or adjust your prompt.`;
3001
+ }
3002
+ return errorDetail ? `An unexpected Azure OpenAI error occurred: ${errorDetail}` : "An unexpected Azure OpenAI error occurred. Please try again.";
3003
+ }
3004
+ isNonRetryableError(error) {
3005
+ const message = error.message.toLowerCase();
3006
+ if (message.includes("cancelled") || message.includes("aborted")) {
3007
+ return true;
3008
+ }
3009
+ if (message.includes("authentication") || message.includes("api key")) {
3010
+ return true;
3011
+ }
3012
+ if (message.includes("payment") || message.includes("access denied")) {
3013
+ return true;
3014
+ }
3015
+ if (message.includes("not found")) {
3016
+ return true;
3017
+ }
3018
+ return false;
3019
+ }
3020
+ combineSignals(signal1, signal2) {
3021
+ const controller = new AbortController();
3022
+ const abort = () => controller.abort();
3023
+ signal1.addEventListener("abort", abort);
3024
+ signal2.addEventListener("abort", abort);
3025
+ if (signal1.aborted || signal2.aborted) {
3026
+ controller.abort();
3027
+ }
3028
+ return controller.signal;
3029
+ }
3030
+ sleep(ms) {
3031
+ return new Promise((resolve) => setTimeout(resolve, ms));
3032
+ }
3033
+ };
3034
+
3035
+ // src/providers/AzureProvider.ts
3036
+ var AzureProvider = class {
3037
+ client;
3038
+ model;
3039
+ constructor(config, networkSettings) {
3040
+ this.client = new AzureClient(
3041
+ {
3042
+ model: config.model,
3043
+ resourceName: config.resourceName,
3044
+ deploymentName: config.deploymentName,
3045
+ baseUrl: config.baseUrl,
3046
+ apiVersion: config.apiVersion,
3047
+ apiKey: config.apiKey,
3048
+ authMethod: config.authMethod ?? "api-key",
3049
+ tenantId: config.tenantId,
3050
+ clientId: config.clientId,
3051
+ clientSecret: config.clientSecret
3052
+ },
3053
+ networkSettings
3054
+ );
3055
+ this.model = config.model;
3056
+ }
3057
+ getName() {
3058
+ return "azure";
3059
+ }
3060
+ setModel(model) {
3061
+ this.model = model;
3062
+ this.client.setDefaultModel(model);
3063
+ }
3064
+ async listModels() {
3065
+ return ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "gpt-4", "gpt-3.5-turbo"];
3066
+ }
3067
+ async isAvailable() {
3068
+ return true;
3069
+ }
3070
+ async complete(request) {
3071
+ return this.client.complete(request);
3072
+ }
3073
+ };
3074
+
3075
+ // src/providers/ZaiProvider.ts
3076
+ var ZAI_DEFAULT_BASE_URL = "https://api.z.ai/api/paas/v4";
3077
+ var ZAI_MODELS = [
3078
+ "glm-4.5",
3079
+ "glm-4.5v",
3080
+ "glm-4.5-air",
3081
+ "glm-4.5-prior",
3082
+ "glm-4.5-flash",
3083
+ "glm-4.5-air-2504",
3084
+ "cogview-4.5"
3085
+ ];
3086
+ var ZaiProvider = class {
3087
+ client;
3088
+ model;
3089
+ constructor(config, networkSettings) {
3090
+ const effectiveConfig = {
3091
+ ...config,
3092
+ baseUrl: config.baseUrl ?? ZAI_DEFAULT_BASE_URL
3093
+ };
3094
+ this.client = new LLMGatewayClient(effectiveConfig, networkSettings, {
3095
+ serviceName: "Z.ai",
3096
+ credentialName: "Z.ai API key",
3097
+ accountName: "Z.ai account"
3098
+ });
3099
+ this.model = config.model;
3100
+ }
3101
+ getName() {
3102
+ return "zai";
3103
+ }
3104
+ setModel(model) {
3105
+ this.model = model;
3106
+ this.client.setDefaultModel(model);
3107
+ }
3108
+ async listModels() {
3109
+ return [...ZAI_MODELS];
3110
+ }
3111
+ async isAvailable() {
3112
+ return true;
3113
+ }
3114
+ async complete(request) {
3115
+ return this.client.complete(request);
3116
+ }
3117
+ };
3118
+
3119
+ // src/utils/gcloudAuth.ts
3120
+ import { exec } from "child_process";
3121
+ import { promisify } from "util";
3122
+ var execAsync = promisify(exec);
3123
+ var tokenCache = null;
3124
+ async function isGcloudInstalled() {
3125
+ try {
3126
+ await execAsync("gcloud --version", { timeout: 5e3 });
3127
+ return true;
3128
+ } catch {
3129
+ return false;
3130
+ }
3131
+ }
3132
+ async function getGcloudProject() {
3133
+ try {
3134
+ const { stdout } = await execAsync("gcloud config get-value project", { timeout: 5e3 });
3135
+ const project = stdout.trim();
3136
+ return project && project !== "(unset)" ? project : null;
3137
+ } catch {
3138
+ return null;
3139
+ }
3140
+ }
3141
+ async function getGcloudAccessToken() {
3142
+ if (tokenCache && Date.now() < tokenCache.expiresAt) {
3143
+ return { token: tokenCache.token };
3144
+ }
3145
+ try {
3146
+ const { stdout } = await execAsync("gcloud auth print-access-token", { timeout: 1e4 });
3147
+ const token = stdout.trim();
3148
+ if (!token || token.length < 10) {
3149
+ return { token: "", error: "Failed to get access token. You may need to run: gcloud auth login" };
3150
+ }
3151
+ tokenCache = {
3152
+ token,
3153
+ expiresAt: Date.now() + 25 * 60 * 1e3
3154
+ // 25 minutes
3155
+ };
3156
+ return { token };
3157
+ } catch (error) {
3158
+ const errorMessage = error instanceof Error ? error.message : String(error);
3159
+ if (errorMessage.includes("not found") || errorMessage.includes("command not found")) {
3160
+ return {
3161
+ token: "",
3162
+ error: "gcloud CLI not found. Install it from: https://cloud.google.com/sdk/docs/install"
3163
+ };
3164
+ }
3165
+ if (errorMessage.includes("Could not determine account")) {
3166
+ return {
3167
+ token: "",
3168
+ error: "Not logged in. Run: gcloud auth login"
3169
+ };
3170
+ }
3171
+ return {
3172
+ token: "",
3173
+ error: `Failed to get access token: ${errorMessage}`
3174
+ };
3175
+ }
3176
+ }
3177
+ function clearGcloudTokenCache() {
3178
+ tokenCache = null;
3179
+ }
3180
+ async function getGcloudAccount() {
3181
+ try {
3182
+ const { stdout } = await execAsync("gcloud auth list --format=value(account)", { timeout: 5e3 });
3183
+ const account = stdout.trim().split("\n")[0];
3184
+ return account || null;
3185
+ } catch {
3186
+ return null;
3187
+ }
3188
+ }
3189
+
3190
+ // src/providers/VertexAIProvider.ts
3191
+ function sanitizeMessages4(messages) {
3192
+ return messages.map((msg) => {
3193
+ const sanitized = {
3194
+ role: msg.role,
3195
+ content: msg.content
3196
+ };
3197
+ if (msg.role === "tool" && msg.tool_call_id) {
3198
+ sanitized.tool_call_id = msg.tool_call_id;
3199
+ }
3200
+ if (msg.role === "assistant" && msg.tool_calls?.length) {
3201
+ sanitized.tool_calls = msg.tool_calls;
3202
+ }
3203
+ if (msg.name) {
3204
+ sanitized.name = msg.name;
3205
+ }
3206
+ return sanitized;
3207
+ });
3208
+ }
3209
+ var DEFAULT_ENDPOINT = "aiplatform.googleapis.com";
3210
+ var DEFAULT_REGION = "global";
3211
+ var DEFAULT_MAX_RETRIES6 = 3;
3212
+ var MAX_ALLOWED_RETRIES6 = 5;
3213
+ var DEFAULT_RETRY_DELAY6 = 1e3;
3214
+ var DEFAULT_TIMEOUT6 = 3e4;
3215
+ var VERTEX_AI_FRIENDLY_MESSAGES = {
3216
+ 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`.",
3217
+ payment_required: "Payment required. Please check billing for the Google Cloud project configured for Vertex AI.",
3218
+ access_denied: "Access denied. Your Google Cloud credentials may not have permission to use Vertex AI or this model.",
3219
+ server_error: "The Google Cloud Vertex AI service encountered an error. Please try again later.",
3220
+ network_error: "Unable to connect to Google Cloud Vertex AI. Please check your internet connection and Vertex AI endpoint.",
3221
+ timeout: "The request timed out. The Google Cloud Vertex AI service may be experiencing high load."
3222
+ };
3223
+ function withVertexAIMessage(error) {
3224
+ const friendlyMessage = VERTEX_AI_FRIENDLY_MESSAGES[error.code];
3225
+ if (!friendlyMessage) {
3226
+ return error;
3227
+ }
3228
+ return new ApiError(
3229
+ error.rawDetail ? `${friendlyMessage}
3230
+ ${error.rawDetail}` : friendlyMessage,
3231
+ error.code,
3232
+ error.httpStatus,
3233
+ error.retryable,
3234
+ error.retryAfterMs,
3235
+ error.rawDetail
3236
+ );
3237
+ }
3238
+ var ANTHROPIC_MODELS = [
3239
+ "claude-3-opus",
3240
+ "claude-3-sonnet",
3241
+ "claude-3-haiku",
3242
+ "claude-3-5-sonnet",
3243
+ "claude-3-5-haiku",
3244
+ "claude-3.5-sonnet",
3245
+ "claude-3.5-haiku",
3246
+ "claude-4",
3247
+ "claude-sonnet-4",
3248
+ "claude-opus-4",
3249
+ "claude-opus-4-7",
3250
+ "claude-opus-4-6",
3251
+ "claude-opus-4.7",
3252
+ "claude-opus-4.6"
3253
+ ];
3254
+ var VERTEX_AI_CODING_MODELS = [
3255
+ // Anthropic Claude (coding-optimized)
3256
+ "anthropic/claude-opus-4-7",
3257
+ "anthropic/claude-opus-4-6",
3258
+ "anthropic/claude-opus-4",
3259
+ "anthropic/claude-sonnet-4",
3260
+ "anthropic/claude-3-5-sonnet",
3261
+ "anthropic/claude-3-opus",
3262
+ "anthropic/claude-3-haiku",
3263
+ // Google Gemini (coding-capable)
3264
+ "google/gemini-3.1-pro",
3265
+ "google/gemini-3.1-flash",
3266
+ "google/gemini-1.5-pro",
3267
+ "google/gemini-1.5-flash",
3268
+ "google/gemini-1.0-pro",
3269
+ // Z.ai models
3270
+ "zai-org/glm-5-maas"
3271
+ ];
3272
+ function isAnthropicModel(model) {
3273
+ const lowerModel = model.toLowerCase();
3274
+ return ANTHROPIC_MODELS.some((m) => lowerModel.includes(m.toLowerCase()));
3275
+ }
3276
+ function extractAnthropicModelId(model) {
3277
+ const lowerModel = model.toLowerCase();
3278
+ if (lowerModel.startsWith("anthropic/")) {
3279
+ return model.substring("anthropic/".length);
3280
+ }
3281
+ return model;
3282
+ }
3283
+ var VertexAIProvider = class {
3284
+ authToken;
3285
+ // Changed from readonly to allow refresh
3286
+ endpoint;
3287
+ region;
3288
+ projectId;
3289
+ baseUrl;
3290
+ defaultModel;
3291
+ maxRetries;
3292
+ retryDelay;
3293
+ timeout;
3294
+ useGcloudRefresh;
3295
+ // Auto-refresh via gcloud CLI
3296
+ constructor(settings, networkSettings) {
3297
+ this.authToken = settings.authToken;
3298
+ this.endpoint = settings.endpoint ?? DEFAULT_ENDPOINT;
3299
+ this.region = settings.region ?? DEFAULT_REGION;
3300
+ this.projectId = settings.projectId;
3301
+ this.defaultModel = settings.model;
3302
+ this.useGcloudRefresh = this.authToken.startsWith("ya29.") && this.authToken.length > 100;
3303
+ this.baseUrl = `https://${this.endpoint}/v1/projects/${this.projectId}/locations/${this.region}/endpoints/openapi`;
3304
+ const configuredRetries = networkSettings?.maxRetries ?? DEFAULT_MAX_RETRIES6;
3305
+ this.maxRetries = Math.min(
3306
+ Math.max(0, configuredRetries),
3307
+ MAX_ALLOWED_RETRIES6
3308
+ );
3309
+ this.retryDelay = networkSettings?.retryDelay ?? DEFAULT_RETRY_DELAY6;
3310
+ this.timeout = networkSettings?.timeout ?? DEFAULT_TIMEOUT6;
3311
+ }
3312
+ getName() {
3313
+ return "vertexai";
3314
+ }
3315
+ setModel(model) {
3316
+ this.defaultModel = model;
3317
+ }
3318
+ async listModels() {
3319
+ return [...VERTEX_AI_CODING_MODELS];
3320
+ }
3321
+ async isAvailable() {
3322
+ try {
3323
+ const token = await this.getValidToken();
3324
+ const response = await fetch(`${this.baseUrl}/models`, {
3325
+ method: "GET",
3326
+ headers: {
3327
+ Authorization: `Bearer ${token}`
3328
+ },
3329
+ signal: AbortSignal.timeout(5e3)
3330
+ });
3331
+ return response.ok;
3332
+ } catch {
3333
+ return false;
3334
+ }
3335
+ }
3336
+ /**
3337
+ * Get a valid auth token, refreshing from gcloud if needed
3338
+ */
3339
+ async getValidToken() {
3340
+ if (this.useGcloudRefresh) {
3341
+ const result = await getGcloudAccessToken();
3342
+ if (result.token) {
3343
+ this.authToken = result.token;
3344
+ return this.authToken;
3345
+ }
3346
+ }
3347
+ return this.authToken;
3348
+ }
3349
+ /**
3350
+ * Refresh the token after an auth error
3351
+ */
3352
+ async refreshToken() {
3353
+ if (!this.useGcloudRefresh) {
3354
+ return false;
3355
+ }
3356
+ clearGcloudTokenCache();
3357
+ const result = await getGcloudAccessToken();
3358
+ if (result.token) {
3359
+ this.authToken = result.token;
3360
+ return true;
3361
+ }
3362
+ return false;
3363
+ }
3364
+ async complete(request) {
3365
+ const model = request.model ?? this.defaultModel;
3366
+ const isAnthropic = isAnthropicModel(model);
3367
+ let payload;
3368
+ let url;
3369
+ if (isAnthropic) {
3370
+ const modelId = extractAnthropicModelId(model);
3371
+ url = `https://${this.endpoint}/v1/projects/${this.projectId}/locations/${this.region}/publishers/anthropic/models/${modelId}:streamRawPredict`;
3372
+ payload = {
3373
+ anthropic_version: "vertex-2023-10-16",
3374
+ messages: sanitizeMessages4(request.messages),
3375
+ max_tokens: request.maxTokens ?? 16e3,
3376
+ stream: request.stream ?? false
3377
+ };
3378
+ if (request.temperature !== void 0) {
3379
+ payload.temperature = request.temperature;
3380
+ }
3381
+ if (request.tools && request.tools.length > 0) {
3382
+ payload.tools = request.tools.map((tool) => ({
3383
+ name: tool.name,
3384
+ description: tool.description,
3385
+ input_schema: tool.parameters ?? { type: "object", properties: {} }
3386
+ }));
3387
+ }
3388
+ } else {
3389
+ payload = {
3390
+ model,
3391
+ messages: sanitizeMessages4(request.messages),
3392
+ temperature: request.temperature ?? 0.2,
3393
+ max_tokens: request.maxTokens ?? 16e3,
3394
+ stream: request.stream ?? false
3395
+ };
3396
+ if (request.tools && request.tools.length > 0) {
3397
+ payload.tools = request.tools.map((tool) => ({
3398
+ type: "function",
3399
+ function: {
3400
+ name: tool.name,
3401
+ description: tool.description,
3402
+ parameters: tool.parameters ?? { type: "object", properties: {} }
3403
+ }
3404
+ }));
3405
+ if (request.toolChoice) {
3406
+ payload.tool_choice = request.toolChoice;
3407
+ }
3408
+ }
3409
+ url = `${this.baseUrl}/chat/completions`;
3410
+ }
3411
+ const headers = {
3412
+ "Content-Type": "application/json",
3413
+ Authorization: `Bearer ${await this.getValidToken()}`
3414
+ };
3415
+ const payloadJson = JSON.stringify(payload);
3416
+ const payloadSizeBytes = payloadJson.length;
3417
+ const maxPayloadSize = 5 * 1024 * 1024;
3418
+ if (payloadSizeBytes > maxPayloadSize) {
3419
+ const sizeMB = (payloadSizeBytes / (1024 * 1024)).toFixed(2);
3420
+ throw new ApiError(
3421
+ `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.`,
3422
+ "context_overflow",
3423
+ 400,
3424
+ false
3425
+ );
3426
+ }
3427
+ let lastError = null;
3428
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
3429
+ try {
3430
+ const response = await this.makeRequest(
3431
+ url,
3432
+ payload,
3433
+ headers,
3434
+ request.signal,
3435
+ payloadJson,
3436
+ isAnthropic
3437
+ );
3438
+ return response;
3439
+ } catch (error) {
3440
+ lastError = error;
3441
+ if (this.isAuthError(error) && await this.refreshToken()) {
3442
+ headers.Authorization = `Bearer ${this.authToken}`;
3443
+ continue;
3444
+ }
3445
+ if (this.isNonRetryableError(error)) {
3446
+ throw error;
3447
+ }
3448
+ if (attempt < this.maxRetries) {
3449
+ const delay = this.retryDelay * Math.pow(2, attempt);
3450
+ await this.sleep(delay);
3451
+ }
3452
+ }
3453
+ }
3454
+ throw lastError ?? new Error("Failed to communicate with Vertex AI. Please try again.");
3455
+ }
3456
+ async makeRequest(url, payload, headers, signal, preSerializedBody, isAnthropic = false) {
3457
+ let response;
3458
+ try {
3459
+ const timeoutController = new AbortController();
3460
+ const timeoutId = setTimeout(
3461
+ () => timeoutController.abort(),
3462
+ this.timeout
3463
+ );
3464
+ const combinedSignal = signal ? this.combineSignals(signal, timeoutController.signal) : timeoutController.signal;
3465
+ try {
3466
+ response = await fetch(url, {
3467
+ method: "POST",
3468
+ headers,
3469
+ body: preSerializedBody ?? JSON.stringify(payload),
3470
+ signal: combinedSignal
3471
+ });
3472
+ } finally {
3473
+ clearTimeout(timeoutId);
3474
+ }
3475
+ } catch (error) {
3476
+ const err = error;
3477
+ if (err.name === "AbortError" && signal?.aborted) {
3478
+ throw new ApiError("Request cancelled.", "cancelled", 0, false);
3479
+ }
3480
+ if (err.name === "AbortError") {
3481
+ throw new ApiError(
3482
+ "Request timed out. The Vertex AI service may be experiencing high load.",
3483
+ "timeout",
3484
+ 504,
3485
+ true
3486
+ );
3487
+ }
3488
+ const classified = classifyApiError(0, err.message);
3489
+ throw withVertexAIMessage(classified);
3490
+ }
3491
+ if (!response.ok) {
3492
+ throw await this.buildFriendlyError(response);
3493
+ }
3494
+ const json = await response.json();
3495
+ if (isAnthropic) {
3496
+ return this.parseAnthropicResponse(json);
3497
+ }
3498
+ const message = json?.choices?.[0]?.message;
3499
+ const text = message?.content ?? "";
3500
+ const finishReason = json?.choices?.[0]?.finish_reason;
3501
+ let toolCalls;
3502
+ if (message?.tool_calls && Array.isArray(message.tool_calls)) {
3503
+ toolCalls = message.tool_calls.map((tc) => {
3504
+ const rawArgs = tc.function?.arguments;
3505
+ return {
3506
+ id: tc.id,
3507
+ type: "function",
3508
+ function: {
3509
+ name: tc.function?.name ?? "",
3510
+ arguments: rawArgs ?? "{}"
3511
+ }
3512
+ };
3513
+ });
3514
+ }
3515
+ const usage = normalizeLLMUsage(json?.usage);
3516
+ return {
3517
+ id: json.id ?? "vertexai-response",
3518
+ created: json.created ?? Date.now(),
3519
+ content: text,
3520
+ toolCalls,
3521
+ finishReason,
3522
+ usage,
3523
+ raw: json
3524
+ };
3525
+ }
3526
+ /**
3527
+ * Parse Anthropic API response format
3528
+ */
3529
+ parseAnthropicResponse(json) {
3530
+ const contentBlocks = json?.content ?? [];
3531
+ const textBlock = contentBlocks.find((b) => b.type === "text");
3532
+ const text = textBlock?.text ?? "";
3533
+ let toolCalls;
3534
+ const toolUseBlocks = contentBlocks.filter((b) => b.type === "tool_use");
3535
+ if (toolUseBlocks.length > 0) {
3536
+ toolCalls = toolUseBlocks.map((block) => ({
3537
+ id: block.id,
3538
+ type: "function",
3539
+ function: {
3540
+ name: block.name ?? "",
3541
+ arguments: JSON.stringify(block.input ?? {})
3542
+ }
3543
+ }));
3544
+ }
3545
+ const usage = normalizeLLMUsage(json?.usage);
3546
+ const stopReason = json?.stop_reason;
3547
+ let finishReason;
3548
+ if (stopReason === "end_turn" || stopReason === "stop_sequence") {
3549
+ finishReason = "stop";
3550
+ } else if (stopReason === "tool_use") {
3551
+ finishReason = "tool_calls";
3552
+ } else if (stopReason === "max_tokens") {
3553
+ finishReason = "length";
3554
+ }
3555
+ return {
3556
+ id: json.id ?? "vertexai-anthropic-response",
3557
+ created: Date.now(),
3558
+ content: text,
3559
+ toolCalls,
3560
+ finishReason,
3561
+ usage,
3562
+ raw: json
3563
+ };
3564
+ }
3565
+ async buildFriendlyError(response) {
3566
+ const status = response.status;
3567
+ let errorDetail = "";
3568
+ try {
3569
+ const body = await response.json();
3570
+ errorDetail = body?.error?.message || body?.error || body?.message || "";
3571
+ if (typeof errorDetail === "object") {
3572
+ errorDetail = JSON.stringify(errorDetail);
3573
+ }
3574
+ } catch {
3575
+ try {
3576
+ errorDetail = await response.text();
3577
+ } catch {
3578
+ }
3579
+ }
3580
+ const classified = classifyApiError(status, errorDetail, response.headers);
3581
+ return withVertexAIMessage(classified);
3582
+ }
3583
+ isNonRetryableError(error) {
3584
+ if (error instanceof ApiError) {
3585
+ return !error.retryable;
3586
+ }
3587
+ const message = error.message.toLowerCase();
3588
+ if (message.includes("cancelled") || message.includes("aborted")) {
3589
+ return true;
3590
+ }
3591
+ if (message.includes("authentication") || message.includes("auth token")) {
3592
+ return true;
3593
+ }
3594
+ if (message.includes("payment") || message.includes("access denied")) {
3595
+ return true;
3596
+ }
3597
+ if (message.includes("not found")) {
3598
+ return true;
3599
+ }
3600
+ return false;
3601
+ }
3602
+ /**
3603
+ * Check if error is an authentication error that can be fixed by refreshing the token
3604
+ */
3605
+ isAuthError(error) {
3606
+ if (error instanceof ApiError) {
3607
+ return error.code === "auth_failed";
3608
+ }
3609
+ const message = error.message.toLowerCase();
3610
+ if (message.includes("401") || message.includes("unauthorized") || message.includes("authentication") || message.includes("auth token") || message.includes("invalid token") || message.includes("token expired")) {
3611
+ return true;
3612
+ }
3613
+ return false;
3614
+ }
3615
+ combineSignals(signal1, signal2) {
3616
+ const controller = new AbortController();
3617
+ const abort = () => controller.abort();
3618
+ signal1.addEventListener("abort", abort);
3619
+ signal2.addEventListener("abort", abort);
3620
+ if (signal1.aborted || signal2.aborted) {
3621
+ controller.abort();
3622
+ }
3623
+ return controller.signal;
3624
+ }
3625
+ sleep(ms) {
3626
+ return new Promise((resolve) => setTimeout(resolve, ms));
3627
+ }
3628
+ };
3629
+
3630
+ // src/providers/XAIProvider.ts
3631
+ var XAI_MODELS = [
3632
+ "grok-4.20-reasoning",
3633
+ "grok-4-1-fast-reasoning-latest",
3634
+ "grok-4.20-0309-reasoning"
3635
+ ];
3636
+ var XAI_DEFAULT_MODEL = "grok-4.20-reasoning";
3637
+ var XAI_API_BASE_URL = "https://api.x.ai/v1";
3638
+ var XAI_FRIENDLY_MESSAGES = {
3639
+ auth_failed: "Authentication failed. Please verify your xAI API key in ~/.autohand/config.json.",
3640
+ payment_required: "Payment required. Please check your xAI account balance or billing settings.",
3641
+ access_denied: "Access denied. Your xAI API key may not have permission for this model.",
3642
+ server_error: "The xAI service encountered an error. Please try again later.",
3643
+ network_error: "Unable to connect to xAI. Please check your internet connection and xAI API configuration.",
3644
+ timeout: "The request timed out. The xAI service may be experiencing high load."
3645
+ };
3646
+ function withXAIMessage(error) {
3647
+ const friendlyMessage = XAI_FRIENDLY_MESSAGES[error.code];
3648
+ if (!friendlyMessage) {
3649
+ return error;
3650
+ }
3651
+ return new ApiError(
3652
+ error.rawDetail ? `${friendlyMessage}
3653
+ ${error.rawDetail}` : friendlyMessage,
3654
+ error.code,
3655
+ error.httpStatus,
3656
+ error.retryable,
3657
+ error.retryAfterMs,
3658
+ error.rawDetail
3659
+ );
3660
+ }
3661
+ var XAIProvider = class {
3662
+ baseUrl;
3663
+ apiKey;
3664
+ model;
3665
+ constructor(config) {
3666
+ this.apiKey = config.apiKey || "";
3667
+ this.baseUrl = (config.baseUrl || XAI_API_BASE_URL).replace(/\/$/, "");
3668
+ this.model = config.model || XAI_DEFAULT_MODEL;
3669
+ }
3670
+ getName() {
3671
+ return "xai";
3672
+ }
3673
+ setModel(model) {
3674
+ this.model = model;
3675
+ }
3676
+ /**
3677
+ * List available models from xAI's REST API (GET /v1/language-models),
3678
+ * falling back to the canonical static list.
3679
+ */
3680
+ async listModels() {
3681
+ try {
3682
+ const headers = await this.buildAuthHeaders();
3683
+ const response = await fetch(`${this.baseUrl}/language-models`, { headers });
3684
+ if (response.ok) {
3685
+ const data = await response.json();
3686
+ if (data?.models && Array.isArray(data.models)) {
3687
+ const ids = /* @__PURE__ */ new Set();
3688
+ for (const m of data.models) {
3689
+ if (m.id) ids.add(m.id);
3690
+ if (Array.isArray(m.aliases)) {
3691
+ for (const a of m.aliases) ids.add(a);
3692
+ }
3693
+ }
3694
+ if (ids.size > 0) {
3695
+ return [...ids];
3696
+ }
3697
+ }
3698
+ }
3699
+ } catch {
3700
+ }
3701
+ return [...XAI_MODELS];
3702
+ }
3703
+ async isAvailable() {
3704
+ if (!this.apiKey) return false;
3705
+ try {
3706
+ const headers = await this.buildAuthHeaders();
3707
+ const response = await fetch(`${this.baseUrl}/models`, { headers });
3708
+ return response.ok;
3709
+ } catch {
3710
+ return false;
3711
+ }
3712
+ }
3713
+ /**
3714
+ * Complete a chat request using the xAI Responses API.
3715
+ *
3716
+ * xAI supports server-side tools (`web_search`, `x_search`, `code_execution`)
3717
+ * in addition to standard function calling. This implementation detects
3718
+ * tool types and emits the appropriate xAI tool format.
3719
+ */
3720
+ async complete(request) {
3721
+ const body = {
3722
+ model: request.model || this.model,
3723
+ stream: true,
3724
+ tool_choice: "auto",
3725
+ input: this.toXAIInputItems(request.messages)
3726
+ };
3727
+ const tools = this.mapToXAITools(request.tools);
3728
+ if (tools.length > 0) {
3729
+ body.tools = tools;
3730
+ }
3731
+ const headers = await this.buildAuthHeaders();
3732
+ let response;
3733
+ try {
3734
+ response = await fetch(`${this.baseUrl}/responses`, {
3735
+ method: "POST",
3736
+ headers: {
3737
+ "Content-Type": "application/json",
3738
+ ...headers
3739
+ },
3740
+ body: JSON.stringify(body),
3741
+ signal: request.signal
3742
+ });
3743
+ } catch (error) {
3744
+ const err = error;
3745
+ if (err.name === "AbortError" && request.signal?.aborted) {
3746
+ throw new ApiError("Request cancelled.", "cancelled", 0, false);
3747
+ }
3748
+ if (err.name === "AbortError") {
3749
+ throw new ApiError(
3750
+ "The request timed out. The xAI service may be experiencing high load.",
3751
+ "timeout",
3752
+ 0,
3753
+ true
3754
+ );
3755
+ }
3756
+ throw new ApiError(
3757
+ `Unable to connect to ${this.baseUrl}. Please check the URL and your xAI API key.`,
3758
+ "network_error",
3759
+ 0,
3760
+ true
3761
+ );
3762
+ }
3763
+ if (!response.ok) {
3764
+ throw await this.buildApiError(response);
3765
+ }
3766
+ const data = await this.parseXAIStream(response);
3767
+ const toolCalls = this.extractXAIToolCalls(data.output);
3768
+ const content = this.extractXAIContent(data);
3769
+ const usage = this.mapXAIUsage(data.usage);
3770
+ return {
3771
+ id: data.id,
3772
+ created: data.created_at ?? Math.floor(Date.now() / 1e3),
3773
+ content,
3774
+ toolCalls,
3775
+ finishReason: toolCalls.length > 0 ? "tool_calls" : data.incomplete_details?.reason === "max_output_tokens" ? "length" : "stop",
3776
+ usage,
3777
+ raw: data
3778
+ };
3779
+ }
3780
+ // ------------------------------------------------------------------
3781
+ // Helpers
3782
+ // ------------------------------------------------------------------
3783
+ async buildAuthHeaders() {
3784
+ return {
3785
+ Authorization: `Bearer ${this.apiKey}`
3786
+ };
3787
+ }
3788
+ // Map the generic LLMRequest.tools (FunctionDefinition[]) to xAI tool payloads.
3789
+ // xAI built-in tools use a simple `{ type: "web_search" }` form.
3790
+ // If the user supplies a custom FunctionDefinition whose name matches a known
3791
+ // server-side tool we emit the server-side variant; everything else becomes a
3792
+ // standard `function` tool.
3793
+ mapToXAITools(tools) {
3794
+ if (!tools?.length) return [];
3795
+ return tools.map((tool) => {
3796
+ const name = tool.name.toLowerCase();
3797
+ if (name === "web_search" || name === "x_search" || name === "code_execution" || name === "code_interpreter") {
3798
+ return { type: name === "code_interpreter" ? "code_execution" : name };
3799
+ }
3800
+ return {
3801
+ type: "function",
3802
+ function: {
3803
+ name: tool.name,
3804
+ description: tool.description,
3805
+ parameters: tool.parameters
3806
+ }
3807
+ };
3808
+ });
3809
+ }
3810
+ // Convert the internal message format to xAI Responses API input items.
3811
+ toXAIInputItems(messages) {
3812
+ const items = [];
3813
+ for (const msg of messages) {
3814
+ if (msg.role === "system") {
3815
+ continue;
3816
+ }
3817
+ if (msg.role === "tool" && msg.tool_call_id && msg.content) {
3818
+ items.push({
3819
+ type: "function_call_output",
3820
+ call_id: msg.tool_call_id,
3821
+ output: msg.content
3822
+ });
3823
+ continue;
3824
+ }
3825
+ if (msg.role === "assistant" && msg.tool_calls?.length) {
3826
+ items.push({
3827
+ type: "message",
3828
+ role: "assistant",
3829
+ content: []
3830
+ // will have function_calls appended
3831
+ });
3832
+ for (const tc of msg.tool_calls) {
3833
+ items.push({
3834
+ type: "function_call",
3835
+ call_id: tc.id,
3836
+ name: tc.function.name,
3837
+ arguments: tc.function.arguments
3838
+ });
3839
+ }
3840
+ continue;
3841
+ }
3842
+ if (msg.content && typeof msg.content === "string" && msg.content.trim()) {
3843
+ items.push({
3844
+ type: "message",
3845
+ role: msg.role === "user" ? "user" : "user",
3846
+ content: [{ type: "input_text", text: msg.content }]
3847
+ });
3848
+ }
3849
+ }
3850
+ return items;
3851
+ }
3852
+ extractXAIToolCalls(output) {
3853
+ if (!Array.isArray(output)) return [];
3854
+ return output.filter((entry) => entry?.type === "function_call").map((toolCall, index) => ({
3855
+ id: toolCall.call_id ?? `call_${index + 1}`,
3856
+ type: "function",
3857
+ function: {
3858
+ name: toolCall.name,
3859
+ arguments: toolCall.arguments
3860
+ }
3861
+ }));
3862
+ }
3863
+ extractXAIContent(data) {
3864
+ if (typeof data.output_text === "string" && data.output_text.trim()) {
3865
+ return data.output_text;
3866
+ }
3867
+ if (!Array.isArray(data.output)) return "";
3868
+ const parts = [];
3869
+ for (const item of data.output) {
3870
+ if (item?.type !== "message" || !Array.isArray(item.content)) continue;
3871
+ for (const ci of item.content) {
3872
+ if (ci?.type === "output_text" && typeof ci.text === "string") {
3873
+ parts.push(ci.text);
3874
+ }
3875
+ }
3876
+ }
3877
+ return parts.join("\n").trim();
3878
+ }
3879
+ mapXAIUsage(usage) {
3880
+ return normalizeLLMUsage(usage);
3881
+ }
3882
+ async parseXAIStream(response) {
3883
+ const text = await response.text();
3884
+ let currentEvent = "";
3885
+ let completedData = null;
3886
+ for (const line of text.split("\n")) {
3887
+ if (line.startsWith("event: ")) {
3888
+ currentEvent = line.slice(7).trim();
3889
+ continue;
3890
+ }
3891
+ if (line.startsWith("data: ") && currentEvent === "response.completed") {
3892
+ completedData = JSON.parse(line.slice(6));
3893
+ break;
3894
+ }
3895
+ }
3896
+ if (!completedData) {
3897
+ throw new ApiError(
3898
+ "No response.completed event found in stream. The API response may be malformed.",
3899
+ "invalid_request",
3900
+ 0,
3901
+ false
3902
+ );
3903
+ }
3904
+ return completedData;
3905
+ }
3906
+ async buildApiError(response) {
3907
+ let errorDetail = "";
3908
+ try {
3909
+ const body = await response.json();
3910
+ const errObj = body?.error;
3911
+ errorDetail = errObj?.message ?? body?.detail ?? body?.error ?? "";
3912
+ if (typeof errorDetail === "object") {
3913
+ errorDetail = JSON.stringify(errorDetail);
3914
+ }
3915
+ } catch {
3916
+ try {
3917
+ errorDetail = await response.text();
3918
+ } catch {
3919
+ }
3920
+ }
3921
+ return withXAIMessage(classifyApiError(response.status, errorDetail, response.headers));
3922
+ }
3923
+ };
3924
+
3925
+ // src/providers/CerebrasClient.ts
3926
+ function sanitizeMessages5(messages) {
3927
+ return messages.map((msg) => {
3928
+ const sanitized = {
3929
+ role: msg.role,
3930
+ content: msg.content
3931
+ };
3932
+ if (msg.role === "tool" && msg.tool_call_id) {
3933
+ sanitized.tool_call_id = msg.tool_call_id;
3934
+ }
3935
+ if (msg.role === "assistant" && msg.tool_calls?.length) {
3936
+ sanitized.tool_calls = msg.tool_calls;
3937
+ }
3938
+ if (msg.name) {
3939
+ sanitized.name = msg.name;
3940
+ }
3941
+ return sanitized;
3942
+ });
3943
+ }
3944
+ var DEFAULT_BASE_URL3 = "https://api.cerebras.ai/v1";
3945
+ var DEFAULT_MAX_RETRIES7 = 3;
3946
+ var MAX_ALLOWED_RETRIES7 = 5;
3947
+ var DEFAULT_RETRY_DELAY7 = 1e3;
3948
+ var DEFAULT_TIMEOUT7 = 3e4;
3949
+ var FRIENDLY_ERRORS2 = {
3950
+ 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.",
3951
+ 401: "Authentication failed. Please verify your Cerebras API key in ~/.autohand/config.json.",
3952
+ 402: "Payment required. Please check your Cerebras account balance or billing settings.",
3953
+ 403: "Access denied. Your API key may not have permission for this model.",
3954
+ 404: "The requested model was not found. Use /model to select a different one.",
3955
+ 429: "Rate limit exceeded. Please wait a moment and try again, or choose a different model.",
3956
+ 500: "The Cerebras service encountered an internal error. Please try again later.",
3957
+ 502: "The Cerebras service is temporarily unavailable. Please try again in a few moments.",
3958
+ 503: "The Cerebras service is currently overloaded. Please try again later.",
3959
+ 504: "The request timed out. The service may be experiencing high load."
3960
+ };
3961
+ var CerebrasClient = class {
3962
+ apiKey;
3963
+ baseUrl;
3964
+ defaultModel;
3965
+ maxRetries;
3966
+ retryDelay;
3967
+ timeout;
3968
+ constructor(settings, networkSettings) {
3969
+ this.apiKey = settings.apiKey;
3970
+ this.baseUrl = settings.baseUrl ?? DEFAULT_BASE_URL3;
3971
+ this.defaultModel = settings.model;
3972
+ this.maxRetries = Math.min(
3973
+ networkSettings?.maxRetries ?? DEFAULT_MAX_RETRIES7,
3974
+ MAX_ALLOWED_RETRIES7
3975
+ );
3976
+ this.retryDelay = DEFAULT_RETRY_DELAY7;
3977
+ this.timeout = networkSettings?.timeout ?? DEFAULT_TIMEOUT7;
3978
+ }
3979
+ setDefaultModel(model) {
3980
+ this.defaultModel = model;
3981
+ }
3982
+ sleep(ms) {
3983
+ return new Promise((resolve) => setTimeout(resolve, ms));
3984
+ }
3985
+ async complete(request) {
3986
+ const payload = {
3987
+ model: request.model ?? this.defaultModel,
3988
+ messages: sanitizeMessages5(request.messages),
3989
+ temperature: request.temperature ?? 0.7,
3990
+ max_tokens: request.maxTokens ?? 2e4,
3991
+ stream: request.stream ?? false
3992
+ };
3993
+ if (request.tools && request.tools.length > 0) {
3994
+ payload.tools = request.tools.map((tool) => ({
3995
+ type: "function",
3996
+ function: {
3997
+ name: tool.name,
3998
+ description: tool.description,
3999
+ parameters: tool.parameters ?? { type: "object", properties: {} }
4000
+ }
4001
+ }));
4002
+ if (request.toolChoice) {
4003
+ payload.tool_choice = request.toolChoice;
4004
+ }
4005
+ }
4006
+ const headers = {
4007
+ "Content-Type": "application/json",
4008
+ "x-source": "Autohand Code CLI"
4009
+ };
4010
+ if (this.apiKey) {
4011
+ headers.Authorization = `Bearer ${this.apiKey}`;
4012
+ }
4013
+ const payloadJson = JSON.stringify(payload);
4014
+ const payloadSizeBytes = payloadJson.length;
4015
+ const maxPayloadSize = 5 * 1024 * 1024;
4016
+ if (payloadSizeBytes > maxPayloadSize) {
4017
+ const sizeMB = (payloadSizeBytes / (1024 * 1024)).toFixed(2);
4018
+ throw new Error(
4019
+ `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.`
4020
+ );
4021
+ }
4022
+ let lastError = null;
4023
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
4024
+ try {
4025
+ const response = await this.makeRequest(
4026
+ payload,
4027
+ headers,
4028
+ request.signal,
4029
+ payloadJson
4030
+ );
4031
+ return response;
4032
+ } catch (error) {
4033
+ lastError = error;
4034
+ if (this.isNonRetryableError(error)) {
4035
+ throw error;
4036
+ }
4037
+ if (attempt < this.maxRetries) {
4038
+ const delay = this.retryDelay * Math.pow(2, attempt);
4039
+ await this.sleep(delay);
4040
+ }
4041
+ }
4042
+ }
4043
+ throw lastError ?? new Error("Failed to communicate with Cerebras API. Please try again.");
4044
+ }
4045
+ async makeRequest(payload, headers, signal, payloadJson) {
4046
+ const timeoutController = new AbortController();
4047
+ const timerId = setTimeout(() => timeoutController.abort(), this.timeout);
4048
+ const combinedSignal = signal ? this.combineSignals(signal, timeoutController.signal) : timeoutController.signal;
4049
+ let response;
4050
+ try {
4051
+ response = await fetch(`${this.baseUrl}/chat/completions`, {
4052
+ method: "POST",
4053
+ headers,
4054
+ body: payloadJson,
4055
+ signal: combinedSignal
4056
+ });
4057
+ } finally {
4058
+ clearTimeout(timerId);
4059
+ }
4060
+ if (!response.ok) {
4061
+ throw await this.buildApiError(response, payload);
4062
+ }
4063
+ if (payload.stream) {
4064
+ return this.handleStreamingResponse(response);
4065
+ }
4066
+ const data = await response.json();
4067
+ const choice = data.choices?.[0];
4068
+ let toolCalls;
4069
+ if (choice?.message?.tool_calls?.length) {
4070
+ toolCalls = choice.message.tool_calls.map((tc) => ({
4071
+ id: tc.id,
4072
+ type: tc.type,
4073
+ function: {
4074
+ name: tc.function.name,
4075
+ arguments: tc.function.arguments
4076
+ }
4077
+ }));
4078
+ }
4079
+ let usage;
4080
+ if (data.usage) {
4081
+ usage = {
4082
+ promptTokens: data.usage.prompt_tokens,
4083
+ completionTokens: data.usage.completion_tokens,
4084
+ totalTokens: data.usage.total_tokens
4085
+ };
4086
+ }
4087
+ const finishReason = toolCalls?.length ? "tool_calls" : choice?.finish_reason === "stop" || choice?.finish_reason === "length" || choice?.finish_reason === "content_filter" ? choice.finish_reason : "stop";
4088
+ return {
4089
+ id: data.id || `cerebras-${Date.now()}`,
4090
+ created: data.created || Math.floor(Date.now() / 1e3),
4091
+ content: choice?.message?.content ?? "",
4092
+ toolCalls,
4093
+ usage,
4094
+ finishReason,
4095
+ raw: data
4096
+ };
4097
+ }
4098
+ async handleStreamingResponse(response) {
4099
+ const reader = response.body?.getReader();
4100
+ if (!reader) {
4101
+ throw new Error("No response body available for streaming");
4102
+ }
4103
+ let content = "";
4104
+ let finishReason;
4105
+ try {
4106
+ while (true) {
4107
+ const { done, value } = await reader.read();
4108
+ if (done) break;
4109
+ const chunk = new TextDecoder().decode(value);
4110
+ const lines = chunk.split("\n");
4111
+ for (const line of lines) {
4112
+ if (line.startsWith("data: ")) {
4113
+ const data = line.slice(6);
4114
+ if (data === "[DONE]") continue;
4115
+ try {
4116
+ const parsed = JSON.parse(data);
4117
+ const delta = parsed.choices?.[0]?.delta;
4118
+ if (delta?.content) {
4119
+ content += delta.content;
4120
+ }
4121
+ if (parsed.choices?.[0]?.finish_reason) {
4122
+ finishReason = parsed.choices[0].finish_reason;
4123
+ }
4124
+ } catch {
4125
+ }
4126
+ }
4127
+ }
4128
+ }
4129
+ } finally {
4130
+ reader.releaseLock();
4131
+ }
4132
+ return {
4133
+ id: `cerebras-${Date.now()}`,
4134
+ created: Math.floor(Date.now() / 1e3),
4135
+ content,
4136
+ finishReason: finishReason || "stop",
4137
+ raw: { content, finishReason }
4138
+ };
4139
+ }
4140
+ combineSignals(userSignal, timeoutSignal) {
4141
+ const controller = new AbortController();
4142
+ const onAbort = () => {
4143
+ controller.abort();
4144
+ };
4145
+ userSignal.addEventListener("abort", onAbort);
4146
+ timeoutSignal.addEventListener("abort", onAbort);
4147
+ if (userSignal.aborted || timeoutSignal.aborted) {
4148
+ controller.abort();
4149
+ }
4150
+ return controller.signal;
4151
+ }
4152
+ async buildApiError(response, _body) {
4153
+ let errorDetail = "";
4154
+ try {
4155
+ const errorData = await response.json();
4156
+ errorDetail = errorData.error?.message || JSON.stringify(errorData);
4157
+ } catch {
4158
+ try {
4159
+ errorDetail = await response.text();
4160
+ } catch {
4161
+ errorDetail = `HTTP ${response.status}`;
4162
+ }
4163
+ }
4164
+ const friendlyMessage = FRIENDLY_ERRORS2[response.status] || `Cerebras API error (${response.status}): ${errorDetail}`;
4165
+ return new Error(friendlyMessage);
4166
+ }
4167
+ isNonRetryableError(error) {
4168
+ const message = error.message.toLowerCase();
4169
+ return message.includes("authentication failed") || message.includes("access denied") || message.includes("not found") || message.includes("malformed");
4170
+ }
4171
+ };
4172
+
4173
+ // src/providers/CerebrasProvider.ts
4174
+ var CEREBRAS_DEFAULT_BASE_URL = "https://api.cerebras.ai/v1";
4175
+ var CEREBRAS_MODELS = [
4176
+ "zai-glm-4.7",
4177
+ "qwen-3-235b-a22b-instruct-2507"
4178
+ ];
4179
+ var CerebrasProvider = class {
4180
+ client;
4181
+ model;
4182
+ constructor(config, networkSettings) {
4183
+ const effectiveConfig = {
4184
+ ...config,
4185
+ baseUrl: config.baseUrl ?? CEREBRAS_DEFAULT_BASE_URL
4186
+ };
4187
+ this.client = new CerebrasClient(effectiveConfig, networkSettings);
4188
+ this.model = config.model;
4189
+ }
4190
+ getName() {
4191
+ return "cerebras";
4192
+ }
4193
+ setModel(model) {
4194
+ this.model = model;
4195
+ this.client.setDefaultModel(model);
4196
+ }
4197
+ async listModels() {
4198
+ return [...CEREBRAS_MODELS];
4199
+ }
4200
+ async isAvailable() {
4201
+ return true;
4202
+ }
4203
+ async complete(request) {
4204
+ return this.client.complete(request);
4205
+ }
4206
+ };
4207
+
4208
+ // src/providers/DeepSeekProvider.ts
4209
+ var DEEPSEEK_DEFAULT_BASE_URL = "https://api.deepseek.com";
4210
+ var DEEPSEEK_MODELS = [
4211
+ "deepseek-v4-flash",
4212
+ "deepseek-v4-pro",
4213
+ "deepseek-chat",
4214
+ "deepseek-reasoner"
4215
+ ];
4216
+ var DeepSeekProvider = class {
4217
+ client;
4218
+ model;
4219
+ constructor(config, networkSettings) {
4220
+ const effectiveConfig = {
4221
+ ...config,
4222
+ baseUrl: config.baseUrl ?? DEEPSEEK_DEFAULT_BASE_URL
4223
+ };
4224
+ this.client = new LLMGatewayClient(effectiveConfig, networkSettings, {
4225
+ serviceName: "DeepSeek",
4226
+ credentialName: "DeepSeek API key",
4227
+ accountName: "DeepSeek account"
4228
+ });
4229
+ this.model = config.model;
4230
+ }
4231
+ getName() {
4232
+ return "deepseek";
4233
+ }
4234
+ setModel(model) {
4235
+ this.model = model;
4236
+ this.client.setDefaultModel(model);
4237
+ }
4238
+ async listModels() {
4239
+ return [...DEEPSEEK_MODELS];
4240
+ }
4241
+ async isAvailable() {
4242
+ return true;
4243
+ }
4244
+ async complete(request) {
4245
+ return this.client.complete(request);
4246
+ }
4247
+ };
4248
+
4249
+ // src/providers/BedrockProvider.ts
4250
+ import {
4251
+ BedrockRuntimeClient,
4252
+ ConverseCommand
4253
+ } from "@aws-sdk/client-bedrock-runtime";
4254
+ import {
4255
+ BedrockClient,
4256
+ ListFoundationModelsCommand
4257
+ } from "@aws-sdk/client-bedrock";
4258
+ import { fromIni } from "@aws-sdk/credential-providers";
4259
+ var BEDROCK_DEFAULT_REGION = "us-east-1";
4260
+ var BEDROCK_DEFAULT_MODEL = "anthropic.claude-3-5-sonnet-20241022-v2:0";
4261
+ var BEDROCK_MODELS = [
4262
+ BEDROCK_DEFAULT_MODEL,
4263
+ "anthropic.claude-3-7-sonnet-20250219-v1:0",
4264
+ "anthropic.claude-sonnet-4-20250514-v1:0",
4265
+ "amazon.nova-pro-v1:0",
4266
+ "amazon.nova-lite-v1:0",
4267
+ "meta.llama3-1-70b-instruct-v1:0",
4268
+ "openai.gpt-oss-120b-1:0"
4269
+ ];
4270
+ function resolveBedrockRegion(region) {
4271
+ return region?.trim() || process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || BEDROCK_DEFAULT_REGION;
4272
+ }
4273
+ function getBedrockRuntimeEndpoint(region) {
4274
+ return `https://bedrock-runtime.${region}.amazonaws.com`;
4275
+ }
4276
+ function getBedrockOpenAIEndpoint(_mode, region) {
4277
+ return `${getBedrockRuntimeEndpoint(region)}/openai/v1`;
4278
+ }
4279
+ function resolveBedrockEndpoint(mode, region, configuredEndpoint) {
4280
+ if (configuredEndpoint?.trim()) {
4281
+ return configuredEndpoint.replace(/\/+$/, "");
4282
+ }
4283
+ if (mode === "converse") {
4284
+ return getBedrockRuntimeEndpoint(region);
4285
+ }
4286
+ return getBedrockOpenAIEndpoint(mode, region);
4287
+ }
4288
+ function resolveBedrockAuthMode(mode, configured) {
4289
+ if (configured) return configured;
4290
+ return mode === "converse" ? "aws-credentials" : "bedrock-api-key";
4291
+ }
4292
+ function parseToolArguments(argumentsJson) {
4293
+ try {
4294
+ return JSON.parse(argumentsJson);
4295
+ } catch {
4296
+ return {};
4297
+ }
4298
+ }
4299
+ function toTextContent(content) {
4300
+ return content ? [{ text: content }] : [];
4301
+ }
4302
+ function toToolUseBlocks(toolCalls) {
4303
+ return toolCalls.map((toolCall) => ({
4304
+ toolUse: {
4305
+ toolUseId: toolCall.id,
4306
+ name: toolCall.function.name,
4307
+ input: parseToolArguments(toolCall.function.arguments)
4308
+ }
4309
+ }));
4310
+ }
4311
+ function toConverseMessage(message) {
4312
+ if (message.role === "system") {
4313
+ return null;
4314
+ }
4315
+ if (message.role === "tool") {
4316
+ return {
4317
+ role: "user",
4318
+ content: [
4319
+ {
4320
+ toolResult: {
4321
+ toolUseId: message.tool_call_id ?? message.name ?? "tool_result",
4322
+ content: [{ text: message.content }]
4323
+ }
4324
+ }
4325
+ ]
4326
+ };
4327
+ }
4328
+ if (message.role === "assistant") {
4329
+ const content = [
4330
+ ...toTextContent(message.content),
4331
+ ...message.tool_calls?.length ? toToolUseBlocks(message.tool_calls) : []
4332
+ ];
4333
+ return {
4334
+ role: "assistant",
4335
+ content: content.length > 0 ? content : [{ text: "" }]
4336
+ };
4337
+ }
4338
+ return {
4339
+ role: "user",
4340
+ content: toTextContent(message.content)
4341
+ };
4342
+ }
4343
+ function toOpenAIMessage(message) {
4344
+ const mapped = {
4345
+ role: message.role,
4346
+ content: message.role === "assistant" && message.tool_calls?.length ? message.content || null : message.content
4347
+ };
4348
+ if (message.name) mapped.name = message.name;
4349
+ if (message.role === "tool" && message.tool_call_id) {
4350
+ mapped.tool_call_id = message.tool_call_id;
4351
+ }
4352
+ if (message.role === "assistant" && message.tool_calls?.length) {
4353
+ mapped.tool_calls = message.tool_calls;
4354
+ }
4355
+ return mapped;
4356
+ }
4357
+ function toResponsesInputItem(message) {
4358
+ if (message.role === "tool" && message.tool_call_id) {
4359
+ return [
4360
+ {
4361
+ type: "function_call_output",
4362
+ call_id: message.tool_call_id,
4363
+ output: message.content
4364
+ }
4365
+ ];
4366
+ }
4367
+ if (message.role === "assistant" && message.tool_calls?.length) {
4368
+ return message.tool_calls.map((toolCall) => ({
4369
+ type: "function_call",
4370
+ call_id: toolCall.id,
4371
+ name: toolCall.function.name,
4372
+ arguments: toolCall.function.arguments
4373
+ }));
4374
+ }
4375
+ if (message.role === "system") {
4376
+ return [];
4377
+ }
4378
+ const contentType = message.role === "assistant" ? "output_text" : "input_text";
4379
+ return [
4380
+ {
4381
+ role: message.role,
4382
+ content: [{ type: contentType, text: message.content }]
4383
+ }
4384
+ ];
4385
+ }
4386
+ function toOpenAITools(tools) {
4387
+ return tools.map((tool) => ({
4388
+ type: "function",
4389
+ function: {
4390
+ name: tool.name,
4391
+ description: tool.description,
4392
+ parameters: tool.parameters ?? { type: "object", properties: {} }
4393
+ }
4394
+ }));
4395
+ }
4396
+ function toResponsesTools(tools) {
4397
+ return tools.map((tool) => ({
4398
+ type: "function",
4399
+ name: tool.name,
4400
+ description: tool.description,
4401
+ parameters: tool.parameters ?? { type: "object", properties: {} }
4402
+ }));
4403
+ }
4404
+ function toConverseTools(tools) {
4405
+ return tools.map((tool) => ({
4406
+ toolSpec: {
4407
+ name: tool.name,
4408
+ description: tool.description,
4409
+ inputSchema: {
4410
+ json: tool.parameters ?? { type: "object", properties: {} }
4411
+ }
4412
+ }
4413
+ }));
4414
+ }
4415
+ function normalizeStopReason(stopReason) {
4416
+ if (stopReason === "tool_use" || stopReason === "tool_calls") {
4417
+ return "tool_calls";
4418
+ }
4419
+ if (stopReason === "max_tokens" || stopReason === "length") {
4420
+ return "length";
4421
+ }
4422
+ if (stopReason === "content_filter") {
4423
+ return "content_filter";
4424
+ }
4425
+ return "stop";
4426
+ }
4427
+ function toolCallsFromConverseBlocks(blocks) {
4428
+ return blocks.filter(
4429
+ (block) => "toolUse" in block && Boolean(block.toolUse)
4430
+ ).map((block) => ({
4431
+ id: block.toolUse.toolUseId,
4432
+ type: "function",
4433
+ function: {
4434
+ name: block.toolUse.name,
4435
+ arguments: JSON.stringify(block.toolUse.input ?? {})
4436
+ }
4437
+ }));
4438
+ }
4439
+ function textFromConverseBlocks(blocks) {
4440
+ return blocks.filter((block) => "text" in block).map((block) => block.text).join("");
4441
+ }
4442
+ function isBedrockAwsError(error) {
4443
+ return error instanceof Error;
4444
+ }
4445
+ function classifyBedrockError(error) {
4446
+ if (!isBedrockAwsError(error)) {
4447
+ return new ApiError(String(error), "unknown", 0, true);
4448
+ }
4449
+ if (error.name === "AbortError") {
4450
+ return new ApiError("Request cancelled.", "cancelled", 0, false);
4451
+ }
4452
+ const status = error["$metadata"]?.httpStatusCode ?? 0;
4453
+ const message = error.message || error.name;
4454
+ const lower = `${error.name} ${message}`.toLowerCase();
4455
+ let code;
4456
+ if (lower.includes("credential") || lower.includes("signature") || lower.includes("unrecognizedclient") || lower.includes("expiredtoken")) {
4457
+ code = "auth_failed";
4458
+ } else if (lower.includes("accessdenied") || lower.includes("access denied") || lower.includes("not authorized") || lower.includes("model access")) {
4459
+ code = "access_denied";
4460
+ } else if (lower.includes("resourcenotfound") || lower.includes("model not found") || lower.includes("not found") || lower.includes("not available")) {
4461
+ code = "model_not_found";
4462
+ } else if (lower.includes("throttl") || lower.includes("quota") || lower.includes("toomanyrequests")) {
4463
+ code = "rate_limited";
4464
+ } else if (lower.includes("validation") || lower.includes("unsupported") || lower.includes("api mode")) {
4465
+ code = "invalid_request";
4466
+ } else if (lower.includes("network") || lower.includes("enotfound") || lower.includes("econn") || lower.includes("private endpoint")) {
4467
+ code = "network_error";
4468
+ }
4469
+ if (code) {
4470
+ const friendly = {
4471
+ auth_failed: "AWS Bedrock credentials were not found or were rejected. Configure AWS credentials, AWS_PROFILE, instance metadata, or choose Bedrock API key auth.",
4472
+ access_denied: "AWS Bedrock denied access. Enable model access in the AWS console and verify IAM permissions for this model.",
4473
+ model_not_found: "The selected Bedrock model is not available in this region. Check the model ID, inference profile, ARN, and region.",
4474
+ invalid_request: "Bedrock rejected the request. The selected model may not support this API mode or native tool use.",
4475
+ rate_limited: "AWS Bedrock throttled the request or quota was exceeded. Wait and retry, or request a quota increase.",
4476
+ network_error: "Unable to reach the AWS Bedrock endpoint. Check region, endpoint, private networking, and proxy settings.",
4477
+ timeout: "The AWS Bedrock request timed out.",
4478
+ cancelled: "Request cancelled.",
4479
+ context_overflow: "The conversation is too long for this Bedrock model.",
4480
+ payment_required: "AWS Bedrock billing or account setup is required.",
4481
+ server_error: "AWS Bedrock encountered a service error. Please try again later.",
4482
+ unknown: "AWS Bedrock returned an unexpected error."
4483
+ };
4484
+ return new ApiError(`${friendly[code]}
4485
+ ${message}`, code, status, code === "rate_limited" || code === "server_error", void 0, message);
4486
+ }
4487
+ return classifyApiError(status, message);
4488
+ }
4489
+ async function readErrorBody(response) {
4490
+ try {
4491
+ return await response.text();
4492
+ } catch {
4493
+ return "";
4494
+ }
4495
+ }
4496
+ var BedrockProvider = class {
4497
+ apiMode;
4498
+ authMode;
4499
+ region;
4500
+ endpoint;
4501
+ profile;
4502
+ apiKey;
4503
+ model;
4504
+ runtimeClient;
4505
+ modelClient;
4506
+ constructor(config) {
4507
+ this.apiMode = config.apiMode ?? "converse";
4508
+ this.authMode = resolveBedrockAuthMode(this.apiMode, config.authMode);
4509
+ this.region = resolveBedrockRegion(config.region);
4510
+ this.endpoint = resolveBedrockEndpoint(this.apiMode, this.region, config.endpoint);
4511
+ this.profile = config.profile;
4512
+ this.apiKey = config.apiKey;
4513
+ this.model = config.model || BEDROCK_DEFAULT_MODEL;
4514
+ }
4515
+ getName() {
4516
+ return "bedrock";
4517
+ }
4518
+ setModel(model) {
4519
+ this.model = model;
4520
+ }
4521
+ getCapabilities() {
4522
+ return { nativeToolCalling: true };
4523
+ }
4524
+ async listModels() {
4525
+ if (this.apiMode !== "converse") {
4526
+ return [...BEDROCK_MODELS];
4527
+ }
4528
+ try {
4529
+ const response = await this.getModelClient().send(
4530
+ new ListFoundationModelsCommand({})
4531
+ );
4532
+ const summaries = response.modelSummaries ?? [];
4533
+ const modelIds = summaries.map((summary) => summary.modelId).filter((modelId) => Boolean(modelId));
4534
+ return modelIds.length > 0 ? modelIds : [...BEDROCK_MODELS];
4535
+ } catch {
4536
+ return [...BEDROCK_MODELS];
4537
+ }
4538
+ }
4539
+ async isAvailable() {
4540
+ if (this.authMode === "bedrock-api-key") {
4541
+ return Boolean(this.apiKey);
4542
+ }
4543
+ try {
4544
+ await this.listModels();
4545
+ return true;
4546
+ } catch {
4547
+ return false;
4548
+ }
4549
+ }
4550
+ async complete(request) {
4551
+ if (!this.region) {
4552
+ throw new ApiError(
4553
+ "AWS Bedrock region is missing. Set bedrock.region, AWS_REGION, or AWS_DEFAULT_REGION.",
4554
+ "invalid_request",
4555
+ 0,
4556
+ false
4557
+ );
4558
+ }
4559
+ if (this.apiMode === "converse") {
4560
+ return this.completeWithConverse(request);
4561
+ }
4562
+ return this.completeWithOpenAICompatible(request);
4563
+ }
4564
+ getCredentials() {
4565
+ if (this.profile) {
4566
+ return fromIni({ profile: this.profile });
4567
+ }
4568
+ return void 0;
4569
+ }
4570
+ getRuntimeClient() {
4571
+ if (!this.runtimeClient) {
4572
+ this.runtimeClient = new BedrockRuntimeClient({
4573
+ region: this.region,
4574
+ endpoint: this.endpoint,
4575
+ credentials: this.getCredentials()
4576
+ });
4577
+ }
4578
+ return this.runtimeClient;
4579
+ }
4580
+ getModelClient() {
4581
+ if (!this.modelClient) {
4582
+ this.modelClient = new BedrockClient({
4583
+ region: this.region,
4584
+ credentials: this.getCredentials()
4585
+ });
4586
+ }
4587
+ return this.modelClient;
4588
+ }
4589
+ async completeWithConverse(request) {
4590
+ if (this.authMode === "bedrock-api-key") {
4591
+ throw new ApiError(
4592
+ "Bedrock Converse uses AWS credential-chain auth. Choose apiMode openai-chat/openai-responses to use Bedrock API keys.",
4593
+ "invalid_request",
4594
+ 0,
4595
+ false
4596
+ );
4597
+ }
4598
+ const contentMessages = request.messages.map(toConverseMessage).filter((message) => message !== null);
4599
+ const system = request.messages.filter((message) => message.role === "system" && message.content).map((message) => ({ text: message.content }));
4600
+ const body = {
4601
+ modelId: request.model ?? this.model,
4602
+ messages: contentMessages,
4603
+ inferenceConfig: {
4604
+ ...request.maxTokens !== void 0 && { maxTokens: request.maxTokens },
4605
+ ...request.temperature !== void 0 && { temperature: request.temperature }
4606
+ }
4607
+ };
4608
+ if (system.length > 0) {
4609
+ body.system = system;
4610
+ }
4611
+ if (request.tools?.length) {
4612
+ body.toolConfig = {
4613
+ tools: toConverseTools(request.tools),
4614
+ ...request.toolChoice && request.toolChoice !== "auto" ? { toolChoice: this.toConverseToolChoice(request.toolChoice) } : {}
4615
+ };
4616
+ }
4617
+ try {
4618
+ const data = await this.getRuntimeClient().send(
4619
+ new ConverseCommand(body)
4620
+ );
4621
+ const blocks = data.output?.message?.content ?? [];
4622
+ const toolCalls = toolCallsFromConverseBlocks(blocks);
4623
+ return {
4624
+ id: `bedrock-${Date.now()}`,
4625
+ created: Math.floor(Date.now() / 1e3),
4626
+ content: textFromConverseBlocks(blocks),
4627
+ ...toolCalls.length > 0 && { toolCalls },
4628
+ finishReason: normalizeStopReason(data.stopReason),
4629
+ usage: normalizeLLMUsage(data.usage),
4630
+ raw: data
4631
+ };
4632
+ } catch (error) {
4633
+ throw classifyBedrockError(error);
4634
+ }
4635
+ }
4636
+ toConverseToolChoice(toolChoice) {
4637
+ if (!toolChoice || toolChoice === "auto") return void 0;
4638
+ if (toolChoice === "none") return { auto: {} };
4639
+ if (toolChoice === "required") return { any: {} };
4640
+ return { tool: { name: toolChoice.function.name } };
4641
+ }
4642
+ async completeWithOpenAICompatible(request) {
4643
+ if (this.authMode !== "bedrock-api-key") {
4644
+ throw new ApiError(
4645
+ "Bedrock OpenAI-compatible modes require authMode bedrock-api-key and bedrock.apiKey.",
4646
+ "auth_failed",
4647
+ 0,
4648
+ false
4649
+ );
4650
+ }
4651
+ if (!this.apiKey) {
4652
+ throw new ApiError(
4653
+ "Bedrock API key is missing. Set bedrock.apiKey for OpenAI-compatible Bedrock modes.",
4654
+ "auth_failed",
4655
+ 0,
4656
+ false
4657
+ );
4658
+ }
4659
+ if (this.apiMode === "openai-chat") {
4660
+ return this.completeWithOpenAIChat(request);
4661
+ }
4662
+ return this.completeWithOpenAIResponses(request);
4663
+ }
4664
+ async completeWithOpenAIChat(request) {
4665
+ const body = {
4666
+ model: request.model ?? this.model,
4667
+ messages: request.messages.map(toOpenAIMessage),
4668
+ ...request.temperature !== void 0 && { temperature: request.temperature },
4669
+ ...request.maxTokens !== void 0 && { max_tokens: request.maxTokens }
4670
+ };
4671
+ if (request.tools?.length) {
4672
+ body.tools = toOpenAITools(request.tools);
4673
+ if (request.toolChoice) body.tool_choice = request.toolChoice;
4674
+ }
4675
+ const data = await this.fetchJson("/chat/completions", body, request.signal);
4676
+ const choice = data.choices?.[0];
4677
+ if (!choice?.message) {
4678
+ throw new ApiError(
4679
+ "Malformed Bedrock OpenAI chat response: missing choice message.",
4680
+ "invalid_request",
4681
+ 200,
4682
+ false,
4683
+ void 0,
4684
+ JSON.stringify(data)
4685
+ );
4686
+ }
4687
+ return {
4688
+ id: data.id ?? `bedrock-chat-${Date.now()}`,
4689
+ created: data.created ?? Math.floor(Date.now() / 1e3),
4690
+ content: choice.message.content ?? "",
4691
+ ...choice.message.tool_calls?.length && { toolCalls: choice.message.tool_calls },
4692
+ finishReason: normalizeStopReason(choice.finish_reason),
4693
+ usage: normalizeLLMUsage(data.usage),
4694
+ raw: data
4695
+ };
4696
+ }
4697
+ async completeWithOpenAIResponses(request) {
4698
+ const instructions = request.messages.filter((message) => message.role === "system").map((message) => message.content).join("\n\n");
4699
+ const body = {
4700
+ model: request.model ?? this.model,
4701
+ input: request.messages.flatMap(toResponsesInputItem),
4702
+ ...instructions && { instructions },
4703
+ ...request.maxTokens !== void 0 && { max_output_tokens: request.maxTokens }
4704
+ };
4705
+ if (request.tools?.length) {
4706
+ body.tools = toResponsesTools(request.tools);
4707
+ if (request.toolChoice) body.tool_choice = request.toolChoice;
4708
+ }
4709
+ const data = await this.fetchJson("/responses", body, request.signal);
4710
+ const functionCalls = (data.output ?? []).filter(
4711
+ (item) => item.type === "function_call" && typeof item.name === "string" && typeof item.arguments === "string"
4712
+ ).map((item) => ({
4713
+ id: item.call_id ?? item.id ?? `call_${Date.now()}`,
4714
+ type: "function",
4715
+ function: {
4716
+ name: item.name,
4717
+ arguments: item.arguments
4718
+ }
4719
+ }));
4720
+ return {
4721
+ id: data.id ?? `bedrock-responses-${Date.now()}`,
4722
+ created: data.created_at ?? Math.floor(Date.now() / 1e3),
4723
+ content: data.output_text ?? "",
4724
+ ...functionCalls.length > 0 && { toolCalls: functionCalls },
4725
+ finishReason: functionCalls.length > 0 ? "tool_calls" : "stop",
4726
+ usage: normalizeLLMUsage(data.usage),
4727
+ raw: data
4728
+ };
4729
+ }
4730
+ async fetchJson(path, body, signal) {
4731
+ let response;
4732
+ try {
4733
+ response = await fetch(`${this.endpoint}${path}`, {
4734
+ method: "POST",
4735
+ headers: {
4736
+ "Content-Type": "application/json",
4737
+ Authorization: `Bearer ${this.apiKey}`
4738
+ },
4739
+ body: JSON.stringify(body),
4740
+ signal
4741
+ });
4742
+ } catch (error) {
4743
+ const err = error;
4744
+ if (err.name === "AbortError" && signal?.aborted) {
4745
+ throw new ApiError("Request cancelled.", "cancelled", 0, false);
4746
+ }
4747
+ if (err.name === "AbortError") {
4748
+ throw new ApiError("The Bedrock request timed out.", "timeout", 0, true);
4749
+ }
4750
+ throw new ApiError(
4751
+ "Unable to connect to the Bedrock OpenAI-compatible endpoint. Check region, endpoint, private networking, and proxy settings.",
4752
+ "network_error",
4753
+ 0,
4754
+ true,
4755
+ void 0,
4756
+ err.message
4757
+ );
4758
+ }
4759
+ if (!response.ok) {
4760
+ const errorBody = await readErrorBody(response);
4761
+ const classified = classifyApiError(response.status, errorBody, response.headers);
4762
+ throw new ApiError(
4763
+ `AWS Bedrock request failed.
4764
+ ${classified.message}`,
4765
+ classified.code,
4766
+ classified.httpStatus,
4767
+ classified.retryable,
4768
+ classified.retryAfterMs,
4769
+ classified.rawDetail
4770
+ );
4771
+ }
4772
+ try {
4773
+ return await response.json();
4774
+ } catch (error) {
4775
+ throw new ApiError(
4776
+ "Malformed Bedrock response: response body was not valid JSON.",
4777
+ "invalid_request",
4778
+ response.status,
4779
+ false,
4780
+ void 0,
4781
+ error instanceof Error ? error.message : String(error)
4782
+ );
4783
+ }
4784
+ }
4785
+ };
4786
+
4787
+ // src/providers/ProviderFactory.ts
4788
+ var ProviderNotConfiguredError = class extends Error {
4789
+ constructor(providerName) {
4790
+ super(`PROVIDER_NOT_CONFIGURED:${providerName}`);
4791
+ this.providerName = providerName;
4792
+ this.name = "ProviderNotConfiguredError";
4793
+ }
4794
+ providerName;
4795
+ };
4796
+ var UnconfiguredProvider = class {
4797
+ constructor(providerName) {
4798
+ this.providerName = providerName;
4799
+ }
4800
+ providerName;
4801
+ getName() {
4802
+ return "unconfigured";
4803
+ }
4804
+ async complete(_request) {
4805
+ throw new ProviderNotConfiguredError(this.providerName);
4806
+ }
4807
+ async listModels() {
4808
+ return [];
4809
+ }
4810
+ async isAvailable() {
4811
+ return false;
4812
+ }
4813
+ setModel(_model) {
4814
+ }
4815
+ };
4816
+ var ProviderFactory = class {
4817
+ /**
4818
+ * Create an LLM provider based on configuration.
4819
+ * Returns an UnconfiguredProvider if the selected provider is not configured,
4820
+ * allowing the agent to handle it gracefully instead of crashing.
4821
+ */
4822
+ static create(config) {
4823
+ const providerName = config.provider || "openrouter";
4824
+ if (providerName === "bedrock" && !isAwsBedrockProviderEnabled(config)) {
4825
+ return new UnconfiguredProvider("bedrock");
4826
+ }
4827
+ switch (providerName) {
4828
+ case "ollama":
4829
+ if (!config.ollama) {
4830
+ return new UnconfiguredProvider("ollama");
4831
+ }
4832
+ return new OllamaProvider(config.ollama, config.network);
4833
+ case "openai":
4834
+ if (!config.openai) {
4835
+ return new UnconfiguredProvider("openai");
4836
+ }
4837
+ return new OpenAIProvider(config.openai);
4838
+ case "llamacpp":
4839
+ if (!config.llamacpp) {
4840
+ return new UnconfiguredProvider("llamacpp");
4841
+ }
4842
+ return new LlamaCppProvider(config.llamacpp);
4843
+ case "mlx":
4844
+ if (!config.mlx) {
4845
+ return new UnconfiguredProvider("mlx");
4846
+ }
4847
+ return new MLXProvider(config.mlx, config.network);
4848
+ case "llmgateway":
4849
+ if (!config.llmgateway) {
4850
+ return new UnconfiguredProvider("llmgateway");
4851
+ }
4852
+ return new LLMGatewayProvider(config.llmgateway, config.network);
4853
+ case "azure":
4854
+ if (!config.azure) {
4855
+ return new UnconfiguredProvider("azure");
4856
+ }
4857
+ return new AzureProvider(config.azure, config.network);
4858
+ case "zai":
4859
+ if (!config.zai) {
4860
+ return new UnconfiguredProvider("zai");
4861
+ }
4862
+ return new ZaiProvider(config.zai, config.network);
4863
+ case "vertexai":
4864
+ if (!config.vertexai) {
4865
+ return new UnconfiguredProvider("vertexai");
4866
+ }
4867
+ return new VertexAIProvider(config.vertexai, config.network);
4868
+ case "xai":
4869
+ if (!config.xai) {
4870
+ return new UnconfiguredProvider("xai");
4871
+ }
4872
+ return new XAIProvider(config.xai);
4873
+ case "cerebras":
4874
+ if (!config.cerebras) {
4875
+ return new UnconfiguredProvider("cerebras");
4876
+ }
4877
+ return new CerebrasProvider(config.cerebras, config.network);
4878
+ case "nvidia":
4879
+ if (!config.nvidia) {
4880
+ return new UnconfiguredProvider("nvidia");
4881
+ }
4882
+ return new NVIDIAProvider(config.nvidia, config.network);
4883
+ case "deepseek":
4884
+ if (!config.deepseek) {
4885
+ return new UnconfiguredProvider("deepseek");
4886
+ }
4887
+ return new DeepSeekProvider(config.deepseek, config.network);
4888
+ case "bedrock":
4889
+ if (!config.bedrock) {
4890
+ return new UnconfiguredProvider("bedrock");
4891
+ }
4892
+ return new BedrockProvider(config.bedrock);
4893
+ case "openrouter":
4894
+ default:
4895
+ if (!config.openrouter) {
4896
+ return new UnconfiguredProvider("openrouter");
4897
+ }
4898
+ return new OpenRouterProvider(config.openrouter);
4899
+ }
4900
+ }
4901
+ /**
4902
+ * Get all available provider names.
4903
+ * MLX is only included on Apple Silicon (macOS + arm64).
4904
+ */
4905
+ static getProviderNames(config) {
4906
+ const providers = ["zai", "xai", "vertexai", "nvidia", "openrouter", "openai", "ollama", "llmgateway", "llamacpp", "deepseek", "cerebras", "azure"];
4907
+ if (isAwsBedrockProviderEnabled(config)) {
4908
+ providers.splice(providers.indexOf("azure"), 0, "bedrock");
4909
+ }
4910
+ if (isMLXSupported()) {
4911
+ providers.push("mlx");
4912
+ }
4913
+ return providers;
4914
+ }
4915
+ /**
4916
+ * Check if a provider name is valid.
4917
+ * Note: This checks if the name is a valid provider type, not if it's available on this platform.
4918
+ * MLX is always a valid provider name, but may not be available on non-Apple Silicon systems.
4919
+ */
4920
+ static isValidProvider(name, config) {
4921
+ if (name === "bedrock" && !isAwsBedrockProviderEnabled(config)) {
4922
+ return false;
4923
+ }
4924
+ const allProviders = ["openrouter", "ollama", "openai", "llamacpp", "mlx", "llmgateway", "azure", "zai", "vertexai", "xai", "cerebras", "nvidia", "deepseek", "bedrock"];
4925
+ return allProviders.includes(name);
4926
+ }
4927
+ };
4928
+
4929
+ export {
4930
+ isChatGPTAuthExpired,
4931
+ authenticateOpenAIChatGPT,
4932
+ OPENAI_MODELS,
4933
+ getOpenRouterModelContextWindow,
4934
+ modelSupportsImages,
4935
+ ZAI_DEFAULT_BASE_URL,
4936
+ ZAI_MODELS,
4937
+ isGcloudInstalled,
4938
+ getGcloudProject,
4939
+ getGcloudAccessToken,
4940
+ getGcloudAccount,
4941
+ VERTEX_AI_CODING_MODELS,
4942
+ CEREBRAS_DEFAULT_BASE_URL,
4943
+ CEREBRAS_MODELS,
4944
+ DEEPSEEK_DEFAULT_BASE_URL,
4945
+ DEEPSEEK_MODELS,
4946
+ BEDROCK_DEFAULT_REGION,
4947
+ BEDROCK_DEFAULT_MODEL,
4948
+ BEDROCK_MODELS,
4949
+ resolveBedrockEndpoint,
4950
+ resolveBedrockAuthMode,
4951
+ ProviderNotConfiguredError,
4952
+ ProviderFactory
4953
+ };
4954
+ /**
4955
+ * @license
4956
+ * Copyright 2025 Autohand AI LLC
4957
+ * SPDX-License-Identifier: Apache-2.0
4958
+ */
4959
+ /**
4960
+ * Google Cloud CLI authentication utilities
4961
+ * @license Apache-2.0
4962
+ */
4963
+ /**
4964
+ * @license
4965
+ * Copyright 2026 Autohand AI LLC
4966
+ * SPDX-License-Identifier: Apache-2.0
4967
+ */