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