@shareai-lab/kode 2.0.1 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +649 -25
- package/README.zh-CN.md +579 -0
- package/cli-acp.js +3 -17
- package/cli.js +5 -7
- package/dist/chunks/Doctor-M3J7GRTJ.js +12 -0
- package/dist/chunks/LogList-ISWZ6DDD.js +121 -0
- package/dist/chunks/LogList-ISWZ6DDD.js.map +7 -0
- package/dist/chunks/REPL-RQ6LO6S7.js +56 -0
- package/dist/chunks/ResumeConversation-6DMVBEGH.js +56 -0
- package/dist/chunks/agentLoader-FCRG3TFJ.js +31 -0
- package/dist/{agentsValidate-7LH4HTNR.js → chunks/agentsValidate-PEWMYN4Q.js} +97 -69
- package/dist/chunks/agentsValidate-PEWMYN4Q.js.map +7 -0
- package/dist/{ask-3NHFFUQG.js → chunks/ask-D7SOHJ6Z.js} +36 -44
- package/dist/chunks/ask-D7SOHJ6Z.js.map +7 -0
- package/dist/chunks/autoUpdater-CNESBOKO.js +19 -0
- package/dist/{chunk-AFFSCMYS.js → chunks/chunk-2JN5MY67.js} +12 -14
- package/dist/chunks/chunk-2JN5MY67.js.map +7 -0
- package/dist/chunks/chunk-2QONJ5MG.js +14 -0
- package/dist/chunks/chunk-2QONJ5MG.js.map +7 -0
- package/dist/chunks/chunk-2WEXPKHH.js +903 -0
- package/dist/chunks/chunk-2WEXPKHH.js.map +7 -0
- package/dist/{chunk-ARZSBOAO.js → chunks/chunk-3BYE3ME6.js} +717 -792
- package/dist/chunks/chunk-3BYE3ME6.js.map +7 -0
- package/dist/chunks/chunk-3JDNWX7W.js +1264 -0
- package/dist/chunks/chunk-3JDNWX7W.js.map +7 -0
- package/dist/chunks/chunk-3OEJVB5A.js +906 -0
- package/dist/chunks/chunk-3OEJVB5A.js.map +7 -0
- package/dist/chunks/chunk-3TNIOEBO.js +369 -0
- package/dist/chunks/chunk-3TNIOEBO.js.map +7 -0
- package/dist/chunks/chunk-4A46ZXMJ.js +67 -0
- package/dist/chunks/chunk-4A46ZXMJ.js.map +7 -0
- package/dist/{chunk-UHYRLID6.js → chunks/chunk-4ATBQOFO.js} +107 -55
- package/dist/chunks/chunk-4ATBQOFO.js.map +7 -0
- package/dist/chunks/chunk-4CRUCZR4.js +0 -0
- package/dist/{chunk-YC6LJCDE.js → chunks/chunk-4EO6SIQY.js} +32 -75
- package/dist/chunks/chunk-4EO6SIQY.js.map +7 -0
- package/dist/chunks/chunk-53M46S5I.js +64 -0
- package/dist/chunks/chunk-53M46S5I.js.map +7 -0
- package/dist/{chunk-JC6NCUG5.js → chunks/chunk-54KOYG5C.js} +0 -2
- package/dist/{chunk-EZXMVTDU.js → chunks/chunk-6BAS4WY6.js} +29 -45
- package/dist/chunks/chunk-6BAS4WY6.js.map +7 -0
- package/dist/{chunk-3IN27HA5.js → chunks/chunk-6KRRFSDN.js} +4 -6
- package/dist/chunks/chunk-6KRRFSDN.js.map +7 -0
- package/dist/chunks/chunk-6LJNZK4K.js +39 -0
- package/dist/chunks/chunk-6LJNZK4K.js.map +7 -0
- package/dist/chunks/chunk-6ZWEOSEI.js +666 -0
- package/dist/chunks/chunk-6ZWEOSEI.js.map +7 -0
- package/dist/chunks/chunk-77XDJMBP.js +3326 -0
- package/dist/chunks/chunk-77XDJMBP.js.map +7 -0
- package/dist/chunks/chunk-7RRW4NTB.js +6454 -0
- package/dist/chunks/chunk-7RRW4NTB.js.map +7 -0
- package/dist/chunks/chunk-7X3TW4JB.js +4520 -0
- package/dist/chunks/chunk-7X3TW4JB.js.map +7 -0
- package/dist/chunks/chunk-B3MW3YGY.js +1409 -0
- package/dist/chunks/chunk-B3MW3YGY.js.map +7 -0
- package/dist/chunks/chunk-BBJFHTBC.js +28 -0
- package/dist/chunks/chunk-BBJFHTBC.js.map +7 -0
- package/dist/chunks/chunk-BHDHXOXB.js +24 -0
- package/dist/chunks/chunk-BHDHXOXB.js.map +7 -0
- package/dist/{chunk-73WGVYLQ.js → chunks/chunk-BTA7SZ26.js} +152 -223
- package/dist/chunks/chunk-BTA7SZ26.js.map +7 -0
- package/dist/chunks/chunk-CDGRYGPZ.js +103 -0
- package/dist/chunks/chunk-CDGRYGPZ.js.map +7 -0
- package/dist/{chunk-S6HRABTA.js → chunks/chunk-CP6E5UG6.js} +1 -4
- package/dist/chunks/chunk-CP6E5UG6.js.map +7 -0
- package/dist/{chunk-QVLYOPO5.js → chunks/chunk-DQ4JHXMT.js} +462 -424
- package/dist/chunks/chunk-DQ4JHXMT.js.map +7 -0
- package/dist/chunks/chunk-DXD76CMV.js +208 -0
- package/dist/chunks/chunk-DXD76CMV.js.map +7 -0
- package/dist/chunks/chunk-GCQCAXJZ.js +0 -0
- package/dist/chunks/chunk-GELCZWMB.js +42 -0
- package/dist/chunks/chunk-GELCZWMB.js.map +7 -0
- package/dist/{chunk-K2CWOTI2.js → chunks/chunk-HJYOH4HC.js} +23 -18
- package/dist/chunks/chunk-HJYOH4HC.js.map +7 -0
- package/dist/chunks/chunk-HPYNW6TT.js +744 -0
- package/dist/chunks/chunk-HPYNW6TT.js.map +7 -0
- package/dist/{chunk-RZWOUA25.js → chunks/chunk-HRJ3ICQK.js} +59 -55
- package/dist/chunks/chunk-HRJ3ICQK.js.map +7 -0
- package/dist/{chunk-DZE5YA7L.js → chunks/chunk-IFCIADS3.js} +571 -573
- package/dist/chunks/chunk-IFCIADS3.js.map +7 -0
- package/dist/chunks/chunk-IN7XZ7BC.js +27 -0
- package/dist/chunks/chunk-IN7XZ7BC.js.map +7 -0
- package/dist/chunks/chunk-L7P4M4KW.js +193 -0
- package/dist/chunks/chunk-L7P4M4KW.js.map +7 -0
- package/dist/chunks/chunk-LB6TCPDI.js +0 -0
- package/dist/{chunk-3RUXVV4S.js → chunks/chunk-LOCXPQNJ.js} +1 -4
- package/dist/{chunk-3RUXVV4S.js.map → chunks/chunk-LOCXPQNJ.js.map} +2 -2
- package/dist/{chunk-7M2YN6TU.js → chunks/chunk-LOD5ZHCI.js} +213 -208
- package/dist/chunks/chunk-LOD5ZHCI.js.map +7 -0
- package/dist/{chunk-S3J2TLV6.js → chunks/chunk-M7P3QNRU.js} +1 -4
- package/dist/{chunk-S3J2TLV6.js.map → chunks/chunk-M7P3QNRU.js.map} +2 -2
- package/dist/chunks/chunk-PPHLQVL7.js +4234 -0
- package/dist/chunks/chunk-PPHLQVL7.js.map +7 -0
- package/dist/{chunk-ABLVTESJ.js → chunks/chunk-QAXE37B5.js} +1 -4
- package/dist/chunks/chunk-QAXE37B5.js.map +7 -0
- package/dist/chunks/chunk-QHQOBUF6.js +60 -0
- package/dist/chunks/chunk-QHQOBUF6.js.map +7 -0
- package/dist/{chunk-W7GRKO7Q.js → chunks/chunk-RPJXO7GG.js} +241 -214
- package/dist/chunks/chunk-RPJXO7GG.js.map +7 -0
- package/dist/{chunk-NPFOMITO.js → chunks/chunk-SWQV4KSY.js} +1 -4
- package/dist/{chunk-NPFOMITO.js.map → chunks/chunk-SWQV4KSY.js.map} +2 -2
- package/dist/chunks/chunk-SZLAPULP.js +28 -0
- package/dist/chunks/chunk-SZLAPULP.js.map +7 -0
- package/dist/{chunk-7U7L4NMD.js → chunks/chunk-T7RB5V5J.js} +23 -25
- package/dist/chunks/chunk-T7RB5V5J.js.map +7 -0
- package/dist/{chunk-HN4E4UUQ.js → chunks/chunk-TI2CTTMA.js} +25 -17
- package/dist/chunks/chunk-TI2CTTMA.js.map +7 -0
- package/dist/{chunk-ZVDRDPII.js → chunks/chunk-TNGVRTO5.js} +45 -20
- package/dist/chunks/chunk-TNGVRTO5.js.map +7 -0
- package/dist/chunks/chunk-TNWB3U5Y.js +2077 -0
- package/dist/chunks/chunk-TNWB3U5Y.js.map +7 -0
- package/dist/chunks/chunk-U2IHWPCU.js +12 -0
- package/dist/chunks/chunk-U2IHWPCU.js.map +7 -0
- package/dist/{chunk-KAA5BGMQ.js → chunks/chunk-UNOY3VJ2.js} +1 -4
- package/dist/{chunk-KAA5BGMQ.js.map → chunks/chunk-UNOY3VJ2.js.map} +2 -2
- package/dist/{chunk-MWRSY4X6.js → chunks/chunk-UVDJL6ZZ.js} +97 -58
- package/dist/chunks/chunk-UVDJL6ZZ.js.map +7 -0
- package/dist/chunks/chunk-VNCW4C2Z.js +13452 -0
- package/dist/chunks/chunk-VNCW4C2Z.js.map +7 -0
- package/dist/chunks/chunk-W5EGGA44.js +15 -0
- package/dist/chunks/chunk-W5EGGA44.js.map +7 -0
- package/dist/chunks/chunk-XR2W3MAM.js +1533 -0
- package/dist/chunks/chunk-XR2W3MAM.js.map +7 -0
- package/dist/{chunk-STSX7GIX.js → chunks/chunk-YIO5EBMQ.js} +423 -377
- package/dist/chunks/chunk-YIO5EBMQ.js.map +7 -0
- package/dist/chunks/chunk-ZBVLKZ5V.js +1062 -0
- package/dist/chunks/chunk-ZBVLKZ5V.js.map +7 -0
- package/dist/{chunk-E6YNABER.js → chunks/chunk-ZCLTZIVP.js} +1 -4
- package/dist/chunks/chunk-ZCLTZIVP.js.map +7 -0
- package/dist/chunks/client-SILZNM5N.js +42 -0
- package/dist/{config-RUSD6G5Y.js → chunks/config-25HRTPSP.js} +48 -10
- package/dist/chunks/cost-tracker-Z2UZT2J5.js +28 -0
- package/dist/{customCommands-TOIJFZAL.js → chunks/customCommands-TYMYZRG5.js} +11 -8
- package/dist/chunks/engine-MRVF6FK6.js +39 -0
- package/dist/{env-XGKBLU3D.js → chunks/env-TJ5NOBEB.js} +7 -5
- package/dist/{kodeAgentSessionId-X6XWQW7B.js → chunks/kodeAgentSessionId-VTNISJ2L.js} +2 -4
- package/dist/chunks/kodeAgentSessionLoad-YB2RKBGJ.js +15 -0
- package/dist/chunks/kodeAgentSessionResume-DZSIVKVA.js +13 -0
- package/dist/chunks/kodeAgentStreamJson-X5PLS2S6.js +11 -0
- package/dist/{kodeAgentStreamJsonSession-UGEZJJEB.js → chunks/kodeAgentStreamJsonSession-RDXM4XYF.js} +38 -24
- package/dist/chunks/kodeAgentStreamJsonSession-RDXM4XYF.js.map +7 -0
- package/dist/{chunk-4RTX4AG4.js → chunks/kodeAgentStructuredStdio-SVGDSB4P.js} +14 -9
- package/dist/chunks/kodeAgentStructuredStdio-SVGDSB4P.js.map +7 -0
- package/dist/{kodeHooks-QWM36A3D.js → chunks/kodeHooks-RVKYRJHG.js} +11 -9
- package/dist/{llm-ZUQC4WYM.js → chunks/llm-62N6T5ZT.js} +1734 -1526
- package/dist/chunks/llm-62N6T5ZT.js.map +7 -0
- package/dist/chunks/llmLazy-ZUSSE3ZA.js +13 -0
- package/dist/{mentionProcessor-EE3XFHCJ.js → chunks/mentionProcessor-RJW5UPJD.js} +46 -16
- package/dist/chunks/mentionProcessor-RJW5UPJD.js.map +7 -0
- package/dist/{messages-EOYQKPGM.js → chunks/messages-EEWWLPHN.js} +2 -6
- package/dist/chunks/model-5TIEKQPD.js +37 -0
- package/dist/{openai-RRCWW33N.js → chunks/openai-XXK3YZG4.js} +13 -10
- package/dist/{outputStyles-62Q3VH2J.js → chunks/outputStyles-FAJTXN2A.js} +6 -9
- package/dist/chunks/permissions-HO7INPWM.js +27 -0
- package/dist/{pluginRuntime-6ETCZ2LL.js → chunks/pluginRuntime-C7K5ULK2.js} +31 -48
- package/dist/chunks/pluginRuntime-C7K5ULK2.js.map +7 -0
- package/dist/chunks/pluginValidation-DAM7WRTC.js +20 -0
- package/dist/chunks/registry-XYJXMOA5.js +60 -0
- package/dist/chunks/responsesStreaming-JNGE2P3D.js +8 -0
- package/dist/chunks/runNonTextPrintMode-SVBLCZQX.js +577 -0
- package/dist/chunks/runNonTextPrintMode-SVBLCZQX.js.map +7 -0
- package/dist/chunks/server-REXXF5IK.js +46 -0
- package/dist/{skillMarketplace-3RXQBVOL.js → chunks/skillMarketplace-N4HVHNST.js} +8 -6
- package/dist/chunks/src-OROQIWP3.js +44 -0
- package/dist/chunks/src-QXLGGMUW.js +1647 -0
- package/dist/chunks/src-QXLGGMUW.js.map +7 -0
- package/dist/{cli-DOPVY2CW.js → chunks/src-SSDT6MVP.js} +2659 -3384
- package/dist/chunks/src-SSDT6MVP.js.map +7 -0
- package/dist/chunks/theme-YBJUIMWK.js +10 -0
- package/dist/{toolPermissionContext-65L65VEZ.js → chunks/toolPermissionContext-MOCTRR7N.js} +2 -4
- package/dist/chunks/toolPermissionSettings-EV2EJAXL.js +18 -0
- package/dist/chunks/toolPermissionSettings-EV2EJAXL.js.map +7 -0
- package/dist/chunks/uuid-6577SO6X.js +7 -0
- package/dist/chunks/uuid-6577SO6X.js.map +7 -0
- package/dist/chunks/webOnlyMode-ALXX7UQY.js +66 -0
- package/dist/chunks/webOnlyMode-ALXX7UQY.js.map +7 -0
- package/dist/entrypoints/cli.js +10 -0
- package/dist/entrypoints/cli.js.map +7 -0
- package/dist/entrypoints/daemon.js +10 -0
- package/dist/entrypoints/daemon.js.map +7 -0
- package/dist/entrypoints/mcp.js +71 -0
- package/dist/entrypoints/mcp.js.map +7 -0
- package/dist/index.js +6 -7
- package/dist/index.js.map +3 -3
- package/dist/sdk/client.cjs +391 -0
- package/dist/sdk/client.cjs.map +7 -0
- package/dist/sdk/client.js +364 -0
- package/dist/sdk/client.js.map +7 -0
- package/dist/sdk/core.cjs +19932 -0
- package/dist/sdk/core.cjs.map +7 -0
- package/dist/sdk/core.js +19893 -0
- package/dist/sdk/core.js.map +7 -0
- package/dist/sdk/daemon-client.cjs +257 -0
- package/dist/sdk/daemon-client.cjs.map +7 -0
- package/dist/sdk/daemon-client.js +221 -0
- package/dist/sdk/daemon-client.js.map +7 -0
- package/dist/sdk/protocol.cjs +170 -0
- package/dist/sdk/protocol.cjs.map +7 -0
- package/dist/sdk/protocol.js +140 -0
- package/dist/sdk/protocol.js.map +7 -0
- package/dist/sdk/runtime-node.cjs +236 -0
- package/dist/sdk/runtime-node.cjs.map +7 -0
- package/dist/sdk/runtime-node.js +222 -0
- package/dist/sdk/runtime-node.js.map +7 -0
- package/dist/sdk/runtime.cjs +17 -0
- package/dist/sdk/runtime.cjs.map +7 -0
- package/dist/sdk/runtime.js +0 -0
- package/dist/sdk/runtime.js.map +7 -0
- package/dist/sdk/tools.cjs +30300 -0
- package/dist/sdk/tools.cjs.map +7 -0
- package/dist/sdk/tools.js +30282 -0
- package/dist/sdk/tools.js.map +7 -0
- package/dist/webui/assets/index-5hlfByVS.css +1 -0
- package/dist/webui/assets/index-BR9lm1lA.js +82 -0
- package/dist/webui/index.html +28 -0
- package/package.json +93 -22
- package/scripts/binary-utils.cjs +12 -4
- package/scripts/cli-acp-wrapper.cjs +3 -17
- package/scripts/cli-wrapper.cjs +5 -7
- package/scripts/postinstall.js +8 -4
- package/dist/REPL-CW7AYLVL.js +0 -42
- package/dist/acp-VEPJ74LT.js +0 -1357
- package/dist/acp-VEPJ74LT.js.map +0 -7
- package/dist/agentsValidate-7LH4HTNR.js.map +0 -7
- package/dist/ask-3NHFFUQG.js.map +0 -7
- package/dist/autoUpdater-ITPIHCOI.js +0 -17
- package/dist/chunk-3IN27HA5.js.map +0 -7
- package/dist/chunk-4FX3IVPT.js +0 -164
- package/dist/chunk-4FX3IVPT.js.map +0 -7
- package/dist/chunk-4RTX4AG4.js.map +0 -7
- package/dist/chunk-5PDP7R6N.js +0 -515
- package/dist/chunk-5PDP7R6N.js.map +0 -7
- package/dist/chunk-73WGVYLQ.js.map +0 -7
- package/dist/chunk-7M2YN6TU.js.map +0 -7
- package/dist/chunk-7U7L4NMD.js.map +0 -7
- package/dist/chunk-ABLVTESJ.js.map +0 -7
- package/dist/chunk-AFFSCMYS.js.map +0 -7
- package/dist/chunk-ARZSBOAO.js.map +0 -7
- package/dist/chunk-CIG63V4E.js +0 -72
- package/dist/chunk-CIG63V4E.js.map +0 -7
- package/dist/chunk-CM3EGTG6.js +0 -1609
- package/dist/chunk-CM3EGTG6.js.map +0 -7
- package/dist/chunk-DZE5YA7L.js.map +0 -7
- package/dist/chunk-E6YNABER.js.map +0 -7
- package/dist/chunk-EZXMVTDU.js.map +0 -7
- package/dist/chunk-F2SJXUDI.js +0 -148
- package/dist/chunk-F2SJXUDI.js.map +0 -7
- package/dist/chunk-FC5ZCKBI.js +0 -30167
- package/dist/chunk-FC5ZCKBI.js.map +0 -7
- package/dist/chunk-HCBELH4J.js +0 -145
- package/dist/chunk-HCBELH4J.js.map +0 -7
- package/dist/chunk-HN4E4UUQ.js.map +0 -7
- package/dist/chunk-IZVMU4S2.js +0 -654
- package/dist/chunk-IZVMU4S2.js.map +0 -7
- package/dist/chunk-K2CWOTI2.js.map +0 -7
- package/dist/chunk-LC4TVOCZ.js +0 -835
- package/dist/chunk-LC4TVOCZ.js.map +0 -7
- package/dist/chunk-MIW7N2MY.js +0 -2613
- package/dist/chunk-MIW7N2MY.js.map +0 -7
- package/dist/chunk-MWRSY4X6.js.map +0 -7
- package/dist/chunk-ND3XWFO6.js +0 -34
- package/dist/chunk-ND3XWFO6.js.map +0 -7
- package/dist/chunk-QVLYOPO5.js.map +0 -7
- package/dist/chunk-RZWOUA25.js.map +0 -7
- package/dist/chunk-S6HRABTA.js.map +0 -7
- package/dist/chunk-STSX7GIX.js.map +0 -7
- package/dist/chunk-UHYRLID6.js.map +0 -7
- package/dist/chunk-UKHTVRJM.js +0 -47
- package/dist/chunk-UKHTVRJM.js.map +0 -7
- package/dist/chunk-UYXEDKOZ.js +0 -24
- package/dist/chunk-UYXEDKOZ.js.map +0 -7
- package/dist/chunk-W7GRKO7Q.js.map +0 -7
- package/dist/chunk-WVHORZQ5.js +0 -17
- package/dist/chunk-WVHORZQ5.js.map +0 -7
- package/dist/chunk-WWUWDNWW.js +0 -49
- package/dist/chunk-WWUWDNWW.js.map +0 -7
- package/dist/chunk-YC6LJCDE.js.map +0 -7
- package/dist/chunk-YXYYDIMI.js +0 -2931
- package/dist/chunk-YXYYDIMI.js.map +0 -7
- package/dist/chunk-ZVDRDPII.js.map +0 -7
- package/dist/cli-DOPVY2CW.js.map +0 -7
- package/dist/commands-2BF2CJ3A.js +0 -46
- package/dist/context-6FXPETYH.js +0 -30
- package/dist/costTracker-6SL26FDB.js +0 -19
- package/dist/kodeAgentSessionLoad-MITZADPB.js +0 -18
- package/dist/kodeAgentSessionResume-GVRWB4WO.js +0 -16
- package/dist/kodeAgentStreamJson-NXFN7TXH.js +0 -13
- package/dist/kodeAgentStreamJsonSession-UGEZJJEB.js.map +0 -7
- package/dist/kodeAgentStructuredStdio-HGWJT7CU.js +0 -10
- package/dist/llm-ZUQC4WYM.js.map +0 -7
- package/dist/llmLazy-54QQHA54.js +0 -15
- package/dist/loader-FYHJQES5.js +0 -28
- package/dist/mcp-J332IKT3.js +0 -49
- package/dist/mentionProcessor-EE3XFHCJ.js.map +0 -7
- package/dist/model-FV3JDJKH.js +0 -30
- package/dist/pluginRuntime-6ETCZ2LL.js.map +0 -7
- package/dist/pluginValidation-I4YKUWGS.js +0 -17
- package/dist/prompts-ZLEKDD77.js +0 -48
- package/dist/query-VFRJPBGD.js +0 -50
- package/dist/responsesStreaming-AW344PQO.js +0 -10
- package/dist/ripgrep-3NTIKQYW.js +0 -17
- package/dist/state-P5G6CO5V.js +0 -16
- package/dist/theme-3LWP3BG7.js +0 -14
- package/dist/toolPermissionSettings-3ROBVTUK.js +0 -18
- package/dist/tools-RO7HSSE5.js +0 -47
- package/dist/userInput-JSBJRFSK.js +0 -311
- package/dist/userInput-JSBJRFSK.js.map +0 -7
- package/dist/uuid-QN2CNKKN.js +0 -9
- /package/dist/{REPL-CW7AYLVL.js.map → chunks/Doctor-M3J7GRTJ.js.map} +0 -0
- /package/dist/{autoUpdater-ITPIHCOI.js.map → chunks/REPL-RQ6LO6S7.js.map} +0 -0
- /package/dist/{chunk-JC6NCUG5.js.map → chunks/ResumeConversation-6DMVBEGH.js.map} +0 -0
- /package/dist/{commands-2BF2CJ3A.js.map → chunks/agentLoader-FCRG3TFJ.js.map} +0 -0
- /package/dist/{config-RUSD6G5Y.js.map → chunks/autoUpdater-CNESBOKO.js.map} +0 -0
- /package/dist/{context-6FXPETYH.js.map → chunks/chunk-4CRUCZR4.js.map} +0 -0
- /package/dist/{costTracker-6SL26FDB.js.map → chunks/chunk-54KOYG5C.js.map} +0 -0
- /package/dist/{customCommands-TOIJFZAL.js.map → chunks/chunk-GCQCAXJZ.js.map} +0 -0
- /package/dist/{env-XGKBLU3D.js.map → chunks/chunk-LB6TCPDI.js.map} +0 -0
- /package/dist/{kodeAgentSessionId-X6XWQW7B.js.map → chunks/client-SILZNM5N.js.map} +0 -0
- /package/dist/{kodeAgentSessionLoad-MITZADPB.js.map → chunks/config-25HRTPSP.js.map} +0 -0
- /package/dist/{kodeAgentSessionResume-GVRWB4WO.js.map → chunks/cost-tracker-Z2UZT2J5.js.map} +0 -0
- /package/dist/{kodeAgentStreamJson-NXFN7TXH.js.map → chunks/customCommands-TYMYZRG5.js.map} +0 -0
- /package/dist/{kodeAgentStructuredStdio-HGWJT7CU.js.map → chunks/engine-MRVF6FK6.js.map} +0 -0
- /package/dist/{kodeHooks-QWM36A3D.js.map → chunks/env-TJ5NOBEB.js.map} +0 -0
- /package/dist/{llmLazy-54QQHA54.js.map → chunks/kodeAgentSessionId-VTNISJ2L.js.map} +0 -0
- /package/dist/{loader-FYHJQES5.js.map → chunks/kodeAgentSessionLoad-YB2RKBGJ.js.map} +0 -0
- /package/dist/{mcp-J332IKT3.js.map → chunks/kodeAgentSessionResume-DZSIVKVA.js.map} +0 -0
- /package/dist/{messages-EOYQKPGM.js.map → chunks/kodeAgentStreamJson-X5PLS2S6.js.map} +0 -0
- /package/dist/{model-FV3JDJKH.js.map → chunks/kodeHooks-RVKYRJHG.js.map} +0 -0
- /package/dist/{openai-RRCWW33N.js.map → chunks/llmLazy-ZUSSE3ZA.js.map} +0 -0
- /package/dist/{outputStyles-62Q3VH2J.js.map → chunks/messages-EEWWLPHN.js.map} +0 -0
- /package/dist/{pluginValidation-I4YKUWGS.js.map → chunks/model-5TIEKQPD.js.map} +0 -0
- /package/dist/{prompts-ZLEKDD77.js.map → chunks/openai-XXK3YZG4.js.map} +0 -0
- /package/dist/{query-VFRJPBGD.js.map → chunks/outputStyles-FAJTXN2A.js.map} +0 -0
- /package/dist/{responsesStreaming-AW344PQO.js.map → chunks/permissions-HO7INPWM.js.map} +0 -0
- /package/dist/{ripgrep-3NTIKQYW.js.map → chunks/pluginValidation-DAM7WRTC.js.map} +0 -0
- /package/dist/{skillMarketplace-3RXQBVOL.js.map → chunks/registry-XYJXMOA5.js.map} +0 -0
- /package/dist/{state-P5G6CO5V.js.map → chunks/responsesStreaming-JNGE2P3D.js.map} +0 -0
- /package/dist/{theme-3LWP3BG7.js.map → chunks/server-REXXF5IK.js.map} +0 -0
- /package/dist/{toolPermissionContext-65L65VEZ.js.map → chunks/skillMarketplace-N4HVHNST.js.map} +0 -0
- /package/dist/{toolPermissionSettings-3ROBVTUK.js.map → chunks/src-OROQIWP3.js.map} +0 -0
- /package/dist/{tools-RO7HSSE5.js.map → chunks/theme-YBJUIMWK.js.map} +0 -0
- /package/dist/{uuid-QN2CNKKN.js.map → chunks/toolPermissionContext-MOCTRR7N.js.map} +0 -0
|
@@ -0,0 +1,4520 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Select
|
|
3
|
+
} from "./chunk-3TNIOEBO.js";
|
|
4
|
+
import {
|
|
5
|
+
PressEnterToContinue
|
|
6
|
+
} from "./chunk-2QONJ5MG.js";
|
|
7
|
+
import {
|
|
8
|
+
models_default
|
|
9
|
+
} from "./chunk-2WEXPKHH.js";
|
|
10
|
+
import {
|
|
11
|
+
fetchCustomModels,
|
|
12
|
+
providers
|
|
13
|
+
} from "./chunk-IFCIADS3.js";
|
|
14
|
+
import {
|
|
15
|
+
normalizeLineEndings,
|
|
16
|
+
shouldAggregatePasteChunk,
|
|
17
|
+
shouldTreatAsSpecialPaste
|
|
18
|
+
} from "./chunk-7RRW4NTB.js";
|
|
19
|
+
import {
|
|
20
|
+
getTheme
|
|
21
|
+
} from "./chunk-T7RB5V5J.js";
|
|
22
|
+
import {
|
|
23
|
+
wrapText
|
|
24
|
+
} from "./chunk-GELCZWMB.js";
|
|
25
|
+
import {
|
|
26
|
+
verifyApiKey
|
|
27
|
+
} from "./chunk-BHDHXOXB.js";
|
|
28
|
+
import {
|
|
29
|
+
getModelManager
|
|
30
|
+
} from "./chunk-6ZWEOSEI.js";
|
|
31
|
+
import {
|
|
32
|
+
debug
|
|
33
|
+
} from "./chunk-YIO5EBMQ.js";
|
|
34
|
+
import {
|
|
35
|
+
ASCII_LOGO,
|
|
36
|
+
PRODUCT_NAME
|
|
37
|
+
} from "./chunk-3OEJVB5A.js";
|
|
38
|
+
import {
|
|
39
|
+
DEFAULT_GLOBAL_CONFIG,
|
|
40
|
+
getGlobalConfig,
|
|
41
|
+
saveGlobalConfig,
|
|
42
|
+
setAllPointersToModel,
|
|
43
|
+
setModelPointer,
|
|
44
|
+
testGPT5Connection,
|
|
45
|
+
validateGPT5Config
|
|
46
|
+
} from "./chunk-XR2W3MAM.js";
|
|
47
|
+
import {
|
|
48
|
+
__export
|
|
49
|
+
} from "./chunk-54KOYG5C.js";
|
|
50
|
+
|
|
51
|
+
// apps/cli/src/ui/hooks/useExitOnCtrlCD.ts
|
|
52
|
+
import { useInput } from "ink";
|
|
53
|
+
|
|
54
|
+
// apps/cli/src/ui/hooks/useDoublePress.ts
|
|
55
|
+
import { useRef } from "react";
|
|
56
|
+
var DOUBLE_PRESS_TIMEOUT_MS = 2e3;
|
|
57
|
+
function useDoublePress(setPending, onDoublePress, onFirstPress) {
|
|
58
|
+
const lastPressRef = useRef(0);
|
|
59
|
+
const timeoutRef = useRef(void 0);
|
|
60
|
+
return () => {
|
|
61
|
+
const now = Date.now();
|
|
62
|
+
const timeSinceLastPress = now - lastPressRef.current;
|
|
63
|
+
if (timeSinceLastPress <= DOUBLE_PRESS_TIMEOUT_MS && timeoutRef.current) {
|
|
64
|
+
if (timeoutRef.current) {
|
|
65
|
+
clearTimeout(timeoutRef.current);
|
|
66
|
+
timeoutRef.current = void 0;
|
|
67
|
+
}
|
|
68
|
+
onDoublePress();
|
|
69
|
+
setPending(false);
|
|
70
|
+
} else {
|
|
71
|
+
onFirstPress?.();
|
|
72
|
+
setPending(true);
|
|
73
|
+
timeoutRef.current = setTimeout(
|
|
74
|
+
() => setPending(false),
|
|
75
|
+
DOUBLE_PRESS_TIMEOUT_MS
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
lastPressRef.current = now;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// apps/cli/src/ui/hooks/useExitOnCtrlCD.ts
|
|
83
|
+
import { useState } from "react";
|
|
84
|
+
function useExitOnCtrlCD(onExit) {
|
|
85
|
+
const [exitState, setExitState] = useState({
|
|
86
|
+
pending: false,
|
|
87
|
+
keyName: null
|
|
88
|
+
});
|
|
89
|
+
const handleCtrlC = useDoublePress(
|
|
90
|
+
(pending) => setExitState({ pending, keyName: "Ctrl-C" }),
|
|
91
|
+
onExit
|
|
92
|
+
);
|
|
93
|
+
const handleCtrlD = useDoublePress(
|
|
94
|
+
(pending) => setExitState({ pending, keyName: "Ctrl-D" }),
|
|
95
|
+
onExit
|
|
96
|
+
);
|
|
97
|
+
useInput((input, key) => {
|
|
98
|
+
if (key.ctrl && input === "c") handleCtrlC();
|
|
99
|
+
if (key.ctrl && input === "d") handleCtrlD();
|
|
100
|
+
});
|
|
101
|
+
return exitState;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// apps/cli/src/utils/terminal.ts
|
|
105
|
+
function setTerminalTitle(title) {
|
|
106
|
+
if (process.platform === "win32") {
|
|
107
|
+
process.title = title ? `\u2733 ${title}` : title;
|
|
108
|
+
} else {
|
|
109
|
+
process.stdout.write(`\x1B]0;${title ? `\u2733 ${title}` : ""}\x07`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function clearTerminal() {
|
|
113
|
+
return new Promise((resolve) => {
|
|
114
|
+
process.stdout.write("\x1B[2J\x1B[3J\x1B[H", () => {
|
|
115
|
+
resolve();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// apps/cli/src/ui/components/Onboarding.tsx
|
|
121
|
+
import React20, { useState as useState4 } from "react";
|
|
122
|
+
import { Box as Box17, Newline as Newline8, Text as Text18, useInput as useInput5 } from "ink";
|
|
123
|
+
import { OrderedList } from "@inkjs/ui";
|
|
124
|
+
|
|
125
|
+
// apps/cli/src/ui/components/Logo.tsx
|
|
126
|
+
import { Box, Text } from "ink";
|
|
127
|
+
import * as React from "react";
|
|
128
|
+
var MIN_LOGO_WIDTH = 60;
|
|
129
|
+
function Logo({
|
|
130
|
+
mcpClients,
|
|
131
|
+
updateBannerVersion,
|
|
132
|
+
terminalColumns
|
|
133
|
+
}) {
|
|
134
|
+
const theme = getTheme();
|
|
135
|
+
const connected = mcpClients.filter((c) => c.type === "connected");
|
|
136
|
+
const failed = mcpClients.filter((c) => c.type !== "connected");
|
|
137
|
+
const separatorWidth = Math.min(terminalColumns || 80, 80) - 16;
|
|
138
|
+
const separator = "\u2500".repeat(Math.max(separatorWidth, 20));
|
|
139
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, updateBannerVersion && /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "Update ", updateBannerVersion, " available: npm i -g @shareai-lab/kode@latest")), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { color: theme.kode }, ASCII_LOGO)), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "/init", " ", "/help", " ", /* @__PURE__ */ React.createElement(Text, { color: theme.bashBorder }, "!"), "shell", " ", /* @__PURE__ */ React.createElement(Text, { color: theme.notingBorder }, "#"), "note", " ", "@file", " ", "opt+m", " ", "opt+g")), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginTop: 2 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "\u2500\u2500 MCP Servers ", separator), /* @__PURE__ */ React.createElement(Box, { marginTop: 1, paddingLeft: 3 }, mcpClients.length === 0 ? /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "No servers configured - run: kode mcp add ", "<name>") : /* @__PURE__ */ React.createElement(React.Fragment, null, connected.map((c) => /* @__PURE__ */ React.createElement(Text, { key: c.name }, /* @__PURE__ */ React.createElement(Text, { color: theme.success }, c.name), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " "))), failed.map((c) => /* @__PURE__ */ React.createElement(Text, { key: c.name }, /* @__PURE__ */ React.createElement(Text, { color: theme.error }, c.name), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " ")))))));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// apps/cli/src/ui/components/StructuredDiff.tsx
|
|
143
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
144
|
+
import * as React2 from "react";
|
|
145
|
+
import { useMemo } from "react";
|
|
146
|
+
function StructuredDiff({
|
|
147
|
+
patch,
|
|
148
|
+
dim,
|
|
149
|
+
width,
|
|
150
|
+
overrideTheme
|
|
151
|
+
}) {
|
|
152
|
+
const diff = useMemo(
|
|
153
|
+
() => formatDiff(patch.lines, patch.oldStart, width, dim, overrideTheme),
|
|
154
|
+
[patch.lines, patch.oldStart, width, dim, overrideTheme]
|
|
155
|
+
);
|
|
156
|
+
return diff.map((_, i) => /* @__PURE__ */ React2.createElement(Box2, { key: i }, _));
|
|
157
|
+
}
|
|
158
|
+
function formatDiff(lines, startingLineNumber, width, dim, overrideTheme) {
|
|
159
|
+
const theme = getTheme(overrideTheme);
|
|
160
|
+
const ls = numberDiffLines(
|
|
161
|
+
lines.map((code) => {
|
|
162
|
+
if (code.startsWith("+")) {
|
|
163
|
+
return {
|
|
164
|
+
code: " " + code.slice(1),
|
|
165
|
+
i: 0,
|
|
166
|
+
type: "add"
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (code.startsWith("-")) {
|
|
170
|
+
return {
|
|
171
|
+
code: " " + code.slice(1),
|
|
172
|
+
i: 0,
|
|
173
|
+
type: "remove"
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
return { code, i: 0, type: "nochange" };
|
|
177
|
+
}),
|
|
178
|
+
startingLineNumber
|
|
179
|
+
);
|
|
180
|
+
const maxLineNumber = Math.max(...ls.map(({ i }) => i));
|
|
181
|
+
const maxWidth = maxLineNumber.toString().length;
|
|
182
|
+
return ls.flatMap(({ type, code, i }) => {
|
|
183
|
+
const wrappedLines = wrapText(code, width - maxWidth);
|
|
184
|
+
return wrappedLines.map((line, lineIndex) => {
|
|
185
|
+
const key = `${type}-${i}-${lineIndex}`;
|
|
186
|
+
switch (type) {
|
|
187
|
+
case "add":
|
|
188
|
+
return /* @__PURE__ */ React2.createElement(React2.Fragment, { key }, /* @__PURE__ */ React2.createElement(Text2, null, /* @__PURE__ */ React2.createElement(
|
|
189
|
+
LineNumber,
|
|
190
|
+
{
|
|
191
|
+
i: lineIndex === 0 ? i : void 0,
|
|
192
|
+
width: maxWidth
|
|
193
|
+
}
|
|
194
|
+
), /* @__PURE__ */ React2.createElement(
|
|
195
|
+
Text2,
|
|
196
|
+
{
|
|
197
|
+
color: overrideTheme ? theme.text : void 0,
|
|
198
|
+
backgroundColor: dim ? theme.diff.addedDimmed : theme.diff.added,
|
|
199
|
+
dimColor: dim
|
|
200
|
+
},
|
|
201
|
+
line
|
|
202
|
+
)));
|
|
203
|
+
case "remove":
|
|
204
|
+
return /* @__PURE__ */ React2.createElement(React2.Fragment, { key }, /* @__PURE__ */ React2.createElement(Text2, null, /* @__PURE__ */ React2.createElement(
|
|
205
|
+
LineNumber,
|
|
206
|
+
{
|
|
207
|
+
i: lineIndex === 0 ? i : void 0,
|
|
208
|
+
width: maxWidth
|
|
209
|
+
}
|
|
210
|
+
), /* @__PURE__ */ React2.createElement(
|
|
211
|
+
Text2,
|
|
212
|
+
{
|
|
213
|
+
color: overrideTheme ? theme.text : void 0,
|
|
214
|
+
backgroundColor: dim ? theme.diff.removedDimmed : theme.diff.removed,
|
|
215
|
+
dimColor: dim
|
|
216
|
+
},
|
|
217
|
+
line
|
|
218
|
+
)));
|
|
219
|
+
case "nochange":
|
|
220
|
+
return /* @__PURE__ */ React2.createElement(React2.Fragment, { key }, /* @__PURE__ */ React2.createElement(Text2, null, /* @__PURE__ */ React2.createElement(
|
|
221
|
+
LineNumber,
|
|
222
|
+
{
|
|
223
|
+
i: lineIndex === 0 ? i : void 0,
|
|
224
|
+
width: maxWidth
|
|
225
|
+
}
|
|
226
|
+
), /* @__PURE__ */ React2.createElement(
|
|
227
|
+
Text2,
|
|
228
|
+
{
|
|
229
|
+
color: overrideTheme ? theme.text : void 0,
|
|
230
|
+
dimColor: dim
|
|
231
|
+
},
|
|
232
|
+
line
|
|
233
|
+
)));
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
function LineNumber({
|
|
239
|
+
i,
|
|
240
|
+
width
|
|
241
|
+
}) {
|
|
242
|
+
return /* @__PURE__ */ React2.createElement(Text2, { color: getTheme().secondaryText }, i !== void 0 ? i.toString().padStart(width) : " ".repeat(width), " ");
|
|
243
|
+
}
|
|
244
|
+
function numberDiffLines(diff, startLine) {
|
|
245
|
+
let i = startLine;
|
|
246
|
+
const result = [];
|
|
247
|
+
const queue = [...diff];
|
|
248
|
+
while (queue.length > 0) {
|
|
249
|
+
const { code, type } = queue.shift();
|
|
250
|
+
const line = {
|
|
251
|
+
code,
|
|
252
|
+
type,
|
|
253
|
+
i
|
|
254
|
+
};
|
|
255
|
+
switch (type) {
|
|
256
|
+
case "nochange":
|
|
257
|
+
i++;
|
|
258
|
+
result.push(line);
|
|
259
|
+
break;
|
|
260
|
+
case "add":
|
|
261
|
+
i++;
|
|
262
|
+
result.push(line);
|
|
263
|
+
break;
|
|
264
|
+
case "remove": {
|
|
265
|
+
result.push(line);
|
|
266
|
+
let numRemoved = 0;
|
|
267
|
+
while (queue[0]?.type === "remove") {
|
|
268
|
+
i++;
|
|
269
|
+
const { code: code2, type: type2 } = queue.shift();
|
|
270
|
+
const line2 = {
|
|
271
|
+
code: code2,
|
|
272
|
+
type: type2,
|
|
273
|
+
i
|
|
274
|
+
};
|
|
275
|
+
result.push(line2);
|
|
276
|
+
numRemoved++;
|
|
277
|
+
}
|
|
278
|
+
i -= numRemoved;
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return result;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// apps/cli/src/ui/components/ModelSelector/ModelSelector.tsx
|
|
287
|
+
import * as React19 from "react";
|
|
288
|
+
|
|
289
|
+
// apps/cli/src/ui/components/ModelSelector/ModelSelectorView.tsx
|
|
290
|
+
import * as React18 from "react";
|
|
291
|
+
import { Box as Box16, Text as Text17 } from "ink";
|
|
292
|
+
import figures from "figures";
|
|
293
|
+
|
|
294
|
+
// apps/cli/src/ui/ui/components/model-selector/screens/ApiKeyScreen.tsx
|
|
295
|
+
import React5 from "react";
|
|
296
|
+
import { Box as Box3, Newline, Text as Text4 } from "ink";
|
|
297
|
+
|
|
298
|
+
// apps/cli/src/ui/components/TextInput.tsx
|
|
299
|
+
import React4 from "react";
|
|
300
|
+
import { Text as Text3, useInput as useInput2 } from "ink";
|
|
301
|
+
import chalk from "chalk";
|
|
302
|
+
|
|
303
|
+
// apps/cli/src/ui/hooks/useTextInput.ts
|
|
304
|
+
import { useState as useState2 } from "react";
|
|
305
|
+
|
|
306
|
+
// apps/cli/src/utils/Cursor/MeasuredText.ts
|
|
307
|
+
import wrapAnsi from "wrap-ansi";
|
|
308
|
+
var WrappedLine = class {
|
|
309
|
+
constructor(text, startOffset, isPrecededByNewline, endsWithNewline = false) {
|
|
310
|
+
this.text = text;
|
|
311
|
+
this.startOffset = startOffset;
|
|
312
|
+
this.isPrecededByNewline = isPrecededByNewline;
|
|
313
|
+
this.endsWithNewline = endsWithNewline;
|
|
314
|
+
}
|
|
315
|
+
equals(other) {
|
|
316
|
+
return this.text === other.text && this.startOffset === other.startOffset;
|
|
317
|
+
}
|
|
318
|
+
get length() {
|
|
319
|
+
return this.text.length + (this.endsWithNewline ? 1 : 0);
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
var MeasuredText = class {
|
|
323
|
+
constructor(text, columns) {
|
|
324
|
+
this.text = text;
|
|
325
|
+
this.columns = columns;
|
|
326
|
+
this.wrappedLines = this.measureWrappedText();
|
|
327
|
+
}
|
|
328
|
+
wrappedLines;
|
|
329
|
+
measureWrappedText() {
|
|
330
|
+
const wrappedText = wrapAnsi(this.text, this.columns, {
|
|
331
|
+
hard: true,
|
|
332
|
+
trim: false
|
|
333
|
+
});
|
|
334
|
+
const wrappedLines = [];
|
|
335
|
+
let searchOffset = 0;
|
|
336
|
+
let lastNewLinePos = -1;
|
|
337
|
+
const lines = wrappedText.split("\n");
|
|
338
|
+
for (let i = 0; i < lines.length; i++) {
|
|
339
|
+
const text = lines[i];
|
|
340
|
+
const isPrecededByNewline = (startOffset) => i == 0 || startOffset > 0 && this.text[startOffset - 1] === "\n";
|
|
341
|
+
if (text.length === 0) {
|
|
342
|
+
lastNewLinePos = this.text.indexOf("\n", lastNewLinePos + 1);
|
|
343
|
+
if (lastNewLinePos !== -1) {
|
|
344
|
+
const startOffset = lastNewLinePos;
|
|
345
|
+
const endsWithNewline = true;
|
|
346
|
+
wrappedLines.push(
|
|
347
|
+
new WrappedLine(
|
|
348
|
+
text,
|
|
349
|
+
startOffset,
|
|
350
|
+
isPrecededByNewline(startOffset),
|
|
351
|
+
endsWithNewline
|
|
352
|
+
)
|
|
353
|
+
);
|
|
354
|
+
} else {
|
|
355
|
+
const startOffset = this.text.length;
|
|
356
|
+
wrappedLines.push(
|
|
357
|
+
new WrappedLine(
|
|
358
|
+
text,
|
|
359
|
+
startOffset,
|
|
360
|
+
isPrecededByNewline(startOffset),
|
|
361
|
+
false
|
|
362
|
+
)
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
} else {
|
|
366
|
+
const startOffset = this.text.indexOf(text, searchOffset);
|
|
367
|
+
if (startOffset === -1) {
|
|
368
|
+
debug.error("CURSOR_WRAP_MISMATCH", {
|
|
369
|
+
currentText: text,
|
|
370
|
+
originalText: this.text,
|
|
371
|
+
searchOffset,
|
|
372
|
+
wrappedText
|
|
373
|
+
});
|
|
374
|
+
throw new Error("Failed to find wrapped line in original text");
|
|
375
|
+
}
|
|
376
|
+
searchOffset = startOffset + text.length;
|
|
377
|
+
const potentialNewlinePos = startOffset + text.length;
|
|
378
|
+
const endsWithNewline = potentialNewlinePos < this.text.length && this.text[potentialNewlinePos] === "\n";
|
|
379
|
+
if (endsWithNewline) {
|
|
380
|
+
lastNewLinePos = potentialNewlinePos;
|
|
381
|
+
}
|
|
382
|
+
wrappedLines.push(
|
|
383
|
+
new WrappedLine(
|
|
384
|
+
text,
|
|
385
|
+
startOffset,
|
|
386
|
+
isPrecededByNewline(startOffset),
|
|
387
|
+
endsWithNewline
|
|
388
|
+
)
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return wrappedLines;
|
|
393
|
+
}
|
|
394
|
+
getWrappedText() {
|
|
395
|
+
return this.wrappedLines.map(
|
|
396
|
+
(line) => line.isPrecededByNewline ? line.text : line.text.trimStart()
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
getLine(line) {
|
|
400
|
+
return this.wrappedLines[Math.max(0, Math.min(line, this.wrappedLines.length - 1))];
|
|
401
|
+
}
|
|
402
|
+
getOffsetFromPosition(position) {
|
|
403
|
+
const wrappedLine = this.getLine(position.line);
|
|
404
|
+
const startOffsetPlusColumn = wrappedLine.startOffset + position.column;
|
|
405
|
+
if (wrappedLine.text.length === 0 && wrappedLine.endsWithNewline) {
|
|
406
|
+
return wrappedLine.startOffset;
|
|
407
|
+
}
|
|
408
|
+
const lineEnd = wrappedLine.startOffset + wrappedLine.text.length;
|
|
409
|
+
const maxOffset = wrappedLine.endsWithNewline ? lineEnd + 1 : lineEnd;
|
|
410
|
+
return Math.min(startOffsetPlusColumn, maxOffset);
|
|
411
|
+
}
|
|
412
|
+
getLineLength(line) {
|
|
413
|
+
const currentLine = this.getLine(line);
|
|
414
|
+
const nextLine = this.getLine(line + 1);
|
|
415
|
+
if (nextLine.equals(currentLine)) {
|
|
416
|
+
return this.text.length - currentLine.startOffset;
|
|
417
|
+
}
|
|
418
|
+
return nextLine.startOffset - currentLine.startOffset - 1;
|
|
419
|
+
}
|
|
420
|
+
getPositionFromOffset(offset) {
|
|
421
|
+
const lines = this.wrappedLines;
|
|
422
|
+
for (let line2 = 0; line2 < lines.length; line2++) {
|
|
423
|
+
const currentLine = lines[line2];
|
|
424
|
+
const nextLine = lines[line2 + 1];
|
|
425
|
+
if (offset >= currentLine.startOffset && (!nextLine || offset < nextLine.startOffset)) {
|
|
426
|
+
const leadingWhitepace = currentLine.isPrecededByNewline ? 0 : currentLine.text.length - currentLine.text.trimStart().length;
|
|
427
|
+
const column = Math.max(
|
|
428
|
+
0,
|
|
429
|
+
Math.min(
|
|
430
|
+
offset - currentLine.startOffset - leadingWhitepace,
|
|
431
|
+
currentLine.text.length
|
|
432
|
+
)
|
|
433
|
+
);
|
|
434
|
+
return {
|
|
435
|
+
line: line2,
|
|
436
|
+
column
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
const line = lines.length - 1;
|
|
441
|
+
return {
|
|
442
|
+
line,
|
|
443
|
+
column: this.wrappedLines[line].text.length
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
get lineCount() {
|
|
447
|
+
return this.wrappedLines.length;
|
|
448
|
+
}
|
|
449
|
+
equals(other) {
|
|
450
|
+
return this.text === other.text && this.columns === other.columns;
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
// apps/cli/src/utils/Cursor.ts
|
|
455
|
+
var Cursor = class _Cursor {
|
|
456
|
+
constructor(measuredText, offset = 0, selection = 0) {
|
|
457
|
+
this.measuredText = measuredText;
|
|
458
|
+
this.selection = selection;
|
|
459
|
+
this.offset = Math.max(0, Math.min(this.measuredText.text.length, offset));
|
|
460
|
+
}
|
|
461
|
+
offset;
|
|
462
|
+
static fromText(text, columns, offset = 0, selection = 0) {
|
|
463
|
+
return new _Cursor(new MeasuredText(text, columns - 1), offset, selection);
|
|
464
|
+
}
|
|
465
|
+
render(cursorChar, mask, invert) {
|
|
466
|
+
const { line, column } = this.getPosition();
|
|
467
|
+
return this.measuredText.getWrappedText().map((text, currentLine, allLines) => {
|
|
468
|
+
let displayText = text;
|
|
469
|
+
if (mask && currentLine === allLines.length - 1) {
|
|
470
|
+
const lastSixStart = Math.max(0, text.length - 6);
|
|
471
|
+
displayText = mask.repeat(lastSixStart) + text.slice(lastSixStart);
|
|
472
|
+
}
|
|
473
|
+
if (line != currentLine) return displayText.trimEnd();
|
|
474
|
+
return displayText.slice(0, column) + invert(displayText[column] || cursorChar) + displayText.trimEnd().slice(column + 1);
|
|
475
|
+
}).join("\n");
|
|
476
|
+
}
|
|
477
|
+
left() {
|
|
478
|
+
return new _Cursor(this.measuredText, this.offset - 1);
|
|
479
|
+
}
|
|
480
|
+
right() {
|
|
481
|
+
return new _Cursor(this.measuredText, this.offset + 1);
|
|
482
|
+
}
|
|
483
|
+
up() {
|
|
484
|
+
const { line, column } = this.getPosition();
|
|
485
|
+
if (line == 0) {
|
|
486
|
+
return new _Cursor(this.measuredText, 0, 0);
|
|
487
|
+
}
|
|
488
|
+
const newOffset = this.getOffset({ line: line - 1, column });
|
|
489
|
+
return new _Cursor(this.measuredText, newOffset, 0);
|
|
490
|
+
}
|
|
491
|
+
down() {
|
|
492
|
+
const { line, column } = this.getPosition();
|
|
493
|
+
if (line >= this.measuredText.lineCount - 1) {
|
|
494
|
+
return new _Cursor(this.measuredText, this.text.length, 0);
|
|
495
|
+
}
|
|
496
|
+
const newOffset = this.getOffset({ line: line + 1, column });
|
|
497
|
+
return new _Cursor(this.measuredText, newOffset, 0);
|
|
498
|
+
}
|
|
499
|
+
startOfLine() {
|
|
500
|
+
const { line } = this.getPosition();
|
|
501
|
+
return new _Cursor(
|
|
502
|
+
this.measuredText,
|
|
503
|
+
this.getOffset({
|
|
504
|
+
line,
|
|
505
|
+
column: 0
|
|
506
|
+
}),
|
|
507
|
+
0
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
endOfLine() {
|
|
511
|
+
const { line } = this.getPosition();
|
|
512
|
+
const column = this.measuredText.getLineLength(line);
|
|
513
|
+
const offset = this.getOffset({ line, column });
|
|
514
|
+
return new _Cursor(this.measuredText, offset, 0);
|
|
515
|
+
}
|
|
516
|
+
nextWord() {
|
|
517
|
+
let nextCursor = this;
|
|
518
|
+
while (nextCursor.isOverWordChar() && !nextCursor.isAtEnd()) {
|
|
519
|
+
nextCursor = nextCursor.right();
|
|
520
|
+
}
|
|
521
|
+
while (!nextCursor.isOverWordChar() && !nextCursor.isAtEnd()) {
|
|
522
|
+
nextCursor = nextCursor.right();
|
|
523
|
+
}
|
|
524
|
+
return nextCursor;
|
|
525
|
+
}
|
|
526
|
+
prevWord() {
|
|
527
|
+
let cursor = this;
|
|
528
|
+
if (!cursor.left().isOverWordChar()) {
|
|
529
|
+
cursor = cursor.left();
|
|
530
|
+
}
|
|
531
|
+
while (!cursor.isOverWordChar() && !cursor.isAtStart()) {
|
|
532
|
+
cursor = cursor.left();
|
|
533
|
+
}
|
|
534
|
+
if (cursor.isOverWordChar()) {
|
|
535
|
+
while (cursor.left().isOverWordChar() && !cursor.isAtStart()) {
|
|
536
|
+
cursor = cursor.left();
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return cursor;
|
|
540
|
+
}
|
|
541
|
+
modifyText(end, insertString = "") {
|
|
542
|
+
const startOffset = this.offset;
|
|
543
|
+
const endOffset = end.offset;
|
|
544
|
+
const newText = this.text.slice(0, startOffset) + insertString + this.text.slice(endOffset);
|
|
545
|
+
return _Cursor.fromText(
|
|
546
|
+
newText,
|
|
547
|
+
this.columns,
|
|
548
|
+
startOffset + insertString.length
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
insert(insertString) {
|
|
552
|
+
const newCursor = this.modifyText(this, insertString);
|
|
553
|
+
return newCursor;
|
|
554
|
+
}
|
|
555
|
+
del() {
|
|
556
|
+
if (this.isAtEnd()) {
|
|
557
|
+
return this;
|
|
558
|
+
}
|
|
559
|
+
return this.modifyText(this.right());
|
|
560
|
+
}
|
|
561
|
+
backspace() {
|
|
562
|
+
if (this.isAtStart()) {
|
|
563
|
+
return this;
|
|
564
|
+
}
|
|
565
|
+
const currentOffset = this.offset;
|
|
566
|
+
const leftCursor = this.left();
|
|
567
|
+
const leftOffset = leftCursor.offset;
|
|
568
|
+
const newText = this.text.slice(0, leftOffset) + this.text.slice(currentOffset);
|
|
569
|
+
return _Cursor.fromText(newText, this.columns, leftOffset);
|
|
570
|
+
}
|
|
571
|
+
deleteToLineStart() {
|
|
572
|
+
return this.startOfLine().modifyText(this);
|
|
573
|
+
}
|
|
574
|
+
deleteToLineEnd() {
|
|
575
|
+
if (this.text[this.offset] === "\n") {
|
|
576
|
+
return this.modifyText(this.right());
|
|
577
|
+
}
|
|
578
|
+
return this.modifyText(this.endOfLine());
|
|
579
|
+
}
|
|
580
|
+
deleteWordBefore() {
|
|
581
|
+
if (this.isAtStart()) {
|
|
582
|
+
return this;
|
|
583
|
+
}
|
|
584
|
+
return this.prevWord().modifyText(this);
|
|
585
|
+
}
|
|
586
|
+
deleteWordAfter() {
|
|
587
|
+
if (this.isAtEnd()) {
|
|
588
|
+
return this;
|
|
589
|
+
}
|
|
590
|
+
return this.modifyText(this.nextWord());
|
|
591
|
+
}
|
|
592
|
+
isOverWordChar() {
|
|
593
|
+
const currentChar = this.text[this.offset] ?? "";
|
|
594
|
+
return /\w/.test(currentChar);
|
|
595
|
+
}
|
|
596
|
+
equals(other) {
|
|
597
|
+
return this.offset === other.offset && this.measuredText == other.measuredText;
|
|
598
|
+
}
|
|
599
|
+
isAtStart() {
|
|
600
|
+
return this.offset == 0;
|
|
601
|
+
}
|
|
602
|
+
isAtEnd() {
|
|
603
|
+
return this.offset == this.text.length;
|
|
604
|
+
}
|
|
605
|
+
get text() {
|
|
606
|
+
return this.measuredText.text;
|
|
607
|
+
}
|
|
608
|
+
get columns() {
|
|
609
|
+
return this.measuredText.columns + 1;
|
|
610
|
+
}
|
|
611
|
+
getPosition() {
|
|
612
|
+
return this.measuredText.getPositionFromOffset(this.offset);
|
|
613
|
+
}
|
|
614
|
+
getOffset(position) {
|
|
615
|
+
return this.measuredText.getOffsetFromPosition(position);
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
// apps/cli/src/ui/hooks/useTextInputMapping.ts
|
|
620
|
+
function mapInput(inputMap, defaultHandler) {
|
|
621
|
+
const handlers = new Map(inputMap);
|
|
622
|
+
return (input) => (handlers.get(input) ?? defaultHandler)(input);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// packages/core/src/utils/imagePaste.ts
|
|
626
|
+
import { execSync } from "child_process";
|
|
627
|
+
import { readFileSync } from "fs";
|
|
628
|
+
var SCREENSHOT_PATH = "/tmp/kode_cli_latest_screenshot.png";
|
|
629
|
+
var CLIPBOARD_ERROR_MESSAGE = "No image found in clipboard. Use Cmd + Ctrl + Shift + 4 to copy a screenshot to clipboard.";
|
|
630
|
+
function getImageFromClipboard() {
|
|
631
|
+
if (process.platform !== "darwin") {
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
try {
|
|
635
|
+
execSync(`osascript -e 'the clipboard as \xABclass PNGf\xBB'`, {
|
|
636
|
+
stdio: "ignore"
|
|
637
|
+
});
|
|
638
|
+
execSync(
|
|
639
|
+
`osascript -e 'set png_data to (the clipboard as \xABclass PNGf\xBB)' -e 'set fp to open for access POSIX file "${SCREENSHOT_PATH}" with write permission' -e 'write png_data to fp' -e 'close access fp'`,
|
|
640
|
+
{ stdio: "ignore" }
|
|
641
|
+
);
|
|
642
|
+
const imageBuffer = readFileSync(SCREENSHOT_PATH);
|
|
643
|
+
const base64Image = imageBuffer.toString("base64");
|
|
644
|
+
execSync(`rm -f "${SCREENSHOT_PATH}"`, { stdio: "ignore" });
|
|
645
|
+
return base64Image;
|
|
646
|
+
} catch {
|
|
647
|
+
return null;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// apps/cli/src/ui/hooks/useTextInputTryImagePaste.ts
|
|
652
|
+
var IMAGE_PLACEHOLDER = "[Image pasted]";
|
|
653
|
+
function tryImagePaste({
|
|
654
|
+
cursor,
|
|
655
|
+
mask,
|
|
656
|
+
onImagePaste,
|
|
657
|
+
onMessage,
|
|
658
|
+
setImagePasteErrorTimeout,
|
|
659
|
+
clearImagePasteErrorTimeout
|
|
660
|
+
}) {
|
|
661
|
+
if (mask) {
|
|
662
|
+
return cursor;
|
|
663
|
+
}
|
|
664
|
+
const base64Image = getImageFromClipboard();
|
|
665
|
+
if (base64Image === null) {
|
|
666
|
+
if (process.platform !== "darwin") {
|
|
667
|
+
return cursor;
|
|
668
|
+
}
|
|
669
|
+
onMessage?.(true, CLIPBOARD_ERROR_MESSAGE);
|
|
670
|
+
clearImagePasteErrorTimeout();
|
|
671
|
+
setImagePasteErrorTimeout(
|
|
672
|
+
setTimeout(() => {
|
|
673
|
+
onMessage?.(false);
|
|
674
|
+
}, 4e3)
|
|
675
|
+
);
|
|
676
|
+
return cursor;
|
|
677
|
+
}
|
|
678
|
+
const placeholder = onImagePaste?.(base64Image);
|
|
679
|
+
return cursor.insert(
|
|
680
|
+
typeof placeholder === "string" ? placeholder : IMAGE_PLACEHOLDER
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// apps/cli/src/ui/hooks/useTextInput.ts
|
|
685
|
+
function useTextInput({
|
|
686
|
+
value: originalValue,
|
|
687
|
+
onChange,
|
|
688
|
+
onSubmit,
|
|
689
|
+
onExit,
|
|
690
|
+
onExitMessage,
|
|
691
|
+
onMessage,
|
|
692
|
+
onHistoryUp,
|
|
693
|
+
onHistoryDown,
|
|
694
|
+
onHistoryReset,
|
|
695
|
+
mask = "",
|
|
696
|
+
multiline = false,
|
|
697
|
+
cursorChar,
|
|
698
|
+
invert,
|
|
699
|
+
columns,
|
|
700
|
+
onImagePaste,
|
|
701
|
+
disableCursorMovementForUpDownKeys = false,
|
|
702
|
+
externalOffset,
|
|
703
|
+
onOffsetChange
|
|
704
|
+
}) {
|
|
705
|
+
const offset = externalOffset;
|
|
706
|
+
const setOffset = onOffsetChange;
|
|
707
|
+
const cursor = Cursor.fromText(originalValue, columns, offset);
|
|
708
|
+
const [imagePasteErrorTimeout, setImagePasteErrorTimeout] = useState2(null);
|
|
709
|
+
function maybeClearImagePasteErrorTimeout() {
|
|
710
|
+
if (!imagePasteErrorTimeout) {
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
clearTimeout(imagePasteErrorTimeout);
|
|
714
|
+
setImagePasteErrorTimeout(null);
|
|
715
|
+
onMessage?.(false);
|
|
716
|
+
}
|
|
717
|
+
function applyCursor(nextCursor) {
|
|
718
|
+
if (cursor.equals(nextCursor)) {
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
setOffset(nextCursor.offset);
|
|
722
|
+
if (cursor.text !== nextCursor.text) {
|
|
723
|
+
onChange(nextCursor.text);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
const handleCtrlC = useDoublePress(
|
|
727
|
+
(show) => {
|
|
728
|
+
maybeClearImagePasteErrorTimeout();
|
|
729
|
+
onExitMessage?.(show, "Ctrl-C");
|
|
730
|
+
},
|
|
731
|
+
() => onExit?.(),
|
|
732
|
+
() => {
|
|
733
|
+
if (originalValue) {
|
|
734
|
+
onChange("");
|
|
735
|
+
onHistoryReset?.();
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
);
|
|
739
|
+
const handleEscape = useDoublePress(
|
|
740
|
+
(show) => {
|
|
741
|
+
maybeClearImagePasteErrorTimeout();
|
|
742
|
+
onMessage?.(!!originalValue && show, `Press Escape again to clear`);
|
|
743
|
+
},
|
|
744
|
+
() => {
|
|
745
|
+
if (originalValue) {
|
|
746
|
+
onChange("");
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
);
|
|
750
|
+
function clear() {
|
|
751
|
+
return Cursor.fromText("", columns, 0);
|
|
752
|
+
}
|
|
753
|
+
const handleEmptyCtrlD = useDoublePress(
|
|
754
|
+
(show) => onExitMessage?.(show, "Ctrl-D"),
|
|
755
|
+
() => onExit?.()
|
|
756
|
+
);
|
|
757
|
+
function handleCtrlD() {
|
|
758
|
+
maybeClearImagePasteErrorTimeout();
|
|
759
|
+
if (cursor.text === "") {
|
|
760
|
+
handleEmptyCtrlD();
|
|
761
|
+
return cursor;
|
|
762
|
+
}
|
|
763
|
+
return cursor.del();
|
|
764
|
+
}
|
|
765
|
+
function handleImagePaste() {
|
|
766
|
+
return tryImagePaste({
|
|
767
|
+
cursor,
|
|
768
|
+
mask,
|
|
769
|
+
onImagePaste,
|
|
770
|
+
onMessage,
|
|
771
|
+
setImagePasteErrorTimeout,
|
|
772
|
+
clearImagePasteErrorTimeout: maybeClearImagePasteErrorTimeout
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
const handleCtrl = mapInput(
|
|
776
|
+
[
|
|
777
|
+
["a", () => cursor.startOfLine()],
|
|
778
|
+
["b", () => cursor.left()],
|
|
779
|
+
["c", handleCtrlC],
|
|
780
|
+
["d", handleCtrlD],
|
|
781
|
+
["e", () => cursor.endOfLine()],
|
|
782
|
+
["f", () => cursor.right()],
|
|
783
|
+
[
|
|
784
|
+
"h",
|
|
785
|
+
() => {
|
|
786
|
+
maybeClearImagePasteErrorTimeout();
|
|
787
|
+
return cursor.backspace();
|
|
788
|
+
}
|
|
789
|
+
],
|
|
790
|
+
["k", () => cursor.deleteToLineEnd()],
|
|
791
|
+
["l", () => clear()],
|
|
792
|
+
["n", () => downOrHistoryDown()],
|
|
793
|
+
["p", () => upOrHistoryUp()],
|
|
794
|
+
["u", () => cursor.deleteToLineStart()],
|
|
795
|
+
["v", handleImagePaste],
|
|
796
|
+
["w", () => cursor.deleteWordBefore()]
|
|
797
|
+
],
|
|
798
|
+
() => void 0
|
|
799
|
+
);
|
|
800
|
+
const handleMeta = mapInput(
|
|
801
|
+
[
|
|
802
|
+
["b", () => cursor.prevWord()],
|
|
803
|
+
["f", () => cursor.nextWord()],
|
|
804
|
+
["d", () => cursor.deleteWordAfter()]
|
|
805
|
+
],
|
|
806
|
+
() => void 0
|
|
807
|
+
);
|
|
808
|
+
function handleEnter(key) {
|
|
809
|
+
if (!multiline) {
|
|
810
|
+
onSubmit?.(originalValue);
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
const optionPressed = (() => {
|
|
814
|
+
if (!("option" in key)) return false;
|
|
815
|
+
const optionValue = key.option;
|
|
816
|
+
return optionValue === true;
|
|
817
|
+
})();
|
|
818
|
+
if (key.meta || optionPressed) {
|
|
819
|
+
return cursor.insert("\n");
|
|
820
|
+
}
|
|
821
|
+
onSubmit?.(originalValue);
|
|
822
|
+
}
|
|
823
|
+
function upOrHistoryUp() {
|
|
824
|
+
if (disableCursorMovementForUpDownKeys) {
|
|
825
|
+
onHistoryUp?.();
|
|
826
|
+
return cursor;
|
|
827
|
+
}
|
|
828
|
+
const cursorUp = cursor.up();
|
|
829
|
+
if (cursorUp.equals(cursor)) {
|
|
830
|
+
onHistoryUp?.();
|
|
831
|
+
}
|
|
832
|
+
return cursorUp;
|
|
833
|
+
}
|
|
834
|
+
function downOrHistoryDown() {
|
|
835
|
+
if (disableCursorMovementForUpDownKeys) {
|
|
836
|
+
onHistoryDown?.();
|
|
837
|
+
return cursor;
|
|
838
|
+
}
|
|
839
|
+
const cursorDown = cursor.down();
|
|
840
|
+
if (cursorDown.equals(cursor)) {
|
|
841
|
+
onHistoryDown?.();
|
|
842
|
+
}
|
|
843
|
+
return cursorDown;
|
|
844
|
+
}
|
|
845
|
+
function onInput(input, key) {
|
|
846
|
+
if (key.tab) {
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
if (key.backspace || key.delete || input === "\b" || input === "\x7F" || input === "\b") {
|
|
850
|
+
applyCursor(cursor.backspace());
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
if (!key.ctrl && !key.meta && input.length > 1) {
|
|
854
|
+
applyCursor(cursor.insert(normalizeLineEndings(input)));
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
const nextCursor = mapKey(key)(input);
|
|
858
|
+
if (nextCursor) {
|
|
859
|
+
applyCursor(nextCursor);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
function mapKey(key) {
|
|
863
|
+
if (key.backspace || key.delete) {
|
|
864
|
+
maybeClearImagePasteErrorTimeout();
|
|
865
|
+
return () => cursor.backspace();
|
|
866
|
+
}
|
|
867
|
+
switch (true) {
|
|
868
|
+
case key.escape:
|
|
869
|
+
return handleEscape;
|
|
870
|
+
case (key.leftArrow && (key.ctrl || key.meta || "fn" in key && key.fn)):
|
|
871
|
+
return () => cursor.prevWord();
|
|
872
|
+
case (key.rightArrow && (key.ctrl || key.meta || "fn" in key && key.fn)):
|
|
873
|
+
return () => cursor.nextWord();
|
|
874
|
+
case key.ctrl:
|
|
875
|
+
return handleCtrl;
|
|
876
|
+
case ("home" in key && key.home):
|
|
877
|
+
return () => cursor.startOfLine();
|
|
878
|
+
case ("end" in key && key.end):
|
|
879
|
+
return () => cursor.endOfLine();
|
|
880
|
+
case key.pageDown:
|
|
881
|
+
return () => cursor.endOfLine();
|
|
882
|
+
case key.pageUp:
|
|
883
|
+
return () => cursor.startOfLine();
|
|
884
|
+
case key.return:
|
|
885
|
+
return () => handleEnter(key);
|
|
886
|
+
case key.meta:
|
|
887
|
+
return handleMeta;
|
|
888
|
+
// Remove Tab handling - let completion system handle it
|
|
889
|
+
case key.upArrow:
|
|
890
|
+
return upOrHistoryUp;
|
|
891
|
+
case key.downArrow:
|
|
892
|
+
return downOrHistoryDown;
|
|
893
|
+
case key.leftArrow:
|
|
894
|
+
return () => cursor.left();
|
|
895
|
+
case key.rightArrow:
|
|
896
|
+
return () => cursor.right();
|
|
897
|
+
}
|
|
898
|
+
return function(input) {
|
|
899
|
+
switch (true) {
|
|
900
|
+
// Home key
|
|
901
|
+
case (input == "\x1B[H" || input == "\x1B[1~"):
|
|
902
|
+
return cursor.startOfLine();
|
|
903
|
+
// End key
|
|
904
|
+
case (input == "\x1B[F" || input == "\x1B[4~"):
|
|
905
|
+
return cursor.endOfLine();
|
|
906
|
+
// Handle backspace character explicitly - this is the key fix
|
|
907
|
+
case (input === "\b" || input === "\x7F" || input === "\b"):
|
|
908
|
+
maybeClearImagePasteErrorTimeout();
|
|
909
|
+
return cursor.backspace();
|
|
910
|
+
default:
|
|
911
|
+
return cursor.insert(input.replace(/\r/g, "\n"));
|
|
912
|
+
}
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
return {
|
|
916
|
+
onInput,
|
|
917
|
+
renderedValue: cursor.render(cursorChar, mask, invert),
|
|
918
|
+
offset,
|
|
919
|
+
setOffset
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// apps/cli/src/ui/components/TextInputBracketedPaste.ts
|
|
924
|
+
import React3 from "react";
|
|
925
|
+
var BRACKETED_PASTE_ENABLE = "\x1B[?2004h";
|
|
926
|
+
var BRACKETED_PASTE_DISABLE = "\x1B[?2004l";
|
|
927
|
+
var BRACKETED_PASTE_START = "\x1B[200~";
|
|
928
|
+
var BRACKETED_PASTE_END = "\x1B[201~";
|
|
929
|
+
var BRACKETED_PASTE_START_NO_ESC = "[200~";
|
|
930
|
+
var BRACKETED_PASTE_END_NO_ESC = "[201~";
|
|
931
|
+
var bracketedPasteRefCount = 0;
|
|
932
|
+
function setBracketedPasteEnabled(enabled) {
|
|
933
|
+
if (!process.stdout?.isTTY) return;
|
|
934
|
+
process.stdout.write(
|
|
935
|
+
enabled ? BRACKETED_PASTE_ENABLE : BRACKETED_PASTE_DISABLE
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
function acquireBracketedPasteMode() {
|
|
939
|
+
if (bracketedPasteRefCount === 0) {
|
|
940
|
+
setBracketedPasteEnabled(true);
|
|
941
|
+
}
|
|
942
|
+
bracketedPasteRefCount++;
|
|
943
|
+
}
|
|
944
|
+
function releaseBracketedPasteMode() {
|
|
945
|
+
bracketedPasteRefCount = Math.max(0, bracketedPasteRefCount - 1);
|
|
946
|
+
if (bracketedPasteRefCount === 0) {
|
|
947
|
+
setBracketedPasteEnabled(false);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
function useBracketedPasteMode() {
|
|
951
|
+
React3.useEffect(() => {
|
|
952
|
+
acquireBracketedPasteMode();
|
|
953
|
+
return () => releaseBracketedPasteMode();
|
|
954
|
+
}, []);
|
|
955
|
+
}
|
|
956
|
+
function longestSuffixPrefix(haystack, needle) {
|
|
957
|
+
const max = Math.min(haystack.length, needle.length - 1);
|
|
958
|
+
for (let len = max; len > 0; len--) {
|
|
959
|
+
if (haystack.endsWith(needle.slice(0, len))) return len;
|
|
960
|
+
}
|
|
961
|
+
return 0;
|
|
962
|
+
}
|
|
963
|
+
function findFirstMarker(haystack, markers) {
|
|
964
|
+
let best = null;
|
|
965
|
+
for (const marker of markers) {
|
|
966
|
+
const index = haystack.indexOf(marker);
|
|
967
|
+
if (index === -1) continue;
|
|
968
|
+
if (!best || index < best.index) {
|
|
969
|
+
best = { index, marker };
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
return best;
|
|
973
|
+
}
|
|
974
|
+
function getSuffixKeepLength(haystack, markers) {
|
|
975
|
+
let keep = 0;
|
|
976
|
+
for (const marker of markers) {
|
|
977
|
+
keep = Math.max(keep, longestSuffixPrefix(haystack, marker));
|
|
978
|
+
}
|
|
979
|
+
return keep;
|
|
980
|
+
}
|
|
981
|
+
function useBracketedPasteSequences({
|
|
982
|
+
insertText,
|
|
983
|
+
onPaste
|
|
984
|
+
}) {
|
|
985
|
+
const stateRef = React3.useRef({
|
|
986
|
+
mode: "normal",
|
|
987
|
+
incomplete: "",
|
|
988
|
+
buffer: ""
|
|
989
|
+
});
|
|
990
|
+
const flushBracketedPasteBuffer = React3.useCallback(
|
|
991
|
+
(rawText) => {
|
|
992
|
+
const normalized = normalizeLineEndings(rawText);
|
|
993
|
+
if (onPaste && shouldTreatAsSpecialPaste(normalized)) {
|
|
994
|
+
Promise.resolve().then(() => onPaste(normalized));
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
insertText(normalized);
|
|
998
|
+
},
|
|
999
|
+
[insertText, onPaste]
|
|
1000
|
+
);
|
|
1001
|
+
return React3.useCallback(
|
|
1002
|
+
(input) => {
|
|
1003
|
+
const state = stateRef.current;
|
|
1004
|
+
let handledAny = false;
|
|
1005
|
+
let data = state.incomplete + input;
|
|
1006
|
+
state.incomplete = "";
|
|
1007
|
+
const startMarkers = [BRACKETED_PASTE_START, BRACKETED_PASTE_START_NO_ESC];
|
|
1008
|
+
const endMarkers = [BRACKETED_PASTE_END, BRACKETED_PASTE_END_NO_ESC];
|
|
1009
|
+
while (data) {
|
|
1010
|
+
if (state.mode === "normal") {
|
|
1011
|
+
const start = findFirstMarker(data, startMarkers);
|
|
1012
|
+
if (!start) {
|
|
1013
|
+
const keep = getSuffixKeepLength(data, startMarkers);
|
|
1014
|
+
if (keep === 0) {
|
|
1015
|
+
if (!handledAny) {
|
|
1016
|
+
return false;
|
|
1017
|
+
}
|
|
1018
|
+
insertText(data);
|
|
1019
|
+
return true;
|
|
1020
|
+
}
|
|
1021
|
+
const toInsert = data.slice(0, -keep);
|
|
1022
|
+
if (toInsert) {
|
|
1023
|
+
insertText(toInsert);
|
|
1024
|
+
}
|
|
1025
|
+
state.incomplete = data.slice(-keep);
|
|
1026
|
+
handledAny = true;
|
|
1027
|
+
return true;
|
|
1028
|
+
}
|
|
1029
|
+
const before = data.slice(0, start.index);
|
|
1030
|
+
if (before) {
|
|
1031
|
+
insertText(before);
|
|
1032
|
+
}
|
|
1033
|
+
data = data.slice(start.index + start.marker.length);
|
|
1034
|
+
state.mode = "in_paste";
|
|
1035
|
+
handledAny = true;
|
|
1036
|
+
continue;
|
|
1037
|
+
}
|
|
1038
|
+
const end = findFirstMarker(data, endMarkers);
|
|
1039
|
+
if (!end) {
|
|
1040
|
+
const keep = getSuffixKeepLength(data, endMarkers);
|
|
1041
|
+
const content = keep > 0 ? data.slice(0, -keep) : data;
|
|
1042
|
+
if (content) {
|
|
1043
|
+
state.buffer += content;
|
|
1044
|
+
}
|
|
1045
|
+
if (keep > 0) {
|
|
1046
|
+
state.incomplete = data.slice(-keep);
|
|
1047
|
+
}
|
|
1048
|
+
handledAny = true;
|
|
1049
|
+
return true;
|
|
1050
|
+
}
|
|
1051
|
+
state.buffer += data.slice(0, end.index);
|
|
1052
|
+
const completedPaste = state.buffer;
|
|
1053
|
+
state.buffer = "";
|
|
1054
|
+
state.mode = "normal";
|
|
1055
|
+
flushBracketedPasteBuffer(completedPaste);
|
|
1056
|
+
data = data.slice(end.index + end.marker.length);
|
|
1057
|
+
handledAny = true;
|
|
1058
|
+
continue;
|
|
1059
|
+
}
|
|
1060
|
+
return true;
|
|
1061
|
+
},
|
|
1062
|
+
[flushBracketedPasteBuffer, insertText]
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// apps/cli/src/ui/components/TextInput.tsx
|
|
1067
|
+
function TextInput({
|
|
1068
|
+
value: originalValue,
|
|
1069
|
+
placeholder = "",
|
|
1070
|
+
focus = true,
|
|
1071
|
+
mask,
|
|
1072
|
+
multiline = false,
|
|
1073
|
+
highlightPastedText = false,
|
|
1074
|
+
showCursor = true,
|
|
1075
|
+
onChange,
|
|
1076
|
+
onSubmit,
|
|
1077
|
+
onExit,
|
|
1078
|
+
onHistoryUp,
|
|
1079
|
+
onHistoryDown,
|
|
1080
|
+
onExitMessage,
|
|
1081
|
+
onMessage,
|
|
1082
|
+
onHistoryReset,
|
|
1083
|
+
columns,
|
|
1084
|
+
onImagePaste,
|
|
1085
|
+
onPaste,
|
|
1086
|
+
isDimmed = false,
|
|
1087
|
+
disableCursorMovementForUpDownKeys = false,
|
|
1088
|
+
onSpecialKey,
|
|
1089
|
+
cursorOffset,
|
|
1090
|
+
onChangeCursorOffset
|
|
1091
|
+
}) {
|
|
1092
|
+
const { onInput, renderedValue } = useTextInput({
|
|
1093
|
+
value: originalValue,
|
|
1094
|
+
onChange,
|
|
1095
|
+
onSubmit,
|
|
1096
|
+
onExit,
|
|
1097
|
+
onExitMessage,
|
|
1098
|
+
onMessage,
|
|
1099
|
+
onHistoryReset,
|
|
1100
|
+
onHistoryUp,
|
|
1101
|
+
onHistoryDown,
|
|
1102
|
+
focus,
|
|
1103
|
+
mask,
|
|
1104
|
+
multiline,
|
|
1105
|
+
cursorChar: showCursor ? " " : "",
|
|
1106
|
+
highlightPastedText,
|
|
1107
|
+
invert: chalk.inverse,
|
|
1108
|
+
themeText: (text) => chalk.hex(getTheme().text)(text),
|
|
1109
|
+
columns,
|
|
1110
|
+
onImagePaste,
|
|
1111
|
+
disableCursorMovementForUpDownKeys,
|
|
1112
|
+
externalOffset: cursorOffset,
|
|
1113
|
+
onOffsetChange: onChangeCursorOffset
|
|
1114
|
+
});
|
|
1115
|
+
useBracketedPasteMode();
|
|
1116
|
+
const [pasteState, setPasteState] = React4.useState({ chunks: [], timeoutId: null });
|
|
1117
|
+
const handleBracketedPasteSequences = useBracketedPasteSequences({
|
|
1118
|
+
insertText: (text) => onInput(text, {}),
|
|
1119
|
+
onPaste
|
|
1120
|
+
});
|
|
1121
|
+
const resetPasteTimeout = (currentTimeoutId) => {
|
|
1122
|
+
if (currentTimeoutId) {
|
|
1123
|
+
clearTimeout(currentTimeoutId);
|
|
1124
|
+
}
|
|
1125
|
+
return setTimeout(() => {
|
|
1126
|
+
setPasteState(({ chunks }) => {
|
|
1127
|
+
const pastedText = chunks.join("");
|
|
1128
|
+
Promise.resolve().then(() => onPaste(pastedText));
|
|
1129
|
+
return { chunks: [], timeoutId: null };
|
|
1130
|
+
});
|
|
1131
|
+
}, 500);
|
|
1132
|
+
};
|
|
1133
|
+
const wrappedOnInput = (input, key) => {
|
|
1134
|
+
if (/^(?:\x1b)?\[13;2(?:u|~)$/.test(input)) {
|
|
1135
|
+
onInput("\r", { ...key, return: true, meta: false, shift: false });
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1138
|
+
if (/^(?:\x1b)?\[13;(?:3|4)(?:u|~)$/.test(input)) {
|
|
1139
|
+
onInput("\r", { ...key, return: true, meta: true });
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
if (input === "\n") {
|
|
1143
|
+
if (multiline) {
|
|
1144
|
+
onInput("\n", key);
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
onInput("\r", { ...key, return: true });
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
if (input === "\x1B\r" || input === "\x1B\n") {
|
|
1151
|
+
onInput("\r", {
|
|
1152
|
+
...key,
|
|
1153
|
+
return: true,
|
|
1154
|
+
meta: true
|
|
1155
|
+
});
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
if (onSpecialKey && onSpecialKey(input, key)) {
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
if (key.backspace || key.delete || input === "\b" || input === "\x7F" || input === "\b") {
|
|
1162
|
+
onInput(input, {
|
|
1163
|
+
...key,
|
|
1164
|
+
backspace: true
|
|
1165
|
+
});
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
if (input && handleBracketedPasteSequences(input)) {
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
if (onPaste && shouldAggregatePasteChunk(input, pasteState.timeoutId !== null)) {
|
|
1172
|
+
setPasteState(({ chunks, timeoutId }) => {
|
|
1173
|
+
return {
|
|
1174
|
+
chunks: [...chunks, input],
|
|
1175
|
+
timeoutId: resetPasteTimeout(timeoutId)
|
|
1176
|
+
};
|
|
1177
|
+
});
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
onInput(input, key);
|
|
1181
|
+
};
|
|
1182
|
+
useInput2(wrappedOnInput, { isActive: focus });
|
|
1183
|
+
let renderedPlaceholder = placeholder ? chalk.hex(getTheme().secondaryText)(placeholder) : void 0;
|
|
1184
|
+
if (showCursor && focus) {
|
|
1185
|
+
renderedPlaceholder = placeholder.length > 0 ? chalk.inverse(placeholder[0]) + chalk.hex(getTheme().secondaryText)(placeholder.slice(1)) : chalk.inverse(" ");
|
|
1186
|
+
}
|
|
1187
|
+
const showPlaceholder = originalValue.length == 0 && placeholder;
|
|
1188
|
+
return /* @__PURE__ */ React4.createElement(Text3, { wrap: "truncate-end", dimColor: isDimmed }, showPlaceholder ? renderedPlaceholder : renderedValue);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// apps/cli/src/ui/ui/components/model-selector/screens/ApiKeyScreen.tsx
|
|
1192
|
+
function ApiKeyScreen({
|
|
1193
|
+
theme,
|
|
1194
|
+
exitState,
|
|
1195
|
+
selectedProvider,
|
|
1196
|
+
apiKey,
|
|
1197
|
+
cursorOffset,
|
|
1198
|
+
handleApiKeyChange,
|
|
1199
|
+
handleApiKeySubmit,
|
|
1200
|
+
handleCursorOffsetChange,
|
|
1201
|
+
apiKeyCleanedNotification,
|
|
1202
|
+
isLoadingModels,
|
|
1203
|
+
providerBaseUrl,
|
|
1204
|
+
modelLoadError,
|
|
1205
|
+
formatApiKeyDisplay,
|
|
1206
|
+
getProviderLabel
|
|
1207
|
+
}) {
|
|
1208
|
+
const modelTypeText = "this model profile";
|
|
1209
|
+
return /* @__PURE__ */ React5.createElement(Box3, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React5.createElement(
|
|
1210
|
+
Box3,
|
|
1211
|
+
{
|
|
1212
|
+
flexDirection: "column",
|
|
1213
|
+
gap: 1,
|
|
1214
|
+
borderStyle: "round",
|
|
1215
|
+
borderColor: theme.secondaryBorder,
|
|
1216
|
+
paddingX: 2,
|
|
1217
|
+
paddingY: 1
|
|
1218
|
+
},
|
|
1219
|
+
/* @__PURE__ */ React5.createElement(Text4, { bold: true }, "API Key Setup", " ", exitState.pending ? `(press ${exitState.keyName} again to exit)` : ""),
|
|
1220
|
+
/* @__PURE__ */ React5.createElement(Box3, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React5.createElement(Text4, { bold: true }, "Enter your ", getProviderLabel(selectedProvider, 0).split(" (")[0], " ", "API key for ", modelTypeText, ":"), /* @__PURE__ */ React5.createElement(Box3, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React5.createElement(Text4, { color: theme.secondaryText }, "This key will be stored locally and used to access the", " ", selectedProvider, " API.", /* @__PURE__ */ React5.createElement(Newline, null), "Your key is never sent to our servers.", /* @__PURE__ */ React5.createElement(Newline, null), /* @__PURE__ */ React5.createElement(Newline, null), selectedProvider === "kimi" && /* @__PURE__ */ React5.createElement(React5.Fragment, null, "\u{1F4A1} Get your API key from:", " ", /* @__PURE__ */ React5.createElement(Text4, { color: theme.suggestion }, "https://platform.moonshot.cn/console/api-keys")), selectedProvider === "deepseek" && /* @__PURE__ */ React5.createElement(React5.Fragment, null, "\u{1F4A1} Get your API key from:", " ", /* @__PURE__ */ React5.createElement(Text4, { color: theme.suggestion }, "https://platform.deepseek.com/api_keys")), selectedProvider === "siliconflow" && /* @__PURE__ */ React5.createElement(React5.Fragment, null, "\u{1F4A1} Get your API key from:", " ", /* @__PURE__ */ React5.createElement(Text4, { color: theme.suggestion }, "https://cloud.siliconflow.cn/i/oJWsm6io")), selectedProvider === "qwen" && /* @__PURE__ */ React5.createElement(React5.Fragment, null, "\u{1F4A1} Get your API key from:", " ", /* @__PURE__ */ React5.createElement(Text4, { color: theme.suggestion }, "https://bailian.console.aliyun.com/?tab=model#/api-key")), selectedProvider === "glm" && /* @__PURE__ */ React5.createElement(React5.Fragment, null, "\u{1F4A1} Get your API key from:", " ", /* @__PURE__ */ React5.createElement(Text4, { color: theme.suggestion }, "https://open.bigmodel.cn (API Keys section)")), selectedProvider === "glm-coding" && /* @__PURE__ */ React5.createElement(React5.Fragment, null, "\u{1F4A1} This is for GLM Coding Plan API.", " ", /* @__PURE__ */ React5.createElement(Text4, { color: theme.suggestion }, "Use the same API key as regular GLM"), /* @__PURE__ */ React5.createElement(Newline, null), /* @__PURE__ */ React5.createElement(Text4, { dimColor: true }, "Note: This uses a special endpoint for coding tasks.")), selectedProvider === "minimax" && /* @__PURE__ */ React5.createElement(React5.Fragment, null, "\u{1F4A1} Get your API key from:", " ", /* @__PURE__ */ React5.createElement(Text4, { color: theme.suggestion }, "https://www.minimax.io/platform/user-center/basic-information")), selectedProvider === "minimax-coding" && /* @__PURE__ */ React5.createElement(React5.Fragment, null, "\u{1F4A1} Get your Coding Plan API key from:", " ", /* @__PURE__ */ React5.createElement(Text4, { color: theme.suggestion }, "https://platform.minimaxi.com/user-center/payment/coding-plan"), /* @__PURE__ */ React5.createElement(Newline, null), /* @__PURE__ */ React5.createElement(Text4, { dimColor: true }, "Note: This requires a MiniMax Coding Plan subscription.")), selectedProvider === "baidu-qianfan" && /* @__PURE__ */ React5.createElement(React5.Fragment, null, "\u{1F4A1} Get your API key from:", " ", /* @__PURE__ */ React5.createElement(Text4, { color: theme.suggestion }, "https://console.bce.baidu.com/iam/#/iam/accesslist")), selectedProvider === "anthropic" && /* @__PURE__ */ React5.createElement(React5.Fragment, null, "\u{1F4A1} Get your API key from your provider dashboard."), selectedProvider === "openai" && /* @__PURE__ */ React5.createElement(React5.Fragment, null, "\u{1F4A1} Get your API key from:", " ", /* @__PURE__ */ React5.createElement(Text4, { color: theme.suggestion }, "https://platform.openai.com/api-keys")))), /* @__PURE__ */ React5.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Box3, null, /* @__PURE__ */ React5.createElement(
|
|
1221
|
+
TextInput,
|
|
1222
|
+
{
|
|
1223
|
+
placeholder: "Paste your API key here...",
|
|
1224
|
+
value: apiKey,
|
|
1225
|
+
onChange: handleApiKeyChange,
|
|
1226
|
+
onSubmit: handleApiKeySubmit,
|
|
1227
|
+
mask: "*",
|
|
1228
|
+
columns: 80,
|
|
1229
|
+
cursorOffset,
|
|
1230
|
+
onChangeCursorOffset: handleCursorOffsetChange,
|
|
1231
|
+
showCursor: true
|
|
1232
|
+
}
|
|
1233
|
+
)), apiKey && /* @__PURE__ */ React5.createElement(Box3, { marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text4, { color: theme.secondaryText }, "Key: ", formatApiKeyDisplay(apiKey), " (", apiKey.length, " chars)"))), apiKeyCleanedNotification && /* @__PURE__ */ React5.createElement(Box3, { marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text4, { color: theme.success }, "\u2713 API key cleaned: removed line breaks and trimmed whitespace")), /* @__PURE__ */ React5.createElement(Box3, { marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.suggestion, dimColor: !apiKey }, "[Submit API Key]"), /* @__PURE__ */ React5.createElement(Text4, null, " - Press Enter to validate and continue"))), isLoadingModels && /* @__PURE__ */ React5.createElement(Box3, { marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text4, { color: theme.suggestion }, "Validating API key and fetching models..."), providerBaseUrl && /* @__PURE__ */ React5.createElement(Text4, { dimColor: true }, "Endpoint: ", providerBaseUrl, "/v1/models")), modelLoadError && /* @__PURE__ */ React5.createElement(Box3, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Text4, { color: "red" }, "\u274C API Key Validation Failed"), /* @__PURE__ */ React5.createElement(Text4, { color: "red" }, modelLoadError), providerBaseUrl && /* @__PURE__ */ React5.createElement(Box3, { marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text4, { dimColor: true }, "Attempted endpoint: ", providerBaseUrl, "/v1/models")), /* @__PURE__ */ React5.createElement(Box3, { marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text4, { color: theme.warning }, "Please check your API key and try again."))), /* @__PURE__ */ React5.createElement(Box3, { marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text4, { dimColor: true }, "Press ", /* @__PURE__ */ React5.createElement(Text4, { color: theme.suggestion }, "Enter"), " to continue,", " ", /* @__PURE__ */ React5.createElement(Text4, { color: theme.suggestion }, "Tab"), " to", " ", selectedProvider === "anthropic" || selectedProvider === "kimi" || selectedProvider === "deepseek" || selectedProvider === "qwen" || selectedProvider === "glm" || selectedProvider === "glm-coding" || selectedProvider === "minimax" || selectedProvider === "minimax-coding" || selectedProvider === "baidu-qianfan" || selectedProvider === "siliconflow" || selectedProvider === "custom-openai" ? "skip to manual model input" : "skip using a key", ", or ", /* @__PURE__ */ React5.createElement(Text4, { color: theme.suggestion }, "Esc"), " to go back")))
|
|
1234
|
+
));
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// apps/cli/src/ui/ui/components/model-selector/screens/BaseUrlScreen.tsx
|
|
1238
|
+
import React6 from "react";
|
|
1239
|
+
import { Box as Box4, Newline as Newline2, Text as Text5 } from "ink";
|
|
1240
|
+
function BaseUrlScreen({
|
|
1241
|
+
theme,
|
|
1242
|
+
exitState,
|
|
1243
|
+
selectedProvider,
|
|
1244
|
+
isLoadingModels,
|
|
1245
|
+
modelLoadError,
|
|
1246
|
+
customBaseUrl,
|
|
1247
|
+
setCustomBaseUrl,
|
|
1248
|
+
handleCustomBaseUrlSubmit,
|
|
1249
|
+
customBaseUrlCursorOffset,
|
|
1250
|
+
setCustomBaseUrlCursorOffset,
|
|
1251
|
+
providerBaseUrl,
|
|
1252
|
+
setProviderBaseUrl,
|
|
1253
|
+
handleProviderBaseUrlSubmit,
|
|
1254
|
+
providerBaseUrlCursorOffset,
|
|
1255
|
+
setProviderBaseUrlCursorOffset
|
|
1256
|
+
}) {
|
|
1257
|
+
const isCustomOpenAI = selectedProvider === "custom-openai";
|
|
1258
|
+
if (isCustomOpenAI) {
|
|
1259
|
+
return /* @__PURE__ */ React6.createElement(Box4, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React6.createElement(
|
|
1260
|
+
Box4,
|
|
1261
|
+
{
|
|
1262
|
+
flexDirection: "column",
|
|
1263
|
+
gap: 1,
|
|
1264
|
+
borderStyle: "round",
|
|
1265
|
+
borderColor: theme.secondaryBorder,
|
|
1266
|
+
paddingX: 2,
|
|
1267
|
+
paddingY: 1
|
|
1268
|
+
},
|
|
1269
|
+
/* @__PURE__ */ React6.createElement(Text5, { bold: true }, "Custom API Server Setup", " ", exitState.pending ? `(press ${exitState.keyName} again to exit)` : ""),
|
|
1270
|
+
/* @__PURE__ */ React6.createElement(Box4, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React6.createElement(Text5, { bold: true }, "Enter your custom API URL:"), /* @__PURE__ */ React6.createElement(Box4, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React6.createElement(Text5, { color: theme.secondaryText }, "This is the base URL for your OpenAI-compatible API.", /* @__PURE__ */ React6.createElement(Newline2, null), "For example: https://api.example.com/v1")), /* @__PURE__ */ React6.createElement(Box4, null, /* @__PURE__ */ React6.createElement(
|
|
1271
|
+
TextInput,
|
|
1272
|
+
{
|
|
1273
|
+
placeholder: "https://api.example.com/v1",
|
|
1274
|
+
value: customBaseUrl,
|
|
1275
|
+
onChange: setCustomBaseUrl,
|
|
1276
|
+
onSubmit: handleCustomBaseUrlSubmit,
|
|
1277
|
+
columns: 100,
|
|
1278
|
+
cursorOffset: customBaseUrlCursorOffset,
|
|
1279
|
+
onChangeCursorOffset: setCustomBaseUrlCursorOffset,
|
|
1280
|
+
showCursor: !isLoadingModels,
|
|
1281
|
+
focus: !isLoadingModels
|
|
1282
|
+
}
|
|
1283
|
+
)), /* @__PURE__ */ React6.createElement(Box4, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text5, null, /* @__PURE__ */ React6.createElement(
|
|
1284
|
+
Text5,
|
|
1285
|
+
{
|
|
1286
|
+
color: isLoadingModels ? theme.secondaryText : theme.suggestion
|
|
1287
|
+
},
|
|
1288
|
+
"[Submit Base URL]"
|
|
1289
|
+
), /* @__PURE__ */ React6.createElement(Text5, null, " - Press Enter or click to continue"))), /* @__PURE__ */ React6.createElement(Box4, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, "Press ", /* @__PURE__ */ React6.createElement(Text5, { color: theme.suggestion }, "Enter"), " to continue or", " ", /* @__PURE__ */ React6.createElement(Text5, { color: theme.suggestion }, "Esc"), " to go back")))
|
|
1290
|
+
));
|
|
1291
|
+
}
|
|
1292
|
+
const providerName = providers[selectedProvider]?.name || selectedProvider;
|
|
1293
|
+
const defaultUrl = providers[selectedProvider]?.baseURL || "";
|
|
1294
|
+
return /* @__PURE__ */ React6.createElement(Box4, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React6.createElement(
|
|
1295
|
+
Box4,
|
|
1296
|
+
{
|
|
1297
|
+
flexDirection: "column",
|
|
1298
|
+
gap: 1,
|
|
1299
|
+
borderStyle: "round",
|
|
1300
|
+
borderColor: theme.secondaryBorder,
|
|
1301
|
+
paddingX: 2,
|
|
1302
|
+
paddingY: 1
|
|
1303
|
+
},
|
|
1304
|
+
/* @__PURE__ */ React6.createElement(Text5, { bold: true }, providerName, " API Configuration", " ", exitState.pending ? `(press ${exitState.keyName} again to exit)` : ""),
|
|
1305
|
+
/* @__PURE__ */ React6.createElement(Box4, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React6.createElement(Text5, { bold: true }, "Configure the API endpoint for ", providerName, ":"), /* @__PURE__ */ React6.createElement(Box4, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React6.createElement(Text5, { color: theme.secondaryText }, selectedProvider === "ollama" ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, "This is the URL of your Ollama server.", /* @__PURE__ */ React6.createElement(Newline2, null), "Default is http://localhost:11434/v1 for local Ollama installations.") : /* @__PURE__ */ React6.createElement(React6.Fragment, null, "This is the base URL for the ", providerName, " API.", /* @__PURE__ */ React6.createElement(Newline2, null), "You can modify this URL or press Enter to use the default."))), /* @__PURE__ */ React6.createElement(Box4, null, /* @__PURE__ */ React6.createElement(
|
|
1306
|
+
TextInput,
|
|
1307
|
+
{
|
|
1308
|
+
placeholder: defaultUrl,
|
|
1309
|
+
value: providerBaseUrl,
|
|
1310
|
+
onChange: setProviderBaseUrl,
|
|
1311
|
+
onSubmit: handleProviderBaseUrlSubmit,
|
|
1312
|
+
columns: 100,
|
|
1313
|
+
cursorOffset: providerBaseUrlCursorOffset,
|
|
1314
|
+
onChangeCursorOffset: setProviderBaseUrlCursorOffset,
|
|
1315
|
+
showCursor: !isLoadingModels,
|
|
1316
|
+
focus: !isLoadingModels
|
|
1317
|
+
}
|
|
1318
|
+
)), /* @__PURE__ */ React6.createElement(Box4, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text5, null, /* @__PURE__ */ React6.createElement(
|
|
1319
|
+
Text5,
|
|
1320
|
+
{
|
|
1321
|
+
color: isLoadingModels ? theme.secondaryText : theme.suggestion
|
|
1322
|
+
},
|
|
1323
|
+
"[Submit Base URL]"
|
|
1324
|
+
), /* @__PURE__ */ React6.createElement(Text5, null, " - Press Enter or click to continue"))), isLoadingModels && /* @__PURE__ */ React6.createElement(Box4, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text5, { color: theme.success }, selectedProvider === "ollama" ? "Connecting to Ollama server..." : `Connecting to ${providerName}...`)), modelLoadError && /* @__PURE__ */ React6.createElement(Box4, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text5, { color: "red" }, "Error: ", modelLoadError)), /* @__PURE__ */ React6.createElement(Box4, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, "Press ", /* @__PURE__ */ React6.createElement(Text5, { color: theme.suggestion }, "Enter"), " to continue or", " ", /* @__PURE__ */ React6.createElement(Text5, { color: theme.suggestion }, "Esc"), " to go back")))
|
|
1325
|
+
));
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
// apps/cli/src/ui/ui/components/model-selector/screens/ConfirmationScreen.tsx
|
|
1329
|
+
import React7 from "react";
|
|
1330
|
+
import { Box as Box5, Text as Text6 } from "ink";
|
|
1331
|
+
|
|
1332
|
+
// apps/cli/src/ui/ui/components/model-selector/options.ts
|
|
1333
|
+
var REASONING_EFFORT_OPTIONS = [
|
|
1334
|
+
{ label: "Low - Faster responses, less thorough reasoning", value: "low" },
|
|
1335
|
+
{ label: "Medium - Balanced speed and reasoning depth", value: "medium" },
|
|
1336
|
+
{
|
|
1337
|
+
label: "High - Slower responses, more thorough reasoning",
|
|
1338
|
+
value: "high"
|
|
1339
|
+
}
|
|
1340
|
+
];
|
|
1341
|
+
var CONTEXT_LENGTH_OPTIONS = [
|
|
1342
|
+
{ label: "32K tokens", value: 32e3 },
|
|
1343
|
+
{ label: "64K tokens", value: 64e3 },
|
|
1344
|
+
{ label: "128K tokens", value: 128e3 },
|
|
1345
|
+
{ label: "200K tokens", value: 2e5 },
|
|
1346
|
+
{ label: "256K tokens", value: 256e3 },
|
|
1347
|
+
{ label: "300K tokens", value: 3e5 },
|
|
1348
|
+
{ label: "512K tokens", value: 512e3 },
|
|
1349
|
+
{ label: "1000K tokens", value: 1e6 },
|
|
1350
|
+
{ label: "2000K tokens", value: 2e6 },
|
|
1351
|
+
{ label: "3000K tokens", value: 3e6 },
|
|
1352
|
+
{ label: "5000K tokens", value: 5e6 },
|
|
1353
|
+
{ label: "10000K tokens", value: 1e7 }
|
|
1354
|
+
];
|
|
1355
|
+
var DEFAULT_CONTEXT_LENGTH = 128e3;
|
|
1356
|
+
var MAX_TOKENS_OPTIONS = [
|
|
1357
|
+
{ label: "1K tokens", value: 1024 },
|
|
1358
|
+
{ label: "2K tokens", value: 2048 },
|
|
1359
|
+
{ label: "4K tokens", value: 4096 },
|
|
1360
|
+
{ label: "8K tokens (recommended)", value: 8192 },
|
|
1361
|
+
{ label: "16K tokens", value: 16384 },
|
|
1362
|
+
{ label: "32K tokens", value: 32768 },
|
|
1363
|
+
{ label: "64K tokens", value: 65536 },
|
|
1364
|
+
{ label: "128K tokens", value: 131072 }
|
|
1365
|
+
];
|
|
1366
|
+
var DEFAULT_MAX_TOKENS = 8192;
|
|
1367
|
+
|
|
1368
|
+
// apps/cli/src/ui/ui/components/model-selector/screens/ConfirmationScreen.tsx
|
|
1369
|
+
function ConfirmationScreen({
|
|
1370
|
+
theme,
|
|
1371
|
+
exitState,
|
|
1372
|
+
selectedProvider,
|
|
1373
|
+
selectedModel,
|
|
1374
|
+
resourceName,
|
|
1375
|
+
ollamaBaseUrl,
|
|
1376
|
+
customBaseUrl,
|
|
1377
|
+
apiKey,
|
|
1378
|
+
maxTokens,
|
|
1379
|
+
contextLength,
|
|
1380
|
+
supportsReasoningEffort,
|
|
1381
|
+
reasoningEffort,
|
|
1382
|
+
validationError,
|
|
1383
|
+
formatApiKeyDisplay,
|
|
1384
|
+
getProviderLabel
|
|
1385
|
+
}) {
|
|
1386
|
+
const providerDisplayName = getProviderLabel(selectedProvider, 0).split(
|
|
1387
|
+
" ("
|
|
1388
|
+
)[0];
|
|
1389
|
+
const showsApiKey = selectedProvider !== "ollama";
|
|
1390
|
+
return /* @__PURE__ */ React7.createElement(Box5, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React7.createElement(
|
|
1391
|
+
Box5,
|
|
1392
|
+
{
|
|
1393
|
+
flexDirection: "column",
|
|
1394
|
+
gap: 1,
|
|
1395
|
+
borderStyle: "round",
|
|
1396
|
+
borderColor: theme.secondaryBorder,
|
|
1397
|
+
paddingX: 2,
|
|
1398
|
+
paddingY: 1
|
|
1399
|
+
},
|
|
1400
|
+
/* @__PURE__ */ React7.createElement(Text6, { bold: true }, "Configuration Confirmation", " ", exitState.pending ? `(press ${exitState.keyName} again to exit)` : ""),
|
|
1401
|
+
/* @__PURE__ */ React7.createElement(Box5, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React7.createElement(Text6, { bold: true }, "Confirm your model configuration:"), /* @__PURE__ */ React7.createElement(Box5, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React7.createElement(Text6, { color: theme.secondaryText }, "Please review your selections before saving.")), validationError && /* @__PURE__ */ React7.createElement(Box5, { flexDirection: "column", marginY: 1, paddingX: 1 }, /* @__PURE__ */ React7.createElement(Text6, { color: theme.error, bold: true }, "\u26A0 Configuration Error:"), /* @__PURE__ */ React7.createElement(Text6, { color: theme.error }, validationError)), /* @__PURE__ */ React7.createElement(Box5, { flexDirection: "column", marginY: 1, paddingX: 1 }, /* @__PURE__ */ React7.createElement(Text6, null, /* @__PURE__ */ React7.createElement(Text6, { bold: true }, "Provider: "), /* @__PURE__ */ React7.createElement(Text6, { color: theme.suggestion }, providerDisplayName)), selectedProvider === "azure" && /* @__PURE__ */ React7.createElement(Text6, null, /* @__PURE__ */ React7.createElement(Text6, { bold: true }, "Resource Name: "), /* @__PURE__ */ React7.createElement(Text6, { color: theme.suggestion }, resourceName)), selectedProvider === "ollama" && /* @__PURE__ */ React7.createElement(Text6, null, /* @__PURE__ */ React7.createElement(Text6, { bold: true }, "Server URL: "), /* @__PURE__ */ React7.createElement(Text6, { color: theme.suggestion }, ollamaBaseUrl)), selectedProvider === "custom-openai" && /* @__PURE__ */ React7.createElement(Text6, null, /* @__PURE__ */ React7.createElement(Text6, { bold: true }, "API Base URL: "), /* @__PURE__ */ React7.createElement(Text6, { color: theme.suggestion }, customBaseUrl)), /* @__PURE__ */ React7.createElement(Text6, null, /* @__PURE__ */ React7.createElement(Text6, { bold: true }, "Model: "), /* @__PURE__ */ React7.createElement(Text6, { color: theme.suggestion }, selectedModel)), apiKey && showsApiKey && /* @__PURE__ */ React7.createElement(Text6, null, /* @__PURE__ */ React7.createElement(Text6, { bold: true }, "API Key: "), /* @__PURE__ */ React7.createElement(Text6, { color: theme.suggestion }, formatApiKeyDisplay(apiKey))), maxTokens && /* @__PURE__ */ React7.createElement(Text6, null, /* @__PURE__ */ React7.createElement(Text6, { bold: true }, "Max Tokens: "), /* @__PURE__ */ React7.createElement(Text6, { color: theme.suggestion }, maxTokens)), /* @__PURE__ */ React7.createElement(Text6, null, /* @__PURE__ */ React7.createElement(Text6, { bold: true }, "Context Length: "), /* @__PURE__ */ React7.createElement(Text6, { color: theme.suggestion }, CONTEXT_LENGTH_OPTIONS.find((opt) => opt.value === contextLength)?.label || `${contextLength.toLocaleString()} tokens`)), supportsReasoningEffort && /* @__PURE__ */ React7.createElement(Text6, null, /* @__PURE__ */ React7.createElement(Text6, { bold: true }, "Reasoning Effort: "), /* @__PURE__ */ React7.createElement(Text6, { color: theme.suggestion }, reasoningEffort))), /* @__PURE__ */ React7.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text6, { dimColor: true }, "Press ", /* @__PURE__ */ React7.createElement(Text6, { color: theme.suggestion }, "Esc"), " to go back to model parameters or ", /* @__PURE__ */ React7.createElement(Text6, { color: theme.suggestion }, "Enter"), " to save configuration")))
|
|
1402
|
+
));
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
// apps/cli/src/ui/ui/components/model-selector/screens/ConnectionTestScreen.tsx
|
|
1406
|
+
import React8 from "react";
|
|
1407
|
+
import { Box as Box6, Newline as Newline3, Text as Text7 } from "ink";
|
|
1408
|
+
function ConnectionTestScreen({
|
|
1409
|
+
theme,
|
|
1410
|
+
exitState,
|
|
1411
|
+
selectedProvider,
|
|
1412
|
+
getProviderLabel,
|
|
1413
|
+
isTestingConnection,
|
|
1414
|
+
connectionTestResult
|
|
1415
|
+
}) {
|
|
1416
|
+
const providerDisplayName = getProviderLabel(selectedProvider, 0).split(
|
|
1417
|
+
" ("
|
|
1418
|
+
)[0];
|
|
1419
|
+
return /* @__PURE__ */ React8.createElement(Box6, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React8.createElement(
|
|
1420
|
+
Box6,
|
|
1421
|
+
{
|
|
1422
|
+
flexDirection: "column",
|
|
1423
|
+
gap: 1,
|
|
1424
|
+
borderStyle: "round",
|
|
1425
|
+
borderColor: theme.secondaryBorder,
|
|
1426
|
+
paddingX: 2,
|
|
1427
|
+
paddingY: 1
|
|
1428
|
+
},
|
|
1429
|
+
/* @__PURE__ */ React8.createElement(Text7, { bold: true }, "Connection Test", " ", exitState.pending ? `(press ${exitState.keyName} again to exit)` : ""),
|
|
1430
|
+
/* @__PURE__ */ React8.createElement(Box6, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React8.createElement(Text7, { bold: true }, "Testing connection to ", providerDisplayName, "..."), /* @__PURE__ */ React8.createElement(Box6, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React8.createElement(Text7, { color: theme.secondaryText }, "This will verify your configuration by sending a test request to the API.", selectedProvider === "minimax" && /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(Newline3, null), "For MiniMax, we'll test both v2 and v1 endpoints to find the best one."))), !connectionTestResult && !isTestingConnection && /* @__PURE__ */ React8.createElement(Box6, { marginY: 1 }, /* @__PURE__ */ React8.createElement(Text7, null, /* @__PURE__ */ React8.createElement(Text7, { color: theme.suggestion }, "Press Enter"), " to start the connection test")), isTestingConnection && /* @__PURE__ */ React8.createElement(Box6, { marginY: 1 }, /* @__PURE__ */ React8.createElement(Text7, { color: theme.suggestion }, "\u{1F504} Testing connection...")), connectionTestResult && /* @__PURE__ */ React8.createElement(Box6, { flexDirection: "column", marginY: 1, paddingX: 1 }, /* @__PURE__ */ React8.createElement(
|
|
1431
|
+
Text7,
|
|
1432
|
+
{
|
|
1433
|
+
color: connectionTestResult.success ? theme.success : "red"
|
|
1434
|
+
},
|
|
1435
|
+
connectionTestResult.message
|
|
1436
|
+
), connectionTestResult.endpoint && /* @__PURE__ */ React8.createElement(Text7, { color: theme.secondaryText }, "Endpoint: ", connectionTestResult.endpoint), connectionTestResult.details && /* @__PURE__ */ React8.createElement(Text7, { color: theme.secondaryText }, "Details: ", connectionTestResult.details), connectionTestResult.success ? /* @__PURE__ */ React8.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(Text7, { color: theme.success }, "\u2705 Automatically proceeding to confirmation...")) : /* @__PURE__ */ React8.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(Text7, null, /* @__PURE__ */ React8.createElement(Text7, { color: theme.suggestion }, "Press Enter"), " to retry test, or ", /* @__PURE__ */ React8.createElement(Text7, { color: theme.suggestion }, "Esc"), " to go back"))), /* @__PURE__ */ React8.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, "Press ", /* @__PURE__ */ React8.createElement(Text7, { color: theme.suggestion }, "Esc"), " to go back to context length")))
|
|
1437
|
+
));
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// apps/cli/src/ui/ui/components/model-selector/screens/ContextLengthScreen.tsx
|
|
1441
|
+
import React9 from "react";
|
|
1442
|
+
import { Box as Box7, Text as Text8 } from "ink";
|
|
1443
|
+
function ContextLengthScreen({
|
|
1444
|
+
theme,
|
|
1445
|
+
exitState,
|
|
1446
|
+
contextLength
|
|
1447
|
+
}) {
|
|
1448
|
+
const selectedOption = CONTEXT_LENGTH_OPTIONS.find((opt) => opt.value === contextLength) || CONTEXT_LENGTH_OPTIONS[2];
|
|
1449
|
+
return /* @__PURE__ */ React9.createElement(Box7, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React9.createElement(
|
|
1450
|
+
Box7,
|
|
1451
|
+
{
|
|
1452
|
+
flexDirection: "column",
|
|
1453
|
+
gap: 1,
|
|
1454
|
+
borderStyle: "round",
|
|
1455
|
+
borderColor: theme.secondaryBorder,
|
|
1456
|
+
paddingX: 2,
|
|
1457
|
+
paddingY: 1
|
|
1458
|
+
},
|
|
1459
|
+
/* @__PURE__ */ React9.createElement(Text8, { bold: true }, "Context Length Configuration", " ", exitState.pending ? `(press ${exitState.keyName} again to exit)` : ""),
|
|
1460
|
+
/* @__PURE__ */ React9.createElement(Box7, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React9.createElement(Text8, { bold: true }, "Choose the context window length for your model:"), /* @__PURE__ */ React9.createElement(Box7, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React9.createElement(Text8, { color: theme.secondaryText }, "This determines how much conversation history and context the model can process at once. Higher values allow for longer conversations but may increase costs.")), /* @__PURE__ */ React9.createElement(Box7, { flexDirection: "column", marginY: 1 }, CONTEXT_LENGTH_OPTIONS.map((option, index) => {
|
|
1461
|
+
const isSelected = option.value === contextLength;
|
|
1462
|
+
return /* @__PURE__ */ React9.createElement(Box7, { key: option.value, flexDirection: "row" }, /* @__PURE__ */ React9.createElement(Text8, { color: isSelected ? "blue" : void 0 }, isSelected ? "\u2192 " : " ", option.label, option.value === DEFAULT_CONTEXT_LENGTH ? " (recommended)" : ""));
|
|
1463
|
+
})), /* @__PURE__ */ React9.createElement(Box7, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "Selected:", " ", /* @__PURE__ */ React9.createElement(Text8, { color: theme.suggestion }, selectedOption.label))))
|
|
1464
|
+
), /* @__PURE__ */ React9.createElement(Box7, { marginLeft: 1 }, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "\u2191/\u2193 to select \xB7 Enter to continue \xB7 Esc to go back")));
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
// apps/cli/src/ui/ui/components/model-selector/screens/ModelInputScreen.tsx
|
|
1468
|
+
import React10 from "react";
|
|
1469
|
+
import { Box as Box8, Newline as Newline4, Text as Text9 } from "ink";
|
|
1470
|
+
function ModelInputScreen({
|
|
1471
|
+
theme,
|
|
1472
|
+
exitState,
|
|
1473
|
+
selectedProvider,
|
|
1474
|
+
customModelName,
|
|
1475
|
+
setCustomModelName,
|
|
1476
|
+
handleCustomModelSubmit,
|
|
1477
|
+
customModelNameCursorOffset,
|
|
1478
|
+
setCustomModelNameCursorOffset
|
|
1479
|
+
}) {
|
|
1480
|
+
const modelTypeText = "this model profile";
|
|
1481
|
+
let screenTitle = "Manual Model Setup";
|
|
1482
|
+
let description = "Enter the model name manually";
|
|
1483
|
+
let placeholder = "gpt-4";
|
|
1484
|
+
let examples = 'For example: "gpt-4", "gpt-3.5-turbo", etc.';
|
|
1485
|
+
if (selectedProvider === "azure") {
|
|
1486
|
+
screenTitle = "Azure Model Setup";
|
|
1487
|
+
description = `Enter your Azure OpenAI deployment name for ${modelTypeText}:`;
|
|
1488
|
+
examples = 'For example: "gpt-4", "gpt-35-turbo", etc.';
|
|
1489
|
+
placeholder = "gpt-4";
|
|
1490
|
+
} else if (selectedProvider === "anthropic") {
|
|
1491
|
+
screenTitle = "Model Setup";
|
|
1492
|
+
description = `Enter the model name for ${modelTypeText}:`;
|
|
1493
|
+
examples = 'For example: "claude-3-5-sonnet-latest", "claude-3-5-haiku-latest", etc.';
|
|
1494
|
+
placeholder = "claude-3-5-sonnet-latest";
|
|
1495
|
+
} else if (selectedProvider === "bigdream") {
|
|
1496
|
+
screenTitle = "BigDream Model Setup";
|
|
1497
|
+
description = `Enter the BigDream model name for ${modelTypeText}:`;
|
|
1498
|
+
examples = 'For example: "claude-3-5-sonnet-latest", "claude-3-5-haiku-latest", etc.';
|
|
1499
|
+
placeholder = "claude-3-5-sonnet-latest";
|
|
1500
|
+
} else if (selectedProvider === "kimi") {
|
|
1501
|
+
screenTitle = "Kimi Model Setup";
|
|
1502
|
+
description = `Enter the Kimi model name for ${modelTypeText}:`;
|
|
1503
|
+
examples = 'For example: "kimi-k2-0711-preview"';
|
|
1504
|
+
placeholder = "kimi-k2-0711-preview";
|
|
1505
|
+
} else if (selectedProvider === "deepseek") {
|
|
1506
|
+
screenTitle = "DeepSeek Model Setup";
|
|
1507
|
+
description = `Enter the DeepSeek model name for ${modelTypeText}:`;
|
|
1508
|
+
examples = 'For example: "deepseek-chat", "deepseek-coder", "deepseek-reasoner", etc.';
|
|
1509
|
+
placeholder = "deepseek-chat";
|
|
1510
|
+
} else if (selectedProvider === "siliconflow") {
|
|
1511
|
+
screenTitle = "SiliconFlow Model Setup";
|
|
1512
|
+
description = `Enter the SiliconFlow model name for ${modelTypeText}:`;
|
|
1513
|
+
examples = 'For example: "Qwen/Qwen2.5-72B-Instruct", "meta-llama/Meta-Llama-3.1-8B-Instruct", etc.';
|
|
1514
|
+
placeholder = "Qwen/Qwen2.5-72B-Instruct";
|
|
1515
|
+
} else if (selectedProvider === "qwen") {
|
|
1516
|
+
screenTitle = "Qwen Model Setup";
|
|
1517
|
+
description = `Enter the Qwen model name for ${modelTypeText}:`;
|
|
1518
|
+
examples = 'For example: "qwen-plus", "qwen-turbo", "qwen-max", etc.';
|
|
1519
|
+
placeholder = "qwen-plus";
|
|
1520
|
+
} else if (selectedProvider === "glm") {
|
|
1521
|
+
screenTitle = "GLM Model Setup";
|
|
1522
|
+
description = `Enter the GLM model name for ${modelTypeText}:`;
|
|
1523
|
+
examples = 'For example: "glm-4", "glm-4v", "glm-3-turbo", etc.';
|
|
1524
|
+
placeholder = "glm-4";
|
|
1525
|
+
} else if (selectedProvider === "glm-coding") {
|
|
1526
|
+
screenTitle = "GLM Coding Plan Model Setup";
|
|
1527
|
+
description = `Enter the GLM model name for ${modelTypeText}:`;
|
|
1528
|
+
examples = 'For Coding Plan, typically use: "GLM-4.6" or other GLM models';
|
|
1529
|
+
placeholder = "GLM-4.6";
|
|
1530
|
+
} else if (selectedProvider === "minimax") {
|
|
1531
|
+
screenTitle = "MiniMax Model Setup";
|
|
1532
|
+
description = `Enter the MiniMax model name for ${modelTypeText}:`;
|
|
1533
|
+
examples = 'For example: "abab6.5s-chat", "abab6.5g-chat", "abab5.5s-chat", etc.';
|
|
1534
|
+
placeholder = "abab6.5s-chat";
|
|
1535
|
+
} else if (selectedProvider === "minimax-coding") {
|
|
1536
|
+
screenTitle = "MiniMax Coding Plan Model Setup";
|
|
1537
|
+
description = `Enter the MiniMax model name for ${modelTypeText}:`;
|
|
1538
|
+
examples = 'For Coding Plan, use: "MiniMax-M2"';
|
|
1539
|
+
placeholder = "MiniMax-M2";
|
|
1540
|
+
} else if (selectedProvider === "baidu-qianfan") {
|
|
1541
|
+
screenTitle = "Baidu Qianfan Model Setup";
|
|
1542
|
+
description = `Enter the Baidu Qianfan model name for ${modelTypeText}:`;
|
|
1543
|
+
examples = 'For example: "ERNIE-4.0-8K", "ERNIE-3.5-8K", "ERNIE-Speed-128K", etc.';
|
|
1544
|
+
placeholder = "ERNIE-4.0-8K";
|
|
1545
|
+
} else if (selectedProvider === "custom-openai") {
|
|
1546
|
+
screenTitle = "Custom API Model Setup";
|
|
1547
|
+
description = `Enter the model name for ${modelTypeText}:`;
|
|
1548
|
+
examples = "Enter the exact model name as supported by your API endpoint.";
|
|
1549
|
+
placeholder = "model-name";
|
|
1550
|
+
}
|
|
1551
|
+
return /* @__PURE__ */ React10.createElement(Box8, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React10.createElement(
|
|
1552
|
+
Box8,
|
|
1553
|
+
{
|
|
1554
|
+
flexDirection: "column",
|
|
1555
|
+
gap: 1,
|
|
1556
|
+
borderStyle: "round",
|
|
1557
|
+
borderColor: theme.secondaryBorder,
|
|
1558
|
+
paddingX: 2,
|
|
1559
|
+
paddingY: 1
|
|
1560
|
+
},
|
|
1561
|
+
/* @__PURE__ */ React10.createElement(Text9, { bold: true }, screenTitle, " ", exitState.pending ? `(press ${exitState.keyName} again to exit)` : ""),
|
|
1562
|
+
/* @__PURE__ */ React10.createElement(Box8, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React10.createElement(Text9, { bold: true }, description), /* @__PURE__ */ React10.createElement(Box8, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React10.createElement(Text9, { color: theme.secondaryText }, selectedProvider === "azure" ? "This is the deployment name you configured in your Azure OpenAI resource." : selectedProvider === "anthropic" ? "This should match a model identifier supported by your API endpoint." : selectedProvider === "bigdream" ? "This should be a valid model identifier supported by BigDream." : selectedProvider === "kimi" ? "This should be a valid Kimi model identifier from Moonshot AI." : selectedProvider === "deepseek" ? "This should be a valid DeepSeek model identifier." : selectedProvider === "siliconflow" ? "This should be a valid SiliconFlow model identifier." : selectedProvider === "qwen" ? "This should be a valid Qwen model identifier from Alibaba Cloud." : selectedProvider === "glm" ? "This should be a valid GLM model identifier from Zhipu AI." : selectedProvider === "minimax" ? "This should be a valid MiniMax model identifier." : selectedProvider === "baidu-qianfan" ? "This should be a valid Baidu Qianfan model identifier." : "This should match the model name supported by your API endpoint.", /* @__PURE__ */ React10.createElement(Newline4, null), examples)), /* @__PURE__ */ React10.createElement(Box8, null, /* @__PURE__ */ React10.createElement(
|
|
1563
|
+
TextInput,
|
|
1564
|
+
{
|
|
1565
|
+
placeholder,
|
|
1566
|
+
value: customModelName,
|
|
1567
|
+
onChange: setCustomModelName,
|
|
1568
|
+
onSubmit: handleCustomModelSubmit,
|
|
1569
|
+
columns: 100,
|
|
1570
|
+
cursorOffset: customModelNameCursorOffset,
|
|
1571
|
+
onChangeCursorOffset: setCustomModelNameCursorOffset,
|
|
1572
|
+
showCursor: true
|
|
1573
|
+
}
|
|
1574
|
+
)), /* @__PURE__ */ React10.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text9, null, /* @__PURE__ */ React10.createElement(Text9, { color: theme.suggestion, dimColor: !customModelName }, "[Submit Model Name]"), /* @__PURE__ */ React10.createElement(Text9, null, " - Press Enter or click to continue"))), /* @__PURE__ */ React10.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "Press ", /* @__PURE__ */ React10.createElement(Text9, { color: theme.suggestion }, "Enter"), " to continue or", " ", /* @__PURE__ */ React10.createElement(Text9, { color: theme.suggestion }, "Esc"), " to go back")))
|
|
1575
|
+
));
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
// apps/cli/src/ui/ui/components/model-selector/screens/ModelParamsScreen.tsx
|
|
1579
|
+
import React11 from "react";
|
|
1580
|
+
import { Box as Box9, Text as Text10 } from "ink";
|
|
1581
|
+
function isReasoningEffortOption(value) {
|
|
1582
|
+
return value === "low" || value === "medium" || value === "high";
|
|
1583
|
+
}
|
|
1584
|
+
function ModelParamsScreen({
|
|
1585
|
+
theme,
|
|
1586
|
+
exitState,
|
|
1587
|
+
selectedModel,
|
|
1588
|
+
formFields,
|
|
1589
|
+
activeFieldIndex,
|
|
1590
|
+
setActiveFieldIndex,
|
|
1591
|
+
maxTokens,
|
|
1592
|
+
setMaxTokens,
|
|
1593
|
+
setSelectedMaxTokensPreset,
|
|
1594
|
+
setMaxTokensCursorOffset,
|
|
1595
|
+
reasoningEffortOptions,
|
|
1596
|
+
reasoningEffort,
|
|
1597
|
+
setReasoningEffort
|
|
1598
|
+
}) {
|
|
1599
|
+
return /* @__PURE__ */ React11.createElement(Box9, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React11.createElement(
|
|
1600
|
+
Box9,
|
|
1601
|
+
{
|
|
1602
|
+
flexDirection: "column",
|
|
1603
|
+
gap: 1,
|
|
1604
|
+
borderStyle: "round",
|
|
1605
|
+
borderColor: theme.secondaryBorder,
|
|
1606
|
+
paddingX: 2,
|
|
1607
|
+
paddingY: 1
|
|
1608
|
+
},
|
|
1609
|
+
/* @__PURE__ */ React11.createElement(Text10, { bold: true }, "Model Parameters", " ", exitState.pending ? `(press ${exitState.keyName} again to exit)` : ""),
|
|
1610
|
+
/* @__PURE__ */ React11.createElement(Box9, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React11.createElement(Text10, { bold: true }, "Configure parameters for ", selectedModel, ":"), /* @__PURE__ */ React11.createElement(Box9, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React11.createElement(Text10, { color: theme.secondaryText }, "Use ", /* @__PURE__ */ React11.createElement(Text10, { color: theme.suggestion }, "Tab"), " to navigate between fields. Press ", /* @__PURE__ */ React11.createElement(Text10, { color: theme.suggestion }, "Enter"), " to submit.")), /* @__PURE__ */ React11.createElement(Box9, { flexDirection: "column" }, formFields.map((field, index) => /* @__PURE__ */ React11.createElement(Box9, { flexDirection: "column", marginY: 1, key: field.name }, field.component !== "button" ? /* @__PURE__ */ React11.createElement(React11.Fragment, null, /* @__PURE__ */ React11.createElement(
|
|
1611
|
+
Text10,
|
|
1612
|
+
{
|
|
1613
|
+
bold: true,
|
|
1614
|
+
color: activeFieldIndex === index ? theme.success : void 0
|
|
1615
|
+
},
|
|
1616
|
+
field.label
|
|
1617
|
+
), field.description && /* @__PURE__ */ React11.createElement(Text10, { color: theme.secondaryText }, field.description)) : /* @__PURE__ */ React11.createElement(
|
|
1618
|
+
Text10,
|
|
1619
|
+
{
|
|
1620
|
+
bold: true,
|
|
1621
|
+
color: activeFieldIndex === index ? theme.success : void 0
|
|
1622
|
+
},
|
|
1623
|
+
field.label
|
|
1624
|
+
), /* @__PURE__ */ React11.createElement(Box9, { marginY: 1 }, activeFieldIndex === index ? field.component === "select" ? field.name === "maxTokens" ? /* @__PURE__ */ React11.createElement(
|
|
1625
|
+
Select,
|
|
1626
|
+
{
|
|
1627
|
+
options: field.options || [],
|
|
1628
|
+
onChange: (value) => {
|
|
1629
|
+
const numValue = parseInt(value);
|
|
1630
|
+
setMaxTokens(numValue.toString());
|
|
1631
|
+
setSelectedMaxTokensPreset(numValue);
|
|
1632
|
+
setMaxTokensCursorOffset(numValue.toString().length);
|
|
1633
|
+
setTimeout(() => {
|
|
1634
|
+
setActiveFieldIndex(index + 1);
|
|
1635
|
+
}, 100);
|
|
1636
|
+
},
|
|
1637
|
+
defaultValue: field.defaultValue,
|
|
1638
|
+
visibleOptionCount: 10
|
|
1639
|
+
}
|
|
1640
|
+
) : /* @__PURE__ */ React11.createElement(
|
|
1641
|
+
Select,
|
|
1642
|
+
{
|
|
1643
|
+
options: reasoningEffortOptions,
|
|
1644
|
+
onChange: (value) => {
|
|
1645
|
+
if (isReasoningEffortOption(value)) {
|
|
1646
|
+
setReasoningEffort(value);
|
|
1647
|
+
}
|
|
1648
|
+
setTimeout(() => {
|
|
1649
|
+
setActiveFieldIndex(index + 1);
|
|
1650
|
+
}, 100);
|
|
1651
|
+
},
|
|
1652
|
+
defaultValue: reasoningEffort ?? void 0,
|
|
1653
|
+
visibleOptionCount: 8
|
|
1654
|
+
}
|
|
1655
|
+
) : null : field.name === "maxTokens" ? /* @__PURE__ */ React11.createElement(Text10, { color: theme.secondaryText }, "Current:", " ", /* @__PURE__ */ React11.createElement(Text10, { color: theme.suggestion }, MAX_TOKENS_OPTIONS.find(
|
|
1656
|
+
(opt) => opt.value === parseInt(maxTokens)
|
|
1657
|
+
)?.label || `${maxTokens} tokens`)) : field.name === "reasoningEffort" ? /* @__PURE__ */ React11.createElement(Text10, { color: theme.secondaryText }, "Current:", " ", /* @__PURE__ */ React11.createElement(Text10, { color: theme.suggestion }, reasoningEffort)) : null))), /* @__PURE__ */ React11.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "Press ", /* @__PURE__ */ React11.createElement(Text10, { color: theme.suggestion }, "Tab"), " to navigate,", " ", /* @__PURE__ */ React11.createElement(Text10, { color: theme.suggestion }, "Enter"), " to continue, or", " ", /* @__PURE__ */ React11.createElement(Text10, { color: theme.suggestion }, "Esc"), " to go back"))))
|
|
1658
|
+
));
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
// apps/cli/src/ui/ui/components/model-selector/screens/ModelSelectionScreen.tsx
|
|
1662
|
+
import React12 from "react";
|
|
1663
|
+
import { Box as Box10, Text as Text11 } from "ink";
|
|
1664
|
+
function ModelSelectionScreen({
|
|
1665
|
+
theme,
|
|
1666
|
+
exitState,
|
|
1667
|
+
selectedProvider,
|
|
1668
|
+
availableModels,
|
|
1669
|
+
modelSearchQuery,
|
|
1670
|
+
modelSearchCursorOffset,
|
|
1671
|
+
handleModelSearchChange,
|
|
1672
|
+
handleModelSearchCursorOffsetChange,
|
|
1673
|
+
modelOptions,
|
|
1674
|
+
handleModelSelection,
|
|
1675
|
+
getProviderLabel
|
|
1676
|
+
}) {
|
|
1677
|
+
const modelTypeText = "this model profile";
|
|
1678
|
+
return /* @__PURE__ */ React12.createElement(Box10, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React12.createElement(
|
|
1679
|
+
Box10,
|
|
1680
|
+
{
|
|
1681
|
+
flexDirection: "column",
|
|
1682
|
+
gap: 1,
|
|
1683
|
+
borderStyle: "round",
|
|
1684
|
+
borderColor: theme.secondaryBorder,
|
|
1685
|
+
paddingX: 2,
|
|
1686
|
+
paddingY: 1
|
|
1687
|
+
},
|
|
1688
|
+
/* @__PURE__ */ React12.createElement(Text11, { bold: true }, "Model Selection", " ", exitState.pending ? `(press ${exitState.keyName} again to exit)` : ""),
|
|
1689
|
+
/* @__PURE__ */ React12.createElement(Box10, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React12.createElement(Text11, { bold: true }, "Select a model from", " ", getProviderLabel(selectedProvider, availableModels.length).split(
|
|
1690
|
+
" ("
|
|
1691
|
+
)[0], " ", "for ", modelTypeText, ":"), /* @__PURE__ */ React12.createElement(Box10, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React12.createElement(Text11, { color: theme.secondaryText }, "This model profile can be assigned to different pointers (main, task, compact, quick) for various use cases.")), /* @__PURE__ */ React12.createElement(Box10, { marginY: 1 }, /* @__PURE__ */ React12.createElement(Text11, { bold: true }, "Search models:"), /* @__PURE__ */ React12.createElement(
|
|
1692
|
+
TextInput,
|
|
1693
|
+
{
|
|
1694
|
+
placeholder: "Type to filter models...",
|
|
1695
|
+
value: modelSearchQuery,
|
|
1696
|
+
onChange: handleModelSearchChange,
|
|
1697
|
+
columns: 100,
|
|
1698
|
+
cursorOffset: modelSearchCursorOffset,
|
|
1699
|
+
onChangeCursorOffset: handleModelSearchCursorOffsetChange,
|
|
1700
|
+
showCursor: true,
|
|
1701
|
+
focus: true
|
|
1702
|
+
}
|
|
1703
|
+
)), modelOptions.length > 0 ? /* @__PURE__ */ React12.createElement(React12.Fragment, null, /* @__PURE__ */ React12.createElement(
|
|
1704
|
+
Select,
|
|
1705
|
+
{
|
|
1706
|
+
options: modelOptions,
|
|
1707
|
+
onChange: handleModelSelection,
|
|
1708
|
+
visibleOptionCount: 15
|
|
1709
|
+
}
|
|
1710
|
+
), /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "Showing ", modelOptions.length, " of ", availableModels.length, " models")) : /* @__PURE__ */ React12.createElement(Box10, null, availableModels.length > 0 ? /* @__PURE__ */ React12.createElement(Text11, { color: "yellow" }, "No models match your search. Try a different query.") : /* @__PURE__ */ React12.createElement(Text11, { color: "yellow" }, "No models available for this provider.")), /* @__PURE__ */ React12.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "Press ", /* @__PURE__ */ React12.createElement(Text11, { color: theme.suggestion }, "Esc"), " to go back to API key input")))
|
|
1711
|
+
));
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
// apps/cli/src/ui/ui/components/model-selector/screens/PartnerCodingPlansScreen.tsx
|
|
1715
|
+
import React13 from "react";
|
|
1716
|
+
import { Box as Box11, Newline as Newline5, Text as Text12 } from "ink";
|
|
1717
|
+
function PartnerCodingPlansScreen({
|
|
1718
|
+
theme,
|
|
1719
|
+
exitState,
|
|
1720
|
+
containerPaddingY,
|
|
1721
|
+
containerGap,
|
|
1722
|
+
tightLayout,
|
|
1723
|
+
compactLayout,
|
|
1724
|
+
codingPlanOptions,
|
|
1725
|
+
codingPlanFocusIndex,
|
|
1726
|
+
codingReservedLines,
|
|
1727
|
+
getSafeVisibleOptionCount,
|
|
1728
|
+
renderWindowedOptions
|
|
1729
|
+
}) {
|
|
1730
|
+
const footerMarginTop = tightLayout ? 0 : 1;
|
|
1731
|
+
return /* @__PURE__ */ React13.createElement(Box11, { flexDirection: "column", gap: containerGap }, /* @__PURE__ */ React13.createElement(
|
|
1732
|
+
Box11,
|
|
1733
|
+
{
|
|
1734
|
+
flexDirection: "column",
|
|
1735
|
+
gap: containerGap,
|
|
1736
|
+
borderStyle: "round",
|
|
1737
|
+
borderColor: theme.secondaryBorder,
|
|
1738
|
+
paddingX: 2,
|
|
1739
|
+
paddingY: containerPaddingY
|
|
1740
|
+
},
|
|
1741
|
+
/* @__PURE__ */ React13.createElement(Text12, { bold: true }, "Partner Coding Plans", " ", exitState.pending ? `(press ${exitState.keyName} again to exit)` : ""),
|
|
1742
|
+
/* @__PURE__ */ React13.createElement(Box11, { flexDirection: "column", gap: containerGap }, /* @__PURE__ */ React13.createElement(Text12, { bold: true }, "Select a partner coding plan for specialized programming assistance:"), /* @__PURE__ */ React13.createElement(Box11, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React13.createElement(Text12, { color: theme.secondaryText }, compactLayout ? "Specialized coding models from partners." : /* @__PURE__ */ React13.createElement(React13.Fragment, null, "These are specialized models optimized for coding and development tasks.", /* @__PURE__ */ React13.createElement(Newline5, null), "They require specific coding plan subscriptions from the respective providers."))), renderWindowedOptions(
|
|
1743
|
+
codingPlanOptions,
|
|
1744
|
+
codingPlanFocusIndex,
|
|
1745
|
+
getSafeVisibleOptionCount(
|
|
1746
|
+
5,
|
|
1747
|
+
codingPlanOptions.length,
|
|
1748
|
+
codingReservedLines
|
|
1749
|
+
)
|
|
1750
|
+
), /* @__PURE__ */ React13.createElement(Box11, { marginTop: footerMarginTop }, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "Press ", /* @__PURE__ */ React13.createElement(Text12, { color: theme.suggestion }, "Esc"), " to go back to main menu")))
|
|
1751
|
+
));
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
// apps/cli/src/ui/ui/components/model-selector/screens/PartnerProvidersScreen.tsx
|
|
1755
|
+
import React14 from "react";
|
|
1756
|
+
import { Box as Box12, Text as Text13 } from "ink";
|
|
1757
|
+
function PartnerProvidersScreen({
|
|
1758
|
+
theme,
|
|
1759
|
+
exitState,
|
|
1760
|
+
containerPaddingY,
|
|
1761
|
+
containerGap,
|
|
1762
|
+
compactLayout,
|
|
1763
|
+
tightLayout,
|
|
1764
|
+
partnerProviderOptions,
|
|
1765
|
+
partnerProviderFocusIndex,
|
|
1766
|
+
partnerReservedLines,
|
|
1767
|
+
getSafeVisibleOptionCount,
|
|
1768
|
+
renderWindowedOptions
|
|
1769
|
+
}) {
|
|
1770
|
+
const footerMarginTop = tightLayout ? 0 : 1;
|
|
1771
|
+
return /* @__PURE__ */ React14.createElement(Box12, { flexDirection: "column", gap: containerGap }, /* @__PURE__ */ React14.createElement(
|
|
1772
|
+
Box12,
|
|
1773
|
+
{
|
|
1774
|
+
flexDirection: "column",
|
|
1775
|
+
gap: containerGap,
|
|
1776
|
+
borderStyle: "round",
|
|
1777
|
+
borderColor: theme.secondaryBorder,
|
|
1778
|
+
paddingX: 2,
|
|
1779
|
+
paddingY: containerPaddingY
|
|
1780
|
+
},
|
|
1781
|
+
/* @__PURE__ */ React14.createElement(Text13, { bold: true }, "Partner Providers", " ", exitState.pending ? `(press ${exitState.keyName} again to exit)` : ""),
|
|
1782
|
+
/* @__PURE__ */ React14.createElement(Box12, { flexDirection: "column", gap: containerGap }, /* @__PURE__ */ React14.createElement(Text13, { bold: true }, "Select a partner AI provider for this model profile:"), /* @__PURE__ */ React14.createElement(Box12, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React14.createElement(Text13, { color: theme.secondaryText }, compactLayout ? "Choose an official partner provider." : "Choose from official partner providers to access their models and services.")), renderWindowedOptions(
|
|
1783
|
+
partnerProviderOptions,
|
|
1784
|
+
partnerProviderFocusIndex,
|
|
1785
|
+
getSafeVisibleOptionCount(
|
|
1786
|
+
6,
|
|
1787
|
+
partnerProviderOptions.length,
|
|
1788
|
+
partnerReservedLines
|
|
1789
|
+
)
|
|
1790
|
+
), /* @__PURE__ */ React14.createElement(Box12, { marginTop: footerMarginTop }, /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "Press ", /* @__PURE__ */ React14.createElement(Text13, { color: theme.suggestion }, "Esc"), " to go back to main menu")))
|
|
1791
|
+
));
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
// apps/cli/src/ui/ui/components/model-selector/screens/ProviderSelectionScreen.tsx
|
|
1795
|
+
import React16 from "react";
|
|
1796
|
+
import { Box as Box14, Newline as Newline6, Text as Text15 } from "ink";
|
|
1797
|
+
|
|
1798
|
+
// apps/cli/src/ui/ui/components/model-selector/ScreenContainer.tsx
|
|
1799
|
+
import React15 from "react";
|
|
1800
|
+
import { Box as Box13, Text as Text14 } from "ink";
|
|
1801
|
+
function ScreenContainer({
|
|
1802
|
+
title,
|
|
1803
|
+
exitState,
|
|
1804
|
+
children,
|
|
1805
|
+
paddingY = 1,
|
|
1806
|
+
gap = 1
|
|
1807
|
+
}) {
|
|
1808
|
+
const theme = getTheme();
|
|
1809
|
+
return /* @__PURE__ */ React15.createElement(
|
|
1810
|
+
Box13,
|
|
1811
|
+
{
|
|
1812
|
+
flexDirection: "column",
|
|
1813
|
+
gap,
|
|
1814
|
+
borderStyle: "round",
|
|
1815
|
+
borderColor: theme.secondaryBorder,
|
|
1816
|
+
paddingX: 2,
|
|
1817
|
+
paddingY
|
|
1818
|
+
},
|
|
1819
|
+
/* @__PURE__ */ React15.createElement(Text14, { bold: true }, title, " ", exitState.pending ? `(press ${exitState.keyName} again to exit)` : ""),
|
|
1820
|
+
children
|
|
1821
|
+
);
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
// apps/cli/src/ui/ui/components/model-selector/screens/ProviderSelectionScreen.tsx
|
|
1825
|
+
function ProviderSelectionScreen({
|
|
1826
|
+
theme,
|
|
1827
|
+
exitState,
|
|
1828
|
+
containerPaddingY,
|
|
1829
|
+
containerGap,
|
|
1830
|
+
compactLayout,
|
|
1831
|
+
tightLayout,
|
|
1832
|
+
mainMenuOptions,
|
|
1833
|
+
providerFocusIndex,
|
|
1834
|
+
providerReservedLines,
|
|
1835
|
+
getSafeVisibleOptionCount,
|
|
1836
|
+
renderWindowedOptions
|
|
1837
|
+
}) {
|
|
1838
|
+
return /* @__PURE__ */ React16.createElement(
|
|
1839
|
+
ScreenContainer,
|
|
1840
|
+
{
|
|
1841
|
+
title: "Provider Selection",
|
|
1842
|
+
exitState,
|
|
1843
|
+
paddingY: containerPaddingY,
|
|
1844
|
+
gap: containerGap,
|
|
1845
|
+
children: /* @__PURE__ */ React16.createElement(Box14, { flexDirection: "column", gap: containerGap }, /* @__PURE__ */ React16.createElement(Text15, { bold: true }, "Select your preferred AI provider for this model profile:"), /* @__PURE__ */ React16.createElement(Box14, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React16.createElement(Text15, { color: theme.secondaryText }, compactLayout ? "Choose the provider to use for this profile." : /* @__PURE__ */ React16.createElement(React16.Fragment, null, "Choose the provider you want to use for this model profile.", /* @__PURE__ */ React16.createElement(Newline6, null), "This will determine which models are available to you."))), renderWindowedOptions(
|
|
1846
|
+
mainMenuOptions,
|
|
1847
|
+
providerFocusIndex,
|
|
1848
|
+
getSafeVisibleOptionCount(
|
|
1849
|
+
5,
|
|
1850
|
+
mainMenuOptions.length,
|
|
1851
|
+
providerReservedLines
|
|
1852
|
+
)
|
|
1853
|
+
), /* @__PURE__ */ React16.createElement(Box14, { marginTop: tightLayout ? 0 : 1 }, /* @__PURE__ */ React16.createElement(Text15, { dimColor: true }, "You can change this later by running", " ", /* @__PURE__ */ React16.createElement(Text15, { color: theme.suggestion }, "/model"), " again")))
|
|
1854
|
+
}
|
|
1855
|
+
);
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
// apps/cli/src/ui/ui/components/model-selector/screens/ResourceNameScreen.tsx
|
|
1859
|
+
import React17 from "react";
|
|
1860
|
+
import { Box as Box15, Newline as Newline7, Text as Text16 } from "ink";
|
|
1861
|
+
function ResourceNameScreen({
|
|
1862
|
+
theme,
|
|
1863
|
+
exitState,
|
|
1864
|
+
resourceName,
|
|
1865
|
+
setResourceName,
|
|
1866
|
+
handleResourceNameSubmit,
|
|
1867
|
+
resourceNameCursorOffset,
|
|
1868
|
+
setResourceNameCursorOffset
|
|
1869
|
+
}) {
|
|
1870
|
+
return /* @__PURE__ */ React17.createElement(Box15, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React17.createElement(
|
|
1871
|
+
Box15,
|
|
1872
|
+
{
|
|
1873
|
+
flexDirection: "column",
|
|
1874
|
+
gap: 1,
|
|
1875
|
+
borderStyle: "round",
|
|
1876
|
+
borderColor: theme.secondaryBorder,
|
|
1877
|
+
paddingX: 2,
|
|
1878
|
+
paddingY: 1
|
|
1879
|
+
},
|
|
1880
|
+
/* @__PURE__ */ React17.createElement(Text16, { bold: true }, "Azure Resource Setup", " ", exitState.pending ? `(press ${exitState.keyName} again to exit)` : ""),
|
|
1881
|
+
/* @__PURE__ */ React17.createElement(Box15, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React17.createElement(Text16, { bold: true }, "Enter your Azure OpenAI resource name:"), /* @__PURE__ */ React17.createElement(Box15, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React17.createElement(Text16, { color: theme.secondaryText }, "This is the name of your Azure OpenAI resource (without the full domain).", /* @__PURE__ */ React17.createElement(Newline7, null), 'For example, if your endpoint is "https://myresource.openai.azure.com", enter "myresource".')), /* @__PURE__ */ React17.createElement(Box15, null, /* @__PURE__ */ React17.createElement(
|
|
1882
|
+
TextInput,
|
|
1883
|
+
{
|
|
1884
|
+
placeholder: "myazureresource",
|
|
1885
|
+
value: resourceName,
|
|
1886
|
+
onChange: setResourceName,
|
|
1887
|
+
onSubmit: handleResourceNameSubmit,
|
|
1888
|
+
columns: 100,
|
|
1889
|
+
cursorOffset: resourceNameCursorOffset,
|
|
1890
|
+
onChangeCursorOffset: setResourceNameCursorOffset,
|
|
1891
|
+
showCursor: true
|
|
1892
|
+
}
|
|
1893
|
+
)), /* @__PURE__ */ React17.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text16, null, /* @__PURE__ */ React17.createElement(Text16, { color: theme.suggestion, dimColor: !resourceName }, "[Submit Resource Name]"), /* @__PURE__ */ React17.createElement(Text16, null, " - Press Enter or click to continue"))), /* @__PURE__ */ React17.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text16, { dimColor: true }, "Press ", /* @__PURE__ */ React17.createElement(Text16, { color: theme.suggestion }, "Enter"), " to continue or", " ", /* @__PURE__ */ React17.createElement(Text16, { color: theme.suggestion }, "Esc"), " to go back")))
|
|
1894
|
+
));
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
// apps/cli/src/ui/components/ModelSelector/ModelSelectorView.tsx
|
|
1898
|
+
function ModelSelectorView(props) {
|
|
1899
|
+
function getSafeVisibleOptionCount(requestedCount, optionLength, reservedLines = 10) {
|
|
1900
|
+
const rows = props.terminalRows;
|
|
1901
|
+
const available = Math.max(1, rows - reservedLines);
|
|
1902
|
+
return Math.max(1, Math.min(requestedCount, optionLength, available));
|
|
1903
|
+
}
|
|
1904
|
+
function renderWindowedOptions(options, focusedIndex, maxVisible) {
|
|
1905
|
+
if (options.length === 0) {
|
|
1906
|
+
return /* @__PURE__ */ React18.createElement(Text17, { color: props.theme.secondaryText }, "No options available.");
|
|
1907
|
+
}
|
|
1908
|
+
const visibleCount = Math.max(1, Math.min(maxVisible, options.length));
|
|
1909
|
+
const half = Math.floor(visibleCount / 2);
|
|
1910
|
+
const start = Math.max(
|
|
1911
|
+
0,
|
|
1912
|
+
Math.min(focusedIndex - half, Math.max(0, options.length - visibleCount))
|
|
1913
|
+
);
|
|
1914
|
+
const end = Math.min(options.length, start + visibleCount);
|
|
1915
|
+
const showUp = start > 0;
|
|
1916
|
+
const showDown = end < options.length;
|
|
1917
|
+
return /* @__PURE__ */ React18.createElement(Box16, { flexDirection: "column", gap: 0 }, showUp && /* @__PURE__ */ React18.createElement(Text17, { color: props.theme.secondaryText }, figures.arrowUp, " More"), options.slice(start, end).map((opt, idx) => {
|
|
1918
|
+
const absoluteIndex = start + idx;
|
|
1919
|
+
const isFocused = absoluteIndex === focusedIndex;
|
|
1920
|
+
return /* @__PURE__ */ React18.createElement(Box16, { key: opt.value, flexDirection: "row" }, /* @__PURE__ */ React18.createElement(
|
|
1921
|
+
Text17,
|
|
1922
|
+
{
|
|
1923
|
+
color: isFocused ? props.theme.kode : props.theme.secondaryText
|
|
1924
|
+
},
|
|
1925
|
+
isFocused ? figures.pointer : " "
|
|
1926
|
+
), /* @__PURE__ */ React18.createElement(
|
|
1927
|
+
Text17,
|
|
1928
|
+
{
|
|
1929
|
+
color: isFocused ? props.theme.text : props.theme.secondaryText,
|
|
1930
|
+
bold: isFocused
|
|
1931
|
+
},
|
|
1932
|
+
" ",
|
|
1933
|
+
opt.label
|
|
1934
|
+
));
|
|
1935
|
+
}), showDown && /* @__PURE__ */ React18.createElement(Text17, { color: props.theme.secondaryText }, figures.arrowDown, " More"));
|
|
1936
|
+
}
|
|
1937
|
+
if (props.currentScreen === "apiKey") {
|
|
1938
|
+
return /* @__PURE__ */ React18.createElement(
|
|
1939
|
+
ApiKeyScreen,
|
|
1940
|
+
{
|
|
1941
|
+
theme: props.theme,
|
|
1942
|
+
exitState: props.exitState,
|
|
1943
|
+
selectedProvider: props.selectedProvider,
|
|
1944
|
+
apiKey: props.apiKey,
|
|
1945
|
+
cursorOffset: props.cursorOffset,
|
|
1946
|
+
handleApiKeyChange: props.handleApiKeyChange,
|
|
1947
|
+
handleApiKeySubmit: props.handleApiKeySubmit,
|
|
1948
|
+
handleCursorOffsetChange: props.handleCursorOffsetChange,
|
|
1949
|
+
apiKeyCleanedNotification: props.apiKeyCleanedNotification,
|
|
1950
|
+
isLoadingModels: props.isLoadingModels,
|
|
1951
|
+
providerBaseUrl: props.providerBaseUrl,
|
|
1952
|
+
modelLoadError: props.modelLoadError,
|
|
1953
|
+
formatApiKeyDisplay: props.formatApiKeyDisplay,
|
|
1954
|
+
getProviderLabel: props.getProviderLabel
|
|
1955
|
+
}
|
|
1956
|
+
);
|
|
1957
|
+
}
|
|
1958
|
+
if (props.currentScreen === "model") {
|
|
1959
|
+
return /* @__PURE__ */ React18.createElement(
|
|
1960
|
+
ModelSelectionScreen,
|
|
1961
|
+
{
|
|
1962
|
+
theme: props.theme,
|
|
1963
|
+
exitState: props.exitState,
|
|
1964
|
+
selectedProvider: props.selectedProvider,
|
|
1965
|
+
availableModels: props.availableModels,
|
|
1966
|
+
modelSearchQuery: props.modelSearchQuery,
|
|
1967
|
+
modelSearchCursorOffset: props.modelSearchCursorOffset,
|
|
1968
|
+
handleModelSearchChange: props.handleModelSearchChange,
|
|
1969
|
+
handleModelSearchCursorOffsetChange: props.handleModelSearchCursorOffsetChange,
|
|
1970
|
+
modelOptions: props.modelOptions,
|
|
1971
|
+
handleModelSelection: props.handleModelSelection,
|
|
1972
|
+
getProviderLabel: props.getProviderLabel
|
|
1973
|
+
}
|
|
1974
|
+
);
|
|
1975
|
+
}
|
|
1976
|
+
if (props.currentScreen === "modelParams") {
|
|
1977
|
+
const formFields = props.getFormFieldsForModelParams();
|
|
1978
|
+
return /* @__PURE__ */ React18.createElement(
|
|
1979
|
+
ModelParamsScreen,
|
|
1980
|
+
{
|
|
1981
|
+
theme: props.theme,
|
|
1982
|
+
exitState: props.exitState,
|
|
1983
|
+
selectedModel: props.selectedModel,
|
|
1984
|
+
formFields,
|
|
1985
|
+
activeFieldIndex: props.activeFieldIndex,
|
|
1986
|
+
setActiveFieldIndex: props.setActiveFieldIndex,
|
|
1987
|
+
maxTokens: props.maxTokens,
|
|
1988
|
+
setMaxTokens: props.setMaxTokens,
|
|
1989
|
+
setSelectedMaxTokensPreset: props.setSelectedMaxTokensPreset,
|
|
1990
|
+
setMaxTokensCursorOffset: props.setMaxTokensCursorOffset,
|
|
1991
|
+
reasoningEffortOptions: props.reasoningEffortOptions,
|
|
1992
|
+
reasoningEffort: props.reasoningEffort,
|
|
1993
|
+
setReasoningEffort: props.setReasoningEffort
|
|
1994
|
+
}
|
|
1995
|
+
);
|
|
1996
|
+
}
|
|
1997
|
+
if (props.currentScreen === "resourceName") {
|
|
1998
|
+
return /* @__PURE__ */ React18.createElement(
|
|
1999
|
+
ResourceNameScreen,
|
|
2000
|
+
{
|
|
2001
|
+
theme: props.theme,
|
|
2002
|
+
exitState: props.exitState,
|
|
2003
|
+
resourceName: props.resourceName,
|
|
2004
|
+
setResourceName: props.setResourceName,
|
|
2005
|
+
handleResourceNameSubmit: props.handleResourceNameSubmit,
|
|
2006
|
+
resourceNameCursorOffset: props.resourceNameCursorOffset,
|
|
2007
|
+
setResourceNameCursorOffset: props.setResourceNameCursorOffset
|
|
2008
|
+
}
|
|
2009
|
+
);
|
|
2010
|
+
}
|
|
2011
|
+
if (props.currentScreen === "baseUrl") {
|
|
2012
|
+
return /* @__PURE__ */ React18.createElement(
|
|
2013
|
+
BaseUrlScreen,
|
|
2014
|
+
{
|
|
2015
|
+
theme: props.theme,
|
|
2016
|
+
exitState: props.exitState,
|
|
2017
|
+
selectedProvider: props.selectedProvider,
|
|
2018
|
+
isLoadingModels: props.isLoadingModels,
|
|
2019
|
+
modelLoadError: props.modelLoadError,
|
|
2020
|
+
customBaseUrl: props.customBaseUrl,
|
|
2021
|
+
setCustomBaseUrl: props.setCustomBaseUrl,
|
|
2022
|
+
handleCustomBaseUrlSubmit: props.handleCustomBaseUrlSubmit,
|
|
2023
|
+
customBaseUrlCursorOffset: props.customBaseUrlCursorOffset,
|
|
2024
|
+
setCustomBaseUrlCursorOffset: props.setCustomBaseUrlCursorOffset,
|
|
2025
|
+
providerBaseUrl: props.providerBaseUrl,
|
|
2026
|
+
setProviderBaseUrl: props.setProviderBaseUrl,
|
|
2027
|
+
handleProviderBaseUrlSubmit: props.handleProviderBaseUrlSubmit,
|
|
2028
|
+
providerBaseUrlCursorOffset: props.providerBaseUrlCursorOffset,
|
|
2029
|
+
setProviderBaseUrlCursorOffset: props.setProviderBaseUrlCursorOffset
|
|
2030
|
+
}
|
|
2031
|
+
);
|
|
2032
|
+
}
|
|
2033
|
+
if (props.currentScreen === "modelInput") {
|
|
2034
|
+
return /* @__PURE__ */ React18.createElement(
|
|
2035
|
+
ModelInputScreen,
|
|
2036
|
+
{
|
|
2037
|
+
theme: props.theme,
|
|
2038
|
+
exitState: props.exitState,
|
|
2039
|
+
selectedProvider: props.selectedProvider,
|
|
2040
|
+
customModelName: props.customModelName,
|
|
2041
|
+
setCustomModelName: props.setCustomModelName,
|
|
2042
|
+
handleCustomModelSubmit: props.handleCustomModelSubmit,
|
|
2043
|
+
customModelNameCursorOffset: props.customModelNameCursorOffset,
|
|
2044
|
+
setCustomModelNameCursorOffset: props.setCustomModelNameCursorOffset
|
|
2045
|
+
}
|
|
2046
|
+
);
|
|
2047
|
+
}
|
|
2048
|
+
if (props.currentScreen === "contextLength") {
|
|
2049
|
+
return /* @__PURE__ */ React18.createElement(
|
|
2050
|
+
ContextLengthScreen,
|
|
2051
|
+
{
|
|
2052
|
+
theme: props.theme,
|
|
2053
|
+
exitState: props.exitState,
|
|
2054
|
+
contextLength: props.contextLength
|
|
2055
|
+
}
|
|
2056
|
+
);
|
|
2057
|
+
}
|
|
2058
|
+
if (props.currentScreen === "connectionTest") {
|
|
2059
|
+
return /* @__PURE__ */ React18.createElement(
|
|
2060
|
+
ConnectionTestScreen,
|
|
2061
|
+
{
|
|
2062
|
+
theme: props.theme,
|
|
2063
|
+
exitState: props.exitState,
|
|
2064
|
+
selectedProvider: props.selectedProvider,
|
|
2065
|
+
getProviderLabel: props.getProviderLabel,
|
|
2066
|
+
isTestingConnection: props.isTestingConnection,
|
|
2067
|
+
connectionTestResult: props.connectionTestResult
|
|
2068
|
+
}
|
|
2069
|
+
);
|
|
2070
|
+
}
|
|
2071
|
+
if (props.currentScreen === "confirmation") {
|
|
2072
|
+
return /* @__PURE__ */ React18.createElement(
|
|
2073
|
+
ConfirmationScreen,
|
|
2074
|
+
{
|
|
2075
|
+
theme: props.theme,
|
|
2076
|
+
exitState: props.exitState,
|
|
2077
|
+
selectedProvider: props.selectedProvider,
|
|
2078
|
+
selectedModel: props.selectedModel,
|
|
2079
|
+
resourceName: props.resourceName,
|
|
2080
|
+
ollamaBaseUrl: props.ollamaBaseUrl,
|
|
2081
|
+
customBaseUrl: props.customBaseUrl,
|
|
2082
|
+
apiKey: props.apiKey,
|
|
2083
|
+
maxTokens: props.maxTokens,
|
|
2084
|
+
contextLength: props.contextLength,
|
|
2085
|
+
supportsReasoningEffort: props.supportsReasoningEffort,
|
|
2086
|
+
reasoningEffort: props.reasoningEffort,
|
|
2087
|
+
validationError: props.validationError,
|
|
2088
|
+
formatApiKeyDisplay: props.formatApiKeyDisplay,
|
|
2089
|
+
getProviderLabel: props.getProviderLabel
|
|
2090
|
+
}
|
|
2091
|
+
);
|
|
2092
|
+
}
|
|
2093
|
+
if (props.currentScreen === "partnerProviders") {
|
|
2094
|
+
return /* @__PURE__ */ React18.createElement(
|
|
2095
|
+
PartnerProvidersScreen,
|
|
2096
|
+
{
|
|
2097
|
+
theme: props.theme,
|
|
2098
|
+
exitState: props.exitState,
|
|
2099
|
+
containerPaddingY: props.containerPaddingY,
|
|
2100
|
+
containerGap: props.containerGap,
|
|
2101
|
+
compactLayout: props.compactLayout,
|
|
2102
|
+
tightLayout: props.tightLayout,
|
|
2103
|
+
partnerProviderOptions: props.partnerProviderOptions,
|
|
2104
|
+
partnerProviderFocusIndex: props.partnerProviderFocusIndex,
|
|
2105
|
+
partnerReservedLines: props.partnerReservedLines,
|
|
2106
|
+
getSafeVisibleOptionCount,
|
|
2107
|
+
renderWindowedOptions
|
|
2108
|
+
}
|
|
2109
|
+
);
|
|
2110
|
+
}
|
|
2111
|
+
if (props.currentScreen === "partnerCodingPlans") {
|
|
2112
|
+
return /* @__PURE__ */ React18.createElement(
|
|
2113
|
+
PartnerCodingPlansScreen,
|
|
2114
|
+
{
|
|
2115
|
+
theme: props.theme,
|
|
2116
|
+
exitState: props.exitState,
|
|
2117
|
+
containerPaddingY: props.containerPaddingY,
|
|
2118
|
+
containerGap: props.containerGap,
|
|
2119
|
+
tightLayout: props.tightLayout,
|
|
2120
|
+
compactLayout: props.compactLayout,
|
|
2121
|
+
codingPlanOptions: props.codingPlanOptions,
|
|
2122
|
+
codingPlanFocusIndex: props.codingPlanFocusIndex,
|
|
2123
|
+
codingReservedLines: props.codingReservedLines,
|
|
2124
|
+
getSafeVisibleOptionCount,
|
|
2125
|
+
renderWindowedOptions
|
|
2126
|
+
}
|
|
2127
|
+
);
|
|
2128
|
+
}
|
|
2129
|
+
return /* @__PURE__ */ React18.createElement(
|
|
2130
|
+
ProviderSelectionScreen,
|
|
2131
|
+
{
|
|
2132
|
+
theme: props.theme,
|
|
2133
|
+
exitState: props.exitState,
|
|
2134
|
+
containerPaddingY: props.containerPaddingY,
|
|
2135
|
+
containerGap: props.containerGap,
|
|
2136
|
+
compactLayout: props.compactLayout,
|
|
2137
|
+
tightLayout: props.tightLayout,
|
|
2138
|
+
mainMenuOptions: props.mainMenuOptions,
|
|
2139
|
+
providerFocusIndex: props.providerFocusIndex,
|
|
2140
|
+
providerReservedLines: props.providerReservedLines,
|
|
2141
|
+
getSafeVisibleOptionCount,
|
|
2142
|
+
renderWindowedOptions
|
|
2143
|
+
}
|
|
2144
|
+
);
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
// apps/cli/src/ui/components/ModelSelector/useModelSelectorController.tsx
|
|
2148
|
+
import { useEffect as useEffect2, useMemo as useMemo4 } from "react";
|
|
2149
|
+
import { useStdout } from "ink";
|
|
2150
|
+
|
|
2151
|
+
// apps/cli/src/ui/ui/components/model-selector/printModelConfig.ts
|
|
2152
|
+
import chalk2 from "chalk";
|
|
2153
|
+
function printModelConfig() {
|
|
2154
|
+
const config = getGlobalConfig();
|
|
2155
|
+
const modelProfiles = config.modelProfiles || [];
|
|
2156
|
+
const activeProfiles = modelProfiles.filter((p) => p.isActive);
|
|
2157
|
+
if (activeProfiles.length === 0) {
|
|
2158
|
+
process.stdout.write(
|
|
2159
|
+
`${chalk2.gray(" \u23BF No active model profiles configured")}
|
|
2160
|
+
`
|
|
2161
|
+
);
|
|
2162
|
+
return;
|
|
2163
|
+
}
|
|
2164
|
+
const profileSummary = activeProfiles.map((p) => `${p.name} (${p.provider}: ${p.modelName})`).join(" | ");
|
|
2165
|
+
process.stdout.write(`${chalk2.gray(` \u23BF ${profileSummary}`)}
|
|
2166
|
+
`);
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
// apps/cli/src/ui/components/ModelSelector/useModelSelectorInput.ts
|
|
2170
|
+
import { useInput as useInput3 } from "ink";
|
|
2171
|
+
function useModelSelectorInput(args) {
|
|
2172
|
+
useInput3((input, key) => {
|
|
2173
|
+
if (args.currentScreen === "provider") {
|
|
2174
|
+
if (key.upArrow) {
|
|
2175
|
+
args.setProviderFocusIndex(
|
|
2176
|
+
(prev) => args.mainMenuOptions.length === 0 ? 0 : (prev - 1 + args.mainMenuOptions.length) % args.mainMenuOptions.length
|
|
2177
|
+
);
|
|
2178
|
+
return;
|
|
2179
|
+
}
|
|
2180
|
+
if (key.downArrow) {
|
|
2181
|
+
args.setProviderFocusIndex(
|
|
2182
|
+
(prev) => args.mainMenuOptions.length === 0 ? 0 : (prev + 1) % args.mainMenuOptions.length
|
|
2183
|
+
);
|
|
2184
|
+
return;
|
|
2185
|
+
}
|
|
2186
|
+
if (key.return) {
|
|
2187
|
+
const opt = args.mainMenuOptions[args.providerFocusIndex];
|
|
2188
|
+
if (opt) {
|
|
2189
|
+
args.handleProviderSelection(opt.value);
|
|
2190
|
+
}
|
|
2191
|
+
return;
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
if (args.currentScreen === "partnerProviders") {
|
|
2195
|
+
if (key.upArrow) {
|
|
2196
|
+
args.setPartnerProviderFocusIndex(
|
|
2197
|
+
(prev) => args.partnerProviderOptions.length === 0 ? 0 : (prev - 1 + args.partnerProviderOptions.length) % args.partnerProviderOptions.length
|
|
2198
|
+
);
|
|
2199
|
+
return;
|
|
2200
|
+
}
|
|
2201
|
+
if (key.downArrow) {
|
|
2202
|
+
args.setPartnerProviderFocusIndex(
|
|
2203
|
+
(prev) => args.partnerProviderOptions.length === 0 ? 0 : (prev + 1) % args.partnerProviderOptions.length
|
|
2204
|
+
);
|
|
2205
|
+
return;
|
|
2206
|
+
}
|
|
2207
|
+
if (key.return) {
|
|
2208
|
+
const opt = args.partnerProviderOptions[args.partnerProviderFocusIndex];
|
|
2209
|
+
if (opt) {
|
|
2210
|
+
args.handleProviderSelection(opt.value);
|
|
2211
|
+
}
|
|
2212
|
+
return;
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
if (args.currentScreen === "partnerCodingPlans") {
|
|
2216
|
+
if (key.upArrow) {
|
|
2217
|
+
args.setCodingPlanFocusIndex(
|
|
2218
|
+
(prev) => args.codingPlanOptions.length === 0 ? 0 : (prev - 1 + args.codingPlanOptions.length) % args.codingPlanOptions.length
|
|
2219
|
+
);
|
|
2220
|
+
return;
|
|
2221
|
+
}
|
|
2222
|
+
if (key.downArrow) {
|
|
2223
|
+
args.setCodingPlanFocusIndex(
|
|
2224
|
+
(prev) => args.codingPlanOptions.length === 0 ? 0 : (prev + 1) % args.codingPlanOptions.length
|
|
2225
|
+
);
|
|
2226
|
+
return;
|
|
2227
|
+
}
|
|
2228
|
+
if (key.return) {
|
|
2229
|
+
const opt = args.codingPlanOptions[args.codingPlanFocusIndex];
|
|
2230
|
+
if (opt) {
|
|
2231
|
+
args.handleProviderSelection(opt.value);
|
|
2232
|
+
}
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
if (args.currentScreen === "apiKey" && key.return) {
|
|
2237
|
+
if (args.apiKey) {
|
|
2238
|
+
void args.handleApiKeySubmit(args.apiKey);
|
|
2239
|
+
}
|
|
2240
|
+
return;
|
|
2241
|
+
}
|
|
2242
|
+
if (args.currentScreen === "apiKey" && key.tab) {
|
|
2243
|
+
if (args.selectedProvider === "anthropic" || args.selectedProvider === "kimi" || args.selectedProvider === "deepseek" || args.selectedProvider === "qwen" || args.selectedProvider === "glm" || args.selectedProvider === "glm-coding" || args.selectedProvider === "minimax" || args.selectedProvider === "minimax-coding" || args.selectedProvider === "baidu-qianfan" || args.selectedProvider === "siliconflow" || args.selectedProvider === "custom-openai") {
|
|
2244
|
+
args.navigateTo("modelInput");
|
|
2245
|
+
return;
|
|
2246
|
+
}
|
|
2247
|
+
void args.fetchModelsWithRetry().catch((error) => {
|
|
2248
|
+
console.error("Final error after retries:", error);
|
|
2249
|
+
});
|
|
2250
|
+
return;
|
|
2251
|
+
}
|
|
2252
|
+
if (args.currentScreen === "resourceName" && key.return) {
|
|
2253
|
+
if (args.resourceName) {
|
|
2254
|
+
args.handleResourceNameSubmit(args.resourceName);
|
|
2255
|
+
}
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2258
|
+
if (args.currentScreen === "baseUrl" && key.return) {
|
|
2259
|
+
if (args.selectedProvider === "custom-openai") {
|
|
2260
|
+
args.handleCustomBaseUrlSubmit(args.customBaseUrl);
|
|
2261
|
+
} else {
|
|
2262
|
+
args.handleProviderBaseUrlSubmit(args.providerBaseUrl);
|
|
2263
|
+
}
|
|
2264
|
+
return;
|
|
2265
|
+
}
|
|
2266
|
+
if (args.currentScreen === "modelInput" && key.return) {
|
|
2267
|
+
if (args.customModelName) {
|
|
2268
|
+
args.handleCustomModelSubmit(args.customModelName);
|
|
2269
|
+
}
|
|
2270
|
+
return;
|
|
2271
|
+
}
|
|
2272
|
+
if (args.currentScreen === "confirmation" && key.return) {
|
|
2273
|
+
void args.handleConfirmation().catch((error) => {
|
|
2274
|
+
console.error("Error in handleConfirmation:", error);
|
|
2275
|
+
args.setValidationError(
|
|
2276
|
+
error instanceof Error ? error.message : "Unexpected error occurred"
|
|
2277
|
+
);
|
|
2278
|
+
});
|
|
2279
|
+
return;
|
|
2280
|
+
}
|
|
2281
|
+
if (args.currentScreen === "connectionTest") {
|
|
2282
|
+
if (key.return) {
|
|
2283
|
+
if (!args.isTestingConnection && !args.connectionTestResult) {
|
|
2284
|
+
args.handleConnectionTest();
|
|
2285
|
+
} else if (args.connectionTestResult && args.connectionTestResult.success) {
|
|
2286
|
+
args.navigateTo("confirmation");
|
|
2287
|
+
} else if (args.connectionTestResult && !args.connectionTestResult.success) {
|
|
2288
|
+
args.handleConnectionTest();
|
|
2289
|
+
}
|
|
2290
|
+
return;
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
if (args.currentScreen === "contextLength") {
|
|
2294
|
+
if (key.return) {
|
|
2295
|
+
args.handleContextLengthSubmit();
|
|
2296
|
+
return;
|
|
2297
|
+
}
|
|
2298
|
+
if (key.upArrow) {
|
|
2299
|
+
const currentIndex = CONTEXT_LENGTH_OPTIONS.findIndex(
|
|
2300
|
+
(opt) => opt.value === args.contextLength
|
|
2301
|
+
);
|
|
2302
|
+
const newIndex = currentIndex > 0 ? currentIndex - 1 : currentIndex === -1 ? CONTEXT_LENGTH_OPTIONS.findIndex(
|
|
2303
|
+
(opt) => opt.value === DEFAULT_CONTEXT_LENGTH
|
|
2304
|
+
) || 0 : CONTEXT_LENGTH_OPTIONS.length - 1;
|
|
2305
|
+
args.setContextLength(CONTEXT_LENGTH_OPTIONS[newIndex].value);
|
|
2306
|
+
return;
|
|
2307
|
+
}
|
|
2308
|
+
if (key.downArrow) {
|
|
2309
|
+
const currentIndex = CONTEXT_LENGTH_OPTIONS.findIndex(
|
|
2310
|
+
(opt) => opt.value === args.contextLength
|
|
2311
|
+
);
|
|
2312
|
+
const newIndex = currentIndex === -1 ? CONTEXT_LENGTH_OPTIONS.findIndex(
|
|
2313
|
+
(opt) => opt.value === DEFAULT_CONTEXT_LENGTH
|
|
2314
|
+
) || 0 : (currentIndex + 1) % CONTEXT_LENGTH_OPTIONS.length;
|
|
2315
|
+
args.setContextLength(CONTEXT_LENGTH_OPTIONS[newIndex].value);
|
|
2316
|
+
return;
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
if (args.currentScreen === "apiKey" && (key.ctrl && input === "v" || key.meta && input === "v")) {
|
|
2320
|
+
args.setModelLoadError(
|
|
2321
|
+
"Please use your terminal's paste functionality or type the API key manually"
|
|
2322
|
+
);
|
|
2323
|
+
return;
|
|
2324
|
+
}
|
|
2325
|
+
if (args.currentScreen === "modelParams" && key.tab) {
|
|
2326
|
+
const formFields = args.getFormFieldsForModelParams();
|
|
2327
|
+
args.setActiveFieldIndex((current) => (current + 1) % formFields.length);
|
|
2328
|
+
return;
|
|
2329
|
+
}
|
|
2330
|
+
if (args.currentScreen === "modelParams" && key.return) {
|
|
2331
|
+
const formFields = args.getFormFieldsForModelParams();
|
|
2332
|
+
const currentField = formFields[args.activeFieldIndex];
|
|
2333
|
+
if (currentField?.name === "submit" || args.activeFieldIndex === formFields.length - 1) {
|
|
2334
|
+
args.handleModelParamsSubmit();
|
|
2335
|
+
} else if (currentField?.component === "select") {
|
|
2336
|
+
args.setActiveFieldIndex(
|
|
2337
|
+
(current) => Math.min(current + 1, formFields.length - 1)
|
|
2338
|
+
);
|
|
2339
|
+
}
|
|
2340
|
+
return;
|
|
2341
|
+
}
|
|
2342
|
+
});
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
// apps/cli/src/ui/components/ModelSelector/useModelSelectorMenus.ts
|
|
2346
|
+
import { useEffect, useMemo as useMemo2 } from "react";
|
|
2347
|
+
function useModelSelectorMenus(args) {
|
|
2348
|
+
function getProviderLabel(provider, modelCount) {
|
|
2349
|
+
if (providers[provider]) {
|
|
2350
|
+
const wipTag = "(WIP)";
|
|
2351
|
+
return `${providers[provider].name} ${providers[provider].status === "wip" ? wipTag : ""}`;
|
|
2352
|
+
}
|
|
2353
|
+
return `${provider}`;
|
|
2354
|
+
}
|
|
2355
|
+
const mainMenuOptions = useMemo2(
|
|
2356
|
+
() => [
|
|
2357
|
+
{ value: "custom-openai", label: "Custom OpenAI-Compatible API" },
|
|
2358
|
+
{ value: "custom-anthropic", label: "Custom Messages API (v1/messages)" },
|
|
2359
|
+
{ value: "partnerProviders", label: "Partner Providers \u2192" },
|
|
2360
|
+
{ value: "partnerCodingPlans", label: "Partner Coding Plans \u2192" },
|
|
2361
|
+
{
|
|
2362
|
+
value: "ollama",
|
|
2363
|
+
label: getProviderLabel("ollama", models_default.ollama?.length || 0)
|
|
2364
|
+
}
|
|
2365
|
+
],
|
|
2366
|
+
[]
|
|
2367
|
+
);
|
|
2368
|
+
const rankedProviders = useMemo2(
|
|
2369
|
+
() => [
|
|
2370
|
+
"openai",
|
|
2371
|
+
"anthropic",
|
|
2372
|
+
"gemini",
|
|
2373
|
+
"glm",
|
|
2374
|
+
"kimi",
|
|
2375
|
+
"minimax",
|
|
2376
|
+
"qwen",
|
|
2377
|
+
"deepseek",
|
|
2378
|
+
"openrouter",
|
|
2379
|
+
"burncloud",
|
|
2380
|
+
"siliconflow",
|
|
2381
|
+
"baidu-qianfan",
|
|
2382
|
+
"mistral",
|
|
2383
|
+
"xai",
|
|
2384
|
+
"groq",
|
|
2385
|
+
"azure"
|
|
2386
|
+
],
|
|
2387
|
+
[]
|
|
2388
|
+
);
|
|
2389
|
+
const partnerProviders = useMemo2(
|
|
2390
|
+
() => rankedProviders.filter(
|
|
2391
|
+
(provider) => providers[provider] && !provider.includes("coding") && provider !== "custom-openai" && provider !== "ollama"
|
|
2392
|
+
),
|
|
2393
|
+
[rankedProviders]
|
|
2394
|
+
);
|
|
2395
|
+
const codingPlanProviders = useMemo2(
|
|
2396
|
+
() => Object.keys(providers).filter((provider) => provider.includes("coding")),
|
|
2397
|
+
[]
|
|
2398
|
+
);
|
|
2399
|
+
const partnerProviderOptions = useMemo2(
|
|
2400
|
+
() => partnerProviders.map((provider) => {
|
|
2401
|
+
const modelCount = models_default[provider]?.length || 0;
|
|
2402
|
+
return {
|
|
2403
|
+
label: getProviderLabel(provider, modelCount),
|
|
2404
|
+
value: provider
|
|
2405
|
+
};
|
|
2406
|
+
}),
|
|
2407
|
+
[partnerProviders]
|
|
2408
|
+
);
|
|
2409
|
+
const codingPlanOptions = useMemo2(
|
|
2410
|
+
() => codingPlanProviders.map((provider) => {
|
|
2411
|
+
const modelCount = models_default[provider]?.length || 0;
|
|
2412
|
+
return {
|
|
2413
|
+
label: getProviderLabel(provider, modelCount),
|
|
2414
|
+
value: provider
|
|
2415
|
+
};
|
|
2416
|
+
}),
|
|
2417
|
+
[codingPlanProviders]
|
|
2418
|
+
);
|
|
2419
|
+
const providerReservedLines = 8 + args.containerPaddingY * 2 + args.containerGap * 2;
|
|
2420
|
+
const partnerReservedLines = 10 + args.containerPaddingY * 2 + args.containerGap * 3;
|
|
2421
|
+
const codingReservedLines = partnerReservedLines;
|
|
2422
|
+
const clampIndex = (index, length) => length === 0 ? 0 : Math.max(0, Math.min(index, length - 1));
|
|
2423
|
+
useEffect(() => {
|
|
2424
|
+
args.setProviderFocusIndex((prev) => clampIndex(prev, mainMenuOptions.length));
|
|
2425
|
+
}, [args, mainMenuOptions.length]);
|
|
2426
|
+
useEffect(() => {
|
|
2427
|
+
args.setPartnerProviderFocusIndex(
|
|
2428
|
+
(prev) => clampIndex(prev, partnerProviderOptions.length)
|
|
2429
|
+
);
|
|
2430
|
+
}, [args, partnerProviderOptions.length]);
|
|
2431
|
+
useEffect(() => {
|
|
2432
|
+
args.setCodingPlanFocusIndex(
|
|
2433
|
+
(prev) => clampIndex(prev, codingPlanOptions.length)
|
|
2434
|
+
);
|
|
2435
|
+
}, [args, codingPlanOptions.length]);
|
|
2436
|
+
return {
|
|
2437
|
+
mainMenuOptions,
|
|
2438
|
+
partnerProviderOptions,
|
|
2439
|
+
codingPlanOptions,
|
|
2440
|
+
providerReservedLines,
|
|
2441
|
+
partnerReservedLines,
|
|
2442
|
+
codingReservedLines,
|
|
2443
|
+
getProviderLabel
|
|
2444
|
+
};
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
// apps/cli/src/ui/components/ModelSelector/useModelSelectorModelOptions.ts
|
|
2448
|
+
import { useMemo as useMemo3 } from "react";
|
|
2449
|
+
function formatNumber(num) {
|
|
2450
|
+
if (num >= 1e6) return `${(num / 1e6).toFixed(1)}M`;
|
|
2451
|
+
if (num >= 1e3) return `${(num / 1e3).toFixed(0)}K`;
|
|
2452
|
+
return num.toString();
|
|
2453
|
+
}
|
|
2454
|
+
function getModelDetails(model) {
|
|
2455
|
+
const details = [];
|
|
2456
|
+
if (model.context_length) {
|
|
2457
|
+
details.push(`${formatNumber(model.context_length)} tokens`);
|
|
2458
|
+
} else if (model.max_tokens) {
|
|
2459
|
+
details.push(`${formatNumber(model.max_tokens)} tokens`);
|
|
2460
|
+
}
|
|
2461
|
+
if (model.supports_vision) details.push("vision");
|
|
2462
|
+
if (model.supports_function_calling) details.push("tools");
|
|
2463
|
+
return details.length > 0 ? ` (${details.join(", ")})` : "";
|
|
2464
|
+
}
|
|
2465
|
+
function sortModelsByPriority(models) {
|
|
2466
|
+
const priorityKeywords = [
|
|
2467
|
+
"claude",
|
|
2468
|
+
"kimi",
|
|
2469
|
+
"deepseek",
|
|
2470
|
+
"minimax",
|
|
2471
|
+
"o3",
|
|
2472
|
+
"gpt",
|
|
2473
|
+
"qwen"
|
|
2474
|
+
];
|
|
2475
|
+
return models.sort((a, b) => {
|
|
2476
|
+
const aModelLower = a.model?.toLowerCase() || "";
|
|
2477
|
+
const bModelLower = b.model?.toLowerCase() || "";
|
|
2478
|
+
const aHasPriority = priorityKeywords.some(
|
|
2479
|
+
(keyword) => aModelLower.includes(keyword)
|
|
2480
|
+
);
|
|
2481
|
+
const bHasPriority = priorityKeywords.some(
|
|
2482
|
+
(keyword) => bModelLower.includes(keyword)
|
|
2483
|
+
);
|
|
2484
|
+
if (aHasPriority && !bHasPriority) return -1;
|
|
2485
|
+
if (!aHasPriority && bHasPriority) return 1;
|
|
2486
|
+
return a.model.localeCompare(b.model);
|
|
2487
|
+
});
|
|
2488
|
+
}
|
|
2489
|
+
function useModelSelectorModelOptions(args) {
|
|
2490
|
+
const ourModelNames = useMemo3(() => {
|
|
2491
|
+
return new Set(
|
|
2492
|
+
(models_default[args.selectedProvider] || []).map(
|
|
2493
|
+
(model) => model.model
|
|
2494
|
+
)
|
|
2495
|
+
);
|
|
2496
|
+
}, [args.selectedProvider]);
|
|
2497
|
+
const filteredModels = useMemo3(() => {
|
|
2498
|
+
return args.modelSearchQuery ? args.availableModels.filter(
|
|
2499
|
+
(model) => model.model?.toLowerCase().includes(args.modelSearchQuery.toLowerCase())
|
|
2500
|
+
) : args.availableModels;
|
|
2501
|
+
}, [args.availableModels, args.modelSearchQuery]);
|
|
2502
|
+
const sortedFilteredModels = useMemo3(
|
|
2503
|
+
() => sortModelsByPriority([...filteredModels]),
|
|
2504
|
+
[filteredModels]
|
|
2505
|
+
);
|
|
2506
|
+
const modelOptions = useMemo3(
|
|
2507
|
+
() => sortedFilteredModels.map((model) => {
|
|
2508
|
+
const _isInOurModels = ourModelNames.has(model.model);
|
|
2509
|
+
return {
|
|
2510
|
+
label: `${model.model}${getModelDetails(model)}`,
|
|
2511
|
+
value: model.model
|
|
2512
|
+
};
|
|
2513
|
+
}),
|
|
2514
|
+
[ourModelNames, sortedFilteredModels]
|
|
2515
|
+
);
|
|
2516
|
+
return { modelOptions };
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
// apps/cli/src/ui/components/ModelSelector/useModelSelectorState.tsx
|
|
2520
|
+
import { useState as useState3 } from "react";
|
|
2521
|
+
|
|
2522
|
+
// apps/cli/src/ui/ui/components/model-selector/state.ts
|
|
2523
|
+
function createInitialScreenStack(_opts) {
|
|
2524
|
+
return ["provider"];
|
|
2525
|
+
}
|
|
2526
|
+
function getCurrentScreen(stack) {
|
|
2527
|
+
return stack[stack.length - 1] ?? "provider";
|
|
2528
|
+
}
|
|
2529
|
+
function pushScreen(stack, screen) {
|
|
2530
|
+
return [...stack, screen];
|
|
2531
|
+
}
|
|
2532
|
+
function handleBackNavigation(stack) {
|
|
2533
|
+
const currentScreen = getCurrentScreen(stack);
|
|
2534
|
+
if (currentScreen === "partnerProviders" || currentScreen === "partnerCodingPlans") {
|
|
2535
|
+
return { stack: ["provider"], effect: { type: "resetProviderFocus" } };
|
|
2536
|
+
}
|
|
2537
|
+
if (currentScreen === "provider") {
|
|
2538
|
+
return { stack, effect: { type: "exit" } };
|
|
2539
|
+
}
|
|
2540
|
+
if (stack.length > 1) {
|
|
2541
|
+
return { stack: stack.slice(0, -1), effect: null };
|
|
2542
|
+
}
|
|
2543
|
+
return { stack: ["provider"], effect: { type: "resetProviderFocus" } };
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
// apps/cli/src/ui/components/ModelSelector/useModelSelectorState.tsx
|
|
2547
|
+
function useModelSelectorState(opts) {
|
|
2548
|
+
const config = getGlobalConfig();
|
|
2549
|
+
const [screenStack, setScreenStack] = useState3(
|
|
2550
|
+
() => createInitialScreenStack({ skipModelType: opts.skipModelType })
|
|
2551
|
+
);
|
|
2552
|
+
const currentScreen = getCurrentScreen(screenStack);
|
|
2553
|
+
const navigateTo = (screen) => {
|
|
2554
|
+
setScreenStack((prev) => pushScreen(prev, screen));
|
|
2555
|
+
};
|
|
2556
|
+
const [selectedProvider, setSelectedProvider] = useState3(
|
|
2557
|
+
config.primaryProvider ?? "anthropic"
|
|
2558
|
+
);
|
|
2559
|
+
const [selectedModel, setSelectedModel] = useState3("");
|
|
2560
|
+
const [apiKey, setApiKey] = useState3("");
|
|
2561
|
+
const [maxTokens, setMaxTokens] = useState3(
|
|
2562
|
+
config.maxTokens?.toString() || DEFAULT_MAX_TOKENS.toString()
|
|
2563
|
+
);
|
|
2564
|
+
const [maxTokensMode, setMaxTokensMode] = useState3(
|
|
2565
|
+
"preset"
|
|
2566
|
+
);
|
|
2567
|
+
const [selectedMaxTokensPreset, setSelectedMaxTokensPreset] = useState3(config.maxTokens || DEFAULT_MAX_TOKENS);
|
|
2568
|
+
const [reasoningEffort, setReasoningEffort] = useState3("medium");
|
|
2569
|
+
const [supportsReasoningEffort, setSupportsReasoningEffort] = useState3(false);
|
|
2570
|
+
const [contextLength, setContextLength] = useState3(
|
|
2571
|
+
DEFAULT_CONTEXT_LENGTH
|
|
2572
|
+
);
|
|
2573
|
+
const [activeFieldIndex, setActiveFieldIndex] = useState3(0);
|
|
2574
|
+
const [maxTokensCursorOffset, setMaxTokensCursorOffset] = useState3(0);
|
|
2575
|
+
const [apiKeyCleanedNotification, setApiKeyCleanedNotification] = useState3(false);
|
|
2576
|
+
const [availableModels, setAvailableModels] = useState3([]);
|
|
2577
|
+
const [isLoadingModels, setIsLoadingModels] = useState3(false);
|
|
2578
|
+
const [modelLoadError, setModelLoadError] = useState3(null);
|
|
2579
|
+
const [modelSearchQuery, setModelSearchQuery] = useState3("");
|
|
2580
|
+
const [modelSearchCursorOffset, setModelSearchCursorOffset] = useState3(0);
|
|
2581
|
+
const [cursorOffset, setCursorOffset] = useState3(0);
|
|
2582
|
+
const [apiKeyEdited, setApiKeyEdited] = useState3(false);
|
|
2583
|
+
const [providerFocusIndex, setProviderFocusIndex] = useState3(0);
|
|
2584
|
+
const [partnerProviderFocusIndex, setPartnerProviderFocusIndex] = useState3(0);
|
|
2585
|
+
const [codingPlanFocusIndex, setCodingPlanFocusIndex] = useState3(0);
|
|
2586
|
+
const [fetchRetryCount, setFetchRetryCount] = useState3(0);
|
|
2587
|
+
const [isRetrying, setIsRetrying] = useState3(false);
|
|
2588
|
+
const [isTestingConnection, setIsTestingConnection] = useState3(false);
|
|
2589
|
+
const [connectionTestResult, setConnectionTestResult] = useState3(null);
|
|
2590
|
+
const [validationError, setValidationError] = useState3(null);
|
|
2591
|
+
const [resourceName, setResourceName] = useState3("");
|
|
2592
|
+
const [resourceNameCursorOffset, setResourceNameCursorOffset] = useState3(0);
|
|
2593
|
+
const [customModelName, setCustomModelName] = useState3("");
|
|
2594
|
+
const [customModelNameCursorOffset, setCustomModelNameCursorOffset] = useState3(0);
|
|
2595
|
+
const [ollamaBaseUrl, setOllamaBaseUrl] = useState3(
|
|
2596
|
+
"http://localhost:11434/v1"
|
|
2597
|
+
);
|
|
2598
|
+
const [customBaseUrl, setCustomBaseUrl] = useState3("");
|
|
2599
|
+
const [customBaseUrlCursorOffset, setCustomBaseUrlCursorOffset] = useState3(0);
|
|
2600
|
+
const [providerBaseUrl, setProviderBaseUrl] = useState3("");
|
|
2601
|
+
const [providerBaseUrlCursorOffset, setProviderBaseUrlCursorOffset] = useState3(0);
|
|
2602
|
+
return {
|
|
2603
|
+
screenStack,
|
|
2604
|
+
setScreenStack,
|
|
2605
|
+
currentScreen,
|
|
2606
|
+
navigateTo,
|
|
2607
|
+
selectedProvider,
|
|
2608
|
+
setSelectedProvider,
|
|
2609
|
+
selectedModel,
|
|
2610
|
+
setSelectedModel,
|
|
2611
|
+
apiKey,
|
|
2612
|
+
setApiKey,
|
|
2613
|
+
maxTokens,
|
|
2614
|
+
setMaxTokens,
|
|
2615
|
+
maxTokensMode,
|
|
2616
|
+
setMaxTokensMode,
|
|
2617
|
+
selectedMaxTokensPreset,
|
|
2618
|
+
setSelectedMaxTokensPreset,
|
|
2619
|
+
reasoningEffort,
|
|
2620
|
+
setReasoningEffort,
|
|
2621
|
+
supportsReasoningEffort,
|
|
2622
|
+
setSupportsReasoningEffort,
|
|
2623
|
+
contextLength,
|
|
2624
|
+
setContextLength,
|
|
2625
|
+
activeFieldIndex,
|
|
2626
|
+
setActiveFieldIndex,
|
|
2627
|
+
maxTokensCursorOffset,
|
|
2628
|
+
setMaxTokensCursorOffset,
|
|
2629
|
+
apiKeyCleanedNotification,
|
|
2630
|
+
setApiKeyCleanedNotification,
|
|
2631
|
+
availableModels,
|
|
2632
|
+
setAvailableModels,
|
|
2633
|
+
isLoadingModels,
|
|
2634
|
+
setIsLoadingModels,
|
|
2635
|
+
modelLoadError,
|
|
2636
|
+
setModelLoadError,
|
|
2637
|
+
modelSearchQuery,
|
|
2638
|
+
setModelSearchQuery,
|
|
2639
|
+
modelSearchCursorOffset,
|
|
2640
|
+
setModelSearchCursorOffset,
|
|
2641
|
+
cursorOffset,
|
|
2642
|
+
setCursorOffset,
|
|
2643
|
+
apiKeyEdited,
|
|
2644
|
+
setApiKeyEdited,
|
|
2645
|
+
providerFocusIndex,
|
|
2646
|
+
setProviderFocusIndex,
|
|
2647
|
+
partnerProviderFocusIndex,
|
|
2648
|
+
setPartnerProviderFocusIndex,
|
|
2649
|
+
codingPlanFocusIndex,
|
|
2650
|
+
setCodingPlanFocusIndex,
|
|
2651
|
+
fetchRetryCount,
|
|
2652
|
+
setFetchRetryCount,
|
|
2653
|
+
isRetrying,
|
|
2654
|
+
setIsRetrying,
|
|
2655
|
+
isTestingConnection,
|
|
2656
|
+
setIsTestingConnection,
|
|
2657
|
+
connectionTestResult,
|
|
2658
|
+
setConnectionTestResult,
|
|
2659
|
+
validationError,
|
|
2660
|
+
setValidationError,
|
|
2661
|
+
resourceName,
|
|
2662
|
+
setResourceName,
|
|
2663
|
+
resourceNameCursorOffset,
|
|
2664
|
+
setResourceNameCursorOffset,
|
|
2665
|
+
customModelName,
|
|
2666
|
+
setCustomModelName,
|
|
2667
|
+
customModelNameCursorOffset,
|
|
2668
|
+
setCustomModelNameCursorOffset,
|
|
2669
|
+
ollamaBaseUrl,
|
|
2670
|
+
setOllamaBaseUrl,
|
|
2671
|
+
customBaseUrl,
|
|
2672
|
+
setCustomBaseUrl,
|
|
2673
|
+
customBaseUrlCursorOffset,
|
|
2674
|
+
setCustomBaseUrlCursorOffset,
|
|
2675
|
+
providerBaseUrl,
|
|
2676
|
+
setProviderBaseUrl,
|
|
2677
|
+
providerBaseUrlCursorOffset,
|
|
2678
|
+
setProviderBaseUrlCursorOffset
|
|
2679
|
+
};
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
// apps/cli/src/ui/ui/components/model-selector/actions/connectionTest/openAICompatibility.ts
|
|
2683
|
+
var OPENAI_COMPATIBLE_PROVIDERS = /* @__PURE__ */ new Set([
|
|
2684
|
+
"minimax",
|
|
2685
|
+
"kimi",
|
|
2686
|
+
"deepseek",
|
|
2687
|
+
"siliconflow",
|
|
2688
|
+
"qwen",
|
|
2689
|
+
"glm",
|
|
2690
|
+
"baidu-qianfan",
|
|
2691
|
+
"openai",
|
|
2692
|
+
"mistral",
|
|
2693
|
+
"xai",
|
|
2694
|
+
"groq",
|
|
2695
|
+
"custom-openai"
|
|
2696
|
+
]);
|
|
2697
|
+
function isOpenAICompatibleProvider(provider) {
|
|
2698
|
+
return OPENAI_COMPATIBLE_PROVIDERS.has(provider);
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
// apps/cli/src/ui/ui/components/model-selector/actions/connectionTest/testChatEndpoint.ts
|
|
2702
|
+
function asRecord(value) {
|
|
2703
|
+
if (!value || typeof value !== "object") return null;
|
|
2704
|
+
if (Array.isArray(value)) return null;
|
|
2705
|
+
return value;
|
|
2706
|
+
}
|
|
2707
|
+
async function testChatEndpoint({
|
|
2708
|
+
baseURL,
|
|
2709
|
+
endpointPath,
|
|
2710
|
+
endpointName,
|
|
2711
|
+
selectedProvider,
|
|
2712
|
+
selectedModel,
|
|
2713
|
+
apiKey,
|
|
2714
|
+
maxTokens
|
|
2715
|
+
}) {
|
|
2716
|
+
const testURL = `${baseURL.replace(/\/+$/, "")}${endpointPath}`;
|
|
2717
|
+
const testPayload = {
|
|
2718
|
+
model: selectedModel,
|
|
2719
|
+
messages: [
|
|
2720
|
+
{
|
|
2721
|
+
role: "user",
|
|
2722
|
+
content: 'Please respond with exactly "YES" (in capital letters) to confirm this connection is working.'
|
|
2723
|
+
}
|
|
2724
|
+
],
|
|
2725
|
+
max_tokens: Math.max(parseInt(maxTokens) || 8192, 8192),
|
|
2726
|
+
temperature: 0,
|
|
2727
|
+
stream: false
|
|
2728
|
+
};
|
|
2729
|
+
if (selectedModel && selectedModel.toLowerCase().includes("gpt-5")) {
|
|
2730
|
+
debug.api("GPT5_PARAMETER_FIX_APPLY", { model: selectedModel });
|
|
2731
|
+
if (typeof testPayload.max_tokens === "number") {
|
|
2732
|
+
testPayload.max_completion_tokens = testPayload.max_tokens;
|
|
2733
|
+
delete testPayload.max_tokens;
|
|
2734
|
+
debug.api("GPT5_PARAMETER_FIX_MAX_TOKENS", {
|
|
2735
|
+
model: selectedModel,
|
|
2736
|
+
max_completion_tokens: testPayload.max_completion_tokens
|
|
2737
|
+
});
|
|
2738
|
+
}
|
|
2739
|
+
if (typeof testPayload.temperature === "number" && testPayload.temperature !== 1) {
|
|
2740
|
+
debug.api("GPT5_PARAMETER_FIX_TEMPERATURE", {
|
|
2741
|
+
model: selectedModel,
|
|
2742
|
+
from: testPayload.temperature,
|
|
2743
|
+
to: 1
|
|
2744
|
+
});
|
|
2745
|
+
testPayload.temperature = 1;
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
const headers = {
|
|
2749
|
+
"Content-Type": "application/json"
|
|
2750
|
+
};
|
|
2751
|
+
if (selectedProvider === "azure") {
|
|
2752
|
+
headers["api-key"] = apiKey;
|
|
2753
|
+
} else {
|
|
2754
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
2755
|
+
}
|
|
2756
|
+
try {
|
|
2757
|
+
const response = await fetch(testURL, {
|
|
2758
|
+
method: "POST",
|
|
2759
|
+
headers,
|
|
2760
|
+
body: JSON.stringify(testPayload)
|
|
2761
|
+
});
|
|
2762
|
+
if (response.ok) {
|
|
2763
|
+
const data = await response.json();
|
|
2764
|
+
debug.api("CONNECTION_TEST_RESPONSE", {
|
|
2765
|
+
provider: selectedProvider,
|
|
2766
|
+
endpoint: endpointPath,
|
|
2767
|
+
ok: true
|
|
2768
|
+
});
|
|
2769
|
+
let responseContent = "";
|
|
2770
|
+
const record = data;
|
|
2771
|
+
if (record && Array.isArray(record.choices) && record.choices.length > 0) {
|
|
2772
|
+
const firstChoice = record.choices[0];
|
|
2773
|
+
const message = firstChoice?.message ?? null;
|
|
2774
|
+
responseContent = String(message?.content ?? "");
|
|
2775
|
+
} else if (record && typeof record.reply === "string") {
|
|
2776
|
+
responseContent = record.reply;
|
|
2777
|
+
} else if (record && record.output) {
|
|
2778
|
+
const output = record.output;
|
|
2779
|
+
responseContent = String(output?.text ?? record.output ?? "");
|
|
2780
|
+
}
|
|
2781
|
+
debug.api("CONNECTION_TEST_RESPONSE_PARSED", {
|
|
2782
|
+
provider: selectedProvider,
|
|
2783
|
+
endpoint: endpointPath,
|
|
2784
|
+
contentLength: responseContent.length
|
|
2785
|
+
});
|
|
2786
|
+
const containsYes = responseContent.toLowerCase().includes("yes");
|
|
2787
|
+
if (containsYes) {
|
|
2788
|
+
return {
|
|
2789
|
+
success: true,
|
|
2790
|
+
message: `\u2705 Connection test passed with ${endpointName}`,
|
|
2791
|
+
endpoint: endpointPath,
|
|
2792
|
+
details: `Model responded correctly: "${responseContent.trim()}"`
|
|
2793
|
+
};
|
|
2794
|
+
}
|
|
2795
|
+
return {
|
|
2796
|
+
success: false,
|
|
2797
|
+
message: `\u26A0\uFE0F ${endpointName} connected but model response unexpected`,
|
|
2798
|
+
endpoint: endpointPath,
|
|
2799
|
+
details: `Expected "YES" but got: "${responseContent.trim() || "(empty response)"}"`
|
|
2800
|
+
};
|
|
2801
|
+
}
|
|
2802
|
+
const errorData = await response.json().catch(() => null);
|
|
2803
|
+
const errorRecord = asRecord(errorData);
|
|
2804
|
+
const nestedErrorMessage = (() => {
|
|
2805
|
+
const nestedError = asRecord(errorRecord?.error);
|
|
2806
|
+
const nestedMessage = nestedError?.message;
|
|
2807
|
+
return typeof nestedMessage === "string" ? nestedMessage : null;
|
|
2808
|
+
})();
|
|
2809
|
+
const errorMessage = nestedErrorMessage || (typeof errorRecord?.message === "string" ? errorRecord.message : null) || response.statusText;
|
|
2810
|
+
return {
|
|
2811
|
+
success: false,
|
|
2812
|
+
message: `\u274C ${endpointName} failed (${response.status})`,
|
|
2813
|
+
endpoint: endpointPath,
|
|
2814
|
+
details: `Error: ${errorMessage}`
|
|
2815
|
+
};
|
|
2816
|
+
} catch (error) {
|
|
2817
|
+
return {
|
|
2818
|
+
success: false,
|
|
2819
|
+
message: `\u274C ${endpointName} connection failed`,
|
|
2820
|
+
endpoint: endpointPath,
|
|
2821
|
+
details: error instanceof Error ? error.message : String(error)
|
|
2822
|
+
};
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
// apps/cli/src/ui/ui/components/model-selector/actions/connectionTest/testProviderSpecificEndpoint.ts
|
|
2827
|
+
async function testProviderSpecificEndpoint({
|
|
2828
|
+
baseURL,
|
|
2829
|
+
selectedProvider,
|
|
2830
|
+
apiKey
|
|
2831
|
+
}) {
|
|
2832
|
+
if (selectedProvider === "anthropic" || selectedProvider === "bigdream") {
|
|
2833
|
+
try {
|
|
2834
|
+
debug.api("PROVIDER_CONNECTION_TEST_NATIVE_SDK", {
|
|
2835
|
+
provider: selectedProvider
|
|
2836
|
+
});
|
|
2837
|
+
let testBaseURL = void 0;
|
|
2838
|
+
if (selectedProvider === "bigdream") {
|
|
2839
|
+
testBaseURL = baseURL || "https://api-key.info";
|
|
2840
|
+
} else if (selectedProvider === "anthropic") {
|
|
2841
|
+
testBaseURL = baseURL && baseURL !== "https://api.anthropic.com" ? baseURL : void 0;
|
|
2842
|
+
}
|
|
2843
|
+
const isValid = await verifyApiKey(apiKey, testBaseURL, selectedProvider);
|
|
2844
|
+
if (isValid) {
|
|
2845
|
+
return {
|
|
2846
|
+
success: true,
|
|
2847
|
+
message: `\u2705 ${selectedProvider} connection test passed`,
|
|
2848
|
+
endpoint: "/messages",
|
|
2849
|
+
details: "API key verified using native SDK"
|
|
2850
|
+
};
|
|
2851
|
+
}
|
|
2852
|
+
return {
|
|
2853
|
+
success: false,
|
|
2854
|
+
message: `\u274C ${selectedProvider} API key verification failed`,
|
|
2855
|
+
endpoint: "/messages",
|
|
2856
|
+
details: "Invalid API key. Please check your API key and try again."
|
|
2857
|
+
};
|
|
2858
|
+
} catch (error) {
|
|
2859
|
+
debug.warn("PROVIDER_CONNECTION_TEST_NATIVE_SDK_ERROR", {
|
|
2860
|
+
provider: selectedProvider,
|
|
2861
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2862
|
+
});
|
|
2863
|
+
return {
|
|
2864
|
+
success: false,
|
|
2865
|
+
message: `\u274C ${selectedProvider} connection failed`,
|
|
2866
|
+
endpoint: "/messages",
|
|
2867
|
+
details: error instanceof Error ? error.message : String(error)
|
|
2868
|
+
};
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
return {
|
|
2872
|
+
success: true,
|
|
2873
|
+
message: `\u2705 Configuration saved for ${selectedProvider}`,
|
|
2874
|
+
details: "Provider-specific testing not implemented yet"
|
|
2875
|
+
};
|
|
2876
|
+
}
|
|
2877
|
+
|
|
2878
|
+
// apps/cli/src/ui/ui/components/model-selector/actions/connectionTest/performConnectionTest.ts
|
|
2879
|
+
async function performConnectionTest({
|
|
2880
|
+
selectedProvider,
|
|
2881
|
+
selectedModel,
|
|
2882
|
+
apiKey,
|
|
2883
|
+
maxTokens,
|
|
2884
|
+
providerBaseUrl,
|
|
2885
|
+
customBaseUrl,
|
|
2886
|
+
resourceName
|
|
2887
|
+
}) {
|
|
2888
|
+
try {
|
|
2889
|
+
let testBaseURL = providerBaseUrl || providers[selectedProvider]?.baseURL || "";
|
|
2890
|
+
if (selectedProvider === "azure") {
|
|
2891
|
+
testBaseURL = `https://${resourceName}.openai.azure.com/openai/deployments/${selectedModel}`;
|
|
2892
|
+
} else if (selectedProvider === "custom-openai") {
|
|
2893
|
+
testBaseURL = customBaseUrl;
|
|
2894
|
+
}
|
|
2895
|
+
const isOpenAICompatible = isOpenAICompatibleProvider(selectedProvider);
|
|
2896
|
+
if (isOpenAICompatible) {
|
|
2897
|
+
const isGPT5 = selectedModel?.toLowerCase().includes("gpt-5");
|
|
2898
|
+
if (isGPT5) {
|
|
2899
|
+
debug.api("GPT5_CONNECTION_TEST_USING_SPECIALIZED", {
|
|
2900
|
+
model: selectedModel,
|
|
2901
|
+
provider: selectedProvider
|
|
2902
|
+
});
|
|
2903
|
+
const configValidation = validateGPT5Config({
|
|
2904
|
+
model: selectedModel,
|
|
2905
|
+
apiKey,
|
|
2906
|
+
baseURL: testBaseURL,
|
|
2907
|
+
maxTokens: parseInt(maxTokens) || 8192,
|
|
2908
|
+
provider: selectedProvider
|
|
2909
|
+
});
|
|
2910
|
+
if (!configValidation.valid) {
|
|
2911
|
+
return {
|
|
2912
|
+
success: false,
|
|
2913
|
+
message: "\u274C GPT-5 configuration validation failed",
|
|
2914
|
+
details: configValidation.errors.join("\n")
|
|
2915
|
+
};
|
|
2916
|
+
}
|
|
2917
|
+
return await testGPT5Connection({
|
|
2918
|
+
model: selectedModel,
|
|
2919
|
+
apiKey,
|
|
2920
|
+
baseURL: testBaseURL,
|
|
2921
|
+
maxTokens: parseInt(maxTokens) || 8192,
|
|
2922
|
+
provider: selectedProvider
|
|
2923
|
+
});
|
|
2924
|
+
}
|
|
2925
|
+
const endpointsToTry = [];
|
|
2926
|
+
if (selectedProvider === "minimax") {
|
|
2927
|
+
endpointsToTry.push(
|
|
2928
|
+
{ path: "/text/chatcompletion_v2", name: "MiniMax v2 (recommended)" },
|
|
2929
|
+
{ path: "/chat/completions", name: "Standard OpenAI" }
|
|
2930
|
+
);
|
|
2931
|
+
} else {
|
|
2932
|
+
endpointsToTry.push({
|
|
2933
|
+
path: "/chat/completions",
|
|
2934
|
+
name: "Standard OpenAI"
|
|
2935
|
+
});
|
|
2936
|
+
}
|
|
2937
|
+
let lastError = null;
|
|
2938
|
+
for (const endpoint of endpointsToTry) {
|
|
2939
|
+
try {
|
|
2940
|
+
const testResult = await testChatEndpoint({
|
|
2941
|
+
baseURL: testBaseURL,
|
|
2942
|
+
endpointPath: endpoint.path,
|
|
2943
|
+
endpointName: endpoint.name,
|
|
2944
|
+
selectedProvider,
|
|
2945
|
+
selectedModel,
|
|
2946
|
+
apiKey,
|
|
2947
|
+
maxTokens
|
|
2948
|
+
});
|
|
2949
|
+
if (testResult.success) {
|
|
2950
|
+
return testResult;
|
|
2951
|
+
}
|
|
2952
|
+
lastError = testResult;
|
|
2953
|
+
} catch (error) {
|
|
2954
|
+
lastError = {
|
|
2955
|
+
success: false,
|
|
2956
|
+
message: `Failed to test ${endpoint.name}`,
|
|
2957
|
+
endpoint: endpoint.path,
|
|
2958
|
+
details: error instanceof Error ? error.message : String(error)
|
|
2959
|
+
};
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2962
|
+
return lastError || {
|
|
2963
|
+
success: false,
|
|
2964
|
+
message: "All endpoints failed",
|
|
2965
|
+
details: "No endpoints could be reached"
|
|
2966
|
+
};
|
|
2967
|
+
}
|
|
2968
|
+
return await testProviderSpecificEndpoint({
|
|
2969
|
+
baseURL: testBaseURL,
|
|
2970
|
+
selectedProvider,
|
|
2971
|
+
apiKey
|
|
2972
|
+
});
|
|
2973
|
+
} catch (error) {
|
|
2974
|
+
return {
|
|
2975
|
+
success: false,
|
|
2976
|
+
message: "Connection test failed",
|
|
2977
|
+
details: error instanceof Error ? error.message : String(error)
|
|
2978
|
+
};
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
|
|
2982
|
+
// apps/cli/src/ui/ui/components/model-selector/actions/connectionTest/runConnectionTestFlow.ts
|
|
2983
|
+
async function runConnectionTestFlow({
|
|
2984
|
+
params,
|
|
2985
|
+
navigateTo,
|
|
2986
|
+
setTimeoutFn,
|
|
2987
|
+
performConnectionTestFn
|
|
2988
|
+
}) {
|
|
2989
|
+
const result = await (performConnectionTestFn ?? performConnectionTest)(
|
|
2990
|
+
params
|
|
2991
|
+
);
|
|
2992
|
+
if (result.success) {
|
|
2993
|
+
const schedule = setTimeoutFn ?? ((callback, delayMs) => setTimeout(callback, delayMs));
|
|
2994
|
+
schedule(() => {
|
|
2995
|
+
navigateTo("confirmation");
|
|
2996
|
+
}, 2e3);
|
|
2997
|
+
}
|
|
2998
|
+
return result;
|
|
2999
|
+
}
|
|
3000
|
+
|
|
3001
|
+
// apps/cli/src/ui/ui/components/model-selector/actions/providerSelection.ts
|
|
3002
|
+
function handleProviderSelection(provider, deps) {
|
|
3003
|
+
const {
|
|
3004
|
+
navigateTo,
|
|
3005
|
+
setPartnerProviderFocusIndex,
|
|
3006
|
+
setCodingPlanFocusIndex,
|
|
3007
|
+
setSelectedProvider,
|
|
3008
|
+
setProviderBaseUrl,
|
|
3009
|
+
saveConfiguration,
|
|
3010
|
+
onDone,
|
|
3011
|
+
selectedModel
|
|
3012
|
+
} = deps;
|
|
3013
|
+
if (provider === "partnerProviders") {
|
|
3014
|
+
setPartnerProviderFocusIndex(0);
|
|
3015
|
+
navigateTo("partnerProviders");
|
|
3016
|
+
return;
|
|
3017
|
+
} else if (provider === "partnerCodingPlans") {
|
|
3018
|
+
setCodingPlanFocusIndex(0);
|
|
3019
|
+
navigateTo("partnerCodingPlans");
|
|
3020
|
+
return;
|
|
3021
|
+
} else if (provider === "custom-anthropic") {
|
|
3022
|
+
setSelectedProvider("anthropic");
|
|
3023
|
+
setProviderBaseUrl("");
|
|
3024
|
+
navigateTo("baseUrl");
|
|
3025
|
+
return;
|
|
3026
|
+
}
|
|
3027
|
+
const providerType = provider;
|
|
3028
|
+
setSelectedProvider(providerType);
|
|
3029
|
+
if (provider === "custom") {
|
|
3030
|
+
saveConfiguration(providerType, selectedModel || "");
|
|
3031
|
+
onDone();
|
|
3032
|
+
} else if (provider === "custom-openai" || provider === "ollama") {
|
|
3033
|
+
const defaultBaseUrl = providers[providerType]?.baseURL || "";
|
|
3034
|
+
setProviderBaseUrl(defaultBaseUrl);
|
|
3035
|
+
navigateTo("baseUrl");
|
|
3036
|
+
} else {
|
|
3037
|
+
const defaultBaseUrl = providers[providerType]?.baseURL || "";
|
|
3038
|
+
setProviderBaseUrl(defaultBaseUrl);
|
|
3039
|
+
navigateTo("apiKey");
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
// apps/cli/src/ui/ui/components/model-selector/actions/saveConfiguration.ts
|
|
3044
|
+
async function saveModelConfiguration({
|
|
3045
|
+
provider,
|
|
3046
|
+
model,
|
|
3047
|
+
providerBaseUrl,
|
|
3048
|
+
resourceName,
|
|
3049
|
+
customBaseUrl,
|
|
3050
|
+
apiKey,
|
|
3051
|
+
maxTokens,
|
|
3052
|
+
contextLength,
|
|
3053
|
+
reasoningEffort,
|
|
3054
|
+
getModelManagerFn
|
|
3055
|
+
}) {
|
|
3056
|
+
let baseURL = providerBaseUrl || providers[provider]?.baseURL || "";
|
|
3057
|
+
let actualProvider = provider;
|
|
3058
|
+
if (provider === "anthropic") {
|
|
3059
|
+
actualProvider = "anthropic";
|
|
3060
|
+
baseURL = baseURL || "https://api.anthropic.com";
|
|
3061
|
+
}
|
|
3062
|
+
if (provider === "azure") {
|
|
3063
|
+
baseURL = `https://${resourceName}.openai.azure.com/openai/deployments/${model}`;
|
|
3064
|
+
} else if (provider === "custom-openai") {
|
|
3065
|
+
baseURL = customBaseUrl;
|
|
3066
|
+
}
|
|
3067
|
+
const modelManager = (getModelManagerFn ?? getModelManager)();
|
|
3068
|
+
const displayModel = model || "default";
|
|
3069
|
+
const modelDisplayName = `${providers[actualProvider]?.name || actualProvider} ${displayModel}`.trim();
|
|
3070
|
+
const modelConfig = {
|
|
3071
|
+
name: modelDisplayName,
|
|
3072
|
+
provider: actualProvider,
|
|
3073
|
+
modelName: model || actualProvider,
|
|
3074
|
+
// Use provider name if no specific model
|
|
3075
|
+
baseURL,
|
|
3076
|
+
apiKey: apiKey || "",
|
|
3077
|
+
maxTokens: parseInt(maxTokens) || DEFAULT_MAX_TOKENS,
|
|
3078
|
+
contextLength: contextLength || DEFAULT_CONTEXT_LENGTH,
|
|
3079
|
+
reasoningEffort
|
|
3080
|
+
};
|
|
3081
|
+
return await modelManager.addModel(modelConfig);
|
|
3082
|
+
}
|
|
3083
|
+
function applyPointersForNewModel({
|
|
3084
|
+
modelId,
|
|
3085
|
+
isOnboarding,
|
|
3086
|
+
targetPointer,
|
|
3087
|
+
setModelPointerFn,
|
|
3088
|
+
setAllPointersToModelFn
|
|
3089
|
+
}) {
|
|
3090
|
+
const setModelPointerImpl = setModelPointerFn ?? setModelPointer;
|
|
3091
|
+
const setAllPointersImpl = setAllPointersToModelFn ?? setAllPointersToModel;
|
|
3092
|
+
setModelPointerImpl("main", modelId);
|
|
3093
|
+
if (isOnboarding) {
|
|
3094
|
+
setAllPointersImpl(modelId);
|
|
3095
|
+
} else if (targetPointer && targetPointer !== "main") {
|
|
3096
|
+
setModelPointerImpl(targetPointer, modelId);
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
|
|
3100
|
+
// apps/cli/src/ui/ui/components/model-selector/actions/fetchModels.ts
|
|
3101
|
+
import OpenAI from "openai";
|
|
3102
|
+
async function fetchModelsForProvider({
|
|
3103
|
+
selectedProvider,
|
|
3104
|
+
apiKey,
|
|
3105
|
+
providerBaseUrl,
|
|
3106
|
+
customBaseUrl,
|
|
3107
|
+
modelFetchers,
|
|
3108
|
+
setIsLoadingModels,
|
|
3109
|
+
setModelLoadError,
|
|
3110
|
+
setAvailableModels,
|
|
3111
|
+
navigateTo
|
|
3112
|
+
}) {
|
|
3113
|
+
setIsLoadingModels(true);
|
|
3114
|
+
setModelLoadError(null);
|
|
3115
|
+
try {
|
|
3116
|
+
if (selectedProvider === "anthropic") {
|
|
3117
|
+
const anthropicModels = await modelFetchers.fetchAnthropicCompatibleProviderModels({
|
|
3118
|
+
apiKey,
|
|
3119
|
+
providerBaseUrl,
|
|
3120
|
+
setModelLoadError
|
|
3121
|
+
});
|
|
3122
|
+
setAvailableModels(anthropicModels);
|
|
3123
|
+
navigateTo("model");
|
|
3124
|
+
return anthropicModels;
|
|
3125
|
+
}
|
|
3126
|
+
if (selectedProvider === "custom-openai") {
|
|
3127
|
+
const customModels = await modelFetchers.fetchCustomOpenAIModels({
|
|
3128
|
+
apiKey,
|
|
3129
|
+
customBaseUrl,
|
|
3130
|
+
setModelLoadError
|
|
3131
|
+
});
|
|
3132
|
+
setAvailableModels(customModels);
|
|
3133
|
+
navigateTo("model");
|
|
3134
|
+
return customModels;
|
|
3135
|
+
}
|
|
3136
|
+
if (selectedProvider === "gemini") {
|
|
3137
|
+
const geminiModels = await modelFetchers.fetchGeminiModels({
|
|
3138
|
+
apiKey,
|
|
3139
|
+
setModelLoadError
|
|
3140
|
+
});
|
|
3141
|
+
setAvailableModels(geminiModels);
|
|
3142
|
+
navigateTo("model");
|
|
3143
|
+
return geminiModels;
|
|
3144
|
+
}
|
|
3145
|
+
if (selectedProvider === "kimi") {
|
|
3146
|
+
const kimiModels = await modelFetchers.fetchKimiModels({
|
|
3147
|
+
apiKey,
|
|
3148
|
+
providerBaseUrl,
|
|
3149
|
+
setModelLoadError
|
|
3150
|
+
});
|
|
3151
|
+
setAvailableModels(kimiModels);
|
|
3152
|
+
navigateTo("model");
|
|
3153
|
+
return kimiModels;
|
|
3154
|
+
}
|
|
3155
|
+
if (selectedProvider === "deepseek") {
|
|
3156
|
+
const deepseekModels = await modelFetchers.fetchDeepSeekModels({
|
|
3157
|
+
apiKey,
|
|
3158
|
+
providerBaseUrl,
|
|
3159
|
+
setModelLoadError
|
|
3160
|
+
});
|
|
3161
|
+
setAvailableModels(deepseekModels);
|
|
3162
|
+
navigateTo("model");
|
|
3163
|
+
return deepseekModels;
|
|
3164
|
+
}
|
|
3165
|
+
if (selectedProvider === "siliconflow") {
|
|
3166
|
+
const siliconflowModels = await modelFetchers.fetchSiliconFlowModels({
|
|
3167
|
+
apiKey,
|
|
3168
|
+
providerBaseUrl,
|
|
3169
|
+
setModelLoadError
|
|
3170
|
+
});
|
|
3171
|
+
setAvailableModels(siliconflowModels);
|
|
3172
|
+
navigateTo("model");
|
|
3173
|
+
return siliconflowModels;
|
|
3174
|
+
}
|
|
3175
|
+
if (selectedProvider === "qwen") {
|
|
3176
|
+
const qwenModels = await modelFetchers.fetchQwenModels({
|
|
3177
|
+
apiKey,
|
|
3178
|
+
providerBaseUrl,
|
|
3179
|
+
setModelLoadError
|
|
3180
|
+
});
|
|
3181
|
+
setAvailableModels(qwenModels);
|
|
3182
|
+
navigateTo("model");
|
|
3183
|
+
return qwenModels;
|
|
3184
|
+
}
|
|
3185
|
+
if (selectedProvider === "glm") {
|
|
3186
|
+
const glmModels = await modelFetchers.fetchGLMModels({
|
|
3187
|
+
apiKey,
|
|
3188
|
+
providerBaseUrl,
|
|
3189
|
+
setModelLoadError
|
|
3190
|
+
});
|
|
3191
|
+
setAvailableModels(glmModels);
|
|
3192
|
+
navigateTo("model");
|
|
3193
|
+
return glmModels;
|
|
3194
|
+
}
|
|
3195
|
+
if (selectedProvider === "baidu-qianfan") {
|
|
3196
|
+
const baiduModels = await modelFetchers.fetchBaiduQianfanModels({
|
|
3197
|
+
apiKey,
|
|
3198
|
+
providerBaseUrl,
|
|
3199
|
+
setModelLoadError
|
|
3200
|
+
});
|
|
3201
|
+
setAvailableModels(baiduModels);
|
|
3202
|
+
navigateTo("model");
|
|
3203
|
+
return baiduModels;
|
|
3204
|
+
}
|
|
3205
|
+
if (selectedProvider === "azure") {
|
|
3206
|
+
navigateTo("modelInput");
|
|
3207
|
+
return [];
|
|
3208
|
+
}
|
|
3209
|
+
let baseURL = providerBaseUrl || providers[selectedProvider]?.baseURL;
|
|
3210
|
+
if (selectedProvider === "custom-openai") {
|
|
3211
|
+
baseURL = customBaseUrl;
|
|
3212
|
+
}
|
|
3213
|
+
const openai = new OpenAI({
|
|
3214
|
+
apiKey: apiKey || "dummy-key-for-ollama",
|
|
3215
|
+
// Ollama doesn't need a real key
|
|
3216
|
+
baseURL,
|
|
3217
|
+
dangerouslyAllowBrowser: true
|
|
3218
|
+
});
|
|
3219
|
+
const response = await openai.models.list();
|
|
3220
|
+
const fetchedModels = [];
|
|
3221
|
+
for (const model of response.data) {
|
|
3222
|
+
const record = model;
|
|
3223
|
+
const modelName = typeof record.modelName === "string" && record.modelName || typeof record.id === "string" && record.id || typeof record.name === "string" && record.name || typeof record.model === "string" && record.model || "unknown";
|
|
3224
|
+
const modelInfo = models_default[selectedProvider]?.find(
|
|
3225
|
+
(m) => m.model === modelName
|
|
3226
|
+
);
|
|
3227
|
+
fetchedModels.push({
|
|
3228
|
+
model: modelName,
|
|
3229
|
+
provider: selectedProvider,
|
|
3230
|
+
max_tokens: modelInfo?.max_output_tokens,
|
|
3231
|
+
supports_vision: modelInfo?.supports_vision || false,
|
|
3232
|
+
supports_function_calling: modelInfo?.supports_function_calling || false,
|
|
3233
|
+
supports_reasoning_effort: modelInfo?.supports_reasoning_effort || false
|
|
3234
|
+
});
|
|
3235
|
+
}
|
|
3236
|
+
setAvailableModels(fetchedModels);
|
|
3237
|
+
navigateTo("model");
|
|
3238
|
+
return fetchedModels;
|
|
3239
|
+
} catch (error) {
|
|
3240
|
+
console.error("Error fetching models:", error);
|
|
3241
|
+
throw error;
|
|
3242
|
+
} finally {
|
|
3243
|
+
setIsLoadingModels(false);
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
|
|
3247
|
+
// apps/cli/src/ui/ui/components/model-selector/modelFetchers.ts
|
|
3248
|
+
var modelFetchers_exports = {};
|
|
3249
|
+
__export(modelFetchers_exports, {
|
|
3250
|
+
fetchAnthropicCompatibleProviderModels: () => fetchAnthropicCompatibleProviderModels,
|
|
3251
|
+
fetchBaiduQianfanModels: () => fetchBaiduQianfanModels,
|
|
3252
|
+
fetchCustomOpenAIModels: () => fetchCustomOpenAIModels,
|
|
3253
|
+
fetchDeepSeekModels: () => fetchDeepSeekModels,
|
|
3254
|
+
fetchGLMModels: () => fetchGLMModels,
|
|
3255
|
+
fetchGeminiModels: () => fetchGeminiModels,
|
|
3256
|
+
fetchKimiModels: () => fetchKimiModels,
|
|
3257
|
+
fetchMinimaxModels: () => fetchMinimaxModels,
|
|
3258
|
+
fetchQwenModels: () => fetchQwenModels,
|
|
3259
|
+
fetchSiliconFlowModels: () => fetchSiliconFlowModels
|
|
3260
|
+
});
|
|
3261
|
+
|
|
3262
|
+
// apps/cli/src/ui/ui/components/model-selector/modelFetchers/anthropic.ts
|
|
3263
|
+
async function fetchAnthropicModels(baseURL, apiKey) {
|
|
3264
|
+
try {
|
|
3265
|
+
const response = await fetch(`${baseURL}/v1/models`, {
|
|
3266
|
+
method: "GET",
|
|
3267
|
+
headers: {
|
|
3268
|
+
"x-api-key": apiKey,
|
|
3269
|
+
"anthropic-version": "2023-06-01",
|
|
3270
|
+
"Content-Type": "application/json",
|
|
3271
|
+
Authorization: `Bearer ${apiKey}`
|
|
3272
|
+
}
|
|
3273
|
+
});
|
|
3274
|
+
if (!response.ok) {
|
|
3275
|
+
if (response.status === 401) {
|
|
3276
|
+
throw new Error(
|
|
3277
|
+
"Invalid API key. Please check your API key and try again."
|
|
3278
|
+
);
|
|
3279
|
+
} else if (response.status === 403) {
|
|
3280
|
+
throw new Error("API key does not have permission to access models.");
|
|
3281
|
+
} else if (response.status === 404) {
|
|
3282
|
+
throw new Error(
|
|
3283
|
+
"API endpoint not found. This provider may not support model listing."
|
|
3284
|
+
);
|
|
3285
|
+
} else if (response.status === 429) {
|
|
3286
|
+
throw new Error(
|
|
3287
|
+
"Too many requests. Please wait a moment and try again."
|
|
3288
|
+
);
|
|
3289
|
+
} else if (response.status >= 500) {
|
|
3290
|
+
throw new Error(
|
|
3291
|
+
"API service is temporarily unavailable. Please try again later."
|
|
3292
|
+
);
|
|
3293
|
+
} else {
|
|
3294
|
+
throw new Error(`Unable to connect to API (${response.status}).`);
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
const data = await response.json();
|
|
3298
|
+
let models = [];
|
|
3299
|
+
if (data && data.data && Array.isArray(data.data)) {
|
|
3300
|
+
models = data.data;
|
|
3301
|
+
} else if (Array.isArray(data)) {
|
|
3302
|
+
models = data;
|
|
3303
|
+
} else if (data && data.models && Array.isArray(data.models)) {
|
|
3304
|
+
models = data.models;
|
|
3305
|
+
} else {
|
|
3306
|
+
throw new Error("API returned unexpected response format.");
|
|
3307
|
+
}
|
|
3308
|
+
return models;
|
|
3309
|
+
} catch (error) {
|
|
3310
|
+
if (error instanceof Error && (error.message.includes("API key") || error.message.includes("API endpoint") || error.message.includes("API service") || error.message.includes("response format"))) {
|
|
3311
|
+
throw error;
|
|
3312
|
+
}
|
|
3313
|
+
if (error instanceof Error && error.message.includes("fetch")) {
|
|
3314
|
+
throw new Error(
|
|
3315
|
+
"Unable to connect to the API. Please check the base URL and your internet connection."
|
|
3316
|
+
);
|
|
3317
|
+
}
|
|
3318
|
+
throw new Error(
|
|
3319
|
+
"Failed to fetch models from API. Please check your configuration and try again."
|
|
3320
|
+
);
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3323
|
+
async function fetchAnthropicCompatibleModelsWithFallback({
|
|
3324
|
+
baseURL,
|
|
3325
|
+
provider,
|
|
3326
|
+
apiKey,
|
|
3327
|
+
apiKeyUrl,
|
|
3328
|
+
setModelLoadError
|
|
3329
|
+
}) {
|
|
3330
|
+
let lastError = null;
|
|
3331
|
+
try {
|
|
3332
|
+
const models = await fetchAnthropicModels(baseURL, apiKey);
|
|
3333
|
+
return models.map((model) => ({
|
|
3334
|
+
model: model.modelName || model.id || model.name || model.model || "unknown",
|
|
3335
|
+
provider,
|
|
3336
|
+
max_tokens: model.max_tokens || 8192,
|
|
3337
|
+
supports_vision: model.supports_vision || true,
|
|
3338
|
+
supports_function_calling: model.supports_function_calling || true,
|
|
3339
|
+
supports_reasoning_effort: false
|
|
3340
|
+
}));
|
|
3341
|
+
} catch (error) {
|
|
3342
|
+
lastError = error;
|
|
3343
|
+
debug.warn("MODEL_FETCH_NATIVE_API_FAILED", {
|
|
3344
|
+
provider,
|
|
3345
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3346
|
+
});
|
|
3347
|
+
}
|
|
3348
|
+
try {
|
|
3349
|
+
const models = await fetchCustomModels(baseURL, apiKey);
|
|
3350
|
+
return models.map((model) => ({
|
|
3351
|
+
model: model.modelName || model.id || model.name || model.model || "unknown",
|
|
3352
|
+
provider,
|
|
3353
|
+
max_tokens: model.max_tokens || 8192,
|
|
3354
|
+
supports_vision: model.supports_vision || false,
|
|
3355
|
+
supports_function_calling: model.supports_function_calling || true,
|
|
3356
|
+
supports_reasoning_effort: false
|
|
3357
|
+
}));
|
|
3358
|
+
} catch (error) {
|
|
3359
|
+
lastError = error;
|
|
3360
|
+
debug.warn("MODEL_FETCH_OPENAI_API_FAILED", {
|
|
3361
|
+
provider,
|
|
3362
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3363
|
+
});
|
|
3364
|
+
}
|
|
3365
|
+
let errorMessage = `Failed to fetch ${provider} models using both native and OpenAI-compatible API formats`;
|
|
3366
|
+
if (lastError instanceof Error) {
|
|
3367
|
+
errorMessage = lastError.message;
|
|
3368
|
+
}
|
|
3369
|
+
if (errorMessage.includes("API key")) {
|
|
3370
|
+
errorMessage += apiKeyUrl ? `
|
|
3371
|
+
|
|
3372
|
+
\u{1F4A1} Tip: Get your API key from ${apiKeyUrl}` : "\n\n\u{1F4A1} Tip: Check that your API key is set and valid for this provider";
|
|
3373
|
+
} else if (errorMessage.includes("permission")) {
|
|
3374
|
+
errorMessage += `
|
|
3375
|
+
|
|
3376
|
+
\u{1F4A1} Tip: Make sure your API key has access to the ${provider} API`;
|
|
3377
|
+
} else if (errorMessage.includes("connection")) {
|
|
3378
|
+
errorMessage += "\n\n\u{1F4A1} Tip: Check your internet connection and try again";
|
|
3379
|
+
}
|
|
3380
|
+
setModelLoadError(errorMessage);
|
|
3381
|
+
throw new Error(errorMessage);
|
|
3382
|
+
}
|
|
3383
|
+
async function fetchAnthropicCompatibleProviderModels({
|
|
3384
|
+
apiKey,
|
|
3385
|
+
providerBaseUrl,
|
|
3386
|
+
setModelLoadError
|
|
3387
|
+
}) {
|
|
3388
|
+
const defaultBaseURL = "https://api.anthropic.com";
|
|
3389
|
+
const apiKeyUrl = "";
|
|
3390
|
+
const actualProvider = "anthropic";
|
|
3391
|
+
const baseURL = providerBaseUrl || defaultBaseURL;
|
|
3392
|
+
return fetchAnthropicCompatibleModelsWithFallback({
|
|
3393
|
+
baseURL,
|
|
3394
|
+
provider: actualProvider,
|
|
3395
|
+
apiKey,
|
|
3396
|
+
apiKeyUrl,
|
|
3397
|
+
setModelLoadError
|
|
3398
|
+
});
|
|
3399
|
+
}
|
|
3400
|
+
|
|
3401
|
+
// apps/cli/src/ui/ui/components/model-selector/modelFetchers/baiduQianfan.ts
|
|
3402
|
+
async function fetchBaiduQianfanModels({
|
|
3403
|
+
apiKey,
|
|
3404
|
+
providerBaseUrl,
|
|
3405
|
+
setModelLoadError
|
|
3406
|
+
}) {
|
|
3407
|
+
try {
|
|
3408
|
+
const baseURL = providerBaseUrl || "https://qianfan.baidubce.com/v2";
|
|
3409
|
+
const models = await fetchCustomModels(baseURL, apiKey);
|
|
3410
|
+
return models.map((model) => ({
|
|
3411
|
+
model: model.modelName || model.id || model.name || model.model || "unknown",
|
|
3412
|
+
provider: "baidu-qianfan",
|
|
3413
|
+
max_tokens: model.max_tokens || 8192,
|
|
3414
|
+
supports_vision: false,
|
|
3415
|
+
supports_function_calling: true,
|
|
3416
|
+
supports_reasoning_effort: false
|
|
3417
|
+
}));
|
|
3418
|
+
} catch (error) {
|
|
3419
|
+
let errorMessage = "Failed to fetch Baidu Qianfan models";
|
|
3420
|
+
if (error instanceof Error) {
|
|
3421
|
+
errorMessage = error.message;
|
|
3422
|
+
}
|
|
3423
|
+
if (errorMessage.includes("API key")) {
|
|
3424
|
+
errorMessage += "\n\n\u{1F4A1} Tip: Get your API key from https://console.bce.baidu.com/iam/#/iam/accesslist";
|
|
3425
|
+
} else if (errorMessage.includes("permission")) {
|
|
3426
|
+
errorMessage += "\n\n\u{1F4A1} Tip: Make sure your API key has access to the Baidu Qianfan API";
|
|
3427
|
+
} else if (errorMessage.includes("connection")) {
|
|
3428
|
+
errorMessage += "\n\n\u{1F4A1} Tip: Check your internet connection and try again";
|
|
3429
|
+
}
|
|
3430
|
+
setModelLoadError(errorMessage);
|
|
3431
|
+
throw error;
|
|
3432
|
+
}
|
|
3433
|
+
}
|
|
3434
|
+
|
|
3435
|
+
// apps/cli/src/ui/ui/components/model-selector/modelFetchers/customOpenAI.ts
|
|
3436
|
+
async function fetchCustomOpenAIModels({
|
|
3437
|
+
apiKey,
|
|
3438
|
+
customBaseUrl,
|
|
3439
|
+
setModelLoadError
|
|
3440
|
+
}) {
|
|
3441
|
+
try {
|
|
3442
|
+
const models = await fetchCustomModels(customBaseUrl, apiKey);
|
|
3443
|
+
return models.map((model) => ({
|
|
3444
|
+
model: model.modelName || model.id || model.name || model.model || "unknown",
|
|
3445
|
+
provider: "custom-openai",
|
|
3446
|
+
max_tokens: model.max_tokens || 4096,
|
|
3447
|
+
supports_vision: false,
|
|
3448
|
+
// Default to false, could be enhanced
|
|
3449
|
+
supports_function_calling: true,
|
|
3450
|
+
supports_reasoning_effort: false
|
|
3451
|
+
}));
|
|
3452
|
+
} catch (error) {
|
|
3453
|
+
let errorMessage = "Failed to fetch custom API models";
|
|
3454
|
+
if (error instanceof Error) {
|
|
3455
|
+
errorMessage = error.message;
|
|
3456
|
+
}
|
|
3457
|
+
if (errorMessage.includes("API key")) {
|
|
3458
|
+
errorMessage += "\n\n\u{1F4A1} Tip: Check that your API key is valid for this endpoint";
|
|
3459
|
+
} else if (errorMessage.includes("endpoint not found")) {
|
|
3460
|
+
errorMessage += "\n\n\u{1F4A1} Tip: Make sure the base URL ends with /v1 and supports OpenAI-compatible API";
|
|
3461
|
+
} else if (errorMessage.includes("connect")) {
|
|
3462
|
+
errorMessage += "\n\n\u{1F4A1} Tip: Verify the base URL is correct and accessible";
|
|
3463
|
+
} else if (errorMessage.includes("response format")) {
|
|
3464
|
+
errorMessage += "\n\n\u{1F4A1} Tip: This API may not be fully OpenAI-compatible";
|
|
3465
|
+
}
|
|
3466
|
+
setModelLoadError(errorMessage);
|
|
3467
|
+
throw error;
|
|
3468
|
+
}
|
|
3469
|
+
}
|
|
3470
|
+
|
|
3471
|
+
// apps/cli/src/ui/ui/components/model-selector/modelFetchers/deepseek.ts
|
|
3472
|
+
async function fetchDeepSeekModels({
|
|
3473
|
+
apiKey,
|
|
3474
|
+
providerBaseUrl,
|
|
3475
|
+
setModelLoadError
|
|
3476
|
+
}) {
|
|
3477
|
+
try {
|
|
3478
|
+
const baseURL = providerBaseUrl || "https://api.deepseek.com";
|
|
3479
|
+
const models = await fetchCustomModels(baseURL, apiKey);
|
|
3480
|
+
return models.map((model) => ({
|
|
3481
|
+
model: model.modelName || model.id || model.name || model.model || "unknown",
|
|
3482
|
+
provider: "deepseek",
|
|
3483
|
+
max_tokens: model.max_tokens || 8192,
|
|
3484
|
+
supports_vision: false,
|
|
3485
|
+
// Default to false, could be enhanced
|
|
3486
|
+
supports_function_calling: true,
|
|
3487
|
+
supports_reasoning_effort: false
|
|
3488
|
+
}));
|
|
3489
|
+
} catch (error) {
|
|
3490
|
+
let errorMessage = "Failed to fetch DeepSeek models";
|
|
3491
|
+
if (error instanceof Error) {
|
|
3492
|
+
errorMessage = error.message;
|
|
3493
|
+
}
|
|
3494
|
+
if (errorMessage.includes("API key")) {
|
|
3495
|
+
errorMessage += "\n\n\u{1F4A1} Tip: Get your API key from https://platform.deepseek.com/api_keys";
|
|
3496
|
+
} else if (errorMessage.includes("permission")) {
|
|
3497
|
+
errorMessage += "\n\n\u{1F4A1} Tip: Make sure your API key has access to the DeepSeek API";
|
|
3498
|
+
} else if (errorMessage.includes("connection")) {
|
|
3499
|
+
errorMessage += "\n\n\u{1F4A1} Tip: Check your internet connection and try again";
|
|
3500
|
+
}
|
|
3501
|
+
setModelLoadError(errorMessage);
|
|
3502
|
+
throw error;
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
|
|
3506
|
+
// apps/cli/src/ui/ui/components/model-selector/modelFetchers/gemini.ts
|
|
3507
|
+
async function fetchGeminiModels({
|
|
3508
|
+
apiKey,
|
|
3509
|
+
setModelLoadError
|
|
3510
|
+
}) {
|
|
3511
|
+
try {
|
|
3512
|
+
const response = await fetch(
|
|
3513
|
+
`https://generativelanguage.googleapis.com/v1beta/models?key=${apiKey}`
|
|
3514
|
+
);
|
|
3515
|
+
if (!response.ok) {
|
|
3516
|
+
const errorData = await response.json();
|
|
3517
|
+
throw new Error(
|
|
3518
|
+
errorData.error?.message || `API error: ${response.status}`
|
|
3519
|
+
);
|
|
3520
|
+
}
|
|
3521
|
+
const { models } = await response.json();
|
|
3522
|
+
return models.filter(
|
|
3523
|
+
(model) => model.supportedGenerationMethods.includes("generateContent")
|
|
3524
|
+
).map((model) => ({
|
|
3525
|
+
model: model.name.replace("models/", ""),
|
|
3526
|
+
provider: "gemini",
|
|
3527
|
+
max_tokens: model.outputTokenLimit,
|
|
3528
|
+
supports_vision: model.supportedGenerationMethods.includes("generateContent"),
|
|
3529
|
+
supports_function_calling: model.supportedGenerationMethods.includes("generateContent")
|
|
3530
|
+
}));
|
|
3531
|
+
} catch (error) {
|
|
3532
|
+
setModelLoadError(error instanceof Error ? error.message : "Unknown error");
|
|
3533
|
+
throw error;
|
|
3534
|
+
}
|
|
3535
|
+
}
|
|
3536
|
+
|
|
3537
|
+
// apps/cli/src/ui/ui/components/model-selector/modelFetchers/glm.ts
|
|
3538
|
+
async function fetchGLMModels({
|
|
3539
|
+
apiKey,
|
|
3540
|
+
providerBaseUrl,
|
|
3541
|
+
setModelLoadError
|
|
3542
|
+
}) {
|
|
3543
|
+
try {
|
|
3544
|
+
const baseURL = providerBaseUrl || "https://open.bigmodel.cn/api/paas/v4";
|
|
3545
|
+
const models = await fetchCustomModels(baseURL, apiKey);
|
|
3546
|
+
return models.map((model) => ({
|
|
3547
|
+
model: model.modelName || model.id || model.name || model.model || "unknown",
|
|
3548
|
+
provider: "glm",
|
|
3549
|
+
max_tokens: model.max_tokens || 8192,
|
|
3550
|
+
supports_vision: false,
|
|
3551
|
+
supports_function_calling: true,
|
|
3552
|
+
supports_reasoning_effort: false
|
|
3553
|
+
}));
|
|
3554
|
+
} catch (error) {
|
|
3555
|
+
let errorMessage = "Failed to fetch GLM models";
|
|
3556
|
+
if (error instanceof Error) {
|
|
3557
|
+
errorMessage = error.message;
|
|
3558
|
+
}
|
|
3559
|
+
if (errorMessage.includes("API key")) {
|
|
3560
|
+
errorMessage += "\n\n\u{1F4A1} Tip: Get your API key from https://open.bigmodel.cn (API Keys section)";
|
|
3561
|
+
} else if (errorMessage.includes("permission")) {
|
|
3562
|
+
errorMessage += "\n\n\u{1F4A1} Tip: Make sure your API key has access to the GLM API";
|
|
3563
|
+
} else if (errorMessage.includes("connection")) {
|
|
3564
|
+
errorMessage += "\n\n\u{1F4A1} Tip: Check your internet connection and try again";
|
|
3565
|
+
}
|
|
3566
|
+
setModelLoadError(errorMessage);
|
|
3567
|
+
throw error;
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
|
|
3571
|
+
// apps/cli/src/ui/ui/components/model-selector/modelFetchers/kimi.ts
|
|
3572
|
+
async function fetchKimiModels({
|
|
3573
|
+
apiKey,
|
|
3574
|
+
providerBaseUrl,
|
|
3575
|
+
setModelLoadError
|
|
3576
|
+
}) {
|
|
3577
|
+
try {
|
|
3578
|
+
const baseURL = providerBaseUrl || "https://api.moonshot.cn/v1";
|
|
3579
|
+
const models = await fetchCustomModels(baseURL, apiKey);
|
|
3580
|
+
return models.map((model) => ({
|
|
3581
|
+
model: model.modelName || model.id || model.name || model.model || "unknown",
|
|
3582
|
+
provider: "kimi",
|
|
3583
|
+
max_tokens: model.max_tokens || 8192,
|
|
3584
|
+
supports_vision: false,
|
|
3585
|
+
// Default to false, could be enhanced
|
|
3586
|
+
supports_function_calling: true,
|
|
3587
|
+
supports_reasoning_effort: false
|
|
3588
|
+
}));
|
|
3589
|
+
} catch (error) {
|
|
3590
|
+
let errorMessage = "Failed to fetch Kimi models";
|
|
3591
|
+
if (error instanceof Error) {
|
|
3592
|
+
errorMessage = error.message;
|
|
3593
|
+
}
|
|
3594
|
+
if (errorMessage.includes("API key")) {
|
|
3595
|
+
errorMessage += "\n\n\u{1F4A1} Tip: Get your API key from https://platform.moonshot.cn/console/api-keys";
|
|
3596
|
+
} else if (errorMessage.includes("permission")) {
|
|
3597
|
+
errorMessage += "\n\n\u{1F4A1} Tip: Make sure your API key has access to the Kimi API";
|
|
3598
|
+
} else if (errorMessage.includes("connection")) {
|
|
3599
|
+
errorMessage += "\n\n\u{1F4A1} Tip: Check your internet connection and try again";
|
|
3600
|
+
}
|
|
3601
|
+
setModelLoadError(errorMessage);
|
|
3602
|
+
throw error;
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3605
|
+
|
|
3606
|
+
// apps/cli/src/ui/ui/components/model-selector/modelFetchers/minimax.ts
|
|
3607
|
+
async function fetchMinimaxModels({
|
|
3608
|
+
apiKey,
|
|
3609
|
+
providerBaseUrl,
|
|
3610
|
+
setModelLoadError
|
|
3611
|
+
}) {
|
|
3612
|
+
try {
|
|
3613
|
+
const baseURL = providerBaseUrl || "https://api.minimaxi.com/v1";
|
|
3614
|
+
const models = await fetchCustomModels(baseURL, apiKey);
|
|
3615
|
+
return models.map((model) => ({
|
|
3616
|
+
model: model.modelName || model.id || model.name || model.model || "unknown",
|
|
3617
|
+
provider: "minimax",
|
|
3618
|
+
max_tokens: model.max_tokens || 8192,
|
|
3619
|
+
supports_vision: false,
|
|
3620
|
+
supports_function_calling: true,
|
|
3621
|
+
supports_reasoning_effort: false
|
|
3622
|
+
}));
|
|
3623
|
+
} catch (error) {
|
|
3624
|
+
let errorMessage = "Failed to fetch MiniMax models";
|
|
3625
|
+
if (error instanceof Error) {
|
|
3626
|
+
errorMessage = error.message;
|
|
3627
|
+
}
|
|
3628
|
+
if (errorMessage.includes("API key")) {
|
|
3629
|
+
errorMessage += "\n\n\u{1F4A1} Tip: Get your API key from https://www.minimax.io/platform/user-center/basic-information";
|
|
3630
|
+
} else if (errorMessage.includes("permission")) {
|
|
3631
|
+
errorMessage += "\n\n\u{1F4A1} Tip: Make sure your API key has access to the MiniMax API";
|
|
3632
|
+
} else if (errorMessage.includes("connection")) {
|
|
3633
|
+
errorMessage += "\n\n\u{1F4A1} Tip: Check your internet connection and try again";
|
|
3634
|
+
}
|
|
3635
|
+
setModelLoadError(errorMessage);
|
|
3636
|
+
throw error;
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
|
|
3640
|
+
// apps/cli/src/ui/ui/components/model-selector/modelFetchers/qwen.ts
|
|
3641
|
+
async function fetchQwenModels({
|
|
3642
|
+
apiKey,
|
|
3643
|
+
providerBaseUrl,
|
|
3644
|
+
setModelLoadError
|
|
3645
|
+
}) {
|
|
3646
|
+
try {
|
|
3647
|
+
const baseURL = providerBaseUrl || "https://dashscope.aliyuncs.com/compatible-mode/v1";
|
|
3648
|
+
const models = await fetchCustomModels(baseURL, apiKey);
|
|
3649
|
+
return models.map((model) => ({
|
|
3650
|
+
model: model.modelName || model.id || model.name || model.model || "unknown",
|
|
3651
|
+
provider: "qwen",
|
|
3652
|
+
max_tokens: model.max_tokens || 8192,
|
|
3653
|
+
supports_vision: false,
|
|
3654
|
+
supports_function_calling: true,
|
|
3655
|
+
supports_reasoning_effort: false
|
|
3656
|
+
}));
|
|
3657
|
+
} catch (error) {
|
|
3658
|
+
let errorMessage = "Failed to fetch Qwen models";
|
|
3659
|
+
if (error instanceof Error) {
|
|
3660
|
+
errorMessage = error.message;
|
|
3661
|
+
}
|
|
3662
|
+
if (errorMessage.includes("API key")) {
|
|
3663
|
+
errorMessage += "\n\n\u{1F4A1} Tip: Get your API key from https://bailian.console.aliyun.com/?tab=model#/api-key";
|
|
3664
|
+
} else if (errorMessage.includes("permission")) {
|
|
3665
|
+
errorMessage += "\n\n\u{1F4A1} Tip: Make sure your API key has access to the Qwen API";
|
|
3666
|
+
} else if (errorMessage.includes("connection")) {
|
|
3667
|
+
errorMessage += "\n\n\u{1F4A1} Tip: Check your internet connection and try again";
|
|
3668
|
+
}
|
|
3669
|
+
setModelLoadError(errorMessage);
|
|
3670
|
+
throw error;
|
|
3671
|
+
}
|
|
3672
|
+
}
|
|
3673
|
+
|
|
3674
|
+
// apps/cli/src/ui/ui/components/model-selector/modelFetchers/siliconflow.ts
|
|
3675
|
+
async function fetchSiliconFlowModels({
|
|
3676
|
+
apiKey,
|
|
3677
|
+
providerBaseUrl,
|
|
3678
|
+
setModelLoadError
|
|
3679
|
+
}) {
|
|
3680
|
+
try {
|
|
3681
|
+
const baseURL = providerBaseUrl || "https://api.siliconflow.cn/v1";
|
|
3682
|
+
const models = await fetchCustomModels(baseURL, apiKey);
|
|
3683
|
+
return models.map((model) => ({
|
|
3684
|
+
model: model.modelName || model.id || model.name || model.model || "unknown",
|
|
3685
|
+
provider: "siliconflow",
|
|
3686
|
+
max_tokens: model.max_tokens || 8192,
|
|
3687
|
+
supports_vision: false,
|
|
3688
|
+
// Default to false, could be enhanced
|
|
3689
|
+
supports_function_calling: true,
|
|
3690
|
+
supports_reasoning_effort: false
|
|
3691
|
+
}));
|
|
3692
|
+
} catch (error) {
|
|
3693
|
+
let errorMessage = "Failed to fetch SiliconFlow models";
|
|
3694
|
+
if (error instanceof Error) {
|
|
3695
|
+
errorMessage = error.message;
|
|
3696
|
+
}
|
|
3697
|
+
if (errorMessage.includes("API key")) {
|
|
3698
|
+
errorMessage += "\n\n\u{1F4A1} Tip: Get your API key from https://cloud.siliconflow.cn/i/oJWsm6io";
|
|
3699
|
+
} else if (errorMessage.includes("permission")) {
|
|
3700
|
+
errorMessage += "\n\n\u{1F4A1} Tip: Make sure your API key has access to the SiliconFlow API";
|
|
3701
|
+
} else if (errorMessage.includes("connection")) {
|
|
3702
|
+
errorMessage += "\n\n\u{1F4A1} Tip: Check your internet connection and try again";
|
|
3703
|
+
}
|
|
3704
|
+
setModelLoadError(errorMessage);
|
|
3705
|
+
throw error;
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3708
|
+
|
|
3709
|
+
// apps/cli/src/ui/components/ModelSelector/fetchOllamaModels.ts
|
|
3710
|
+
function isRecord(value) {
|
|
3711
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3712
|
+
}
|
|
3713
|
+
function getNestedValue(value, path) {
|
|
3714
|
+
let current = value;
|
|
3715
|
+
for (const key of path) {
|
|
3716
|
+
if (!isRecord(current)) return void 0;
|
|
3717
|
+
current = current[key];
|
|
3718
|
+
}
|
|
3719
|
+
return current;
|
|
3720
|
+
}
|
|
3721
|
+
function toPositiveFiniteNumber(value) {
|
|
3722
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
|
|
3723
|
+
}
|
|
3724
|
+
async function fetchOllamaModels(args) {
|
|
3725
|
+
try {
|
|
3726
|
+
const response = await fetch(`${args.ollamaBaseUrl}/models`);
|
|
3727
|
+
if (!response.ok) {
|
|
3728
|
+
throw new Error(`HTTP error ${response.status}: ${response.statusText}`);
|
|
3729
|
+
}
|
|
3730
|
+
const responseData = await response.json();
|
|
3731
|
+
let models = [];
|
|
3732
|
+
if (isRecord(responseData) && Array.isArray(responseData.data)) {
|
|
3733
|
+
models = responseData.data;
|
|
3734
|
+
} else if (isRecord(responseData) && Array.isArray(responseData.models)) {
|
|
3735
|
+
models = responseData.models;
|
|
3736
|
+
} else if (Array.isArray(responseData)) {
|
|
3737
|
+
models = responseData;
|
|
3738
|
+
} else {
|
|
3739
|
+
throw new Error("Invalid response from Ollama API: missing models array");
|
|
3740
|
+
}
|
|
3741
|
+
const getModelName = (model) => {
|
|
3742
|
+
if (typeof model === "string") return model;
|
|
3743
|
+
if (!isRecord(model)) return "";
|
|
3744
|
+
const candidates = [
|
|
3745
|
+
model.id,
|
|
3746
|
+
model.name,
|
|
3747
|
+
model.modelName,
|
|
3748
|
+
model.model,
|
|
3749
|
+
model.model_name
|
|
3750
|
+
];
|
|
3751
|
+
for (const c of candidates) {
|
|
3752
|
+
if (typeof c === "string") return c;
|
|
3753
|
+
}
|
|
3754
|
+
return "";
|
|
3755
|
+
};
|
|
3756
|
+
const ollamaModels = models.map((model) => ({
|
|
3757
|
+
model: getModelName(model),
|
|
3758
|
+
provider: "ollama",
|
|
3759
|
+
max_tokens: DEFAULT_MAX_TOKENS,
|
|
3760
|
+
supports_vision: false,
|
|
3761
|
+
supports_function_calling: true,
|
|
3762
|
+
supports_reasoning_effort: false
|
|
3763
|
+
}));
|
|
3764
|
+
const validModels = ollamaModels.filter((model) => model.model);
|
|
3765
|
+
const normalizeOllamaRoot = (url) => {
|
|
3766
|
+
try {
|
|
3767
|
+
const u = new URL(url);
|
|
3768
|
+
let pathname = u.pathname.replace(/\/+$/g, "").replace(/^$/, "");
|
|
3769
|
+
if (pathname.endsWith("/v1")) {
|
|
3770
|
+
pathname = pathname.slice(0, -3);
|
|
3771
|
+
}
|
|
3772
|
+
u.pathname = pathname;
|
|
3773
|
+
return u.toString().replace(/\/+$/g, "");
|
|
3774
|
+
} catch {
|
|
3775
|
+
return url.replace(/\/v1\/?$/g, "");
|
|
3776
|
+
}
|
|
3777
|
+
};
|
|
3778
|
+
const extractContextTokens = (data) => {
|
|
3779
|
+
if (!isRecord(data)) return null;
|
|
3780
|
+
const modelInfoValue = data.model_info;
|
|
3781
|
+
if (isRecord(modelInfoValue)) {
|
|
3782
|
+
for (const key of Object.keys(modelInfoValue)) {
|
|
3783
|
+
if (key.endsWith(".context_length") || key.endsWith("_context_length")) {
|
|
3784
|
+
const val = toPositiveFiniteNumber(modelInfoValue[key]);
|
|
3785
|
+
if (val) return val;
|
|
3786
|
+
}
|
|
3787
|
+
}
|
|
3788
|
+
}
|
|
3789
|
+
const paths = [
|
|
3790
|
+
["parameters", "num_ctx"],
|
|
3791
|
+
["model_info", "num_ctx"],
|
|
3792
|
+
["config", "num_ctx"],
|
|
3793
|
+
["details", "context_length"],
|
|
3794
|
+
["context_length"],
|
|
3795
|
+
["num_ctx"],
|
|
3796
|
+
["max_tokens"],
|
|
3797
|
+
["max_new_tokens"]
|
|
3798
|
+
];
|
|
3799
|
+
const candidates = paths.map((path) => toPositiveFiniteNumber(getNestedValue(data, path))).filter((v) => typeof v === "number");
|
|
3800
|
+
if (candidates.length > 0) return Math.max(...candidates);
|
|
3801
|
+
const parametersValue = getNestedValue(data, ["parameters"]);
|
|
3802
|
+
if (typeof parametersValue === "string") {
|
|
3803
|
+
const m = parametersValue.match(/num_ctx\s*[:=]\s*(\d+)/i);
|
|
3804
|
+
if (!m) return null;
|
|
3805
|
+
const n = parseInt(m[1], 10);
|
|
3806
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
3807
|
+
}
|
|
3808
|
+
return null;
|
|
3809
|
+
};
|
|
3810
|
+
const ollamaRoot = normalizeOllamaRoot(args.ollamaBaseUrl);
|
|
3811
|
+
const enrichedModels = await Promise.all(
|
|
3812
|
+
validModels.map(async (m) => {
|
|
3813
|
+
try {
|
|
3814
|
+
const showResp = await fetch(`${ollamaRoot}/api/show`, {
|
|
3815
|
+
method: "POST",
|
|
3816
|
+
headers: { "Content-Type": "application/json" },
|
|
3817
|
+
body: JSON.stringify({ name: m.model })
|
|
3818
|
+
});
|
|
3819
|
+
if (showResp.ok) {
|
|
3820
|
+
const showData = await showResp.json();
|
|
3821
|
+
const ctx = extractContextTokens(showData);
|
|
3822
|
+
if (typeof ctx === "number" && isFinite(ctx) && ctx > 0) {
|
|
3823
|
+
return { ...m, context_length: ctx };
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
return m;
|
|
3827
|
+
} catch {
|
|
3828
|
+
return m;
|
|
3829
|
+
}
|
|
3830
|
+
})
|
|
3831
|
+
);
|
|
3832
|
+
args.setAvailableModels(enrichedModels);
|
|
3833
|
+
if (enrichedModels.length > 0) {
|
|
3834
|
+
args.navigateTo("model");
|
|
3835
|
+
} else {
|
|
3836
|
+
args.setModelLoadError("No models found in your Ollama installation");
|
|
3837
|
+
}
|
|
3838
|
+
return enrichedModels;
|
|
3839
|
+
} catch (error) {
|
|
3840
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3841
|
+
if (errorMessage.includes("fetch")) {
|
|
3842
|
+
args.setModelLoadError(
|
|
3843
|
+
`Could not connect to Ollama server at ${args.ollamaBaseUrl}. Make sure Ollama is running and the URL is correct.`
|
|
3844
|
+
);
|
|
3845
|
+
} else {
|
|
3846
|
+
args.setModelLoadError(`Error loading Ollama models: ${errorMessage}`);
|
|
3847
|
+
}
|
|
3848
|
+
console.error("Error fetching Ollama models:", error);
|
|
3849
|
+
return [];
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3852
|
+
|
|
3853
|
+
// apps/cli/src/ui/components/ModelSelector/useModelSelectorModelFlow.tsx
|
|
3854
|
+
function useModelSelectorModelFlow(state) {
|
|
3855
|
+
async function fetchModels() {
|
|
3856
|
+
return await fetchModelsForProvider({
|
|
3857
|
+
selectedProvider: state.selectedProvider,
|
|
3858
|
+
apiKey: state.apiKey,
|
|
3859
|
+
providerBaseUrl: state.providerBaseUrl,
|
|
3860
|
+
customBaseUrl: state.customBaseUrl,
|
|
3861
|
+
modelFetchers: modelFetchers_exports,
|
|
3862
|
+
setIsLoadingModels: state.setIsLoadingModels,
|
|
3863
|
+
setModelLoadError: state.setModelLoadError,
|
|
3864
|
+
setAvailableModels: state.setAvailableModels,
|
|
3865
|
+
navigateTo: state.navigateTo
|
|
3866
|
+
});
|
|
3867
|
+
}
|
|
3868
|
+
async function fetchModelsWithRetry() {
|
|
3869
|
+
const MAX_RETRIES = 2;
|
|
3870
|
+
let lastError = null;
|
|
3871
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
3872
|
+
state.setFetchRetryCount(attempt);
|
|
3873
|
+
state.setIsRetrying(attempt > 1);
|
|
3874
|
+
if (attempt > 1) {
|
|
3875
|
+
state.setModelLoadError(
|
|
3876
|
+
`Attempt ${attempt}/${MAX_RETRIES}: Retrying model discovery...`
|
|
3877
|
+
);
|
|
3878
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
3879
|
+
}
|
|
3880
|
+
try {
|
|
3881
|
+
const models = await fetchModels();
|
|
3882
|
+
state.setFetchRetryCount(0);
|
|
3883
|
+
state.setIsRetrying(false);
|
|
3884
|
+
state.setModelLoadError(null);
|
|
3885
|
+
return models;
|
|
3886
|
+
} catch (error) {
|
|
3887
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
3888
|
+
debug.warn("MODEL_FETCH_RETRY_FAILED", {
|
|
3889
|
+
attempt,
|
|
3890
|
+
maxRetries: MAX_RETRIES,
|
|
3891
|
+
error: lastError.message,
|
|
3892
|
+
provider: state.selectedProvider
|
|
3893
|
+
});
|
|
3894
|
+
if (attempt === MAX_RETRIES) break;
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3897
|
+
state.setIsRetrying(false);
|
|
3898
|
+
const errorMessage = lastError?.message || "Unknown error";
|
|
3899
|
+
state.setModelLoadError(
|
|
3900
|
+
`Failed to validate API key after ${MAX_RETRIES} attempts: ${errorMessage}
|
|
3901
|
+
|
|
3902
|
+
Please check your API key and try again, or press Tab to manually enter model name.`
|
|
3903
|
+
);
|
|
3904
|
+
throw new Error(`API key validation failed: ${errorMessage}`);
|
|
3905
|
+
}
|
|
3906
|
+
async function handleApiKeySubmit(key) {
|
|
3907
|
+
const cleanedKey = key.replace(/[\r\n]/g, "").trim();
|
|
3908
|
+
state.setApiKey(cleanedKey);
|
|
3909
|
+
state.setModelLoadError(null);
|
|
3910
|
+
if (state.selectedProvider === "azure") {
|
|
3911
|
+
state.navigateTo("resourceName");
|
|
3912
|
+
return;
|
|
3913
|
+
}
|
|
3914
|
+
try {
|
|
3915
|
+
state.setIsLoadingModels(true);
|
|
3916
|
+
const models = await fetchModelsWithRetry();
|
|
3917
|
+
if (models.length === 0) {
|
|
3918
|
+
state.navigateTo("modelInput");
|
|
3919
|
+
}
|
|
3920
|
+
} catch (error) {
|
|
3921
|
+
console.error("API key validation failed:", error);
|
|
3922
|
+
} finally {
|
|
3923
|
+
state.setIsLoadingModels(false);
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
function handleResourceNameSubmit(name) {
|
|
3927
|
+
state.setResourceName(name);
|
|
3928
|
+
state.navigateTo("modelInput");
|
|
3929
|
+
}
|
|
3930
|
+
function handleCustomBaseUrlSubmit(url) {
|
|
3931
|
+
const cleanUrl = url.replace(/\/+$/, "");
|
|
3932
|
+
state.setCustomBaseUrl(cleanUrl);
|
|
3933
|
+
state.navigateTo("apiKey");
|
|
3934
|
+
}
|
|
3935
|
+
function handleProviderBaseUrlSubmit(url) {
|
|
3936
|
+
const cleanUrl = url.replace(/\/+$/, "");
|
|
3937
|
+
state.setProviderBaseUrl(cleanUrl);
|
|
3938
|
+
if (state.selectedProvider === "ollama") {
|
|
3939
|
+
state.setOllamaBaseUrl(cleanUrl);
|
|
3940
|
+
state.setIsLoadingModels(true);
|
|
3941
|
+
state.setModelLoadError(null);
|
|
3942
|
+
fetchOllamaModels({
|
|
3943
|
+
ollamaBaseUrl: cleanUrl,
|
|
3944
|
+
setAvailableModels: state.setAvailableModels,
|
|
3945
|
+
setModelLoadError: state.setModelLoadError,
|
|
3946
|
+
navigateTo: () => state.navigateTo("model")
|
|
3947
|
+
}).finally(() => {
|
|
3948
|
+
state.setIsLoadingModels(false);
|
|
3949
|
+
});
|
|
3950
|
+
} else {
|
|
3951
|
+
state.navigateTo("apiKey");
|
|
3952
|
+
}
|
|
3953
|
+
}
|
|
3954
|
+
function handleCustomModelSubmit(model) {
|
|
3955
|
+
state.setCustomModelName(model);
|
|
3956
|
+
state.setSelectedModel(model);
|
|
3957
|
+
state.setSupportsReasoningEffort(false);
|
|
3958
|
+
state.setReasoningEffort(null);
|
|
3959
|
+
state.setMaxTokensMode("preset");
|
|
3960
|
+
state.setSelectedMaxTokensPreset(DEFAULT_MAX_TOKENS);
|
|
3961
|
+
state.setMaxTokens(DEFAULT_MAX_TOKENS.toString());
|
|
3962
|
+
state.setMaxTokensCursorOffset(DEFAULT_MAX_TOKENS.toString().length);
|
|
3963
|
+
state.navigateTo("modelParams");
|
|
3964
|
+
state.setActiveFieldIndex(0);
|
|
3965
|
+
}
|
|
3966
|
+
function handleModelSelection(model) {
|
|
3967
|
+
state.setSelectedModel(model);
|
|
3968
|
+
const modelInfo = state.availableModels.find((m) => m.model === model);
|
|
3969
|
+
state.setSupportsReasoningEffort(
|
|
3970
|
+
Boolean(modelInfo?.supports_reasoning_effort)
|
|
3971
|
+
);
|
|
3972
|
+
if (!modelInfo?.supports_reasoning_effort) {
|
|
3973
|
+
state.setReasoningEffort(null);
|
|
3974
|
+
}
|
|
3975
|
+
state.setContextLength(modelInfo?.context_length ?? DEFAULT_CONTEXT_LENGTH);
|
|
3976
|
+
const modelMaxTokens = modelInfo?.max_tokens;
|
|
3977
|
+
if (typeof modelMaxTokens === "number" && Number.isFinite(modelMaxTokens)) {
|
|
3978
|
+
const matchingPreset = MAX_TOKENS_OPTIONS.find(
|
|
3979
|
+
(option) => option.value === modelMaxTokens
|
|
3980
|
+
);
|
|
3981
|
+
if (matchingPreset) {
|
|
3982
|
+
state.setMaxTokensMode("preset");
|
|
3983
|
+
state.setSelectedMaxTokensPreset(modelMaxTokens);
|
|
3984
|
+
state.setMaxTokens(modelMaxTokens.toString());
|
|
3985
|
+
} else {
|
|
3986
|
+
state.setMaxTokensMode("custom");
|
|
3987
|
+
state.setMaxTokens(modelMaxTokens.toString());
|
|
3988
|
+
}
|
|
3989
|
+
state.setMaxTokensCursorOffset(modelMaxTokens.toString().length);
|
|
3990
|
+
} else {
|
|
3991
|
+
state.setMaxTokensMode("preset");
|
|
3992
|
+
state.setSelectedMaxTokensPreset(DEFAULT_MAX_TOKENS);
|
|
3993
|
+
state.setMaxTokens(DEFAULT_MAX_TOKENS.toString());
|
|
3994
|
+
state.setMaxTokensCursorOffset(DEFAULT_MAX_TOKENS.toString().length);
|
|
3995
|
+
}
|
|
3996
|
+
state.navigateTo("modelParams");
|
|
3997
|
+
state.setActiveFieldIndex(0);
|
|
3998
|
+
}
|
|
3999
|
+
const handleModelParamsSubmit = () => {
|
|
4000
|
+
if (!CONTEXT_LENGTH_OPTIONS.find((opt) => opt.value === state.contextLength)) {
|
|
4001
|
+
state.setContextLength(DEFAULT_CONTEXT_LENGTH);
|
|
4002
|
+
}
|
|
4003
|
+
state.navigateTo("contextLength");
|
|
4004
|
+
};
|
|
4005
|
+
const getFormFieldsForModelParams = () => {
|
|
4006
|
+
const fields = [
|
|
4007
|
+
{
|
|
4008
|
+
name: "maxTokens",
|
|
4009
|
+
label: "Maximum Tokens",
|
|
4010
|
+
description: "Select the maximum number of tokens to generate.",
|
|
4011
|
+
component: "select",
|
|
4012
|
+
options: MAX_TOKENS_OPTIONS.map((option) => ({
|
|
4013
|
+
label: option.label,
|
|
4014
|
+
value: option.value.toString()
|
|
4015
|
+
})),
|
|
4016
|
+
defaultValue: state.maxTokens
|
|
4017
|
+
}
|
|
4018
|
+
];
|
|
4019
|
+
if (state.supportsReasoningEffort) {
|
|
4020
|
+
fields.push({
|
|
4021
|
+
name: "reasoningEffort",
|
|
4022
|
+
label: "Reasoning Effort",
|
|
4023
|
+
description: "Controls reasoning depth for complex problems.",
|
|
4024
|
+
component: "select"
|
|
4025
|
+
});
|
|
4026
|
+
}
|
|
4027
|
+
fields.push({ name: "submit", label: "Continue \u2192", component: "button" });
|
|
4028
|
+
return fields;
|
|
4029
|
+
};
|
|
4030
|
+
const reasoningEffortOptions = REASONING_EFFORT_OPTIONS;
|
|
4031
|
+
const handleContextLengthSubmit = () => state.navigateTo("connectionTest");
|
|
4032
|
+
return {
|
|
4033
|
+
fetchModelsWithRetry,
|
|
4034
|
+
handleApiKeySubmit,
|
|
4035
|
+
handleResourceNameSubmit,
|
|
4036
|
+
handleCustomBaseUrlSubmit,
|
|
4037
|
+
handleProviderBaseUrlSubmit,
|
|
4038
|
+
handleCustomModelSubmit,
|
|
4039
|
+
handleModelSelection,
|
|
4040
|
+
handleModelParamsSubmit,
|
|
4041
|
+
getFormFieldsForModelParams,
|
|
4042
|
+
reasoningEffortOptions,
|
|
4043
|
+
handleContextLengthSubmit
|
|
4044
|
+
};
|
|
4045
|
+
}
|
|
4046
|
+
|
|
4047
|
+
// apps/cli/src/ui/components/ModelSelector/useModelSelectorTextHandlers.ts
|
|
4048
|
+
function useModelSelectorTextHandlers(state) {
|
|
4049
|
+
function handleCursorOffsetChange(offset) {
|
|
4050
|
+
state.setCursorOffset(offset);
|
|
4051
|
+
}
|
|
4052
|
+
function formatApiKeyDisplay(key) {
|
|
4053
|
+
if (!key) return "";
|
|
4054
|
+
if (key.length <= 10) return "*".repeat(key.length);
|
|
4055
|
+
const prefix = key.slice(0, 4);
|
|
4056
|
+
const suffix = key.slice(-4);
|
|
4057
|
+
const middleLength = Math.max(0, key.length - 8);
|
|
4058
|
+
const middle = "*".repeat(Math.min(middleLength, 30));
|
|
4059
|
+
return `${prefix}${middle}${suffix}`;
|
|
4060
|
+
}
|
|
4061
|
+
function handleApiKeyChange(value) {
|
|
4062
|
+
state.setApiKeyEdited(true);
|
|
4063
|
+
const cleanedValue = value.replace(/[\\r\\n]/g, "").trim();
|
|
4064
|
+
if (value !== cleanedValue && value.length > 0) {
|
|
4065
|
+
state.setApiKeyCleanedNotification(true);
|
|
4066
|
+
setTimeout(() => state.setApiKeyCleanedNotification(false), 3e3);
|
|
4067
|
+
}
|
|
4068
|
+
state.setApiKey(cleanedValue);
|
|
4069
|
+
state.setCursorOffset(cleanedValue.length);
|
|
4070
|
+
}
|
|
4071
|
+
function handleModelSearchChange(value) {
|
|
4072
|
+
state.setModelSearchQuery(value);
|
|
4073
|
+
state.setModelSearchCursorOffset(value.length);
|
|
4074
|
+
}
|
|
4075
|
+
function handleModelSearchCursorOffsetChange(offset) {
|
|
4076
|
+
state.setModelSearchCursorOffset(offset);
|
|
4077
|
+
}
|
|
4078
|
+
return {
|
|
4079
|
+
handleCursorOffsetChange,
|
|
4080
|
+
formatApiKeyDisplay,
|
|
4081
|
+
handleApiKeyChange,
|
|
4082
|
+
handleModelSearchChange,
|
|
4083
|
+
handleModelSearchCursorOffsetChange
|
|
4084
|
+
};
|
|
4085
|
+
}
|
|
4086
|
+
|
|
4087
|
+
// apps/cli/src/ui/components/ModelSelector/useModelSelectorActions.tsx
|
|
4088
|
+
function useModelSelectorActions({ props, state, onDone }) {
|
|
4089
|
+
const modelFlow = useModelSelectorModelFlow(state);
|
|
4090
|
+
const textHandlers = useModelSelectorTextHandlers(state);
|
|
4091
|
+
async function saveConfiguration(provider, model) {
|
|
4092
|
+
try {
|
|
4093
|
+
return await saveModelConfiguration({
|
|
4094
|
+
provider,
|
|
4095
|
+
model,
|
|
4096
|
+
providerBaseUrl: state.providerBaseUrl,
|
|
4097
|
+
resourceName: state.resourceName,
|
|
4098
|
+
customBaseUrl: state.customBaseUrl,
|
|
4099
|
+
apiKey: state.apiKey,
|
|
4100
|
+
maxTokens: state.maxTokens,
|
|
4101
|
+
contextLength: state.contextLength,
|
|
4102
|
+
reasoningEffort: state.reasoningEffort ?? void 0
|
|
4103
|
+
});
|
|
4104
|
+
} catch (error) {
|
|
4105
|
+
state.setValidationError(
|
|
4106
|
+
error instanceof Error ? error.message : "Failed to add model"
|
|
4107
|
+
);
|
|
4108
|
+
return null;
|
|
4109
|
+
}
|
|
4110
|
+
}
|
|
4111
|
+
async function handleConfirmation() {
|
|
4112
|
+
state.setValidationError(null);
|
|
4113
|
+
const modelId = await saveConfiguration(
|
|
4114
|
+
state.selectedProvider,
|
|
4115
|
+
state.selectedModel
|
|
4116
|
+
);
|
|
4117
|
+
if (!modelId) return;
|
|
4118
|
+
applyPointersForNewModel({
|
|
4119
|
+
modelId,
|
|
4120
|
+
isOnboarding: Boolean(props.isOnboarding),
|
|
4121
|
+
targetPointer: props.targetPointer
|
|
4122
|
+
});
|
|
4123
|
+
onDone();
|
|
4124
|
+
}
|
|
4125
|
+
const handleBack = () => {
|
|
4126
|
+
const { stack: nextStack, effect } = handleBackNavigation(state.screenStack);
|
|
4127
|
+
if (effect?.type === "resetProviderFocus") {
|
|
4128
|
+
state.setProviderFocusIndex(0);
|
|
4129
|
+
}
|
|
4130
|
+
if (effect?.type === "exit") {
|
|
4131
|
+
if (props.onCancel) props.onCancel();
|
|
4132
|
+
else onDone();
|
|
4133
|
+
return;
|
|
4134
|
+
}
|
|
4135
|
+
if (nextStack !== state.screenStack) {
|
|
4136
|
+
state.setScreenStack(nextStack);
|
|
4137
|
+
}
|
|
4138
|
+
};
|
|
4139
|
+
function handleProviderSelection2(provider) {
|
|
4140
|
+
handleProviderSelection(provider, {
|
|
4141
|
+
navigateTo: state.navigateTo,
|
|
4142
|
+
setPartnerProviderFocusIndex: state.setPartnerProviderFocusIndex,
|
|
4143
|
+
setCodingPlanFocusIndex: state.setCodingPlanFocusIndex,
|
|
4144
|
+
setSelectedProvider: state.setSelectedProvider,
|
|
4145
|
+
setProviderBaseUrl: state.setProviderBaseUrl,
|
|
4146
|
+
saveConfiguration,
|
|
4147
|
+
onDone,
|
|
4148
|
+
selectedModel: state.selectedModel
|
|
4149
|
+
});
|
|
4150
|
+
}
|
|
4151
|
+
async function handleConnectionTest() {
|
|
4152
|
+
state.setIsTestingConnection(true);
|
|
4153
|
+
state.setConnectionTestResult(null);
|
|
4154
|
+
try {
|
|
4155
|
+
const result = await runConnectionTestFlow({
|
|
4156
|
+
params: {
|
|
4157
|
+
selectedProvider: state.selectedProvider,
|
|
4158
|
+
selectedModel: state.selectedModel,
|
|
4159
|
+
apiKey: state.apiKey,
|
|
4160
|
+
maxTokens: state.maxTokens,
|
|
4161
|
+
providerBaseUrl: state.providerBaseUrl,
|
|
4162
|
+
customBaseUrl: state.customBaseUrl,
|
|
4163
|
+
resourceName: state.resourceName
|
|
4164
|
+
},
|
|
4165
|
+
navigateTo: () => state.navigateTo("confirmation")
|
|
4166
|
+
});
|
|
4167
|
+
state.setConnectionTestResult(result);
|
|
4168
|
+
} finally {
|
|
4169
|
+
state.setIsTestingConnection(false);
|
|
4170
|
+
}
|
|
4171
|
+
}
|
|
4172
|
+
return {
|
|
4173
|
+
handleBack,
|
|
4174
|
+
handleProviderSelection: handleProviderSelection2,
|
|
4175
|
+
...modelFlow,
|
|
4176
|
+
handleConnectionTest,
|
|
4177
|
+
handleConfirmation,
|
|
4178
|
+
...textHandlers
|
|
4179
|
+
};
|
|
4180
|
+
}
|
|
4181
|
+
|
|
4182
|
+
// apps/cli/src/ui/ui/components/model-selector/useEscapeNavigation.ts
|
|
4183
|
+
import { useRef as useRef2 } from "react";
|
|
4184
|
+
import { useInput as useInput4 } from "ink";
|
|
4185
|
+
function useEscapeNavigation(onEscape, _abortController) {
|
|
4186
|
+
const handledRef = useRef2(false);
|
|
4187
|
+
useInput4(
|
|
4188
|
+
(_input, key) => {
|
|
4189
|
+
if (key.escape && !handledRef.current) {
|
|
4190
|
+
handledRef.current = true;
|
|
4191
|
+
setTimeout(() => {
|
|
4192
|
+
handledRef.current = false;
|
|
4193
|
+
}, 100);
|
|
4194
|
+
onEscape();
|
|
4195
|
+
}
|
|
4196
|
+
},
|
|
4197
|
+
{ isActive: true }
|
|
4198
|
+
);
|
|
4199
|
+
}
|
|
4200
|
+
|
|
4201
|
+
// apps/cli/src/ui/components/ModelSelector/useModelSelectorController.tsx
|
|
4202
|
+
function useModelSelectorController(props) {
|
|
4203
|
+
const theme = getTheme();
|
|
4204
|
+
const { stdout } = useStdout();
|
|
4205
|
+
const terminalRows = stdout?.rows ?? 24;
|
|
4206
|
+
const compactLayout = terminalRows <= 22;
|
|
4207
|
+
const tightLayout = terminalRows <= 18;
|
|
4208
|
+
const containerPaddingY = tightLayout ? 0 : compactLayout ? 0 : 1;
|
|
4209
|
+
const containerGap = tightLayout ? 0 : 1;
|
|
4210
|
+
const exitState = useExitOnCtrlCD(() => process.exit(0));
|
|
4211
|
+
const exitStateForScreens = useMemo4(
|
|
4212
|
+
() => ({ pending: exitState.pending, keyName: exitState.keyName ?? "" }),
|
|
4213
|
+
[exitState.pending, exitState.keyName]
|
|
4214
|
+
);
|
|
4215
|
+
const onDone = () => {
|
|
4216
|
+
printModelConfig();
|
|
4217
|
+
props.onDone();
|
|
4218
|
+
};
|
|
4219
|
+
const state = useModelSelectorState({
|
|
4220
|
+
skipModelType: props.skipModelType ?? false
|
|
4221
|
+
});
|
|
4222
|
+
const menus = useModelSelectorMenus({
|
|
4223
|
+
containerPaddingY,
|
|
4224
|
+
containerGap,
|
|
4225
|
+
setProviderFocusIndex: state.setProviderFocusIndex,
|
|
4226
|
+
setPartnerProviderFocusIndex: state.setPartnerProviderFocusIndex,
|
|
4227
|
+
setCodingPlanFocusIndex: state.setCodingPlanFocusIndex
|
|
4228
|
+
});
|
|
4229
|
+
const { modelOptions } = useModelSelectorModelOptions({
|
|
4230
|
+
selectedProvider: state.selectedProvider,
|
|
4231
|
+
availableModels: state.availableModels,
|
|
4232
|
+
modelSearchQuery: state.modelSearchQuery
|
|
4233
|
+
});
|
|
4234
|
+
useEffect2(() => {
|
|
4235
|
+
if (!state.apiKeyEdited && state.selectedProvider) {
|
|
4236
|
+
const envKey = `${state.selectedProvider.toUpperCase()}_API_KEY`;
|
|
4237
|
+
if (process.env[envKey]) {
|
|
4238
|
+
state.setApiKey(process.env[envKey]);
|
|
4239
|
+
} else {
|
|
4240
|
+
state.setApiKey("");
|
|
4241
|
+
}
|
|
4242
|
+
}
|
|
4243
|
+
}, [state.apiKeyEdited, state.selectedProvider, state.setApiKey]);
|
|
4244
|
+
useEffect2(() => {
|
|
4245
|
+
if (state.currentScreen === "contextLength" && !CONTEXT_LENGTH_OPTIONS.find((opt) => opt.value === state.contextLength)) {
|
|
4246
|
+
state.setContextLength(DEFAULT_CONTEXT_LENGTH);
|
|
4247
|
+
}
|
|
4248
|
+
}, [state.contextLength, state.currentScreen, state.setContextLength]);
|
|
4249
|
+
const actions = useModelSelectorActions({ props, state, onDone });
|
|
4250
|
+
useEscapeNavigation(actions.handleBack, props.abortController);
|
|
4251
|
+
useModelSelectorInput({
|
|
4252
|
+
currentScreen: state.currentScreen,
|
|
4253
|
+
mainMenuOptions: menus.mainMenuOptions,
|
|
4254
|
+
providerFocusIndex: state.providerFocusIndex,
|
|
4255
|
+
setProviderFocusIndex: state.setProviderFocusIndex,
|
|
4256
|
+
partnerProviderOptions: menus.partnerProviderOptions,
|
|
4257
|
+
partnerProviderFocusIndex: state.partnerProviderFocusIndex,
|
|
4258
|
+
setPartnerProviderFocusIndex: state.setPartnerProviderFocusIndex,
|
|
4259
|
+
codingPlanOptions: menus.codingPlanOptions,
|
|
4260
|
+
codingPlanFocusIndex: state.codingPlanFocusIndex,
|
|
4261
|
+
setCodingPlanFocusIndex: state.setCodingPlanFocusIndex,
|
|
4262
|
+
selectedProvider: state.selectedProvider,
|
|
4263
|
+
apiKey: state.apiKey,
|
|
4264
|
+
resourceName: state.resourceName,
|
|
4265
|
+
providerBaseUrl: state.providerBaseUrl,
|
|
4266
|
+
customBaseUrl: state.customBaseUrl,
|
|
4267
|
+
customModelName: state.customModelName,
|
|
4268
|
+
contextLength: state.contextLength,
|
|
4269
|
+
setContextLength: state.setContextLength,
|
|
4270
|
+
isTestingConnection: state.isTestingConnection,
|
|
4271
|
+
connectionTestResult: state.connectionTestResult,
|
|
4272
|
+
activeFieldIndex: state.activeFieldIndex,
|
|
4273
|
+
setActiveFieldIndex: state.setActiveFieldIndex,
|
|
4274
|
+
handleProviderSelection: actions.handleProviderSelection,
|
|
4275
|
+
handleApiKeySubmit: actions.handleApiKeySubmit,
|
|
4276
|
+
fetchModelsWithRetry: actions.fetchModelsWithRetry,
|
|
4277
|
+
navigateTo: state.navigateTo,
|
|
4278
|
+
handleResourceNameSubmit: actions.handleResourceNameSubmit,
|
|
4279
|
+
handleCustomBaseUrlSubmit: actions.handleCustomBaseUrlSubmit,
|
|
4280
|
+
handleProviderBaseUrlSubmit: actions.handleProviderBaseUrlSubmit,
|
|
4281
|
+
handleCustomModelSubmit: actions.handleCustomModelSubmit,
|
|
4282
|
+
handleConfirmation: actions.handleConfirmation,
|
|
4283
|
+
setValidationError: state.setValidationError,
|
|
4284
|
+
handleConnectionTest: actions.handleConnectionTest,
|
|
4285
|
+
handleContextLengthSubmit: actions.handleContextLengthSubmit,
|
|
4286
|
+
setModelLoadError: state.setModelLoadError,
|
|
4287
|
+
getFormFieldsForModelParams: actions.getFormFieldsForModelParams,
|
|
4288
|
+
handleModelParamsSubmit: actions.handleModelParamsSubmit
|
|
4289
|
+
});
|
|
4290
|
+
return {
|
|
4291
|
+
theme,
|
|
4292
|
+
exitState: exitStateForScreens,
|
|
4293
|
+
terminalRows,
|
|
4294
|
+
compactLayout,
|
|
4295
|
+
tightLayout,
|
|
4296
|
+
containerPaddingY,
|
|
4297
|
+
containerGap,
|
|
4298
|
+
currentScreen: state.currentScreen,
|
|
4299
|
+
selectedProvider: state.selectedProvider,
|
|
4300
|
+
selectedModel: state.selectedModel,
|
|
4301
|
+
apiKey: state.apiKey,
|
|
4302
|
+
cursorOffset: state.cursorOffset,
|
|
4303
|
+
handleApiKeyChange: actions.handleApiKeyChange,
|
|
4304
|
+
handleApiKeySubmit: actions.handleApiKeySubmit,
|
|
4305
|
+
handleCursorOffsetChange: actions.handleCursorOffsetChange,
|
|
4306
|
+
apiKeyCleanedNotification: state.apiKeyCleanedNotification,
|
|
4307
|
+
isLoadingModels: state.isLoadingModels,
|
|
4308
|
+
modelLoadError: state.modelLoadError,
|
|
4309
|
+
providerBaseUrl: state.providerBaseUrl,
|
|
4310
|
+
setProviderBaseUrl: state.setProviderBaseUrl,
|
|
4311
|
+
providerBaseUrlCursorOffset: state.providerBaseUrlCursorOffset,
|
|
4312
|
+
setProviderBaseUrlCursorOffset: state.setProviderBaseUrlCursorOffset,
|
|
4313
|
+
customBaseUrl: state.customBaseUrl,
|
|
4314
|
+
setCustomBaseUrl: state.setCustomBaseUrl,
|
|
4315
|
+
customBaseUrlCursorOffset: state.customBaseUrlCursorOffset,
|
|
4316
|
+
setCustomBaseUrlCursorOffset: state.setCustomBaseUrlCursorOffset,
|
|
4317
|
+
customModelName: state.customModelName,
|
|
4318
|
+
setCustomModelName: state.setCustomModelName,
|
|
4319
|
+
customModelNameCursorOffset: state.customModelNameCursorOffset,
|
|
4320
|
+
setCustomModelNameCursorOffset: state.setCustomModelNameCursorOffset,
|
|
4321
|
+
resourceName: state.resourceName,
|
|
4322
|
+
setResourceName: state.setResourceName,
|
|
4323
|
+
resourceNameCursorOffset: state.resourceNameCursorOffset,
|
|
4324
|
+
setResourceNameCursorOffset: state.setResourceNameCursorOffset,
|
|
4325
|
+
availableModels: state.availableModels,
|
|
4326
|
+
modelSearchQuery: state.modelSearchQuery,
|
|
4327
|
+
modelSearchCursorOffset: state.modelSearchCursorOffset,
|
|
4328
|
+
handleModelSearchChange: actions.handleModelSearchChange,
|
|
4329
|
+
handleModelSearchCursorOffsetChange: actions.handleModelSearchCursorOffsetChange,
|
|
4330
|
+
modelOptions,
|
|
4331
|
+
handleResourceNameSubmit: actions.handleResourceNameSubmit,
|
|
4332
|
+
handleCustomBaseUrlSubmit: actions.handleCustomBaseUrlSubmit,
|
|
4333
|
+
handleProviderBaseUrlSubmit: actions.handleProviderBaseUrlSubmit,
|
|
4334
|
+
handleCustomModelSubmit: actions.handleCustomModelSubmit,
|
|
4335
|
+
handleModelSelection: actions.handleModelSelection,
|
|
4336
|
+
handleModelParamsSubmit: actions.handleModelParamsSubmit,
|
|
4337
|
+
maxTokens: state.maxTokens,
|
|
4338
|
+
setMaxTokens: state.setMaxTokens,
|
|
4339
|
+
setSelectedMaxTokensPreset: state.setSelectedMaxTokensPreset,
|
|
4340
|
+
setMaxTokensCursorOffset: state.setMaxTokensCursorOffset,
|
|
4341
|
+
supportsReasoningEffort: state.supportsReasoningEffort,
|
|
4342
|
+
reasoningEffortOptions: actions.reasoningEffortOptions,
|
|
4343
|
+
reasoningEffort: state.reasoningEffort,
|
|
4344
|
+
setReasoningEffort: state.setReasoningEffort,
|
|
4345
|
+
contextLength: state.contextLength,
|
|
4346
|
+
isTestingConnection: state.isTestingConnection,
|
|
4347
|
+
connectionTestResult: state.connectionTestResult,
|
|
4348
|
+
validationError: state.validationError,
|
|
4349
|
+
ollamaBaseUrl: state.ollamaBaseUrl,
|
|
4350
|
+
activeFieldIndex: state.activeFieldIndex,
|
|
4351
|
+
setActiveFieldIndex: state.setActiveFieldIndex,
|
|
4352
|
+
getFormFieldsForModelParams: actions.getFormFieldsForModelParams,
|
|
4353
|
+
mainMenuOptions: menus.mainMenuOptions,
|
|
4354
|
+
providerFocusIndex: state.providerFocusIndex,
|
|
4355
|
+
providerReservedLines: menus.providerReservedLines,
|
|
4356
|
+
partnerProviderOptions: menus.partnerProviderOptions,
|
|
4357
|
+
partnerProviderFocusIndex: state.partnerProviderFocusIndex,
|
|
4358
|
+
partnerReservedLines: menus.partnerReservedLines,
|
|
4359
|
+
codingPlanOptions: menus.codingPlanOptions,
|
|
4360
|
+
codingPlanFocusIndex: state.codingPlanFocusIndex,
|
|
4361
|
+
codingReservedLines: menus.codingReservedLines,
|
|
4362
|
+
formatApiKeyDisplay: actions.formatApiKeyDisplay,
|
|
4363
|
+
getProviderLabel: menus.getProviderLabel
|
|
4364
|
+
};
|
|
4365
|
+
}
|
|
4366
|
+
|
|
4367
|
+
// apps/cli/src/ui/components/ModelSelector/ModelSelector.tsx
|
|
4368
|
+
function ModelSelector(props) {
|
|
4369
|
+
const viewProps = useModelSelectorController(props);
|
|
4370
|
+
return /* @__PURE__ */ React19.createElement(ModelSelectorView, { ...viewProps });
|
|
4371
|
+
}
|
|
4372
|
+
|
|
4373
|
+
// apps/cli/src/ui/components/Onboarding.tsx
|
|
4374
|
+
function Onboarding({ onDone }) {
|
|
4375
|
+
const [currentStepIndex, setCurrentStepIndex] = useState4(0);
|
|
4376
|
+
const [showModelSelector, setShowModelSelector] = useState4(false);
|
|
4377
|
+
const config = getGlobalConfig();
|
|
4378
|
+
const [selectedTheme, setSelectedTheme] = useState4(
|
|
4379
|
+
DEFAULT_GLOBAL_CONFIG.theme
|
|
4380
|
+
);
|
|
4381
|
+
const theme = getTheme();
|
|
4382
|
+
function goToNextStep() {
|
|
4383
|
+
if (currentStepIndex < steps.length - 1) {
|
|
4384
|
+
const nextIndex = currentStepIndex + 1;
|
|
4385
|
+
setCurrentStepIndex(nextIndex);
|
|
4386
|
+
}
|
|
4387
|
+
}
|
|
4388
|
+
function handleThemeSelection(newTheme) {
|
|
4389
|
+
saveGlobalConfig({
|
|
4390
|
+
...config,
|
|
4391
|
+
theme: newTheme
|
|
4392
|
+
});
|
|
4393
|
+
goToNextStep();
|
|
4394
|
+
}
|
|
4395
|
+
function handleThemePreview(newTheme) {
|
|
4396
|
+
setSelectedTheme(newTheme);
|
|
4397
|
+
}
|
|
4398
|
+
function handleProviderSelectionDone() {
|
|
4399
|
+
goToNextStep();
|
|
4400
|
+
}
|
|
4401
|
+
function handleModelSelectionDone() {
|
|
4402
|
+
onDone();
|
|
4403
|
+
}
|
|
4404
|
+
const exitState = useExitOnCtrlCD(() => process.exit(0));
|
|
4405
|
+
useInput5(
|
|
4406
|
+
async (_, key) => {
|
|
4407
|
+
const currentStep = steps[currentStepIndex];
|
|
4408
|
+
if (key.return && currentStep && ["usage", "providers", "model"].includes(currentStep.id)) {
|
|
4409
|
+
if (currentStep.id === "model") {
|
|
4410
|
+
setShowModelSelector(true);
|
|
4411
|
+
} else if (currentStepIndex === steps.length - 1) {
|
|
4412
|
+
onDone();
|
|
4413
|
+
} else {
|
|
4414
|
+
await clearTerminal();
|
|
4415
|
+
goToNextStep();
|
|
4416
|
+
}
|
|
4417
|
+
}
|
|
4418
|
+
},
|
|
4419
|
+
{ isActive: !showModelSelector }
|
|
4420
|
+
);
|
|
4421
|
+
const themeStep = /* @__PURE__ */ React20.createElement(Box17, { flexDirection: "column", gap: 1, paddingLeft: 1 }, /* @__PURE__ */ React20.createElement(Text18, null, "Let's get started."), /* @__PURE__ */ React20.createElement(Box17, { flexDirection: "column" }, /* @__PURE__ */ React20.createElement(Text18, { bold: true }, "Choose the option that looks best when you select it:"), /* @__PURE__ */ React20.createElement(Text18, { dimColor: true }, "To change this later, run /config")), /* @__PURE__ */ React20.createElement(
|
|
4422
|
+
Select,
|
|
4423
|
+
{
|
|
4424
|
+
options: [
|
|
4425
|
+
{ label: "Light text", value: "dark" },
|
|
4426
|
+
{ label: "Dark text", value: "light" },
|
|
4427
|
+
{
|
|
4428
|
+
label: "Light text (colorblind-friendly)",
|
|
4429
|
+
value: "dark-daltonized"
|
|
4430
|
+
},
|
|
4431
|
+
{
|
|
4432
|
+
label: "Dark text (colorblind-friendly)",
|
|
4433
|
+
value: "light-daltonized"
|
|
4434
|
+
}
|
|
4435
|
+
],
|
|
4436
|
+
onFocus: handleThemePreview,
|
|
4437
|
+
onChange: handleThemeSelection
|
|
4438
|
+
}
|
|
4439
|
+
), /* @__PURE__ */ React20.createElement(Box17, { flexDirection: "column" }, /* @__PURE__ */ React20.createElement(
|
|
4440
|
+
Box17,
|
|
4441
|
+
{
|
|
4442
|
+
paddingLeft: 1,
|
|
4443
|
+
marginRight: 1,
|
|
4444
|
+
borderStyle: "round",
|
|
4445
|
+
borderColor: "gray",
|
|
4446
|
+
flexDirection: "column"
|
|
4447
|
+
},
|
|
4448
|
+
/* @__PURE__ */ React20.createElement(
|
|
4449
|
+
StructuredDiff,
|
|
4450
|
+
{
|
|
4451
|
+
patch: {
|
|
4452
|
+
oldStart: 1,
|
|
4453
|
+
newStart: 1,
|
|
4454
|
+
oldLines: 3,
|
|
4455
|
+
newLines: 3,
|
|
4456
|
+
lines: [
|
|
4457
|
+
"function greet() {",
|
|
4458
|
+
'- console.log("Hello, World!");',
|
|
4459
|
+
'+ console.log("Hello, anon!");',
|
|
4460
|
+
"}"
|
|
4461
|
+
]
|
|
4462
|
+
},
|
|
4463
|
+
dim: false,
|
|
4464
|
+
width: 40,
|
|
4465
|
+
overrideTheme: selectedTheme
|
|
4466
|
+
}
|
|
4467
|
+
)
|
|
4468
|
+
)));
|
|
4469
|
+
const providersStep = /* @__PURE__ */ React20.createElement(Box17, { flexDirection: "column", gap: 1, paddingLeft: 1 }, /* @__PURE__ */ React20.createElement(Box17, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React20.createElement(Text18, { color: theme.secondaryText }, "Next, let's select your preferred AI provider and model.")), /* @__PURE__ */ React20.createElement(
|
|
4470
|
+
ModelSelector,
|
|
4471
|
+
{
|
|
4472
|
+
onDone: handleProviderSelectionDone,
|
|
4473
|
+
skipModelType: true,
|
|
4474
|
+
isOnboarding: true
|
|
4475
|
+
}
|
|
4476
|
+
));
|
|
4477
|
+
const usageStep = /* @__PURE__ */ React20.createElement(Box17, { flexDirection: "column", gap: 1, paddingLeft: 1 }, /* @__PURE__ */ React20.createElement(Text18, { bold: true }, "Using ", PRODUCT_NAME, " effectively:"), /* @__PURE__ */ React20.createElement(Box17, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React20.createElement(OrderedList, null, /* @__PURE__ */ React20.createElement(OrderedList.Item, null, /* @__PURE__ */ React20.createElement(Text18, null, "Start in your project directory", /* @__PURE__ */ React20.createElement(Newline8, null), /* @__PURE__ */ React20.createElement(Text18, { color: theme.secondaryText }, "Files are automatically added to context when needed."), /* @__PURE__ */ React20.createElement(Newline8, null))), /* @__PURE__ */ React20.createElement(OrderedList.Item, null, /* @__PURE__ */ React20.createElement(Text18, null, "Use ", PRODUCT_NAME, " as a development partner", /* @__PURE__ */ React20.createElement(Newline8, null), /* @__PURE__ */ React20.createElement(Text18, { color: theme.secondaryText }, "Get help with file analysis, editing, bash commands,", /* @__PURE__ */ React20.createElement(Newline8, null), "and git history.", /* @__PURE__ */ React20.createElement(Newline8, null)))), /* @__PURE__ */ React20.createElement(OrderedList.Item, null, /* @__PURE__ */ React20.createElement(Text18, null, "Provide clear context", /* @__PURE__ */ React20.createElement(Newline8, null), /* @__PURE__ */ React20.createElement(Text18, { color: theme.secondaryText }, "Be as specific as you would with another engineer. ", /* @__PURE__ */ React20.createElement(Newline8, null), "The better the context, the better the results. ", /* @__PURE__ */ React20.createElement(Newline8, null)))))), /* @__PURE__ */ React20.createElement(PressEnterToContinue, null));
|
|
4478
|
+
const modelStep = /* @__PURE__ */ React20.createElement(Box17, { flexDirection: "column", gap: 1, paddingLeft: 1 }, /* @__PURE__ */ React20.createElement(Text18, { bold: true }, "Configure your models:"), /* @__PURE__ */ React20.createElement(Box17, { flexDirection: "column", width: 70 }, /* @__PURE__ */ React20.createElement(Text18, null, "You can customize which models ", PRODUCT_NAME, " uses for different tasks.", /* @__PURE__ */ React20.createElement(Newline8, null), /* @__PURE__ */ React20.createElement(Text18, { color: theme.secondaryText }, "Let's set up your preferred models for large and small tasks.")), /* @__PURE__ */ React20.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React20.createElement(Text18, null, "Press ", /* @__PURE__ */ React20.createElement(Text18, { color: theme.suggestion }, "Enter"), " to continue to the model selection screen."))), /* @__PURE__ */ React20.createElement(PressEnterToContinue, null));
|
|
4479
|
+
const steps = [];
|
|
4480
|
+
steps.push({ id: "theme", component: themeStep });
|
|
4481
|
+
steps.push({ id: "usage", component: usageStep });
|
|
4482
|
+
steps.push({ id: "model", component: modelStep });
|
|
4483
|
+
if (showModelSelector) {
|
|
4484
|
+
return /* @__PURE__ */ React20.createElement(
|
|
4485
|
+
ModelSelector,
|
|
4486
|
+
{
|
|
4487
|
+
onDone: handleModelSelectionDone,
|
|
4488
|
+
skipModelType: true,
|
|
4489
|
+
isOnboarding: true
|
|
4490
|
+
}
|
|
4491
|
+
);
|
|
4492
|
+
}
|
|
4493
|
+
return /* @__PURE__ */ React20.createElement(Box17, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React20.createElement(React20.Fragment, null, /* @__PURE__ */ React20.createElement(Box17, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React20.createElement(Text18, { bold: true }, PRODUCT_NAME, " ", exitState.pending ? `(press ${exitState.keyName} again to exit)` : ""), steps[currentStepIndex]?.component)));
|
|
4494
|
+
}
|
|
4495
|
+
function WelcomeBox() {
|
|
4496
|
+
const theme = getTheme();
|
|
4497
|
+
return /* @__PURE__ */ React20.createElement(
|
|
4498
|
+
Box17,
|
|
4499
|
+
{
|
|
4500
|
+
borderColor: theme.kode,
|
|
4501
|
+
borderStyle: "round",
|
|
4502
|
+
paddingX: 1,
|
|
4503
|
+
width: MIN_LOGO_WIDTH
|
|
4504
|
+
},
|
|
4505
|
+
/* @__PURE__ */ React20.createElement(Text18, null, /* @__PURE__ */ React20.createElement(Text18, { color: theme.kode }, "\u273B"), " Welcome to", " ", /* @__PURE__ */ React20.createElement(Text18, { bold: true }, PRODUCT_NAME), " research preview!")
|
|
4506
|
+
);
|
|
4507
|
+
}
|
|
4508
|
+
|
|
4509
|
+
export {
|
|
4510
|
+
useDoublePress,
|
|
4511
|
+
useExitOnCtrlCD,
|
|
4512
|
+
StructuredDiff,
|
|
4513
|
+
TextInput,
|
|
4514
|
+
setTerminalTitle,
|
|
4515
|
+
clearTerminal,
|
|
4516
|
+
Logo,
|
|
4517
|
+
ModelSelector,
|
|
4518
|
+
Onboarding,
|
|
4519
|
+
WelcomeBox
|
|
4520
|
+
};
|