From bbee587cfe57c7fbd82672412259143a9e7ab7af Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Fri, 17 Jan 2025 09:53:29 -0800 Subject: [PATCH 01/49] New icon --- CHANGELOG.md | 4 ++++ assets/icons/icon.png | Bin 9385 -> 5047 bytes assets/icons/icon.svg | 16 ++++++++++++++++ assets/icons/robot_panel_dark.png | Bin 718 -> 902 bytes assets/icons/robot_panel_light.png | Bin 689 -> 666 bytes package.json | 4 ++-- 6 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 assets/icons/icon.svg diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c3c07d673..fad3f4aae0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## [3.1.10] + +- New icon! + ## [3.1.9] - Add Mistral API provider with codestral-latest model diff --git a/assets/icons/icon.png b/assets/icons/icon.png index e8736aaa02433a094b7696f9b0f17ade498c7b62..db6f1d8fd14162365d2002436980460c921111f4 100644 GIT binary patch delta 4964 zcmV-q6PxU*Nw+7EfPWJqNkl^oDvih440z{Am9uPpo5s<@3fCZ8nKnRKW zyp~h$Ll>U7UhlEukHrhu=Zd;N_IV(zOE_Fb#NGE6S4oISl1U_x#6>|NT#G`GBOwwX zgv@+f^)VA>CNq=o>^bt)d*94=bXQkb)z{r$cU3imFoo3Y?0*hz>KeN12=&aO;k z2_mr0OmrpygH9q7;zg$z<pG6*@40I1vv#Z;J>0$6~q|5b=PfL%?%PDjn`~yu?%h zPS?Ntyb-S(F=Zn@rSk?n>hZXS2LZm%YT?Y?0}dNG8i%To*qsnrdS+~6p^tMBqjwJn}P)O!Gi=FO>hY-a{}vF ziFK@GqFCv$SSkgPRhAX*s0vj@xha6vYQ3diY@ZGmuz@Jru)@E=Bie+lR*rR;aW61d z1P&;QV^>P-b~`DI;b5c!u(Wa6V+E%;4Fx_Ho6=OQjeo`P8bwC|4(v|$qe2uq1-7lC zAh!^!MO~Bbpt}N$vSki%0Gp3OpNADl)}7+kh)@(yATKvtK-^SXlz&)H7U-q`!>yC1 z;(%-g5;s~;0!>`aIIsr?o~z64J3njoM8yrw3V?zigWCBPj(jszly7EH$pQQ#D#liJ zIV!?8Dt}Q(*;FgQsBzhS98Tx!h-HQr0?fRcIk43t2+x%kc6QxEA}Jg-QxA({knjV;kzLZ+~5S;J{Xzxoi<=np75;!k5LS&?mwd z6lFzpERsWvtS*3>eVm7&PdpPMg9F2`JE)V>9kkd5xCVTmW4&qd#DvJn5N;(WiKhXGpvCJ1jve9g@YNE`?zOTR6EYhh6J^zqz!brWQH~g_B zOJUlSX^N`wb-`|U{jE2k;o3Dt`NBCd+7k@)wI^sc29S(7!nqa-X?x=F-zyeA6>;~t z@$ljwpT{nu**l_;g78W1Pqw}afXn)5$rB}zos|s_-T$EK+Kd@B8s^VkpgMopWq-UO zTQ80FQUJ2yixxAkxVSi2^2no_WiV^TOz7IRt7bf5fq=1ONyZ+;cqxEn!0GWYl20dR z-vbGq6Es&LHZ~S!OjS01&|Ij22z+Jir2yFck2SDtuBdG|>emab8T#SUizd~0d}Q3D z0A#oCGTl~xD!O; z|HX?nIGoYfk)|4_i=zfSWPMWs07gbj1sfY1;Z)UW-Kuk~{u-P;cUCuiVSj+AN!F4A zq*=3Qf;_4mHA`?|M=#i0whtPvU)Kv=IN*tQ$z1ZA$6*2M4`@IX&Jf$SEhHu+;E^c( z)AxBWsI9F9TnE=>D!1y)X~@c)6s$OCY;@qN;sv0t;=+XsaJBww(DWgHNHPaFEm;M- z{PROjlP>}N`}K#6^bD|NSbw2k-@d+&xn0kgrri1I=hLU4xVRYX#dbLO<3ULoxe+X* zAd)#IE07d`f-}g-!!1dAdO9pw^a$L3dos5&^)9GaQcsvUeTHp31;Et-ip&vm7&u@6EL-{*+&T2lkkg_k0koE|V)?T$W7>3Bvw!Y=*jHAjCwf)5 zNamDL@(bv3Ti=y<-c1!Ioia_nXEMC}{7cZIM-QDah98>Fw|0g}6El%8jQ;TbccCV* z5&Z_c6QHT({HiJ@=fJU9{2Jde{31FZ{MDKxg>jm zB5?99IeSueM1SM!&96=!JHf-#XX=f%MSLWCYynv37E|h&fPbcN7cVelO!%^N#?|!9NG+ z1xdY=-#Q4%UVp%uNp1?z)$IHIOWxyd*F=8fBa%H?1aJ6Wiv|}z3ZorA9+B+nGmu*g&{E~i=bSl!vZqaz z^!K9RP!9^+P*S=F$YS@GGiTsBnu1gFITRO}C>jVEkbmk0SFc`?!pD?Vl*8GxXTj-k zNUmbT?ivO*t2J`JY*6-O5E9(HRxd8d|3CvI?4Lh7wxXyU&H%ob~WXpDX<8q zoV9xN{CoR$$)2zF*IM7A;H$59!F%toftlz+K5zCMDc(@CMB#lSHU_%@Y6T(&-dXc5 zeD<$@27jfup`ih`Y~2FiANUSlUG*y560iJAmrTl6|K$zH+nE=%EHeMzuxSI9^*y}u z!plHYx)B2?djW;-HVX@Tx}ei=n*!W%#~rG=c)WXM~Y(ejvMpfB2fApauXHli;J&D@{7n@v*+?^1#tC&Lm2q<(PLnchUUMz`aJj>g)8CmvE#>NC8w@NYivML0xdwT z07W?C0FnaWjG%EqcJTB$rUj~Nq#ZD{pn&R&@^IAD)MzS)GV6h3-mE!5p=}KYMlFD$ z3Sh$kOXTFK1spi>q7~%gkee!1%F%u?j%ezl#?v2*xY(Sr87)#j$|F zt$?HeK}gW{XyV-yP3vC`ETrgU_ZU1Dpf13Wc|iJ>oj-5BhU%MHi34+u9Sex00LrJy zWXOiTWywe01Z^DXhOGcaJv9iNhP43nEo*{49GLy^Y<&L%EORO!bVGsgEAuqx=IoTS}UPcB0Dge`80f^A`IqMCew{@b-*{FOOTo$KL z_GA#Edka8?QwyMFRZUg}T0tV>V}Heezcr?&D0}Jx8jQh?t-uDe70}At^&4`uq6n71 zffS%VSPCuAXy8(0w`)Pss`JvDzyG`aM)IJZ6u{7p@z!t3(HsRUCPsTGnHZVjXxg;V zz67h+0*+yQ|4o&;My_FowE$Gki4!Ma*X~_6i&SxzfB~Ylf?S_FJvN$3)PG^KDl#&q z(Z7*EvM1LtLluB3KxWfg*4Yy}cLuov48*VjDJg*-dEM`cvzYDLsSnsh1~4fy%knDN z=R%DEtPhG7FG=>)1zc`1rN#uOPM?MydAX{w5L3t7#;MNVyH{`MliWvEe(D0O7_3?? z3?4K{^9EC;drRTq4?n0SL4QfU%L4AK!X--R#t%1X+H~Pz^uD1z1JuC6`O221=gm=X zl(}%8b_1B4%^THJQ*j=Wy}$&_P~ zL{T0}{J{P91DU^T0UGrmI&?@23fXx{_7o+Iivkc;w2OxpxOeisuz&P-OJwf?Li-Lp zhhf^)0LAT_GfVz5Y!*fVe{$IqYKrp8Q~T?$Ue`umknG7Xu+~_!P<^XFf=TJ0A~|IS zGWDi7MDym(g((mI7Pmsiv93yZ6gRCHJa7=K`P*9f`P9#Tv5*%s`8<$vMjW8*V=z(_ zt}@_wFoIzCuryfv?tdC-E0(?c_Q9E7et|~Z)F}}+cT62R6h;moshg7~wIZW2ve|by zB}<=X}3$J;^=>_lvKIEb-r97rog-1`Ux8qZvriyY5Q8 zOFE1$izj}PJ;n(X4OwU8SrM}=bsQks<4mA%PF;58xkS+r`+rmAgoJete(f>IUJxCQ zGragwTgz{R%d1X$mvga3+n16OdPw%d_4pWW3P2kxnh=}=)iwNhM6wt5=jPH>FqgRi z+ELSl;2b#5k4KkC_Oup2aQw!fvA^VcZ9;Gk{CtWVk4_LJk^A$W8!dn+ za#MgJ+)UX7{~X|HKF3@#mlVKZv2as>BS(*f#ooF8MG(5XCx7}0qmlA^IovXr6aXCJ z5uR0Fi!t~_T#Pp1CI>#q`9M`X!-+Kp6SCQVeZn$*m68w4fYTis}lJ!jm zV62EI6;$!(Uwi>55_f9_8u71s^%edvf`_bcDu4sHDgX)n#L54`E3dt($yP8@m50Va ze|`6Dpnp)d+`&WEO$rdlu9P4l4&KDj7V67aEH}#gm3MVyl}8cAUs&}LZ2x?_tn@l3 za=2tIp>0wCyWM^X4TB8zLkd(8GJ$^lsV9JfG)Fs}!Obn6eCBB=E-4O8RZ+&0tX-vh z`CKL4-fuA856RAsSFT)vFLrzZ=g*&q0Rsj?dw-65=kSoknZCv&^!Rg%Xei*8M+H_$m?)WijLM9|9Cx?m*UCO05o^~<$ zWZPEwm7~RPAmW{ZFutN7x4`|}O9AjE8)3~nhNiU1?gXL!PwJ5bw5Kk4+>m=%VnP>4 zKz~bIty{t9T`vk{PceeY=7E;ID{;n?);+>%@kl-cw9V%?i&;e1_TaYu2s856A3 zn?Y*v(OiL=%^TE~7uET0$hJxNRM)0Kpu6H05I4Ec&Hw1)fIrzbQ-IQD42T=GfS;-tsM3HRe z353hMuPX;|oW@q*oBx&)tcn$Ykbe)iWlmLkl}NyZ4zMb=6G)+{BEsWq%HE0Gp3X ziu1zBvdB$eME;{C4;kvvdcMCUT`hIeT>(6ZVQ6DGR&a{bP#MNzXOoK0WX(Hx;ApL< z4#`5Bu|lW7wxJDQAu__tl1DGb7^whWG+M3JTk6I3>8JuWNg)76VuErdo ziFK?b|9r_s8$GjK&bj_rWca<1DnPK>rDkV$Xj9kNomnj1MR0ayB1^!LZ)Xf}oPga) zCk%5MFFM6|9C(tVbij(lA!Fn4SsXT&ZSh_#o>MGjGJJI(^w~=X)m__t;HlKSQWHxD zEqu{IyQwu|sz$s^=M8v%4bST_V?8FU$9tD?KvqXlx22%l7<-q(uS(HZF$g2Y;iFJ& i6phOjj0>}b7yKWr7wH-%078`j0000WZGdnXo zGnviKra)dko0<8Q_bczsue|quZ-`o;r3YGi;Kc0#;rMR)aDPu`CdF09QCz&w6jA80v^8$?RWXy0hiks5UQ%h@|aj(T&1U~+!LtuR!~Jw4pk7X@Ex!H z)MOc;TmSy8W66{0a(gn|qU%&a5Kkh()m|W>jo=bmlS@bN9)Ce3k%90>Vu0`b98fI^fhrEEpnzDyp@%v85J%^WqOi{|1bz(oeR-AS z%h|nY(_v?7<17}3y2!dxVoqj9suu4hx9er0@7A4i>R!EiB~yHImYZB7TyA%N!R1bO zxrELx!JQ(w-EPqFcmRoS{Cv>CbE3fFU=N0|I^xplh z<$ndi)k8|@?2NkWwrOV!eYh%t{=Ghd{8~P-s`CjSS5aO{m1U)c zyw4rCBX{%SNIE2Z2H4aa+qmulktUyhy@w}rVM=^*GI?X-Xv~Nb8h_={uRCff+5r3%U zXfcyT+!*Wf56j8P32wRS&JOXZJz&wNi}KsO!KqV_!^NyqR^_Jk+ha8dcxs>@fAP@L zwF#7(6rhY#LYV+WpYU=U%H0zqF_H#B%R^W_;Nk_M+wI})Vr3`4=*lcAJos)n2<(s7 zn=;4nEOUw)`L*YlQc|4p>aEHr(0}wrt!Z7JmO=7xpo%+M)u>^3%#oXh(dkn5swqE(>*Lv2e!>y0r7thY#nI%T=>Rx#{V)yjIXo z8)kW<5k8>*88rO-;gr?4FSTvkRw|;fu#mF1Y@rojub>T^Hb$e^2Ekc!q<@%2%RJ%= z!gY3c2X!)l_|HQbr?xju7tM$+ zoll}k6aP&8vieyTvpIV+Ju~N7+Piwn8+V_x=S(+<&y~XRl#0FPAc`Zw0;cRU&QNshtuGGdLWW)u%7bdU6(> zab;5r^jSE>2T_03zk-%NmQVLx zbA&MW%fNVPHxK^}O?;Gp@1%ReCO-0kVUV}E0jt?rY%GnkanQ@uRe$Mql!p3p_XE6m zxRE*Mq`ETMi3ia!+x z!0lx#IUWfZTx^Lo$|`#e1NdEwIMK&Uw&1vJmCI%v#KM37G=G_nmTG1io4F%TR@aK( zP66eFj}->x{KS!$OSXGHx|!$x3H;o_D1)7_4Lf4#t!0U{ zDkrYdw0y89Y?Ic;PIWEQ0Ec8_iaD(rfj2geDp_mDFLiI$6$~)5EYiH1LT1h{LciP@ zRCe^##mil*D}SoOTe0ul@1?PG+DN~zw}cY^v@X^3`SgSIyYo=7=mMr7QH*@*0S@45 zZooFREPSikjWO$*jshA6kQ*tSySIGGE3QtL-S46w57=ux7JH+fY)5$e&0ys4QZ^9r z(@htZM7>kl`j&p)yVtBLw!?t~xSAud0f|&?X5h=r>VI#@02vtrJMlEoefKW{jhUH3 zc(biw-e<}5^!q8~vsgiZ@FV({OK3W!`Ds`mK5r3a_~~cNY>|NzI3lY-{d2oLwRB@z zN;EY4g6}*zZE003-8-L^HxJiHr_K}}OR3|B)25CVarh=eT~1Pd_W{KtPni45~@ zNdC;$M<=!Q)8vtd>3voP^*vQfY#55pn{bHc+*u^)2~eKXu=d+^#D^~GjTU)MIcyd2M6z@Q)n7DY7IS9{3%UpETkTTrT z_I)0DrH;8jmyJIbvAsaiF*gl5t%_hSriKq!C(xw7x1rC!i|1)1It+mF_U+q8x!>ne z#_6Y1N-J~Q1*H9X@6R;-shRX$GZX)!?texQ2>AJ=ku?3f{O*_~#Rb3YSEmaZ0o;Cn zPdNe9i)zUIiH$yw)`YFxYOeoR=fTJUqmjS7lBNzOSOQOfr!^g_#ixmk!o!A*8|c8ws1UwPT( zbj78nTV~60%F8RLsQ3^S9y&w?1%76%#zMcKiZTP`qU;$^wHI)xg?4(8)3tBko^HAEX1ef#3z-2!swXFFR#!+8mwmO2UVGyolz-m{Rz}AT zA0ilvF#MNw0%xf^FQkW=0bov&3L!#btJdnYQNREF)xW1(Z@QUcVuAvUs2AFR1z>CW z>g)fY_dk5U0ZSz+W!nHB+XyfeGXrQAV;Tm4TNY1cpcpA(RT7_My<$c>C&0@mM*lqi z&-9y%EKLucD5^=|`1gOlpnqqde>SYGX(SadFF8V0l{IHIeX*`owYwV4IP9qwI?DwK z>~5zaAT}4s4^7@}jRNi^@(SLmaqAHl}mFileq#C-eBs&8o3nw50% z|6NSu?zmI7)D5Zjn9*Zc@U)S(=H`UNITfIxrHaPDubJpYK~z?VRss_zDmM1^+ok=f z9@mkhN9ZpP+(#cQ`Jh=6U)>&T|33NbQ=0gfNpy$>L`HyqMt@&K6V$X+(->uH7(fV+ za`sQdH4fMA+;h&Q%#6$stax)Cd}uOdZ`~3S*PH_&5d>40mDT7`^ld?3piiO&YFesk zjPf)Lz~@3#BS5sOLo|Bi^@hlwp7TtT5T6=WF2cY)Y@IZ1=5$jmW_@CsA&iLHWu25m zO=ART7=X_OwSNlY)kX!wBfEoGm(`LXNN_55*;mU$!jGqbb^raAR<2%Y*d+8x^hDk& zT54#lX8wo zD%OD6kf=nmIw*`H>?b|ztg}NB@7%eQw(r;;5`H`eV1H}7#bk|R)HhD_ftr?T8Uwe6 z0aym0FhG8WOV3HDJjP4<>Deq0cp1_Qo55mcV;W0{?w*T zTS*3A#p+H7TlVeSPwO|XH{ElGRR||eOHHGJ0|ros4js6x8v6*8bMW9nTEAffTLD3P z9v&P%Y~Eh<5u8&v4xnj`%0Z)n(ldY*sp1w#>wlq^NU0ISaM2DxZG71!m(e8`UqanZ z>1vtJIcxA>8h_WHsAETsF2gW67-jI8gQefv zb${z5-TR>;dv7XC(e9Zr$xv3IFAT)W?A=@XUCW0^WlX4Hq^V{EP^|&3 zf&Mp`ECfU21^nT$uskVr(ujM&spG_~nFqdFhS{Ix*8}nnhqbpKJd1iZ-0%*J`i*Fvrj!k={?d-X>-B(!|AUNJxp2b#5)u+r3=mxd1-qSd!TA@^s6QC%4ea8O)4}>?>Z6ZS zYO12|lB4Lfo;~TlN%xx0^-4k;H<%WH3pkq=1Zk?KwZZ@;B|LG~LXcPF5NEF4x__E4 z-1H^j1ka=i6ZOHS&t~FPb%u8B+R?Z_-lYzbMwa{UxzAKzuSv`gcFKW42yoWNsyUoK z($~%ESxr+ltreoj$5*&31F{*II=K~E`*Cp5I{Cf_Y-bqFmNLw#mg%89`uNjN=!YFY zu+d``Z`o35AbSS8kqu4E7K_7&oqtd7eej;5b^P^+UsJbk-7L$2_t3@fzsF|890Ia^Dq8w6m1%YKAQ5^hoj)KHb_&@TCRXH0`S%(m?C&5 zr;F%%2JhFWJ@$m{w=AB|Bwl*SrKV*T7Z=k*k3B-upPV6W^C~MUr7aj%`hQ=r#qDi> z97{_-{n#)+hUja4_j@Jo+H0;=?)5jYPk3?eOLWiue<5h{KzzV4{!qhe7Uxd5XClpi z?G;n&UMt-CvgBus9_TCdnWLbhshZXb0|WwzswB=84Pni+$EMmQK1v>Z=9v^z>)u3P z6Ij{&_3_7OEuXkC1K1Bf_kY6k^q*y48YVf5?e(x#>~c~U>T2OW_Qk)=r6p{ZW@faB z>0r^~clp4_Ylh_jH_8YXYM5ZEBI zq}D=aG*uWtD08dQC?kYvgRLTPNFow}ireH+O7Y>tR>c~|6&D-l!KqoZa*ncPGT!QN zprV4mHP1gicvQ!;^>KEe(U<7c$RJ2lHLVo}U}q3Yixss&WPc^rJNF?I4O@LWA-R=t z$3cr%SY)h*#x^fpz@pWN>CMUr7k}jG74outB1mf$1C%N$l$*#)trv!Zf_%e#-B0aq z*vW>8YXdVgdK*TgjDms!!#L>Wt;EJgnKDevF)qUhM?;0VTtQkZ48Z;Xf^-kwHB_B6 z<*etEGDZNxTz_%ts72$ z6$U6Tk5iQLq651CUH^pjxNhBA(>eI2F*jQ7-PHxb*TBgJHi$TAT3%j8KWyJl_+XR~ zjQG`sH00bNMo}SQ7mc`x&M`1{1#Xnl6hKYWnDPXL)_)2EBqgc+sHxbc5o(Xc9?sSo z0p9AzAAO8^rkg8U;N0pu7SmpR!De(`xtG=KC6b2#krM;sqoqP))KQrNYAXy; zR*g**H-^W=+Tg~!fOt4nyK~o0_P8;|@LYja?J$)iUqioXVi-GCKf3--?9MUsOCv&E!Qe(XNJ+wdKYymlmAdHLmpVc!a{ zF8FBaQdSEua_lUEzC6?)eOg}tEmfe<7-5=MoP7lD=CH1aNJ>f5IEqGCIDkF$BY%@0 zvc&{f{{E`4lsqsP8N`-9{rmN!cKiY8w6r!GlsXfeBv5@*7%BF!ufT3{QauAe z1(t!x#FSL-0I!^`Ue}>RhS0+g)_=_e;W9wID$|5A*<}C_|A`q>Y1Qi05!K$uUlcDr zqMSy`V+K&<(wa_Oy=(iw4>W^xsB$`ewJ{0I8x}` zsWZEDeTw}Vj+PWqb7sx9)PF%lS|0MFkL(D)!YNBbOHn+c&Z}VnfdWNxq>gLk;E1kf zju9e3dO4pK#z`E!sWQOr+Wfim=hde4 zAAItx_~vW+k;TA!_UtC;4%7e>acm`Fi$8?@^_|VCT{T0g(?Txr8E}0hE^Zp#1)bUO2UQJ8O5r8Ep1+w_puG5CbY%J^s?w@==UC)ju zAtp9^YxQ-6@(90h0ouK}^In#ui{a*N%=8*sg2pNcPMjPVa0|c4aZqdo9NY=Wc%8mt zfdl2sr2For9Xoe8jb6crx%tjN-PQbv&oopkxYRU8<1|x%OMeI)U{|6_qw$>?mnZnF zMAY*}U6FdCFwKT}{*G~XN)|jf{9$yxoIDsIhp2ZUtEC&tw$JH4s{K}w(lpRmdEgrj z15|rF2Ry2|U-YxxK!oC8bzwMEX#}xs_wJNqB)EjZ4`&jGoO>?e9ALOx!UG2m(EnH= z^d+Ay%*hS!fPaWl`}AS47Dg*he+)ySp{fF)vFcIF0Qi;+D}=C3kh~~BC=r=9KwT7n zd6d8Ofm1hFb*lj2>eBfn8Z>ASGr~!{+E0_bipi&s ziuj~)F`p4Q#GcB2+OtQ}1VV)zO=!zI4kJL{n7t%M>3{8n>4a~^>hA?1zm^g;7-7ab z7`OW2qb9ok|1rE3@nHAepS4I%!~S11BanptOny60Bsh~**+yX0H%95g3DeL}MOzt1C1hkxAq?PmyEO~|V*)2(|AGC6mbozX zva;%RvJQ=f3#ROFd1nN$Rz}}A33}S;X^XN#7=OU)D~I)@qPNw|Ar93J_H%l``S(eG znxGFpuAcum_720NYG|ZUpG4Yt9qs(_R2|}`JcI$ZZQF*;y5jA?@AEsp4)~J2dl`LA z8;39874&$7%Pzf?uD;?b!=|Ax&?im;=6}#m1=)NcL}LqpxDWz;#vbDRo8xZ zR(}qO^op&{{8#52qQMY3?#{bx%k>SBG@(G)|H0eo#JkN$H}r+m+rJu`$*Mg)O@kd| zX!zZxeS7whAY3CSgSfwPBsqdE>}J_E`j1$f9^EE9m||q&rr8+-G3}m zV@`vl?9ibiI)aZ!^EA?AfQg)(lq@-&f~XmcT^C^0k1vWkE&4^d75w&BR+I&+AkeNI zx!FB}0l_;{2I!HIHHRaWi3?w#PUf#?sJ}!VL=)Lve0%~Qvz|<64?Zh0M9m~OMzyIk zrb|vLoUSs4{bQ`#P!Iq7syw$dFMoTYQCX&}+NNCxIO@40n5pKC^LS%|K@Qzvzq;b< zuPBy}d-Y~}KC5BVp&}av4c1bN-(SpzpR-vh=B#{0Zct@8AIsAT@AyR?S5$Z~WcWuO z(-8oX=^1_5C8waAB0=)x)@kIjF~lK%eE?tV7<=njKKeDVKG~aWp6}ML=YKDv&okWq zI#p>X?~WaY$)9?96mxgvZOJq%!*m2dhO~Aaup1bm&I}7vZ*0v0Vs*Gv_izOLafOq}L%c}E zC*cYU3)mK6Gk?8o10PzgYLM!{vw3;R5q(+wwo=y}+1Y#b5%RMo10WF_h`h;{8{`bQ zVrP@sDwR(UJ83SWE}`W zRCWyRRGYr(xKW#qr!dd}v@jrkMJ??tn`^lSK+fX){G(~@+p_>b7^=nu(Iq&~%l2)@ z>#AMd-llkut)BEE^5QQ91o7#eIa@yr3D*VGIRYSkpdw}_UqWQEV1N?-3Z_4F;h`(0 z<Kp-(ytw#4Wm@}=SPUxq0T~TOf@)p>x!9YA+ihfG zD5q?>I@JT!>j`Ez z$O&u#rUW+@i=G&1t>EUbY@F!RgtVPz#Sj!?eqR;;e7v}`{}M9+r)|&6ewYbhKa+}D zkuZRSCcSsRYq`cR@k3h`UR$@2D7tv(fZc=3?d7XWQhzknD4Qa#nyorCTp~Vg%|kkV zem;L0@H4@AD_3K;S1;NX^4l|RNABjuk+i{)Uhdv~V2Uf&_aHOj1g=cPnj$cU;BvEF zAbu{s=O5c6V6;%~rEq~4F-Yu4Gs5pjepC7IHM?QM567_iCtdlu_8vPUoX-zPhi>ad zXnnm{Dt{|H7pU~i*tKhYi11&ZxUOeMd#*>HJ|_!)*EBA46#v3qVAfSv>v%8+ctKj$ z)Z8P0Jz|hp%OJHE#$(p(8Uo29F#FWwyqp|YDT=T7!+lMFb)B4zh1~ruh6Ew33L(VtJD}TIYrPZr%=R}18)Rkem!2o_?#_-#B z8NWQ;)p0HNCOyD5d_2H>%*1(<_w}|UdM23a430VjsB0iu8wC!436Riewa$}4(S8d>i~tGx}%0QyGs>eVZ`+7o}e+wbqm&gpydbkUP1 zZGXNJeyRHO&HTBT>%W~B72A0Mu^k6BaaIpU@oGw|;+^v8A)}CsiGb^1t zy8L2CUeL7@1u;zsP%8T|V#7qS73WV90%8)6Ca_me;2(A%_{G-#oN6DIeeko7A8aG= zD}&?ZTHm`R#9mh6XcN8`;ckx?i3M=T#Bq|3UwmG1zuq$k=Wi0=R0lPQ#)MYl8h3j7 zMh_p>@RXEzJ@N4sJlObo21?{#K#;$pjc@7lc9ValBHQ$f_=X}rqlYi4;bU60-%|=j m-xAW&11&w!(gV%92mT*8^CXM5>GxCs0000 + + Group Copy 2 + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/icons/robot_panel_dark.png b/assets/icons/robot_panel_dark.png index 0ed7cc6274eef604044cc14e1225f918d8f65806..36c37766f978d1628145ce3f63622cdd0fb315d1 100644 GIT binary patch delta 845 zcmV-T1G4p(3Kxqu{|bO}a^Gn(XJB zO=hy0-DsC=2PQM$oA67^6C>!t`6D5YtcK4b6+> z1VHG7OUac}%ygfG3K-3yM)Ja$1}UTWS(u&-rTL9P+ntcuYE|ySYy#t3e_k$IG#Crf zTw|TC=5oBI@h(#sj=}Jm9!#o*@$68BuV_7ETCB<8lt*7Nkkx}pws>3@_oR7VX_}3c z5>hkS7DHtuLf2KRiZLaR&a|Ly`#r6dJElx~HMmW!(eIc>AB#FR!A&gvelJCNY=FlR z6TPr%A7E47_g4qgOVEJ&V&OV8!mJu5XGZoUjvBjHOx$;Woxp zvN@4?N>6Cab>*N_smO7}y)JTFuitp*Cea1QV>;QdQk;D3rV+;xe_D!@)Grs0@{5zr z@^=XR=vQou`5Oa7@BNL0zdRY0)O6^Gjbol!3^t_ZNV`8fZn+PDe0~U)07hRoqcttV$v>_p1Ei` zrbLStDe3T&7Br>}rWt@0J&<&Zt8q$e8qrkSa}Nmx5DtLRHMwv`BM4D%o<*GADSY?7wMYK~ XPD&o3EPm}P00000NkvXXu0mjfc+HC+ delta 659 zcmV;E0&M+;2hIf{iBL{Q4GJ0x0000DNk~Le0000S0000U2nGNE06)ckpOGO*e;l9# z00aO40096103HAU0016+r@{aL0ys%TK~zW$y_T^~95E1v_Yi@Cq)CGaP|?!l9a5x7 zmx>4AIoOgWFF=Qo@&G9iQiPN;Wx7a_QYE@nRHTUzEZ^_-7-M@Ed^v8U|7JY@%s;o@ zJ>ND>g>_xG0uI5{L-9(5L|v&Le}=nfhjl6y)r`@Vr8cdQXqIYG-T4uuau;=K&HZ%Bbtw*q~W!2-G0_YQu{93f6K@j6Vh^u zXlD`-_JEZ4JnMKkfu^=sc9~W8`L2rza$Dddu(MO~meoAOtBx9H))Rb6u-32(%ST>0 zaihEmTc53+C)uV1Yh8`(6xaa8GI6844RF={N1F{(J}R90;&cm@7B|XAI$p-PR%hvf zkqht#V~6(_{{@ctZf;9ie;@grI6*eV_iXwZU$0{0{gAmmG&UzZwjp5?@qVZt4!dWH z(|bC|OUiW3E796QKAJa|FxDn+ou~pMN>kw8gENqwLgco=XYgjA7s{C%7EW=c)G)!~ zM$&7nzk_Y?4}1dOfa+d@cVHF#0K2qb;e!q7hP@7zQ9#os(7n2mX+UDvtWUst)Dl9g zwM4iY2pzD`!7IMyA1jDGHF{v)f*qg#i9e(Mw_o3L)aw@Lt?n;1FH&;he@_el177Sa thW1x`4(z>#@V`x3*-t6Gmrwedcnfk?=P!2SW4HhS002ovPDHLkV1g+cE3W_m diff --git a/assets/icons/robot_panel_light.png b/assets/icons/robot_panel_light.png index bbc7fca4ac5917a7fccbe634c7ee7a1f19c3ee09..2f028e0a20f19d6b6addbf4cb8e138c0512ddeca 100644 GIT binary patch delta 607 zcmV-l0-*h|1)2pRiBL{Q4GJ0x0000DNk~Le0000T0000U2nGNE0MG{&p^+g-e;uF$ z00aO40096103HAU005cWY=Hm(0t87!K~zW$y;eOh1W^=S5>y(62x2=Qkwro@Dv>Bi zM57?9K>PreM56EmXy`Puk&tLb1QApU8Vd0holp=|2+KJ#clNn6vzbli%}vhU_wGIK zo;UAfXA+6RtrJ5Ifj8g`NCQ@_e;L(3fFFf%o2nTlCgOKCMUMkSvxt|56)p}zECPo- z;v#^5i};tr2~*RzOe>-rz$3t?kYx<(Uod7L7z9Gu3|M}LC9I(4t)gCeKIKUy^`I#F znIO?xBwj6va^n4d!d%uUo+_xuwuqW1twVyAe7n>&MJps|$-DJUQQk*wf1e0U0hK@! zSOBzfIp=^HpcU5X<$7hMJ#mjNU$tF|5lqzHYRCZOM>IKqENyo$@(0Mr9yPK8v`=4iGU(QMcW{ zM%h!&;E6vL?OAG@qNftH=#n3QM`FSy`}&F002ovPDHLkV1nfQ{9FJ4 delta 630 zcmV-+0*U>a1+fJoiBL{Q4GJ0x0000DNk~Le0000S0000U2nGNE06)ckpOGO*e;l9# z00aO40096103HAU0016+r@{aL0vkz0K~zW$y_UO513?&tqj)LQ#zwqVwl=Dk5(#99C6Cn>^A%X-_TG|PMl@co}ZA84BZ?gv`yR(_ZBpx`KfBwt)XJ&`pG#aB} zLFp%Aw;(pYseuMRCE}YJ*jc$Df4-qHJ9K6*aXykC^F`8nu2CDFN0ef|NZQ|Ab1FL( zeXQ8{#=?xMkHuT@O|n zAT*;p#n}WOb7QT4VE;FG^K`GG%7p$1<2VI(4&)P Q<^TWy07*qoM6N<$f}z+Do&W#< diff --git a/package.json b/package.json index def6b5b323..72daa500db 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "claude-dev", "displayName": "Cline", "description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.", - "version": "3.1.9", + "version": "3.1.10", "icon": "assets/icons/icon.png", "galleryBanner": { "color": "#617A91", @@ -51,7 +51,7 @@ { "id": "claude-dev-ActivityBar", "title": "Cline", - "icon": "$(robot)" + "icon": "assets/icons/icon.svg" } ] }, From 3109fdb0f4ba6cf25a9ec8852c69756f104032af Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Fri, 17 Jan 2025 10:25:18 -0800 Subject: [PATCH 02/49] Fix codestral link --- package.json | 2 +- webview-ui/src/components/settings/ApiOptions.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 72daa500db..ce4278f676 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "claude-dev", "displayName": "Cline", "description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.", - "version": "3.1.10", + "version": "3.1.11", "icon": "assets/icons/icon.png", "galleryBanner": { "color": "#617A91", diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 28cbb6fd7c..24e1871cfb 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -292,7 +292,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage }: This key is stored locally and only used to make API requests from this extension. {!apiConfiguration?.mistralApiKey && ( Date: Mon, 20 Jan 2025 20:36:11 +0800 Subject: [PATCH 03/49] removing logging method --- src/core/webview/ClineProvider.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 13d630cae0..fc9d1b9fcc 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -84,10 +84,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { mcpHub?: McpHub private latestAnnouncementId = "jan-6-2025" // update to some unique identifier when we add a new announcement - public log(message: string) { - this.outputChannel.appendLine(message) - } - constructor( readonly context: vscode.ExtensionContext, private readonly outputChannel: vscode.OutputChannel, From d22c6c5540cc474422d324a9325297ebb490ca5c Mon Sep 17 00:00:00 2001 From: pashpashpash Date: Mon, 20 Jan 2025 15:05:11 -0800 Subject: [PATCH 04/49] prettier fixed mcp-quickstart.md to fix workflow approval errors --- docs/mcp/mcp-quickstart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/mcp/mcp-quickstart.md b/docs/mcp/mcp-quickstart.md index a62d5e7a47..13e194e47c 100644 --- a/docs/mcp/mcp-quickstart.md +++ b/docs/mcp/mcp-quickstart.md @@ -35,7 +35,7 @@ STOP! Before proceeding, you MUST verify these requirements: 1. From the Cline extension, click the `MCP Server` tab 1. Click the `Edit MCP Settings` button - MCP Server Panel + MCP Server Panel 1. The MCP settings files should be display in a tab in VS Code. 1. Replce the file's contents with this code: From f4ae4c66dfe518cdaf75ae6645bdff222e6c3a81 Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Fri, 17 Jan 2025 18:52:11 -0800 Subject: [PATCH 05/49] Add advisor model to openrouter --- src/api/index.ts | 5 +- src/api/providers/openrouter.ts | 36 ++++-- src/core/webview/ClineProvider.ts | 19 +++ src/shared/api.ts | 15 +++ .../src/components/settings/ApiOptions.tsx | 112 +++++++++++++++++- .../settings/OpenRouterModelPicker.tsx | 52 ++++++-- .../src/components/settings/SettingsView.tsx | 11 +- .../src/context/ExtensionStateContext.tsx | 11 +- webview-ui/src/utils/validate.ts | 22 +++- 9 files changed, 252 insertions(+), 31 deletions(-) diff --git a/src/api/index.ts b/src/api/index.ts index d3308df5c6..061b61b8be 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,5 +1,5 @@ import { Anthropic } from "@anthropic-ai/sdk" -import { ApiConfiguration, ModelInfo } from "../shared/api" +import { ApiConfiguration, ModelInfo, ModelType } from "../shared/api" import { AnthropicHandler } from "./providers/anthropic" import { AwsBedrockHandler } from "./providers/bedrock" import { OpenRouterHandler } from "./providers/openrouter" @@ -14,8 +14,9 @@ import { DeepSeekHandler } from "./providers/deepseek" import { MistralHandler } from "./providers/mistral" export interface ApiHandler { - createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream + createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], modelType?: ModelType): ApiStream getModel(): { id: string; info: ModelInfo } + getAdvisorModel?(): { id: string; info: ModelInfo } } export function buildApiHandler(configuration: ApiConfiguration): ApiHandler { diff --git a/src/api/providers/openrouter.ts b/src/api/providers/openrouter.ts index 3b9d7a354a..ce91c2f1ed 100644 --- a/src/api/providers/openrouter.ts +++ b/src/api/providers/openrouter.ts @@ -2,7 +2,15 @@ import { Anthropic } from "@anthropic-ai/sdk" import axios from "axios" import OpenAI from "openai" import { ApiHandler } from "../" -import { ApiHandlerOptions, ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "../../shared/api" +import { + ApiHandlerOptions, + ModelInfo, + ModelType, + openRouterDefaultAdvisorModelId, + openRouterDefaultAdvisorModelInfo, + openRouterDefaultModelId, + openRouterDefaultModelInfo, +} from "../../shared/api" import { convertToOpenAiMessages } from "../transform/openai-format" import { ApiStream } from "../transform/stream" import delay from "delay" @@ -23,7 +31,9 @@ export class OpenRouterHandler implements ApiHandler { }) } - async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], modelType?: ModelType): ApiStream { + const model = modelType === "advisor" ? this.getAdvisorModel() : this.getModel() + // Convert Anthropic messages to OpenAI format const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [ { role: "system", content: systemPrompt }, @@ -32,7 +42,7 @@ export class OpenRouterHandler implements ApiHandler { // prompt caching: https://openrouter.ai/docs/prompt-caching // this is specifically for claude models (some models may 'support prompt caching' automatically without this) - switch (this.getModel().id) { + switch (model.id) { case "anthropic/claude-3.5-sonnet": case "anthropic/claude-3.5-sonnet:beta": case "anthropic/claude-3.5-sonnet-20240620": @@ -83,7 +93,7 @@ export class OpenRouterHandler implements ApiHandler { // Not sure how openrouter defaults max tokens when no value is provided, but the anthropic api requires this value and since they offer both 4096 and 8192 variants, we should ensure 8192. // (models usually default to max tokens allowed) let maxTokens: number | undefined - switch (this.getModel().id) { + switch (model.id) { case "anthropic/claude-3.5-sonnet": case "anthropic/claude-3.5-sonnet:beta": case "anthropic/claude-3.5-sonnet-20240620": @@ -97,15 +107,15 @@ export class OpenRouterHandler implements ApiHandler { } // Removes messages in the middle when close to context window limit. Should not be applied to models that support prompt caching since it would continuously break the cache. - let shouldApplyMiddleOutTransform = !this.getModel().info.supportsPromptCache + let shouldApplyMiddleOutTransform = !model.info.supportsPromptCache // except for deepseek (which we set supportsPromptCache to true for), where because the context window is so small our truncation algo might miss and we should use openrouter's middle-out transform as a fallback to ensure we don't exceed the context window (FIXME: once we have a more robust token estimator we should not rely on this) - if (this.getModel().id === "deepseek/deepseek-chat") { + if (model.id === "deepseek/deepseek-chat") { shouldApplyMiddleOutTransform = true } // @ts-ignore-next-line const stream = await this.client.chat.completions.create({ - model: this.getModel().id, + model: model.id, max_tokens: maxTokens, temperature: 0, messages: openAiMessages, @@ -181,4 +191,16 @@ export class OpenRouterHandler implements ApiHandler { info: openRouterDefaultModelInfo, } } + + getAdvisorModel(): { id: string; info: ModelInfo } { + const modelId = this.options.openRouterAdvisorModelId + const modelInfo = this.options.openRouterAdvisorModelInfo + if (modelId && modelInfo) { + return { id: modelId, info: modelInfo } + } + return { + id: openRouterDefaultAdvisorModelId, + info: openRouterDefaultAdvisorModelInfo, + } + } } diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 54e47055f2..12b46b997b 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -61,7 +61,9 @@ type GlobalStateKey = | "anthropicBaseUrl" | "azureApiVersion" | "openRouterModelId" + | "openRouterAdvisorModelId" | "openRouterModelInfo" + | "openRouterAdvisorModelInfo" | "autoApprovalSettings" | "browserSettings" @@ -354,6 +356,13 @@ export class ClineProvider implements vscode.WebviewViewProvider { ) await this.postStateToWebview() } + if (apiConfiguration.openRouterAdvisorModelId) { + await this.updateGlobalState( + "openRouterAdvisorModelInfo", + openRouterModels[apiConfiguration.openRouterAdvisorModelId], + ) + await this.postStateToWebview() + } } }) break @@ -397,6 +406,8 @@ export class ClineProvider implements vscode.WebviewViewProvider { azureApiVersion, openRouterModelId, openRouterModelInfo, + openRouterAdvisorModelId, + openRouterAdvisorModelInfo, } = message.apiConfiguration await this.updateGlobalState("apiProvider", apiProvider) await this.updateGlobalState("apiModelId", apiModelId) @@ -424,6 +435,8 @@ export class ClineProvider implements vscode.WebviewViewProvider { await this.updateGlobalState("azureApiVersion", azureApiVersion) await this.updateGlobalState("openRouterModelId", openRouterModelId) await this.updateGlobalState("openRouterModelInfo", openRouterModelInfo) + await this.updateGlobalState("openRouterAdvisorModelId", openRouterAdvisorModelId) + await this.updateGlobalState("openRouterAdvisorModelInfo", openRouterAdvisorModelInfo) if (this.cline) { this.cline.api = buildApiHandler(message.apiConfiguration) } @@ -1030,6 +1043,8 @@ export class ClineProvider implements vscode.WebviewViewProvider { azureApiVersion, openRouterModelId, openRouterModelInfo, + openRouterAdvisorModelId, + openRouterAdvisorModelInfo, lastShownAnnouncementId, customInstructions, taskHistory, @@ -1062,6 +1077,8 @@ export class ClineProvider implements vscode.WebviewViewProvider { this.getGlobalState("azureApiVersion") as Promise, this.getGlobalState("openRouterModelId") as Promise, this.getGlobalState("openRouterModelInfo") as Promise, + this.getGlobalState("openRouterAdvisorModelId") as Promise, + this.getGlobalState("openRouterAdvisorModelInfo") as Promise, this.getGlobalState("lastShownAnnouncementId") as Promise, this.getGlobalState("customInstructions") as Promise, this.getGlobalState("taskHistory") as Promise, @@ -1111,6 +1128,8 @@ export class ClineProvider implements vscode.WebviewViewProvider { azureApiVersion, openRouterModelId, openRouterModelInfo, + openRouterAdvisorModelId, + openRouterAdvisorModelInfo, }, lastShownAnnouncementId, customInstructions, diff --git a/src/shared/api.ts b/src/shared/api.ts index f5ff3017fe..9a1c11d582 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -17,7 +17,9 @@ export interface ApiHandlerOptions { anthropicBaseUrl?: string openRouterApiKey?: string openRouterModelId?: string + openRouterAdvisorModelId?: string openRouterModelInfo?: ModelInfo + openRouterAdvisorModelInfo?: ModelInfo awsAccessKey?: string awsSecretKey?: string awsSessionToken?: string @@ -178,6 +180,19 @@ export const openRouterDefaultModelInfo: ModelInfo = { description: "The new Claude 3.5 Sonnet delivers better-than-Opus capabilities, faster-than-Sonnet speeds, at the same Sonnet prices. Sonnet is particularly good at:\n\n- Coding: New Sonnet scores ~49% on SWE-Bench Verified, higher than the last best score, and without any fancy prompt scaffolding\n- Data science: Augments human data science expertise; navigates unstructured data while using multiple tools for insights\n- Visual processing: excelling at interpreting charts, graphs, and images, accurately transcribing text to derive insights beyond just the text alone\n- Agentic tasks: exceptional tool use, making it great at agentic tasks (i.e. complex, multi-step problem solving tasks that require engaging with other systems)\n\n#multimodal\n\n_This is a faster endpoint, made available in collaboration with Anthropic, that is self-moderated: response moderation happens on the provider's side instead of OpenRouter's. For requests that pass moderation, it's identical to the [Standard](/anthropic/claude-3.5-sonnet) variant._", } +export const openRouterDefaultAdvisorModelId = "openai/o1-preview" // will always exist in openRouterModels +export const openRouterDefaultAdvisorModelInfo: ModelInfo = { + maxTokens: 33_000, + contextWindow: 128_000, + supportsImages: true, + supportsComputerUse: false, + supportsPromptCache: false, + inputPrice: 15, + outputPrice: 60, + description: + "The latest and strongest model family from OpenAI, o1 is designed to spend more time thinking before responding.\n\nThe o1 models are optimized for math, science, programming, and other STEM-related tasks. They consistently exhibit PhD-level accuracy on benchmarks in physics, chemistry, and biology. Learn more in the [launch announcement](https://openai.com/o1).\n\nNote: This model is currently experimental and not suitable for production use-cases, and may be heavily rate-limited.", +} +export type ModelType = "base" | "advisor" // Vertex AI // https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-claude diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 24e1871cfb..f242f1a868 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -11,6 +11,7 @@ import { Fragment, memo, useCallback, useEffect, useMemo, useState } from "react import { useEvent, useInterval } from "react-use" import { ApiConfiguration, + ApiProvider, ModelInfo, anthropicDefaultModelId, anthropicModels, @@ -26,6 +27,8 @@ import { openAiModelInfoSaneDefaults, openAiNativeDefaultModelId, openAiNativeModels, + openRouterDefaultAdvisorModelId, + openRouterDefaultAdvisorModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo, vertexDefaultModelId, @@ -41,15 +44,49 @@ interface ApiOptionsProps { showModelOptions: boolean apiErrorMessage?: string modelIdErrorMessage?: string + advisorModelIdErrorMessage?: string } -const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) => { +const TabPanel = ({ children, isSelected }: { children: React.ReactNode; isSelected: boolean }) => { + if (!isSelected) return null + return
{children}
+} + +const TabButton = ({ + isSelected, + onClick, + children, +}: { + isSelected: boolean + onClick: () => void + children: React.ReactNode +}) => { + return ( + + ) +} + +const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, advisorModelIdErrorMessage }: ApiOptionsProps) => { const { apiConfiguration, setApiConfiguration, uriScheme } = useExtensionState() const [ollamaModels, setOllamaModels] = useState([]) const [lmStudioModels, setLmStudioModels] = useState([]) const [anthropicBaseUrlSelected, setAnthropicBaseUrlSelected] = useState(!!apiConfiguration?.anthropicBaseUrl) const [azureApiVersionSelected, setAzureApiVersionSelected] = useState(!!apiConfiguration?.azureApiVersion) const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false) + const [selectedTab, setSelectedTab] = useState("base") const handleInputChange = (field: keyof ApiConfiguration) => (event: any) => { setApiConfiguration({ @@ -713,8 +750,6 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage }:

)} - {selectedProvider === "openrouter" && showModelOptions && } - {selectedProvider !== "openrouter" && selectedProvider !== "openai" && selectedProvider !== "ollama" && @@ -743,7 +778,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage }: )} - {modelIdErrorMessage && ( + {selectedProvider !== "openrouter" && modelIdErrorMessage && (

)} + + {selectedProvider === "openrouter" && showModelOptions && ( +

+
+ setSelectedTab("base")}> + Cline Model + + setSelectedTab("advisor")}> + Advisor Model + +
+ + +

+ This is the default driver model for Cline. It will read and edit files, run commands, and more, with + your permission at each step. +

+ + {modelIdErrorMessage && ( +

+ {modelIdErrorMessage} +

+ )} +
+ + +

+ The Cline model can call this smarter, more powerful model to ask for help on planning out a task, + fixing a hard bug, and other complex problems. +

+ + {advisorModelIdErrorMessage && ( +

+ {advisorModelIdErrorMessage} +

+ )} +
+
+ )} ) } @@ -895,7 +989,13 @@ const ModelInfoSupportsItem = ({ ) -export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration) { +export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration): { + selectedProvider: ApiProvider + selectedModelId: string + selectedModelInfo: ModelInfo + selectedAdvisorModelId?: string + selectedAdvisorModelInfo?: ModelInfo +} { const provider = apiConfiguration?.apiProvider || "anthropic" const modelId = apiConfiguration?.apiModelId @@ -935,6 +1035,8 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration) { selectedProvider: provider, selectedModelId: apiConfiguration?.openRouterModelId || openRouterDefaultModelId, selectedModelInfo: apiConfiguration?.openRouterModelInfo || openRouterDefaultModelInfo, + selectedAdvisorModelId: apiConfiguration?.openRouterAdvisorModelId || openRouterDefaultAdvisorModelId, + selectedAdvisorModelInfo: apiConfiguration?.openRouterAdvisorModelInfo || openRouterDefaultAdvisorModelInfo, } case "openai": return { diff --git a/webview-ui/src/components/settings/OpenRouterModelPicker.tsx b/webview-ui/src/components/settings/OpenRouterModelPicker.tsx index cdace4472b..b8cb4992e7 100644 --- a/webview-ui/src/components/settings/OpenRouterModelPicker.tsx +++ b/webview-ui/src/components/settings/OpenRouterModelPicker.tsx @@ -4,15 +4,28 @@ import React, { KeyboardEvent, memo, useEffect, useMemo, useRef, useState } from import { useRemark } from "react-remark" import { useMount } from "react-use" import styled from "styled-components" -import { openRouterDefaultModelId } from "../../../../src/shared/api" +import { + ModelType, + openRouterDefaultAdvisorModelId, + openRouterDefaultAdvisorModelInfo, + openRouterDefaultModelId, +} from "../../../../src/shared/api" import { useExtensionState } from "../../context/ExtensionStateContext" import { vscode } from "../../utils/vscode" import { highlight } from "../history/HistoryView" import { ModelInfoView, normalizeApiConfiguration } from "./ApiOptions" -const OpenRouterModelPicker: React.FC = () => { +export interface OpenRouterModelPickerProps { + modelType: ModelType +} + +const OpenRouterModelPicker: React.FC = ({ modelType }) => { const { apiConfiguration, setApiConfiguration, openRouterModels } = useExtensionState() - const [searchTerm, setSearchTerm] = useState(apiConfiguration?.openRouterModelId || openRouterDefaultModelId) + const [searchTerm, setSearchTerm] = useState( + modelType === "advisor" + ? apiConfiguration?.openRouterAdvisorModelId || openRouterDefaultAdvisorModelId + : apiConfiguration?.openRouterModelId || openRouterDefaultModelId, + ) const [isDropdownVisible, setIsDropdownVisible] = useState(false) const [selectedIndex, setSelectedIndex] = useState(-1) const dropdownRef = useRef(null) @@ -24,13 +37,20 @@ const OpenRouterModelPicker: React.FC = () => { // could be setting invalid model id/undefined info but validation will catch it setApiConfiguration({ ...apiConfiguration, - openRouterModelId: newModelId, - openRouterModelInfo: openRouterModels[newModelId], + ...(modelType === "advisor" + ? { + openRouterAdvisorModelId: newModelId, + openRouterAdvisorModelInfo: openRouterModels[newModelId], + } + : { + openRouterModelId: newModelId, + openRouterModelInfo: openRouterModels[newModelId], + }), }) setSearchTerm(newModelId) } - const { selectedModelId, selectedModelInfo } = useMemo(() => { + const { selectedModelId, selectedModelInfo, selectedAdvisorModelId, selectedAdvisorModelInfo } = useMemo(() => { return normalizeApiConfiguration(apiConfiguration) }, [apiConfiguration]) @@ -129,7 +149,7 @@ const OpenRouterModelPicker: React.FC = () => { }, [selectedIndex]) return ( - <> +
-
-
diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index d48a5e5084..a0363209c3 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -2,7 +2,14 @@ import React, { createContext, useCallback, useContext, useEffect, useState } fr import { useEvent } from "react-use" import { DEFAULT_AUTO_APPROVAL_SETTINGS } from "../../../src/shared/AutoApprovalSettings" import { ExtensionMessage, ExtensionState } from "../../../src/shared/ExtensionMessage" -import { ApiConfiguration, ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "../../../src/shared/api" +import { + ApiConfiguration, + ModelInfo, + openRouterDefaultAdvisorModelId, + openRouterDefaultAdvisorModelInfo, + openRouterDefaultModelId, + openRouterDefaultModelInfo, +} from "../../../src/shared/api" import { findLastIndex } from "../../../src/shared/array" import { McpServer } from "../../../src/shared/mcp" import { convertTextMateToHljs } from "../utils/textMateToHljs" @@ -40,6 +47,7 @@ export const ExtensionStateContextProvider: React.FC<{ const [filePaths, setFilePaths] = useState([]) const [openRouterModels, setOpenRouterModels] = useState>({ [openRouterDefaultModelId]: openRouterDefaultModelInfo, + [openRouterDefaultAdvisorModelId]: openRouterDefaultAdvisorModelInfo, }) const [mcpServers, setMcpServers] = useState([]) @@ -96,6 +104,7 @@ export const ExtensionStateContextProvider: React.FC<{ const updatedModels = message.openRouterModels ?? {} setOpenRouterModels({ [openRouterDefaultModelId]: openRouterDefaultModelInfo, // in case the extension sent a model list without the default model + [openRouterDefaultAdvisorModelId]: openRouterDefaultAdvisorModelInfo, ...updatedModels, }) break diff --git a/webview-ui/src/utils/validate.ts b/webview-ui/src/utils/validate.ts index 7dce99bebd..302c45d6a2 100644 --- a/webview-ui/src/utils/validate.ts +++ b/webview-ui/src/utils/validate.ts @@ -1,4 +1,4 @@ -import { ApiConfiguration, openRouterDefaultModelId } from "../../../src/shared/api" +import { ApiConfiguration, openRouterDefaultAdvisorModelId, openRouterDefaultModelId } from "../../../src/shared/api" import { ModelInfo } from "../../../src/shared/api" export function validateApiConfiguration(apiConfiguration?: ApiConfiguration): string | undefined { if (apiConfiguration) { @@ -83,3 +83,23 @@ export function validateModelId( } return undefined } + +export function validateAdvisorModelId( + apiConfiguration?: ApiConfiguration, + openRouterModels?: Record, +): string | undefined { + if (apiConfiguration) { + switch (apiConfiguration.apiProvider) { + case "openrouter": + const advisorModelId = apiConfiguration.openRouterAdvisorModelId || openRouterDefaultAdvisorModelId // in case the user hasn't changed the model id, it will be undefined by default + if (!advisorModelId) { + return "You must provide a model ID." + } + if (openRouterModels && !Object.keys(openRouterModels).includes(advisorModelId)) { + return "The model ID you provided is not available. Please choose a different model." + } + break + } + } + return undefined +} From 43bf3837842fc029d1b5062f3528db472bdc34cd Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Fri, 17 Jan 2025 20:35:05 -0800 Subject: [PATCH 06/49] Add consult advisor row --- src/core/Cline.ts | 67 +++++++++++++++ src/core/assistant-message/index.ts | 2 + src/core/prompts/system.ts | 86 +++++++++++++++++++ src/shared/AutoApprovalSettings.ts | 2 + src/shared/ExtensionMessage.ts | 8 ++ .../src/components/chat/AutoApproveMenu.tsx | 15 +++- webview-ui/src/components/chat/ChatRow.tsx | 72 +++++++++++++++- webview-ui/src/components/chat/ChatView.tsx | 14 +++ 8 files changed, 262 insertions(+), 4 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 5b4204105d..6db345bba2 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -31,6 +31,7 @@ import { ClineApiReqInfo, ClineAsk, ClineAskUseMcpServer, + ClineConsultAdvisor, ClineMessage, ClineSay, ClineSayBrowserAction, @@ -1060,6 +1061,8 @@ export class Cline { message.ask === "followup" || message.say === "use_mcp_server" || message.ask === "use_mcp_server" || + message.say === "consult_advisor" || + message.ask === "consult_advisor" || message.say === "browser_action" || message.say === "browser_action_launch" || message.ask === "browser_action_launch" @@ -1170,6 +1173,8 @@ export class Cline { case "access_mcp_resource": case "use_mcp_tool": return this.autoApprovalSettings.actions.useMcp + case "consult_advisor": + return this.autoApprovalSettings.actions.consultAdvisor ?? false } } return false @@ -1388,6 +1393,8 @@ export class Cline { return `[${block.name} for '${block.params.server_name}']` case "access_mcp_resource": return `[${block.name} for '${block.params.server_name}']` + case "consult_advisor": + return `[${block.name} for '${block.params.problem}']` case "ask_followup_question": return `[${block.name} for '${block.params.question}']` case "attempt_completion": @@ -2474,6 +2481,66 @@ export class Cline { break } } + case "consult_advisor": { + const problem: string | undefined = block.params.problem + try { + if (block.partial) { + const partialMessage = JSON.stringify({ + problem: removeClosingTag("problem", problem), + } satisfies ClineConsultAdvisor) + + if (this.shouldAutoApproveTool(block.name)) { + this.removeLastPartialMessageIfExistsWithType("ask", "consult_advisor") + await this.say("consult_advisor", partialMessage, undefined, block.partial) + } else { + this.removeLastPartialMessageIfExistsWithType("say", "consult_advisor") + await this.ask("consult_advisor", partialMessage, block.partial).catch(() => {}) + } + + break + } else { + if (!problem) { + this.consecutiveMistakeCount++ + pushToolResult(await this.sayAndCreateMissingParamError("consult_advisor", "problem")) + await this.saveCheckpoint() + break + } + + this.consecutiveMistakeCount = 0 + const completeMessage = JSON.stringify({ + problem: removeClosingTag("problem", problem), + } satisfies ClineConsultAdvisor) + + if (this.shouldAutoApproveTool(block.name)) { + this.removeLastPartialMessageIfExistsWithType("ask", "consult_advisor") + await this.say("consult_advisor", completeMessage, undefined, false) + this.consecutiveAutoApprovedRequestsCount++ + } else { + showNotificationForApprovalIfAutoApprovalEnabled( + `Cline wants to consult the Advisor model about: ${problem}`, + ) + this.removeLastPartialMessageIfExistsWithType("say", "consult_advisor") + const didApprove = await askApproval("consult_advisor", completeMessage) + if (!didApprove) { + await this.saveCheckpoint() + break + } + } + + // now execute the tool + await this.say("consult_advisor_request_started") + const resourceResult = "Just try again bro." //await this.providerRef.deref()?.mcpHub?.readResource(server_name, uri) + await this.say("consult_advisor_response", resourceResult) + pushToolResult(formatResponse.toolResult(resourceResult)) + await this.saveCheckpoint() + break + } + } catch (error) { + await handleError("consulting advisor", error) + await this.saveCheckpoint() + break + } + } case "ask_followup_question": { const question: string | undefined = block.params.question try { diff --git a/src/core/assistant-message/index.ts b/src/core/assistant-message/index.ts index 7ad2c27d7b..8da46213fb 100644 --- a/src/core/assistant-message/index.ts +++ b/src/core/assistant-message/index.ts @@ -19,6 +19,7 @@ export const toolUseNames = [ "browser_action", "use_mcp_tool", "access_mcp_resource", + "consult_advisor", "ask_followup_question", "attempt_completion", ] as const @@ -43,6 +44,7 @@ export const toolParamNames = [ "tool_name", "arguments", "uri", + "problem", "question", "result", ] as const diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index d6b0d2ca22..de72c18cad 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -206,6 +206,15 @@ Usage: resource URI here +## consult_advisor +Description: Request to consult a higher-reasoning advisor model about a problem or question you are facing. This can be used to outline a plan, discuss potential solutions, or resolve errors you are stuck on. The relevant conversation history leading to the problem will also be provided to the advisor for additional context. +Parameters: +- problem: (required) A string describing the issue, question, or context you want the advisor to address. +Usage: + +Your problem or question here + + ## ask_followup_question Description: Ask the user a question to gather additional information needed to complete the task. This tool should be used when you encounter ambiguities, need clarification, or require more details to proceed effectively. It allows for interactive problem-solving by enabling direct communication with the user. Use this tool judiciously to maintain a balance between gathering necessary information and avoiding excessive back-and-forth. Parameters: @@ -816,6 +825,83 @@ You have access to two tools for working with files: **write_to_file** and **rep By thoughtfully selecting between write_to_file and replace_in_file, you can make your file editing process smoother, safer, and more efficient. +==== + +CONSULTING THE ADVISOR MODEL + +You can use the consult_advisor tool to get higher-level reasoning or suggestions from an advisor model. The advisor is a more powerful AI model that can provide strategic guidance and help solve complex problems. The conversation history that led to the current situation is automatically passed to the advisor, allowing it to provide contextually relevant guidance based on the full picture of the task at hand. + +# When to Use the Advisor + +1. Architecting Complex Tasks +- Before starting implementation of large features or systems +- When planning new applications or major refactors +- To break down complex requirements into actionable steps +- To identify potential technical challenges early +- To evaluate different technical approaches and their tradeoffs +- When the solution requires careful consideration of multiple system components + +2. Resolving Challenging Bugs +- When stuck on persistent bugs that you cannot resolve +- If you've tried multiple approaches without success +- When facing complex type errors or package incompatibilities +- When debugging intricate interactions between multiple systems +- If you need deeper insight into system behavior that may not be apparent + +# How to Use Effectively + +1. Provide Clear Context +- Explain the current situation and challenge +- Include relevant code snippets or error messages +- Describe what you've already tried +- Specify what kind of guidance you're seeking + +2. Ask Specific Questions +- Instead of "Why isn't this working?" +- Better: "I'm encountering this specific type error when integrating these packages, here's what I've tried..." + +Example Usage: + + +I'm encountering persistent type errors while working with @types/react-query v4.0.0: + +Error: Type 'QueryClient' is not assignable to parameter of type 'never'. + The types of 'getQueryCache().notify' are incompatible between these types. + +I've tried: +- Checking package versions compatibility +- Explicitly typing the QueryClient instance +- Updating @types/react and @types/react-query + +Current package versions: +react-query: ^3.39.3 +@types/react-query: ^4.0.0 +react: ^18.2.0 +typescript: ^4.9.5 + +The error persists despite these attempts. Could this be due to version mismatches or breaking changes I'm not aware of? + + + +# Benefits of Using the Advisor + +1. Strategic Guidance +- Get high-level architectural direction +- Identify potential pitfalls early +- Make informed technical decisions +- Consider long-term implications + +2. Problem Resolution +- Break through debugging roadblocks +- Get fresh perspectives on complex issues +- Understand root causes of persistent bugs +- Solve challenging technical issues + +Remember: While you should attempt to solve problems with your own reasoning first, the advisor is a powerful resource available when you're either planning complex systems or truly stuck on a bug. Don't hesitate to consult it when: +- The scope of the task requires careful architectural planning +- You've hit a persistent roadblock that you cannot resolve +- You need deeper insight into complex system interactions + ==== CAPABILITIES diff --git a/src/shared/AutoApprovalSettings.ts b/src/shared/AutoApprovalSettings.ts index 28376d4e06..80f5f5a932 100644 --- a/src/shared/AutoApprovalSettings.ts +++ b/src/shared/AutoApprovalSettings.ts @@ -8,6 +8,7 @@ export interface AutoApprovalSettings { executeCommands: boolean // Execute safe commands useBrowser: boolean // Use browser useMcp: boolean // Use MCP servers + consultAdvisor?: boolean // Consult the advisor model } // Global settings maxRequests: number // Maximum number of auto-approved requests @@ -22,6 +23,7 @@ export const DEFAULT_AUTO_APPROVAL_SETTINGS: AutoApprovalSettings = { executeCommands: false, useBrowser: false, useMcp: false, + consultAdvisor: false, }, maxRequests: 20, enableNotifications: false, diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index fe5584c54d..1de338af9d 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -74,6 +74,7 @@ export type ClineAsk = | "auto_approval_max_req_reached" | "browser_action_launch" | "use_mcp_server" + | "consult_advisor" export type ClineSay = | "task" @@ -95,6 +96,9 @@ export type ClineSay = | "mcp_server_request_started" | "mcp_server_response" | "use_mcp_server" + | "consult_advisor" + | "consult_advisor_request_started" + | "consult_advisor_response" | "diff_error" | "deleted_api_reqs" @@ -139,6 +143,10 @@ export interface ClineAskUseMcpServer { uri?: string } +export interface ClineConsultAdvisor { + problem: string +} + export interface ClineApiReqInfo { request?: string tokensIn?: number diff --git a/webview-ui/src/components/chat/AutoApproveMenu.tsx b/webview-ui/src/components/chat/AutoApproveMenu.tsx index 0c2d9afc72..acf02b5823 100644 --- a/webview-ui/src/components/chat/AutoApproveMenu.tsx +++ b/webview-ui/src/components/chat/AutoApproveMenu.tsx @@ -46,16 +46,25 @@ const ACTION_METADATA: { shortName: "MCP", description: "Allows use of configured MCP servers which may modify filesystem or interact with APIs.", }, + { + id: "consultAdvisor", + label: "Consult the Advisor model", + shortName: "Advisor", + description: "Allows Cline to consult the Advisor model to get advice on how to proceed.", + }, ] const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => { - const { autoApprovalSettings } = useExtensionState() + const { autoApprovalSettings, apiConfiguration } = useExtensionState() const [isExpanded, setIsExpanded] = useState(false) const [isHoveringCollapsibleSection, setIsHoveringCollapsibleSection] = useState(false) // Careful not to use partials to mutate since spread operator only does shallow copy - const enabledActions = ACTION_METADATA.filter((action) => autoApprovalSettings.actions[action.id]) + const supportsAdvisor = apiConfiguration?.apiProvider === "openrouter" + const actionMetadata = ACTION_METADATA.filter((action) => supportsAdvisor || action.id !== "consultAdvisor") + + const enabledActions = actionMetadata.filter((action) => autoApprovalSettings.actions[action.id]) const enabledActionsList = enabledActions.map((action) => action.shortName).join(", ") const hasEnabledActions = enabledActions.length > 0 @@ -219,7 +228,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => { Auto-approve allows Cline to perform the following actions without asking for permission. Please use with caution and only enable if you understand the risks.
- {ACTION_METADATA.map((action) => ( + {actionMetadata.map((action) => (
, ] + case "consult_advisor": + // const consultAdvisor = JSON.parse(message.text || "{}") as ClineConsultAdvisor + return [ + isConsultAdvisorResponding ? ( + + ) : ( + + ), + + {message.type === "ask" ? ( + <>Cline wants to consult the Advisor model about: + ) : ( + <>Cline consulted the Advisor model about: + )} + , + ] case "completion_result": return [ server.name === useMcpServer.serverName) + return ( + <> +
+ {icon} + {title} +
+ +
+ {consultAdvisor.problem} +
+ + ) + } + switch (message.type) { case "say": switch (message.say) { @@ -1041,6 +1089,28 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi
) + case "consult_advisor_response": + return ( + <> +
+
+ Response +
+ +
+ + ) default: return ( <> diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index db534e16e6..22c1b96638 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -148,6 +148,13 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie setPrimaryButtonText("Approve") setSecondaryButtonText("Reject") break + case "consult_advisor": + setTextAreaDisabled(isPartial) + setClineAsk("consult_advisor") + setEnableButtons(!isPartial) + setPrimaryButtonText("Approve") + setSecondaryButtonText("Reject") + break case "completion_result": // extension waiting for feedback. but we can just present a new task button setTextAreaDisabled(isPartial) @@ -196,9 +203,12 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie case "browser_action_launch": case "command": case "use_mcp_server": + case "consult_advisor": case "command_output": case "mcp_server_request_started": case "mcp_server_response": + case "consult_advisor_request_started": + case "consult_advisor_response": case "completion_result": case "tool": break @@ -267,6 +277,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie case "command": // user can provide feedback to a tool or command use case "command_output": // user can send input to command stdin case "use_mcp_server": + case "consult_advisor": case "completion_result": // if this happens then the user has feedback for the completion result case "resume_task": case "resume_completed_task": @@ -309,6 +320,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie case "tool": case "browser_action_launch": case "use_mcp_server": + case "consult_advisor": case "resume_task": case "mistake_limit_reached": case "auto_approval_max_req_reached": @@ -348,6 +360,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie case "tool": case "browser_action_launch": case "use_mcp_server": + case "consult_advisor": // responds to the API with a "This operation failed" and lets it try again vscode.postMessage({ type: "askResponse", @@ -459,6 +472,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie } break case "mcp_server_request_started": + case "consult_advisor_request_started": return false } return true From 7aeab15ecfa060a7bbef8cf932f79bdfb6dc6cbd Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Fri, 17 Jan 2025 21:29:27 -0800 Subject: [PATCH 07/49] Add advisor model to anthropic --- src/api/providers/anthropic.ts | 31 ++++++-- src/core/webview/ClineProvider.ts | 6 ++ src/shared/api.ts | 5 +- .../src/components/settings/ApiOptions.tsx | 72 +++++++++++++------ 4 files changed, 85 insertions(+), 29 deletions(-) diff --git a/src/api/providers/anthropic.ts b/src/api/providers/anthropic.ts index 6fbe1f2509..bd141b1f57 100644 --- a/src/api/providers/anthropic.ts +++ b/src/api/providers/anthropic.ts @@ -1,6 +1,14 @@ import { Anthropic } from "@anthropic-ai/sdk" import { Stream as AnthropicStream } from "@anthropic-ai/sdk/streaming" -import { anthropicDefaultModelId, AnthropicModelId, anthropicModels, ApiHandlerOptions, ModelInfo } from "../../shared/api" +import { + anthropicDefaultAdvisorModelId, + anthropicDefaultModelId, + AnthropicModelId, + anthropicModels, + ApiHandlerOptions, + ModelInfo, + ModelType, +} from "../../shared/api" import { ApiHandler } from "../index" import { ApiStream } from "../transform/stream" @@ -16,9 +24,10 @@ export class AnthropicHandler implements ApiHandler { }) } - async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], modelType: ModelType): ApiStream { + const model = modelType === "advisor" ? this.getAdvisorModel() : this.getModel() let stream: AnthropicStream - const modelId = this.getModel().id + const modelId = model.id switch (modelId) { // 'latest' alias does not support cache_control case "claude-3-5-sonnet-20241022": @@ -37,7 +46,7 @@ export class AnthropicHandler implements ApiHandler { stream = await this.client.beta.promptCaching.messages.create( { model: modelId, - max_tokens: this.getModel().info.maxTokens || 8192, + max_tokens: model.info.maxTokens || 8192, temperature: 0, system: [ { @@ -104,7 +113,7 @@ export class AnthropicHandler implements ApiHandler { default: { stream = (await this.client.messages.create({ model: modelId, - max_tokens: this.getModel().info.maxTokens || 8192, + max_tokens: model.info.maxTokens || 8192, temperature: 0, system: [{ text: systemPrompt, type: "text" }], messages, @@ -185,4 +194,16 @@ export class AnthropicHandler implements ApiHandler { info: anthropicModels[anthropicDefaultModelId], } } + + getAdvisorModel(): { id: string; info: ModelInfo } { + const modelId = this.options.anthropicAdvisorModelId + if (modelId && modelId in anthropicModels) { + const id = modelId as AnthropicModelId + return { id, info: anthropicModels[id] } + } + return { + id: anthropicDefaultAdvisorModelId, + info: anthropicModels[anthropicDefaultAdvisorModelId], + } + } } diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 12b46b997b..53a24acc0f 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -45,6 +45,7 @@ type SecretKey = type GlobalStateKey = | "apiProvider" | "apiModelId" + | "anthropicAdvisorModelId" | "awsRegion" | "awsUseCrossRegionInference" | "vertexProjectId" @@ -382,6 +383,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { const { apiProvider, apiModelId, + anthropicAdvisorModelId, apiKey, openRouterApiKey, awsAccessKey, @@ -411,6 +413,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { } = message.apiConfiguration await this.updateGlobalState("apiProvider", apiProvider) await this.updateGlobalState("apiModelId", apiModelId) + await this.updateGlobalState("anthropicAdvisorModelId", anthropicAdvisorModelId) await this.storeSecret("apiKey", apiKey) await this.storeSecret("openRouterApiKey", openRouterApiKey) await this.storeSecret("awsAccessKey", awsAccessKey) @@ -1019,6 +1022,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { const [ storedApiProvider, apiModelId, + anthropicAdvisorModelId, apiKey, openRouterApiKey, awsAccessKey, @@ -1053,6 +1057,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { ] = await Promise.all([ this.getGlobalState("apiProvider") as Promise, this.getGlobalState("apiModelId") as Promise, + this.getGlobalState("anthropicAdvisorModelId") as Promise, this.getSecret("apiKey") as Promise, this.getSecret("openRouterApiKey") as Promise, this.getSecret("awsAccessKey") as Promise, @@ -1104,6 +1109,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { apiConfiguration: { apiProvider, apiModelId, + anthropicAdvisorModelId, apiKey, openRouterApiKey, awsAccessKey, diff --git a/src/shared/api.ts b/src/shared/api.ts index 9a1c11d582..013a063777 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -14,6 +14,7 @@ export type ApiProvider = export interface ApiHandlerOptions { apiModelId?: string apiKey?: string // anthropic + anthropicAdvisorModelId?: string anthropicBaseUrl?: string openRouterApiKey?: string openRouterModelId?: string @@ -60,10 +61,13 @@ export interface ModelInfo { description?: string } +export type ModelType = "base" | "advisor" + // Anthropic // https://docs.anthropic.com/en/docs/about-claude/models // prices updated 2025-01-02 export type AnthropicModelId = keyof typeof anthropicModels export const anthropicDefaultModelId: AnthropicModelId = "claude-3-5-sonnet-20241022" +export const anthropicDefaultAdvisorModelId: AnthropicModelId = "claude-3-opus-20240229" export const anthropicModels = { "claude-3-5-sonnet-20241022": { maxTokens: 8192, @@ -192,7 +196,6 @@ export const openRouterDefaultAdvisorModelInfo: ModelInfo = { description: "The latest and strongest model family from OpenAI, o1 is designed to spend more time thinking before responding.\n\nThe o1 models are optimized for math, science, programming, and other STEM-related tasks. They consistently exhibit PhD-level accuracy on benchmarks in physics, chemistry, and biology. Learn more in the [launch announcement](https://openai.com/o1).\n\nNote: This model is currently experimental and not suitable for production use-cases, and may be heavily rate-limited.", } -export type ModelType = "base" | "advisor" // Vertex AI // https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-claude diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index f242f1a868..52e7a170d8 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -13,6 +13,8 @@ import { ApiConfiguration, ApiProvider, ModelInfo, + ModelType, + anthropicDefaultAdvisorModelId, anthropicDefaultModelId, anthropicModels, azureOpenAiDefaultApiVersion, @@ -39,6 +41,7 @@ import { useExtensionState } from "../../context/ExtensionStateContext" import { vscode } from "../../utils/vscode" import VSCodeButtonLink from "../common/VSCodeButtonLink" import OpenRouterModelPicker, { ModelDescriptionMarkdown, OPENROUTER_MODEL_PICKER_Z_INDEX } from "./OpenRouterModelPicker" +import styled from "styled-components" interface ApiOptionsProps { showModelOptions: boolean @@ -52,6 +55,21 @@ const TabPanel = ({ children, isSelected }: { children: React.ReactNode; isSelec return
{children}
} +const StyledTabButton = styled.button<{ isSelected: boolean }>` + background: transparent; + border: none; + padding: 8px 16px; + color: ${(props) => (props.isSelected ? "var(--vscode-tab-activeForeground)" : "var(--vscode-tab-inactiveForeground)")}; + cursor: pointer; + border-bottom: 2px solid ${(props) => (props.isSelected ? "var(--vscode-foreground)" : "transparent")}; + font-size: 12px; + font-weight: 500; + + &:hover { + color: var(--vscode-tab-activeForeground); + } +` + const TabButton = ({ isSelected, onClick, @@ -62,20 +80,9 @@ const TabButton = ({ children: React.ReactNode }) => { return ( - + ) } @@ -95,7 +102,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, ad }) } - const { selectedProvider, selectedModelId, selectedModelInfo } = useMemo(() => { + const { selectedProvider, selectedModelId, selectedModelInfo, selectedAdvisorModelId } = useMemo(() => { return normalizeApiConfiguration(apiConfiguration) }, [apiConfiguration]) @@ -138,12 +145,16 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, ad As a workaround, we create separate instances of the dropdown for each provider, and then conditionally render the one that matches the current provider. */ - const createDropdown = (models: Record) => { + const createDropdown = (models: Record, modelType?: ModelType) => { return ( Select a model... {Object.keys(models).map((modelId) => ( @@ -751,6 +762,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, ad )} {selectedProvider !== "openrouter" && + selectedProvider !== "anthropic" && selectedProvider !== "openai" && selectedProvider !== "ollama" && selectedProvider !== "lmstudio" && @@ -760,7 +772,6 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, ad - {selectedProvider === "anthropic" && createDropdown(anthropicModels)} {selectedProvider === "bedrock" && createDropdown(bedrockModels)} {selectedProvider === "vertex" && createDropdown(vertexModels)} {selectedProvider === "gemini" && createDropdown(geminiModels)} @@ -778,7 +789,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, ad )} - {selectedProvider !== "openrouter" && modelIdErrorMessage && ( + {selectedProvider !== "openrouter" && selectedProvider !== "anthropic" && modelIdErrorMessage && (

)} - {selectedProvider === "openrouter" && showModelOptions && ( + {(selectedProvider === "openrouter" || selectedProvider === "anthropic") && showModelOptions && (

setSelectedTab("base")}> @@ -810,7 +821,12 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, ad This is the default driver model for Cline. It will read and edit files, run commands, and more, with your permission at each step.

- + {selectedProvider === "anthropic" && ( +
+ {createDropdown(anthropicModels, "base")} +
+ )} + {selectedProvider === "openrouter" && } {modelIdErrorMessage && (

- + {selectedProvider === "anthropic" && ( +

+ {createDropdown(anthropicModels, "advisor")} +
+ )} + {selectedProvider === "openrouter" && ( + + )} {advisorModelIdErrorMessage && (

Date: Sat, 18 Jan 2025 12:07:34 -0800 Subject: [PATCH 08/49] Fix spacing in task header --- webview-ui/src/components/chat/TaskHeader.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/webview-ui/src/components/chat/TaskHeader.tsx b/webview-ui/src/components/chat/TaskHeader.tsx index 899c25242e..ed2017d955 100644 --- a/webview-ui/src/components/chat/TaskHeader.tsx +++ b/webview-ui/src/components/chat/TaskHeader.tsx @@ -259,6 +259,7 @@ const TaskHeader: React.FC = ({ display: "flex", justifyContent: "space-between", alignItems: "center", + height: 17, }}>

= ({ display: "flex", justifyContent: "space-between", alignItems: "center", + height: 17, }}>
Date: Sat, 18 Jan 2025 15:35:50 -0800 Subject: [PATCH 09/49] Implement advisor model calling --- src/core/Cline.ts | 118 +++++++++++++++--- src/core/prompts/advisor.ts | 52 ++++++++ src/shared/ExtensionMessage.ts | 3 +- .../src/components/chat/AutoApproveMenu.tsx | 2 +- webview-ui/src/components/chat/ChatRow.tsx | 59 ++++----- webview-ui/src/components/chat/ChatView.tsx | 4 +- .../src/components/settings/ApiOptions.tsx | 6 +- 7 files changed, 184 insertions(+), 60 deletions(-) create mode 100644 src/core/prompts/advisor.ts diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 6db345bba2..117cf3e902 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -58,6 +58,7 @@ import { OpenAiHandler } from "../api/providers/openai" import CheckpointTracker from "../integrations/checkpoints/CheckpointTracker" import getFolderSize from "get-folder-size" import { BrowserSettings } from "../shared/BrowserSettings" +import { ADVISOR_SYSTEM_PROMPT } from "./prompts/advisor" const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution @@ -93,6 +94,7 @@ export class Cline { checkpointTrackerErrorMessage?: string conversationHistoryDeletedRange?: [number, number] isInitialized = false + private advisorProblem?: string // streaming isStreaming = false @@ -105,6 +107,7 @@ export class Cline { private didRejectTool = false private didAlreadyUseTool = false private didCompleteReadingStream = false + private didAutomaticallyRetryFailedApiRequest = false constructor( provider: ClineProvider, @@ -1261,7 +1264,49 @@ export class Cline { this.conversationHistoryDeletedRange, ) - const stream = this.api.createMessage(systemPrompt, truncatedConversationHistory) + let stream = this.api.createMessage(systemPrompt, truncatedConversationHistory) + + // If we're consulting the advisor, override the request + const advisorModel = this.api.getAdvisorModel?.() + if (this.advisorProblem && advisorModel) { + // Generate markdown + const markdownContent = truncatedConversationHistory + .map((message) => { + const role = message.role === "user" ? "**User:**" : "**Coding Agent:**" + const content = Array.isArray(message.content) + ? message.content.map((block) => formatContentBlockToMarkdown(block)).join("\n") + : message.content + return `${role}\n\n${content}\n\n` + }) + .join("---\n\n") + + // Don't want to send the entire conv history, just the most recent context + // Get approximate char count from token limit + const advisorContextWindow = advisorModel.info.contextWindow || 128_000 + const tokensToKeep = Math.floor(advisorContextWindow / 2) + // Estimate ~3 chars per token as a rough approximation + const charsToKeep = tokensToKeep * 3 + // Get last n chars of markdown content + const isTruncated = markdownContent.length > charsToKeep + const recentContext = (isTruncated ? "... (truncated for brevity)\n\n" : "") + markdownContent.slice(-charsToKeep) + const advisorMessage: Anthropic.Messages.MessageParam[] = [ + { + role: "user", + content: [ + { + type: "text", + text: + "\n\nThe conversation history leading up to this point: " + + recentContext + + "\n\nThe problem the coding agent needs advice on: " + + this.advisorProblem, + }, + ], + }, + ] + stream = this.api.createMessage(ADVISOR_SYSTEM_PROMPT(), advisorMessage, "advisor") + } + const iterator = stream[Symbol.asyncIterator]() try { @@ -1269,13 +1314,23 @@ export class Cline { const firstChunk = await iterator.next() yield firstChunk.value } catch (error) { - // note that this api_req_failed ask is unique in that we only present this option if the api hasn't streamed any content yet (ie it fails on the first chunk due), as it would allow them to hit a retry button. However if the api failed mid-stream, it could be in any arbitrary state where some tools may have executed, so that error is handled differently and requires cancelling the task entirely. - const { response } = await this.ask("api_req_failed", error.message ?? JSON.stringify(serializeError(error), null, 2)) - if (response !== "yesButtonClicked") { - // this will never happen since if noButtonClicked, we will clear current task, aborting this instance - throw new Error("API request failed") + if (!this.didAutomaticallyRetryFailedApiRequest) { + console.log("first chunk failed, waiting 1 second before retrying") + await delay(1000) + this.didAutomaticallyRetryFailedApiRequest = true + } else { + // request failed after retrying automatically once, ask user if they want to retry again + // note that this api_req_failed ask is unique in that we only present this option if the api hasn't streamed any content yet (ie it fails on the first chunk due), as it would allow them to hit a retry button. However if the api failed mid-stream, it could be in any arbitrary state where some tools may have executed, so that error is handled differently and requires cancelling the task entirely. + const { response } = await this.ask( + "api_req_failed", + error.message ?? JSON.stringify(serializeError(error), null, 2), + ) + if (response !== "yesButtonClicked") { + // this will never happen since if noButtonClicked, we will clear current task, aborting this instance + throw new Error("API request failed") + } + await this.say("api_req_retried") } - await this.say("api_req_retried") // delegate generator output from the recursive call yield* this.attemptApiRequest(previousApiReqIndex) return @@ -1313,6 +1368,11 @@ export class Cline { const block = cloneDeep(this.assistantMessageContent[this.currentStreamingContentIndex]) // need to create copy bc while stream is updating the array, it could be updating the reference block properties too switch (block.type) { case "text": { + if (this.advisorProblem) { + await this.say("advisor_response", block.content, undefined, block.partial) + break + } + if (this.didRejectTool || this.didAlreadyUseTool) { break } @@ -2528,10 +2588,11 @@ export class Cline { } // now execute the tool - await this.say("consult_advisor_request_started") - const resourceResult = "Just try again bro." //await this.providerRef.deref()?.mcpHub?.readResource(server_name, uri) - await this.say("consult_advisor_response", resourceResult) - pushToolResult(formatResponse.toolResult(resourceResult)) + this.advisorProblem = problem + // await this.say("consult_advisor_request_started") + // const resourceResult = "Just try again bro." //await this.providerRef.deref()?.mcpHub?.readResource(server_name, uri) + // await this.say("consult_advisor_response", resourceResult) + pushToolResult(formatResponse.toolResult("Awaiting response from the Advisor model...")) await this.saveCheckpoint() break } @@ -2830,10 +2891,13 @@ export class Cline { // getting verbose details is an expensive operation, it uses globby to top-down build file structure of project which for large projects can take a few seconds // for the best UX we show a placeholder api_req_started message with a loading spinner as this happens + const advisorRequest = this.advisorProblem ? `(...conversation history)\n\n${this.advisorProblem}` : undefined await this.say( "api_req_started", JSON.stringify({ - request: userContent.map((block) => formatContentBlockToMarkdown(block)).join("\n\n") + "\n\nLoading...", + request: + advisorRequest || + userContent.map((block) => formatContentBlockToMarkdown(block)).join("\n\n") + "\n\nLoading...", }), ) @@ -2864,7 +2928,7 @@ export class Cline { // since we sent off a placeholder api_req_started message to update the webview while waiting to actually start the API request (to load potential details for example), we need to update the text of that message const lastApiReqIndex = findLastIndex(this.clineMessages, (m) => m.say === "api_req_started") this.clineMessages[lastApiReqIndex].text = JSON.stringify({ - request: userContent.map((block) => formatContentBlockToMarkdown(block)).join("\n\n"), + request: advisorRequest || userContent.map((block) => formatContentBlockToMarkdown(block)).join("\n\n"), } satisfies ClineApiReqInfo) await this.saveClineMessages() await this.providerRef.deref()?.postStateToWebview() @@ -2944,8 +3008,11 @@ export class Cline { this.didAlreadyUseTool = false this.presentAssistantMessageLocked = false this.presentAssistantMessageHasPendingUpdates = false + this.didAutomaticallyRetryFailedApiRequest = false await this.diffViewProvider.reset() + const isCallingAdvisor = this.advisorProblem !== undefined + const stream = this.attemptApiRequest(previousApiReqIndex) // yields only if the first chunk is successful, otherwise will allow the user to retry the request (most likely due to rate limit error, which gets thrown on the first chunk) let assistantMessage = "" this.isStreaming = true @@ -3033,6 +3100,11 @@ export class Cline { await this.saveClineMessages() await this.providerRef.deref()?.postStateToWebview() + // If this last request was to the advisor model, then reset advisor problem to give control back to base model + if (isCallingAdvisor) { + this.advisorProblem = undefined + } + // now add to apiconversationhistory // need to save assistant responses to file before proceeding to tool use since user can exit at any moment and we wouldn't be able to save the assistant's response let didEndLoop = false @@ -3054,12 +3126,22 @@ export class Cline { // if the model did not tool use, then we need to tell it to either use a tool or attempt_completion const didToolUse = this.assistantMessageContent.some((block) => block.type === "tool_use") + if (!didToolUse) { - this.userMessageContent.push({ - type: "text", - text: formatResponse.noToolsUsed(), - }) - this.consecutiveMistakeCount++ + if (isCallingAdvisor) { + // if the last request was a request to advisor then it wouldn't have used a tool + this.userMessageContent.push({ + type: "text", + text: "Please continue with the task, taking into account the advisor's response provided above.", + }) + } else { + // normal request where tool use is required + this.userMessageContent.push({ + type: "text", + text: formatResponse.noToolsUsed(), + }) + this.consecutiveMistakeCount++ + } } const recDidEndLoop = await this.recursivelyMakeClineRequests(this.userMessageContent) diff --git a/src/core/prompts/advisor.ts b/src/core/prompts/advisor.ts new file mode 100644 index 0000000000..267067d6c7 --- /dev/null +++ b/src/core/prompts/advisor.ts @@ -0,0 +1,52 @@ +export const ADVISOR_SYSTEM_PROMPT = + () => `You are a senior AI advisor with deep expertise in software development, system architecture, and technical problem-solving. Your role is to assist another AI agent by providing strategic guidance and solutions to coding challenges. + +==== + +INPUT FORMAT + +You will receive: +1. The autonomous agent's conversation history thus far +2. A specific problem or question the agent needs help with + +==== + +RESPONSE FORMAT + +Your responses should generally follow this structure: + +1. Problem Analysis +A summary of the context and key challenges, focusing on the most critical aspects that need to be addressed. + +2. Solution Approach +The recommended strategy or solution, broken down into clear, actionable steps. Include rationale for key decisions and potential trade-offs considered. Use specific technical guidance, including code snippets, architecture recommendations, or debugging strategies as needed. Focus on practical, implementable advice the agent can use to apply the solution. + +==== + +ADVISORY PRINCIPLES + +1. Focus on providing actionable, concrete guidance rather than theoretical discussions. Your advice should enable immediate progress. + +2. Consider both immediate solutions and long-term implications. Guide the agent toward maintainable, scalable solutions while solving the current problem. + +3. Adapt your guidance based on the context. Account for: +- Existing codebase and architecture +- Applied technologies and constraints +- Performance and scalability requirements +- Project conventions and standards + +4. When analyzing problems: +- Start with a systematic evaluation of the issue +- Consider common pitfalls and edge cases +- Look for patterns in error messages or behavior +- Think about interaction between system components + +5. For architectural guidance: +- Recommend established patterns when appropriate +- Consider system boundaries and integration points +- Address scalability and maintenance concerns +- Focus on practical, implementable solutions + +==== + +Remember: Your goal is to provide clear, actionable guidance that helps the agent make immediate progress while following good software development practices. Focus on practical solutions rather than theoretical discussions.` diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 1de338af9d..6f1ce9e123 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -97,10 +97,9 @@ export type ClineSay = | "mcp_server_response" | "use_mcp_server" | "consult_advisor" - | "consult_advisor_request_started" - | "consult_advisor_response" | "diff_error" | "deleted_api_reqs" + | "advisor_response" export interface ClineSayTool { tool: diff --git a/webview-ui/src/components/chat/AutoApproveMenu.tsx b/webview-ui/src/components/chat/AutoApproveMenu.tsx index acf02b5823..4ede2742c1 100644 --- a/webview-ui/src/components/chat/AutoApproveMenu.tsx +++ b/webview-ui/src/components/chat/AutoApproveMenu.tsx @@ -61,7 +61,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => { // Careful not to use partials to mutate since spread operator only does shallow copy - const supportsAdvisor = apiConfiguration?.apiProvider === "openrouter" + const supportsAdvisor = apiConfiguration?.apiProvider === "openrouter" || apiConfiguration?.apiProvider === "anthropic" const actionMetadata = ACTION_METADATA.filter((action) => supportsAdvisor || action.id !== "consultAdvisor") const enabledActions = actionMetadata.filter((action) => autoApprovalSettings.actions[action.id]) diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 7472b39725..6cb2df3bb8 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -124,7 +124,6 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi lastModifiedMessage?.text?.includes(COMMAND_OUTPUT_STRING) const isMcpServerResponding = isLast && lastModifiedMessage?.say === "mcp_server_request_started" - const isConsultAdvisorResponding = isLast && lastModifiedMessage?.say === "consult_advisor_request_started" const type = message.type === "ask" ? message.ask : message.say @@ -223,16 +222,12 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi case "consult_advisor": // const consultAdvisor = JSON.parse(message.text || "{}") as ClineConsultAdvisor return [ - isConsultAdvisorResponding ? ( - - ) : ( - - ), + , {message.type === "ask" ? ( <>Cline wants to consult the Advisor model about: @@ -884,6 +879,26 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi
) + case "advisor_response": + return ( +
+
+ Response +
+ +
+ ) case "user_feedback": return (
) - case "consult_advisor_response": - return ( - <> -
-
- Response -
- -
- - ) default: return ( <> diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 22c1b96638..70e879f497 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -198,6 +198,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie case "error": case "api_req_finished": case "text": + case "advisor_response": case "browser_action": case "browser_action_result": case "browser_action_launch": @@ -207,8 +208,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie case "command_output": case "mcp_server_request_started": case "mcp_server_response": - case "consult_advisor_request_started": - case "consult_advisor_response": case "completion_result": case "tool": break @@ -472,7 +471,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie } break case "mcp_server_request_started": - case "consult_advisor_request_started": return false } return true diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 52e7a170d8..213fcdf6d5 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -818,7 +818,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, ad marginBottom: "10px", color: "var(--vscode-foreground)", }}> - This is the default driver model for Cline. It will read and edit files, run commands, and more, with + This model is the default driver for Cline. It will read and edit files, run commands, and more, with your permission at each step.

{selectedProvider === "anthropic" && ( @@ -846,8 +846,8 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, ad marginBottom: "10px", color: "var(--vscode-foreground)", }}> - The Cline model can call this smarter, more powerful model to ask for help on planning out a task, - fixing a hard bug, and other complex problems. + The Cline model can consult this smarter, more powerful model for help on planning out a task, fixing + a hard bug, and other complex problems.

{selectedProvider === "anthropic" && (
From d6e308d679777c59da6cb057fd828bf6154bc23e Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Sat, 18 Jan 2025 18:59:39 -0800 Subject: [PATCH 10/49] Final touches to advisor --- src/core/Cline.ts | 33 ++++++++-- src/core/prompts/advisor.ts | 38 ++--------- src/core/prompts/system.ts | 54 ++++++++-------- src/core/webview/ClineProvider.ts | 5 ++ src/shared/ExtensionMessage.ts | 2 + src/shared/WebviewMessage.ts | 1 + webview-ui/src/App.tsx | 15 ++++- webview-ui/src/components/chat/ChatRow.tsx | 63 +++++++++++++------ .../src/components/settings/ApiOptions.tsx | 15 +++-- .../settings/OpenRouterModelPicker.tsx | 36 +++++++---- .../src/components/settings/SettingsView.tsx | 4 +- 11 files changed, 164 insertions(+), 102 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 117cf3e902..bb275aba31 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -1194,11 +1194,15 @@ export class Cline { throw new Error("MCP hub not available") } + const advisorModel = this.api.getAdvisorModel?.() + const supportsConsultAdvisor = advisorModel !== undefined + let systemPrompt = await SYSTEM_PROMPT( cwd, this.api.getModel().info.supportsComputerUse ?? false, mcpHub, this.browserSettings, + supportsConsultAdvisor, ) let settingsCustomInstructions = this.customInstructions?.trim() const clineRulesFilePath = path.resolve(cwd, GlobalFileNames.clineRules) @@ -1267,7 +1271,6 @@ export class Cline { let stream = this.api.createMessage(systemPrompt, truncatedConversationHistory) // If we're consulting the advisor, override the request - const advisorModel = this.api.getAdvisorModel?.() if (this.advisorProblem && advisorModel) { // Generate markdown const markdownContent = truncatedConversationHistory @@ -1288,7 +1291,15 @@ export class Cline { const charsToKeep = tokensToKeep * 3 // Get last n chars of markdown content const isTruncated = markdownContent.length > charsToKeep - const recentContext = (isTruncated ? "... (truncated for brevity)\n\n" : "") + markdownContent.slice(-charsToKeep) + const firstMessage = truncatedConversationHistory.at(0) + const firstMessageContent = firstMessage + ? Array.isArray(firstMessage.content) + ? firstMessage.content.map((block) => (block.type === "text" ? block.text : "")).join("\n") + : firstMessage.content + : "" + const recentContext = + (isTruncated ? `**User:**:\n\n${firstMessageContent}\n\n... (older messages removed for brevity) ...\n\n` : "") + + markdownContent.slice(-charsToKeep) const advisorMessage: Anthropic.Messages.MessageParam[] = [ { role: "user", @@ -1296,9 +1307,9 @@ export class Cline { { type: "text", text: - "\n\nThe conversation history leading up to this point: " + + "\n\n# The conversation history leading up to this point:\n\n" + recentContext + - "\n\nThe problem the coding agent needs advice on: " + + "\n\n# The problem the coding agent needs advice on:\n\n" + this.advisorProblem, }, ], @@ -2547,6 +2558,7 @@ export class Cline { if (block.partial) { const partialMessage = JSON.stringify({ problem: removeClosingTag("problem", problem), + advisorModelId: this.api.getAdvisorModel?.().id, } satisfies ClineConsultAdvisor) if (this.shouldAutoApproveTool(block.name)) { @@ -2569,6 +2581,7 @@ export class Cline { this.consecutiveMistakeCount = 0 const completeMessage = JSON.stringify({ problem: removeClosingTag("problem", problem), + advisorModelId: this.api.getAdvisorModel?.().id, } satisfies ClineConsultAdvisor) if (this.shouldAutoApproveTool(block.name)) { @@ -2587,6 +2600,18 @@ export class Cline { } } + // Update the last consult_advisor message in case the advisor model changed + const lastMessage = findLast( + this.clineMessages, + (m) => m.ask === "consult_advisor" || m.say === "consult_advisor", + ) + if (lastMessage) { + lastMessage.text = JSON.stringify({ + problem: removeClosingTag("problem", problem), + advisorModelId: this.api.getAdvisorModel?.().id, + } satisfies ClineConsultAdvisor) + } + // now execute the tool this.advisorProblem = problem // await this.say("consult_advisor_request_started") diff --git a/src/core/prompts/advisor.ts b/src/core/prompts/advisor.ts index 267067d6c7..e0e3403e85 100644 --- a/src/core/prompts/advisor.ts +++ b/src/core/prompts/advisor.ts @@ -11,42 +11,12 @@ You will receive: ==== -RESPONSE FORMAT +HOW TO RESPOND -Your responses should generally follow this structure: +After being given the necessary context, you may start by assessing the problem and key challenges, focusing on the most critical aspects that need to be addressed. -1. Problem Analysis -A summary of the context and key challenges, focusing on the most critical aspects that need to be addressed. - -2. Solution Approach -The recommended strategy or solution, broken down into clear, actionable steps. Include rationale for key decisions and potential trade-offs considered. Use specific technical guidance, including code snippets, architecture recommendations, or debugging strategies as needed. Focus on practical, implementable advice the agent can use to apply the solution. +You may then recommend a strategy or solution, broken down into clear, actionable steps. Include rationale for key decisions and potential trade-offs considered. Use specific technical guidance, including code snippets, architecture recommendations, or debugging strategies as needed. Focus on practical, implementable advice the agent can use to apply the solution. ==== -ADVISORY PRINCIPLES - -1. Focus on providing actionable, concrete guidance rather than theoretical discussions. Your advice should enable immediate progress. - -2. Consider both immediate solutions and long-term implications. Guide the agent toward maintainable, scalable solutions while solving the current problem. - -3. Adapt your guidance based on the context. Account for: -- Existing codebase and architecture -- Applied technologies and constraints -- Performance and scalability requirements -- Project conventions and standards - -4. When analyzing problems: -- Start with a systematic evaluation of the issue -- Consider common pitfalls and edge cases -- Look for patterns in error messages or behavior -- Think about interaction between system components - -5. For architectural guidance: -- Recommend established patterns when appropriate -- Consider system boundaries and integration points -- Address scalability and maintenance concerns -- Focus on practical, implementable solutions - -==== - -Remember: Your goal is to provide clear, actionable guidance that helps the agent make immediate progress while following good software development practices. Focus on practical solutions rather than theoretical discussions.` +Remember: Your goal is to provide clear, actionable guidance that helps the agent make progress. Focus on practical solutions rather than theoretical discussions.` diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index de72c18cad..a0fe39e7e1 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -9,6 +9,7 @@ export const SYSTEM_PROMPT = async ( supportsComputerUse: boolean, mcpHub: McpHub, browserSettings: BrowserSettings, + supportsConsultAdvisor: boolean, ) => `You are Cline, a highly skilled software engineer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices. ==== @@ -204,16 +205,20 @@ Usage: server name here resource URI here - +${ + supportsConsultAdvisor + ? ` ## consult_advisor -Description: Request to consult a higher-reasoning advisor model about a problem or question you are facing. This can be used to outline a plan, discuss potential solutions, or resolve errors you are stuck on. The relevant conversation history leading to the problem will also be provided to the advisor for additional context. +Description: Request to consult an advanced-reasoning AI model about a problem or question you are facing. This can be used to resolve errors you are stuck on, or get input from the model to work through a challenge you are facing. The relevant conversation history leading to the problem will also be provided to the advisor for additional context. Parameters: - problem: (required) A string describing the issue, question, or context you want the advisor to address. Usage: Your problem or question here - +` + : "" +} ## ask_followup_question Description: Ask the user a question to gather additional information needed to complete the task. This tool should be used when you encounter ambiguities, need clarification, or require more details to proceed effectively. It allows for interactive problem-solving by enabling direct communication with the user. Use this tool judiciously to maintain a balance between gathering necessary information and avoiding excessive back-and-forth. @@ -823,25 +828,18 @@ You have access to two tools for working with files: **write_to_file** and **rep 3. For major overhauls or initial file creation, rely on write_to_file. 4. Once the file has been edited with either write_to_file or replace_in_file, the system will provide you with the final state of the modified file. Use this updated content as the reference point for any subsequent SEARCH/REPLACE operations, since it reflects any auto-formatting or user-applied changes. -By thoughtfully selecting between write_to_file and replace_in_file, you can make your file editing process smoother, safer, and more efficient. +By thoughtfully selecting between write_to_file and replace_in_file, you can make your file editing process smoother, safer, and more efficient.${ + supportsConsultAdvisor + ? ` ==== CONSULTING THE ADVISOR MODEL -You can use the consult_advisor tool to get higher-level reasoning or suggestions from an advisor model. The advisor is a more powerful AI model that can provide strategic guidance and help solve complex problems. The conversation history that led to the current situation is automatically passed to the advisor, allowing it to provide contextually relevant guidance based on the full picture of the task at hand. +You can use the consult_advisor tool to get suggestions from an advisor model, a powerful AI model that can provide strategic guidance and help solve complex problems. The conversation history that led to the current situation is automatically passed to the advisor, allowing it to provide contextually relevant guidance based on the full picture of the task at hand. # When to Use the Advisor -1. Architecting Complex Tasks -- Before starting implementation of large features or systems -- When planning new applications or major refactors -- To break down complex requirements into actionable steps -- To identify potential technical challenges early -- To evaluate different technical approaches and their tradeoffs -- When the solution requires careful consideration of multiple system components - -2. Resolving Challenging Bugs - When stuck on persistent bugs that you cannot resolve - If you've tried multiple approaches without success - When facing complex type errors or package incompatibilities @@ -850,13 +848,13 @@ You can use the consult_advisor tool to get higher-level reasoning or suggestion # How to Use Effectively -1. Provide Clear Context +## Provide Clear Context - Explain the current situation and challenge - Include relevant code snippets or error messages - Describe what you've already tried - Specify what kind of guidance you're seeking -2. Ask Specific Questions +## Ask Specific Questions - Instead of "Why isn't this working?" - Better: "I'm encountering this specific type error when integrating these packages, here's what I've tried..." @@ -885,22 +883,14 @@ The error persists despite these attempts. Could this be due to version mismatch # Benefits of Using the Advisor -1. Strategic Guidance -- Get high-level architectural direction -- Identify potential pitfalls early -- Make informed technical decisions -- Consider long-term implications - -2. Problem Resolution - Break through debugging roadblocks - Get fresh perspectives on complex issues - Understand root causes of persistent bugs - Solve challenging technical issues -Remember: While you should attempt to solve problems with your own reasoning first, the advisor is a powerful resource available when you're either planning complex systems or truly stuck on a bug. Don't hesitate to consult it when: -- The scope of the task requires careful architectural planning -- You've hit a persistent roadblock that you cannot resolve -- You need deeper insight into complex system interactions +Remember: While you should attempt to solve problems with your own reasoning first, the advisor is a powerful resource available when you're stuck on a bug. Don't hesitate to consult it when you've hit a persistent roadblock that you cannot resolve.` + : "" +} ==== @@ -908,7 +898,9 @@ CAPABILITIES - You have access to tools that let you execute CLI commands on the user's computer, list files, view source code definitions, regex search${ supportsComputerUse ? ", use the browser" : "" -}, read and edit files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more. +}, read and edit files${ + supportsConsultAdvisor ? ", consult an advisor" : "" +}, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more. - When the user initially gives you a task, a recursive list of all filepaths in the current working directory ('${cwd.toPosix()}') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current working directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop. - You can use search_files to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring. - You can use the list_code_definition_names tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task. @@ -918,7 +910,11 @@ CAPABILITIES ? "\n- You can use the browser_action tool to interact with websites (including html files and locally running development servers) through a Puppeteer-controlled browser when you feel it is necessary in accomplishing the user's task. This tool is particularly useful for web development tasks as it allows you to launch a browser, navigate to pages, interact with elements through clicks and keyboard input, and capture the results through screenshots and console logs. This tool may be useful at key stages of web development tasks-such as after implementing new features, making substantial changes, when troubleshooting issues, or to verify the result of your work. You can analyze the provided screenshots to ensure correct rendering or identify errors, and review console logs for runtime issues.\n - For example, if asked to add a component to a react website, you might create the necessary files, use execute_command to run the site locally, then use browser_action to launch the browser, navigate to the local server, and verify the component renders & functions correctly before closing the browser." : "" } -- You have access to MCP servers that may provide additional tools and resources. Each server may provide different capabilities that you can use to accomplish tasks more effectively. +- You have access to MCP servers that may provide additional tools and resources. Each server may provide different capabilities that you can use to accomplish tasks more effectively.${ + supportsConsultAdvisor + ? "\n- When you hit a roadblock, such as an error you've attempted to resolve several times without success, you can use the consult_advisor tool to get suggestions from an advanced-reasoning AI model. The conversation history that led to the current situation is automatically passed to the advisor, allowing it to provide contextually relevant guidance based on the full picture of the task at hand." + : "" +} ==== diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 53a24acc0f..9474d2866d 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -565,6 +565,11 @@ export class ClineProvider implements vscode.WebviewViewProvider { case "cancelTask": this.cancelTask() break + case "openAdvisorModelSettings": + this.postMessageToWebview({ + type: "openAdvisorModelSettings", + }) + break case "openMcpSettings": { const mcpSettingsFilePath = await this.mcpHub?.getMcpSettingsFilePath() if (mcpSettingsFilePath) { diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 6f1ce9e123..56ed6e0a1f 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -21,6 +21,7 @@ export interface ExtensionMessage { | "openRouterModels" | "mcpServers" | "relinquishControl" + | "openAdvisorModelSettings" text?: string action?: "chatButtonClicked" | "mcpButtonClicked" | "settingsButtonClicked" | "historyButtonClicked" | "didBecomeVisible" invoke?: "sendMessage" | "primaryButtonClick" | "secondaryButtonClick" @@ -144,6 +145,7 @@ export interface ClineAskUseMcpServer { export interface ClineConsultAdvisor { problem: string + advisorModelId?: string } export interface ClineApiReqInfo { diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 4fa90b3e36..461e9b57ee 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -31,6 +31,7 @@ export interface WebviewMessage { | "checkpointDiff" | "checkpointRestore" | "taskCompletionViewChanges" + | "openAdvisorModelSettings" // | "relaunchChromeDebugMode" text?: string askResponse?: ClineAskResponse diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx index f06453aca9..954ca8dd8d 100644 --- a/webview-ui/src/App.tsx +++ b/webview-ui/src/App.tsx @@ -15,6 +15,7 @@ const AppContent = () => { const [showHistory, setShowHistory] = useState(false) const [showMcp, setShowMcp] = useState(false) const [showAnnouncement, setShowAnnouncement] = useState(false) + const [showAdvisorModelSettings, setShowAdvisorModelSettings] = useState(false) const handleMessage = useCallback((e: MessageEvent) => { const message: ExtensionMessage = e.data @@ -23,26 +24,36 @@ const AppContent = () => { switch (message.action!) { case "settingsButtonClicked": setShowSettings(true) + setShowAdvisorModelSettings(false) setShowHistory(false) setShowMcp(false) break case "historyButtonClicked": setShowSettings(false) + setShowAdvisorModelSettings(false) setShowHistory(true) setShowMcp(false) break case "mcpButtonClicked": setShowSettings(false) + setShowAdvisorModelSettings(false) setShowHistory(false) setShowMcp(true) break case "chatButtonClicked": setShowSettings(false) + setShowAdvisorModelSettings(false) setShowHistory(false) setShowMcp(false) break } break + case "openAdvisorModelSettings": + setShowSettings(true) + setShowAdvisorModelSettings(true) + setShowHistory(false) + setShowMcp(false) + break } }, []) @@ -65,7 +76,9 @@ const AppContent = () => { ) : ( <> - {showSettings && setShowSettings(false)} />} + {showSettings && ( + setShowSettings(false)} showAdvisorModelSettings={showAdvisorModelSettings} /> + )} {showHistory && setShowHistory(false)} />} {showMcp && setShowMcp(false)} />} {/* Do not conditionally load ChatView, it's expensive and there's state we don't want to lose (user input, disableInput, askResponse promise, etc.) */} diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 6cb2df3bb8..7375472c92 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -1,4 +1,4 @@ -import { VSCodeBadge, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react" +import { VSCodeBadge, VSCodeLink, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react" import deepEqual from "fast-deep-equal" import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from "react" import { useEvent, useSize } from "react-use" @@ -25,6 +25,7 @@ import Thumbnails from "../common/Thumbnails" import McpResourceRow from "../mcp/McpResourceRow" import McpToolRow from "../mcp/McpToolRow" import { highlightMentions } from "./TaskHeader" +import { normalizeApiConfiguration } from "../settings/ApiOptions" const ChatRowContainer = styled.div` padding: 10px 6px 10px 15px; @@ -102,7 +103,7 @@ const ChatRow = memo( export default ChatRow export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifiedMessage, isLast }: ChatRowContentProps) => { - const { mcpServers } = useExtensionState() + const { mcpServers, apiConfiguration } = useExtensionState() const [seeNewChangesDisabled, setSeeNewChangesDisabled] = useState(false) @@ -144,6 +145,10 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi useEvent("message", handleMessage) + const { selectedAdvisorModelId } = useMemo(() => { + return normalizeApiConfiguration(apiConfiguration) + }, [apiConfiguration]) + const [icon, title] = useMemo(() => { switch (type) { case "error": @@ -221,19 +226,19 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi ] case "consult_advisor": // const consultAdvisor = JSON.parse(message.text || "{}") as ClineConsultAdvisor + const consultAdvisor = JSON.parse(message.text || "{}") as ClineConsultAdvisor return [ , - {message.type === "ask" ? ( - <>Cline wants to consult the Advisor model about: - ) : ( - <>Cline consulted the Advisor model about: - )} + <> + Cline wants to consult{" "} + {{isLast ? selectedAdvisorModelId : consultAdvisor.advisorModelId} || "Advisor model"}: + , ] case "completion_result": @@ -327,6 +332,8 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi isMcpServerResponding, message.text, message.type, + selectedAdvisorModelId, + isLast, ]) const headerStyle: React.CSSProperties = { @@ -757,6 +764,20 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi }}> {consultAdvisor.problem}
+ +
+ You can change the Advisor model Cline consults with{" "} + vscode.postMessage({ type: "openAdvisorModelSettings" })}> + in Settings. + +
) } @@ -881,22 +902,28 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi ) case "advisor_response": return ( -
+
- Response + Advisor Response
- +
) case "user_feedback": diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 213fcdf6d5..2588bd10b9 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -48,6 +48,7 @@ interface ApiOptionsProps { apiErrorMessage?: string modelIdErrorMessage?: string advisorModelIdErrorMessage?: string + showAdvisorModelSettings?: boolean } const TabPanel = ({ children, isSelected }: { children: React.ReactNode; isSelected: boolean }) => { @@ -86,14 +87,20 @@ const TabButton = ({ ) } -const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, advisorModelIdErrorMessage }: ApiOptionsProps) => { +const ApiOptions = ({ + showModelOptions, + apiErrorMessage, + modelIdErrorMessage, + advisorModelIdErrorMessage, + showAdvisorModelSettings, +}: ApiOptionsProps) => { const { apiConfiguration, setApiConfiguration, uriScheme } = useExtensionState() const [ollamaModels, setOllamaModels] = useState([]) const [lmStudioModels, setLmStudioModels] = useState([]) const [anthropicBaseUrlSelected, setAnthropicBaseUrlSelected] = useState(!!apiConfiguration?.anthropicBaseUrl) const [azureApiVersionSelected, setAzureApiVersionSelected] = useState(!!apiConfiguration?.azureApiVersion) const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false) - const [selectedTab, setSelectedTab] = useState("base") + const [selectedTab, setSelectedTab] = useState(showAdvisorModelSettings ? "advisor" : "base") const handleInputChange = (field: keyof ApiConfiguration) => (event: any) => { setApiConfiguration({ @@ -846,8 +853,8 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, ad marginBottom: "10px", color: "var(--vscode-foreground)", }}> - The Cline model can consult this smarter, more powerful model for help on planning out a task, fixing - a hard bug, and other complex problems. + The Cline model can consult this more powerful model for advice when running into roadblocks, such as + an error it cannot resolve.

{selectedProvider === "anthropic" && (
diff --git a/webview-ui/src/components/settings/OpenRouterModelPicker.tsx b/webview-ui/src/components/settings/OpenRouterModelPicker.tsx index b8cb4992e7..f687e4171b 100644 --- a/webview-ui/src/components/settings/OpenRouterModelPicker.tsx +++ b/webview-ui/src/components/settings/OpenRouterModelPicker.tsx @@ -238,17 +238,31 @@ const OpenRouterModelPicker: React.FC = ({ modelType marginTop: 0, color: "var(--vscode-descriptionForeground)", }}> - The extension automatically fetches the latest list of models available on{" "} - - OpenRouter. - - If you're unsure which model to choose, Cline works best with{" "} - handleModelChange("anthropic/claude-3.5-sonnet:beta")}> - anthropic/claude-3.5-sonnet:beta. - - You can also try searching "free" for no-cost options currently available. + {modelType === "base" ? ( + <> + The extension automatically fetches the latest list of models available on{" "} + + OpenRouter. + + If you're unsure which model to choose, Cline works best with{" "} + handleModelChange("anthropic/claude-3.5-sonnet:beta")}> + anthropic/claude-3.5-sonnet:beta. + + You can also try searching "free" for no-cost options currently available. + + ) : ( + <> + It's recommended using a higher-reasoning model such as{" "} + handleModelChange("openai/o1-preview")}> + openai/o1-preview + + for the best results. + + )}

)}
diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index 91e9d136c8..921f311c56 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -8,10 +8,11 @@ import ApiOptions from "./ApiOptions" const IS_DEV = false // FIXME: use flags when packaging type SettingsViewProps = { + showAdvisorModelSettings: boolean onDone: () => void } -const SettingsView = ({ onDone }: SettingsViewProps) => { +const SettingsView = ({ showAdvisorModelSettings, onDone }: SettingsViewProps) => { const { apiConfiguration, version, customInstructions, setCustomInstructions, openRouterModels } = useExtensionState() const [apiErrorMessage, setApiErrorMessage] = useState(undefined) const [modelIdErrorMessage, setModelIdErrorMessage] = useState(undefined) @@ -93,6 +94,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
Date: Sat, 18 Jan 2025 21:47:27 -0800 Subject: [PATCH 11/49] Add chat settings --- src/core/Cline.ts | 12 + src/core/prompts/system.ts | 2 + src/core/webview/ClineProvider.ts | 48 +- src/shared/ChatSettings.ts | 7 + src/shared/ExtensionMessage.ts | 2 + src/shared/WebviewMessage.ts | 3 + .../src/components/chat/ChatTextArea.tsx | 493 ++++++++++++------ webview-ui/src/components/chat/ChatView.tsx | 4 +- .../src/context/ExtensionStateContext.tsx | 2 + 9 files changed, 403 insertions(+), 170 deletions(-) create mode 100644 src/shared/ChatSettings.ts diff --git a/src/core/Cline.ts b/src/core/Cline.ts index bb275aba31..abdcb7a253 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -59,6 +59,7 @@ import CheckpointTracker from "../integrations/checkpoints/CheckpointTracker" import getFolderSize from "get-folder-size" import { BrowserSettings } from "../shared/BrowserSettings" import { ADVISOR_SYSTEM_PROMPT } from "./prompts/advisor" +import { ChatSettings } from "../shared/ChatSettings" const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution @@ -77,6 +78,7 @@ export class Cline { customInstructions?: string autoApprovalSettings: AutoApprovalSettings private browserSettings: BrowserSettings + private chatSettings: ChatSettings apiConversationHistory: Anthropic.MessageParam[] = [] clineMessages: ClineMessage[] = [] private askResponse?: ClineAskResponse @@ -97,6 +99,7 @@ export class Cline { private advisorProblem?: string // streaming + isWaitingForFirstChunk = false isStreaming = false private currentStreamingContentIndex = 0 private assistantMessageContent: AssistantMessageContent[] = [] @@ -114,6 +117,7 @@ export class Cline { apiConfiguration: ApiConfiguration, autoApprovalSettings: AutoApprovalSettings, browserSettings: BrowserSettings, + chatSettings: ChatSettings, customInstructions?: string, task?: string, images?: string[], @@ -128,6 +132,7 @@ export class Cline { this.customInstructions = customInstructions this.autoApprovalSettings = autoApprovalSettings this.browserSettings = browserSettings + this.chatSettings = chatSettings if (historyItem) { this.taskId = historyItem.id this.conversationHistoryDeletedRange = historyItem.conversationHistoryDeletedRange @@ -145,6 +150,10 @@ export class Cline { this.browserSession.browserSettings = browserSettings } + updateChatSettings(chatSettings: ChatSettings) { + this.chatSettings = chatSettings + } + // Storing task to disk for history private async ensureTaskDirectoryExists(): Promise { @@ -1202,6 +1211,7 @@ export class Cline { this.api.getModel().info.supportsComputerUse ?? false, mcpHub, this.browserSettings, + this.chatSettings, supportsConsultAdvisor, ) let settingsCustomInstructions = this.customInstructions?.trim() @@ -1322,8 +1332,10 @@ export class Cline { try { // awaiting first chunk to see if it will throw an error + this.isWaitingForFirstChunk = true const firstChunk = await iterator.next() yield firstChunk.value + this.isWaitingForFirstChunk = false } catch (error) { if (!this.didAutomaticallyRetryFailedApiRequest) { console.log("first chunk failed, waiting 1 second before retrying") diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index a0fe39e7e1..e2865a4e25 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -3,12 +3,14 @@ import os from "os" import osName from "os-name" import { McpHub } from "../../services/mcp/McpHub" import { BrowserSettings } from "../../shared/BrowserSettings" +import { ChatSettings } from "../../shared/ChatSettings" export const SYSTEM_PROMPT = async ( cwd: string, supportsComputerUse: boolean, mcpHub: McpHub, browserSettings: BrowserSettings, + chatSettings: ChatSettings, supportsConsultAdvisor: boolean, ) => `You are Cline, a highly skilled software engineer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices. diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 9474d2866d..903d1a4745 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -24,6 +24,7 @@ import { getNonce } from "./getNonce" import { getUri } from "./getUri" import { AutoApprovalSettings, DEFAULT_AUTO_APPROVAL_SETTINGS } from "../../shared/AutoApprovalSettings" import { BrowserSettings, DEFAULT_BROWSER_SETTINGS } from "../../shared/BrowserSettings" +import { ChatSettings, DEFAULT_CHAT_SETTINGS } from "../../shared/ChatSettings" /* https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts @@ -67,6 +68,7 @@ type GlobalStateKey = | "openRouterAdvisorModelInfo" | "autoApprovalSettings" | "browserSettings" + | "chatSettings" export const GlobalFileNames = { apiConversationHistory: "api_conversation_history.json", @@ -216,18 +218,30 @@ export class ClineProvider implements vscode.WebviewViewProvider { async initClineWithTask(task?: string, images?: string[]) { await this.clearTask() // ensures that an exising task doesn't exist before starting a new one, although this shouldn't be possible since user must clear task before starting a new one - const { apiConfiguration, customInstructions, autoApprovalSettings, browserSettings } = await this.getState() - this.cline = new Cline(this, apiConfiguration, autoApprovalSettings, browserSettings, customInstructions, task, images) - } - - async initClineWithHistoryItem(historyItem: HistoryItem) { - await this.clearTask() - const { apiConfiguration, customInstructions, autoApprovalSettings, browserSettings } = await this.getState() + const { apiConfiguration, customInstructions, autoApprovalSettings, browserSettings, chatSettings } = + await this.getState() this.cline = new Cline( this, apiConfiguration, autoApprovalSettings, browserSettings, + chatSettings, + customInstructions, + task, + images, + ) + } + + async initClineWithHistoryItem(historyItem: HistoryItem) { + await this.clearTask() + const { apiConfiguration, customInstructions, autoApprovalSettings, browserSettings, chatSettings } = + await this.getState() + this.cline = new Cline( + this, + apiConfiguration, + autoApprovalSettings, + browserSettings, + chatSettings, customInstructions, undefined, undefined, @@ -467,6 +481,15 @@ export class ClineProvider implements vscode.WebviewViewProvider { await this.postStateToWebview() } break + case "chatSettings": + if (message.chatSettings) { + await this.updateGlobalState("chatSettings", message.chatSettings) + if (this.cline) { + this.cline.updateChatSettings(message.chatSettings) + } + await this.postStateToWebview() + } + break // case "relaunchChromeDebugMode": // if (this.cline) { // this.cline.browserSession.relaunchChromeDebugMode() @@ -603,7 +626,11 @@ export class ClineProvider implements vscode.WebviewViewProvider { console.error("Failed to abort task", error) } await pWaitFor( - () => this.cline === undefined || this.cline.isStreaming === false || this.cline.didFinishAbortingStream, + () => + this.cline === undefined || + this.cline.isStreaming === false || + this.cline.didFinishAbortingStream || + this.cline.isWaitingForFirstChunk, // if only first chunk is processed, then there's no need to wait for graceful abort (closes edits, browser, etc) { timeout: 3_000, }, @@ -956,6 +983,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { taskHistory, autoApprovalSettings, browserSettings, + chatSettings, } = await this.getState() return { version: this.context.extension?.packageJSON?.version ?? "", @@ -969,6 +997,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { shouldShowAnnouncement: lastShownAnnouncementId !== this.latestAnnouncementId, autoApprovalSettings, browserSettings, + chatSettings, } } @@ -1059,6 +1088,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { taskHistory, autoApprovalSettings, browserSettings, + chatSettings, ] = await Promise.all([ this.getGlobalState("apiProvider") as Promise, this.getGlobalState("apiModelId") as Promise, @@ -1094,6 +1124,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { this.getGlobalState("taskHistory") as Promise, this.getGlobalState("autoApprovalSettings") as Promise, this.getGlobalState("browserSettings") as Promise, + this.getGlobalState("chatSettings") as Promise, ]) let apiProvider: ApiProvider @@ -1147,6 +1178,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { taskHistory, autoApprovalSettings: autoApprovalSettings || DEFAULT_AUTO_APPROVAL_SETTINGS, // default value can be 0 or empty string browserSettings: browserSettings || DEFAULT_BROWSER_SETTINGS, + chatSettings: chatSettings || DEFAULT_CHAT_SETTINGS, } } diff --git a/src/shared/ChatSettings.ts b/src/shared/ChatSettings.ts new file mode 100644 index 0000000000..5d0e48c264 --- /dev/null +++ b/src/shared/ChatSettings.ts @@ -0,0 +1,7 @@ +export interface ChatSettings { + mode: "code" | "chat" +} + +export const DEFAULT_CHAT_SETTINGS: ChatSettings = { + mode: "code", +} diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 56ed6e0a1f..d5388d0467 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -3,6 +3,7 @@ import { ApiConfiguration, ModelInfo } from "./api" import { AutoApprovalSettings } from "./AutoApprovalSettings" import { BrowserSettings } from "./BrowserSettings" +import { ChatSettings } from "./ChatSettings" import { HistoryItem } from "./HistoryItem" import { McpServer } from "./mcp" @@ -47,6 +48,7 @@ export interface ExtensionState { shouldShowAnnouncement: boolean autoApprovalSettings: AutoApprovalSettings browserSettings: BrowserSettings + chatSettings: ChatSettings } export interface ClineMessage { diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 461e9b57ee..b18738316b 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -1,6 +1,7 @@ import { ApiConfiguration } from "./api" import { AutoApprovalSettings } from "./AutoApprovalSettings" import { BrowserSettings } from "./BrowserSettings" +import { ChatSettings } from "./ChatSettings" export interface WebviewMessage { type: @@ -28,6 +29,7 @@ export interface WebviewMessage { | "restartMcpServer" | "autoApprovalSettings" | "browserSettings" + | "chatSettings" | "checkpointDiff" | "checkpointRestore" | "taskCompletionViewChanges" @@ -41,6 +43,7 @@ export interface WebviewMessage { number?: number autoApprovalSettings?: AutoApprovalSettings browserSettings?: BrowserSettings + chatSettings?: ChatSettings } export type ClineAskResponse = "yesButtonClicked" | "noButtonClicked" | "messageResponse" diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index 5cac05b399..2d4833a350 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -12,6 +12,10 @@ import { import { MAX_IMAGES_PER_MESSAGE } from "./ChatView" import ContextMenu from "./ContextMenu" import Thumbnails from "../common/Thumbnails" +import { VSCodeButton } from "@vscode/webview-ui-toolkit/react" +import styled from "styled-components" +import { useWindowSize } from "react-use" +import { vscode } from "../../utils/vscode" interface ChatTextAreaProps { inputValue: string @@ -26,6 +30,74 @@ interface ChatTextAreaProps { onHeightChange?: (height: number) => void } +const SwitchOption = styled.div<{ isActive: boolean }>` + padding: 2px 8px; + color: ${(props) => (props.isActive ? "var(--vscode-badge-foreground)" : "var(--vscode-input-foreground)")}; + z-index: 1; + transition: color 0.2s ease; + font-size: 12px; + width: 50%; + text-align: center; + + &:hover { + background-color: ${(props) => (!props.isActive ? "var(--vscode-toolbar-hoverBackground)" : "transparent")}; + } +` + +const SwitchContainer = styled.div<{ disabled: boolean }>` + display: flex; + align-items: center; + background-color: var(--vscode-editor-background); + border: 1px solid var(--vscode-input-border); + border-radius: 12px; + overflow: hidden; + position: absolute; + right: 15px; + cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")}; + opacity: ${(props) => (props.disabled ? 0.5 : 1)}; + transform: scale(0.85); + transform-origin: right center; + flex-shrink: 0; +` + +const Slider = styled.div<{ isChat: boolean }>` + position: absolute; + height: 100%; + width: 50%; + background-color: var(--vscode-badge-background); + transition: transform 0.2s ease; + transform: translateX(${(props) => (props.isChat ? "100%" : "0%")}); +` + +const ButtonContainer = styled.div` + display: flex; + align-items: center; + gap: 3px; + font-size: 10px; + white-space: nowrap; +` + +const ACTUAL_SWITCH_WIDTH = 90 +const SWITCH_WIDTH = ACTUAL_SWITCH_WIDTH * 0.85 // Account for the 0.85 scale transform +const CONTEXT_BUTTON_WIDTH = 60 +const IMAGES_BUTTON_WIDTH = 80 +const CONTAINER_PADDING = 30 // 15px left + 15px right +const TOTAL_WIDTH = SWITCH_WIDTH + 4 + CONTEXT_BUTTON_WIDTH + IMAGES_BUTTON_WIDTH + CONTAINER_PADDING + +const ControlsContainer = styled.div` + display: flex; + align-items: center; + margin-top: -3px; + position: relative; + padding: 0px 15px 5px 15px; +` + +const ButtonGroup = styled.div` + display: flex; + align-items: center; + gap: 4px; +` + const ChatTextArea = forwardRef( ( { @@ -42,7 +114,7 @@ const ChatTextArea = forwardRef( }, ref, ) => { - const { filePaths } = useExtensionState() + const { filePaths, chatSettings } = useExtensionState() const [isTextAreaFocused, setIsTextAreaFocused] = useState(false) const [thumbnailsHeight, setThumbnailsHeight] = useState(0) const [textAreaBaseHeight, setTextAreaBaseHeight] = useState(undefined) @@ -57,6 +129,8 @@ const ChatTextArea = forwardRef( const [justDeletedSpaceAfterMention, setJustDeletedSpaceAfterMention] = useState(false) const [intendedCursorPosition, setIntendedCursorPosition] = useState(null) const contextMenuContainerRef = useRef(null) + const { width: windowWidth } = useWindowSize() + const showButtonText = windowWidth - CONTAINER_PADDING > TOTAL_WIDTH - CONTAINER_PADDING const queryItems = useMemo(() => { return [ @@ -406,181 +480,280 @@ const ChatTextArea = forwardRef( [updateCursorPosition], ) + const onModeToggle = useCallback(() => { + if (textAreaDisabled) return + const newMode = chatSettings.mode === "chat" ? "code" : "chat" + vscode.postMessage({ + type: "chatSettings", + chatSettings: { + mode: newMode, + }, + }) + }, [chatSettings.mode, textAreaDisabled]) + + const handleContextButtonClick = useCallback(() => { + if (textAreaDisabled) return + + // Focus the textarea first + textAreaRef.current?.focus() + + // If input is empty, just insert @ + if (!inputValue.trim()) { + const event = { + target: { + value: "@", + selectionStart: 1, + }, + } as React.ChangeEvent + handleInputChange(event) + updateHighlights() + return + } + + // If input ends with space or is empty, just append @ + if (inputValue.endsWith(" ")) { + const event = { + target: { + value: inputValue + "@", + selectionStart: inputValue.length + 1, + }, + } as React.ChangeEvent + handleInputChange(event) + updateHighlights() + return + } + + // Otherwise add space then @ + const event = { + target: { + value: inputValue + " @", + selectionStart: inputValue.length + 2, + }, + } as React.ChangeEvent + handleInputChange(event) + updateHighlights() + }, [inputValue, textAreaDisabled, handleInputChange, updateHighlights]) + return ( -
- {showContextMenu && ( -
- -
- )} - {!isTextAreaFocused && ( -
- )} -
- { - if (typeof ref === "function") { - ref(el) - } else if (ref) { - ref.current = el - } - textAreaRef.current = el - }} - value={inputValue} - disabled={textAreaDisabled} - onChange={(e) => { - handleInputChange(e) - updateHighlights() - }} - onKeyDown={handleKeyDown} - onKeyUp={handleKeyUp} - onFocus={() => setIsTextAreaFocused(true)} - onBlur={handleBlur} - onPaste={handlePaste} - onSelect={updateCursorPosition} - onMouseUp={updateCursorPosition} - onHeightChange={(height) => { - if (textAreaBaseHeight === undefined || height < textAreaBaseHeight) { - setTextAreaBaseHeight(height) - } - onHeightChange?.(height) - }} - placeholder={placeholderText} - maxRows={10} - autoFocus={true} - style={{ - width: "100%", - boxSizing: "border-box", - backgroundColor: "transparent", - color: "var(--vscode-input-foreground)", - //border: "1px solid var(--vscode-input-border)", - borderRadius: 2, - fontFamily: "var(--vscode-font-family)", - fontSize: "var(--vscode-editor-font-size)", - lineHeight: "var(--vscode-editor-line-height)", - resize: "none", - overflowX: "hidden", - overflowY: "scroll", - scrollbarWidth: "none", - // Since we have maxRows, when text is long enough it starts to overflow the bottom padding, appearing behind the thumbnails. To fix this, we use a transparent border to push the text up instead. (https://stackoverflow.com/questions/42631947/maintaining-a-padding-inside-of-text-area/52538410#52538410) - // borderTop: "9px solid transparent", - borderLeft: 0, - borderRight: 0, - borderTop: 0, - borderBottom: `${thumbnailsHeight + 6}px solid transparent`, - borderColor: "transparent", - // borderRight: "54px solid transparent", - // borderLeft: "9px solid transparent", // NOTE: react-textarea-autosize doesn't calculate correct height when using borderLeft/borderRight so we need to use horizontal padding instead - // Instead of using boxShadow, we use a div with a border to better replicate the behavior when the textarea is focused - // boxShadow: "0px 0px 0px 1px var(--vscode-input-border)", - padding: "9px 49px 3px 9px", - cursor: textAreaDisabled ? "not-allowed" : undefined, - flex: 1, - zIndex: 1, - }} - onScroll={() => updateHighlights()} - /> - {selectedImages.length > 0 && ( - - )} +
+ {showContextMenu && ( +
+ +
+ )} + {!isTextAreaFocused && ( +
+ )} +
+ { + if (typeof ref === "function") { + ref(el) + } else if (ref) { + ref.current = el + } + textAreaRef.current = el + }} + value={inputValue} + disabled={textAreaDisabled} + onChange={(e) => { + handleInputChange(e) + updateHighlights() + }} + onKeyDown={handleKeyDown} + onKeyUp={handleKeyUp} + onFocus={() => setIsTextAreaFocused(true)} + onBlur={handleBlur} + onPaste={handlePaste} + onSelect={updateCursorPosition} + onMouseUp={updateCursorPosition} + onHeightChange={(height) => { + if (textAreaBaseHeight === undefined || height < textAreaBaseHeight) { + setTextAreaBaseHeight(height) + } + onHeightChange?.(height) + }} + placeholder={placeholderText} + maxRows={10} + autoFocus={true} + style={{ + width: "100%", + boxSizing: "border-box", + backgroundColor: "transparent", + color: "var(--vscode-input-foreground)", + //border: "1px solid var(--vscode-input-border)", + borderRadius: 2, + fontFamily: "var(--vscode-font-family)", + fontSize: "var(--vscode-editor-font-size)", + lineHeight: "var(--vscode-editor-line-height)", + resize: "none", + overflowX: "hidden", + overflowY: "scroll", + scrollbarWidth: "none", + // Since we have maxRows, when text is long enough it starts to overflow the bottom padding, appearing behind the thumbnails. To fix this, we use a transparent border to push the text up instead. (https://stackoverflow.com/questions/42631947/maintaining-a-padding-inside-of-text-area/52538410#52538410) + // borderTop: "9px solid transparent", + borderLeft: 0, + borderRight: 0, + borderTop: 0, + borderBottom: `${thumbnailsHeight + 6}px solid transparent`, + borderColor: "transparent", + // borderRight: "54px solid transparent", + // borderLeft: "9px solid transparent", // NOTE: react-textarea-autosize doesn't calculate correct height when using borderLeft/borderRight so we need to use horizontal padding instead + // Instead of using boxShadow, we use a div with a border to better replicate the behavior when the textarea is focused + // boxShadow: "0px 0px 0px 1px var(--vscode-input-border)", + padding: "9px 28px 3px 9px", + cursor: textAreaDisabled ? "not-allowed" : undefined, + flex: 1, + zIndex: 1, + }} + onScroll={() => updateHighlights()} + /> + {selectedImages.length > 0 && ( + + )}
+ {/*
{ + if (!shouldDisableImages) { + onSelectImages() + } + }} + style={{ + marginRight: 5.5, + fontSize: 16.5, + }} + /> */} +
{ + if (!textAreaDisabled) { + onSend() + } + }} + style={{ fontSize: 15 }}>
+
+
+
+ + + + + + @ + {showButtonText && Context} + + + + { if (!shouldDisableImages) { onSelectImages() } }} style={{ - marginRight: 5.5, - fontSize: 16.5, - }} - /> -
{ - if (!textAreaDisabled) { - onSend() - } - }} - style={{ fontSize: 15 }}>
-
-
+ padding: "0px 0px", + height: "20px", + opacity: shouldDisableImages ? 0.5 : 1, + cursor: shouldDisableImages ? "not-allowed" : undefined, + }}> + + + {showButtonText && Add images} + + + + + + + Code + Chat + +
) }, diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 70e879f497..c1894ba03c 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -20,11 +20,11 @@ import { vscode } from "../../utils/vscode" import HistoryPreview from "../history/HistoryPreview" import { normalizeApiConfiguration } from "../settings/ApiOptions" import Announcement from "./Announcement" +import AutoApproveMenu from "./AutoApproveMenu" import BrowserSessionRow from "./BrowserSessionRow" import ChatRow from "./ChatRow" import ChatTextArea from "./ChatTextArea" import TaskHeader from "./TaskHeader" -import AutoApproveMenu from "./AutoApproveMenu" interface ChatViewProps { isHidden: boolean @@ -670,7 +670,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie useEvent("wheel", handleWheel, window, { passive: true }) // passive improves scrolling performance const placeholderText = useMemo(() => { - const text = task ? "Type a message (@ to add context)..." : "Type your task here (@ to add context)..." + const text = task ? "Type a message..." : "Type your task here..." return text }, [task]) diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index a0363209c3..425b35db88 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -15,6 +15,7 @@ import { McpServer } from "../../../src/shared/mcp" import { convertTextMateToHljs } from "../utils/textMateToHljs" import { vscode } from "../utils/vscode" import { DEFAULT_BROWSER_SETTINGS } from "../../../src/shared/BrowserSettings" +import { DEFAULT_CHAT_SETTINGS } from "../../../src/shared/ChatSettings" interface ExtensionStateContextType extends ExtensionState { didHydrateState: boolean @@ -40,6 +41,7 @@ export const ExtensionStateContextProvider: React.FC<{ shouldShowAnnouncement: false, autoApprovalSettings: DEFAULT_AUTO_APPROVAL_SETTINGS, browserSettings: DEFAULT_BROWSER_SETTINGS, + chatSettings: DEFAULT_CHAT_SETTINGS, }) const [didHydrateState, setDidHydrateState] = useState(false) const [showWelcome, setShowWelcome] = useState(false) From 771332ca3ed08e8207eb201a766454622c104d0f Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Sun, 19 Jan 2025 11:28:11 -0800 Subject: [PATCH 12/49] Add respond_to_inquiry --- src/api/providers/anthropic.ts | 14 + src/api/providers/openrouter.ts | 5 + src/core/Cline.ts | 198 +++++--- src/core/assistant-message/index.ts | 2 + src/core/prompts/chat.ts | 427 ++++++++++++++++++ src/core/prompts/system.ts | 2 - src/core/webview/ClineProvider.ts | 1 + src/shared/ExtensionMessage.ts | 2 + webview-ui/src/components/chat/ChatRow.tsx | 20 +- .../src/components/chat/ChatTextArea.tsx | 4 + webview-ui/src/components/chat/ChatView.tsx | 8 + 11 files changed, 627 insertions(+), 56 deletions(-) create mode 100644 src/core/prompts/chat.ts diff --git a/src/api/providers/anthropic.ts b/src/api/providers/anthropic.ts index bd141b1f57..c84cbf0921 100644 --- a/src/api/providers/anthropic.ts +++ b/src/api/providers/anthropic.ts @@ -34,6 +34,20 @@ export class AnthropicHandler implements ApiHandler { case "claude-3-5-haiku-20241022": case "claude-3-opus-20240229": case "claude-3-haiku-20240307": { + // don't use prompt caching for advisor model requests + if (modelType === "advisor") { + stream = (await this.client.messages.create({ + model: modelId, + max_tokens: model.info.maxTokens || 8192, + temperature: 0, + system: [{ text: systemPrompt, type: "text" }], + messages, + // tools, + // tool_choice: { type: "auto" }, + stream: true, + })) as any + break + } /* The latest message will be the new user message, one before will be the assistant message from a previous request, and the user message before that will be a previously cached user message. So we need to mark the latest user message as ephemeral to cache it for the next request, and mark the second to last user message as ephemeral to let the server know the last message to retrieve from the cache for the current request.. */ diff --git a/src/api/providers/openrouter.ts b/src/api/providers/openrouter.ts index ce91c2f1ed..d044caad19 100644 --- a/src/api/providers/openrouter.ts +++ b/src/api/providers/openrouter.ts @@ -55,6 +55,11 @@ export class OpenRouterHandler implements ApiHandler { case "anthropic/claude-3-haiku:beta": case "anthropic/claude-3-opus": case "anthropic/claude-3-opus:beta": + // don't use prompt caching for advisor model requests + if (modelType === "advisor") { + break + } + openAiMessages[0] = { role: "system", content: [ diff --git a/src/core/Cline.ts b/src/core/Cline.ts index abdcb7a253..345719d657 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -18,7 +18,7 @@ import { UrlContentFetcher } from "../services/browser/UrlContentFetcher" import { listFiles } from "../services/glob/list-files" import { regexSearchFiles } from "../services/ripgrep" import { parseSourceCodeForDefinitionsTopLevel } from "../services/tree-sitter" -import { ApiConfiguration } from "../shared/api" +import { ApiConfiguration, ModelInfo } from "../shared/api" import { findLast, findLastIndex } from "../shared/array" import { AutoApprovalSettings } from "../shared/AutoApprovalSettings" import { combineApiRequests } from "../shared/combineApiRequests" @@ -60,6 +60,7 @@ import getFolderSize from "get-folder-size" import { BrowserSettings } from "../shared/BrowserSettings" import { ADVISOR_SYSTEM_PROMPT } from "./prompts/advisor" import { ChatSettings } from "../shared/ChatSettings" +import { CHAT_SYSTEM_PROMPT } from "./prompts/chat" const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution @@ -1192,6 +1193,71 @@ export class Cline { return false } + estimateAdvisorModelCost(problem: string) { + const truncatedConversationHistory = getTruncatedMessages( + this.apiConversationHistory, + this.conversationHistoryDeletedRange, + ) + const advisorModel = this.api.getAdvisorModel?.() + if (!advisorModel) { + return 0 + } + const advisorMessage = this.createAdvisorMessage(truncatedConversationHistory, advisorModel, problem) + const prompt = ADVISOR_SYSTEM_PROMPT() + advisorMessage + // Estimate ~3 chars per token as a rough approximation + const estimatedInputTokens = Math.ceil(prompt.length / 3) + const estimatedOutputTokens = 300 // typical response size + // Note: we don't prompt cache since we only send up one request at a time + const inputCost = (estimatedInputTokens * (advisorModel.info.inputPrice ?? 0)) / 1_000_000 // Convert from per million tokens + const outputCost = (estimatedOutputTokens * (advisorModel.info.outputPrice ?? 0)) / 1_000_000 + return inputCost + outputCost + } + + createAdvisorMessage( + truncatedConversationHistory: Anthropic.Messages.MessageParam[], + advisorModel: { + id: string + info: ModelInfo + }, + advisorProblem: string, + ) { + // Generate markdown + const markdownContent = truncatedConversationHistory + .map((message) => { + const role = message.role === "user" ? "**User:**" : "**Coding Agent:**" + const content = Array.isArray(message.content) + ? message.content.map((block) => formatContentBlockToMarkdown(block)).join("\n") + : message.content + return `${role}\n\n${content}\n\n` + }) + .join("---\n\n") + + // Don't want to send the entire conv history, just the most recent context + // Get approximate char count from token limit + const advisorContextWindow = advisorModel.info.contextWindow || 128_000 + const tokensToKeep = Math.floor(advisorContextWindow / 2) + // Estimate ~3 chars per token as a rough approximation + const charsToKeep = tokensToKeep * 3 + // Get last n chars of markdown content + const isTruncated = markdownContent.length > charsToKeep + const firstMessage = truncatedConversationHistory.at(0) + const firstMessageContent = firstMessage + ? Array.isArray(firstMessage.content) + ? firstMessage.content.map((block) => (block.type === "text" ? block.text : "")).join("\n") + : firstMessage.content + : "" + const recentContext = + (isTruncated ? `**User:**:\n\n${firstMessageContent}\n\n... (older messages removed for brevity) ...\n\n` : "") + + markdownContent.slice(-charsToKeep) + const advisorMessage = + "\n\n# The conversation history leading up to this point:\n\n" + + recentContext + + "\n\n# The problem the coding agent needs advice on:\n\n" + + advisorProblem + + return advisorMessage + } + async *attemptApiRequest(previousApiReqIndex: number): ApiStream { // Wait for MCP servers to be connected before generating system prompt await pWaitFor(() => this.providerRef.deref()?.mcpHub?.isConnecting !== true, { timeout: 10_000 }).catch(() => { @@ -1206,14 +1272,26 @@ export class Cline { const advisorModel = this.api.getAdvisorModel?.() const supportsConsultAdvisor = advisorModel !== undefined - let systemPrompt = await SYSTEM_PROMPT( - cwd, - this.api.getModel().info.supportsComputerUse ?? false, - mcpHub, - this.browserSettings, - this.chatSettings, - supportsConsultAdvisor, - ) + let systemPrompt: string + + if (this.chatSettings.mode === "chat") { + systemPrompt = await CHAT_SYSTEM_PROMPT( + cwd, + this.api.getModel().info.supportsComputerUse ?? false, + mcpHub, + this.browserSettings, + supportsConsultAdvisor, + ) + } else { + systemPrompt = await SYSTEM_PROMPT( + cwd, + this.api.getModel().info.supportsComputerUse ?? false, + mcpHub, + this.browserSettings, + supportsConsultAdvisor, + ) + } + let settingsCustomInstructions = this.customInstructions?.trim() const clineRulesFilePath = path.resolve(cwd, GlobalFileNames.clineRules) let clineRulesFileInstructions: string | undefined @@ -1282,50 +1360,22 @@ export class Cline { // If we're consulting the advisor, override the request if (this.advisorProblem && advisorModel) { - // Generate markdown - const markdownContent = truncatedConversationHistory - .map((message) => { - const role = message.role === "user" ? "**User:**" : "**Coding Agent:**" - const content = Array.isArray(message.content) - ? message.content.map((block) => formatContentBlockToMarkdown(block)).join("\n") - : message.content - return `${role}\n\n${content}\n\n` - }) - .join("---\n\n") - - // Don't want to send the entire conv history, just the most recent context - // Get approximate char count from token limit - const advisorContextWindow = advisorModel.info.contextWindow || 128_000 - const tokensToKeep = Math.floor(advisorContextWindow / 2) - // Estimate ~3 chars per token as a rough approximation - const charsToKeep = tokensToKeep * 3 - // Get last n chars of markdown content - const isTruncated = markdownContent.length > charsToKeep - const firstMessage = truncatedConversationHistory.at(0) - const firstMessageContent = firstMessage - ? Array.isArray(firstMessage.content) - ? firstMessage.content.map((block) => (block.type === "text" ? block.text : "")).join("\n") - : firstMessage.content - : "" - const recentContext = - (isTruncated ? `**User:**:\n\n${firstMessageContent}\n\n... (older messages removed for brevity) ...\n\n` : "") + - markdownContent.slice(-charsToKeep) - const advisorMessage: Anthropic.Messages.MessageParam[] = [ - { - role: "user", - content: [ - { - type: "text", - text: - "\n\n# The conversation history leading up to this point:\n\n" + - recentContext + - "\n\n# The problem the coding agent needs advice on:\n\n" + - this.advisorProblem, - }, - ], - }, - ] - stream = this.api.createMessage(ADVISOR_SYSTEM_PROMPT(), advisorMessage, "advisor") + const advisorMessage = this.createAdvisorMessage(truncatedConversationHistory, advisorModel, this.advisorProblem) + stream = this.api.createMessage( + ADVISOR_SYSTEM_PROMPT(), + [ + { + role: "user", + content: [ + { + type: "text", + text: advisorMessage, + }, + ], + }, + ], + "advisor", + ) } const iterator = stream[Symbol.asyncIterator]() @@ -1480,6 +1530,8 @@ export class Cline { return `[${block.name} for '${block.params.problem}']` case "ask_followup_question": return `[${block.name} for '${block.params.question}']` + case "respond_to_inquiry": + return `[${block.name} for '${block.params.response}']` case "attempt_completion": return `[${block.name}]` } @@ -2591,9 +2643,12 @@ export class Cline { } this.consecutiveMistakeCount = 0 + + const estimatedCost = undefined //this.estimateAdvisorModelCost(problem) const completeMessage = JSON.stringify({ problem: removeClosingTag("problem", problem), advisorModelId: this.api.getAdvisorModel?.().id, + estimatedCost, } satisfies ClineConsultAdvisor) if (this.shouldAutoApproveTool(block.name)) { @@ -2621,6 +2676,7 @@ export class Cline { lastMessage.text = JSON.stringify({ problem: removeClosingTag("problem", problem), advisorModelId: this.api.getAdvisorModel?.().id, + estimatedCost, } satisfies ClineConsultAdvisor) } @@ -2673,6 +2729,42 @@ export class Cline { break } } + case "respond_to_inquiry": { + const response: string | undefined = block.params.response + try { + if (block.partial) { + await this.ask("respond_to_inquiry", removeClosingTag("response", response), block.partial).catch( + () => {}, + ) + break + } else { + if (!response) { + this.consecutiveMistakeCount++ + pushToolResult(await this.sayAndCreateMissingParamError("respond_to_inquiry", "response")) + await this.saveCheckpoint() + break + } + this.consecutiveMistakeCount = 0 + + // if (this.autoApprovalSettings.enabled && this.autoApprovalSettings.enableNotifications) { + // showSystemNotification({ + // subtitle: "Cline has a response...", + // message: response.replace(/\n/g, " "), + // }) + // } + + const { text, images } = await this.ask("respond_to_inquiry", response, false) + await this.say("user_feedback", text ?? "", images) + pushToolResult(formatResponse.toolResult(`\n${text}\n`, images)) + await this.saveCheckpoint() + break + } + } catch (error) { + await handleError("responding to inquiry", error) + await this.saveCheckpoint() + break + } + } case "attempt_completion": { /* this.consecutiveMistakeCount = 0 diff --git a/src/core/assistant-message/index.ts b/src/core/assistant-message/index.ts index 8da46213fb..de2ade7a30 100644 --- a/src/core/assistant-message/index.ts +++ b/src/core/assistant-message/index.ts @@ -21,6 +21,7 @@ export const toolUseNames = [ "access_mcp_resource", "consult_advisor", "ask_followup_question", + "respond_to_inquiry", "attempt_completion", ] as const @@ -46,6 +47,7 @@ export const toolParamNames = [ "uri", "problem", "question", + "response", "result", ] as const diff --git a/src/core/prompts/chat.ts b/src/core/prompts/chat.ts new file mode 100644 index 0000000000..76e41d6f05 --- /dev/null +++ b/src/core/prompts/chat.ts @@ -0,0 +1,427 @@ +import defaultShell from "default-shell" +import os from "os" +import osName from "os-name" +import { McpHub } from "../../services/mcp/McpHub" +import { BrowserSettings } from "../../shared/BrowserSettings" + +export const CHAT_SYSTEM_PROMPT = async ( + cwd: string, + supportsComputerUse: boolean, + mcpHub: McpHub, + browserSettings: BrowserSettings, + supportsConsultAdvisor: boolean, +) => `You are Cline, a highly skilled software engineer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices. + +==== + +TOOL USE + +You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to respond to the user's inquiry, with each tool use informed by the result of the previous tool use. + +# Tool Use Formatting + +Tool use is formatted using XML-style tags. The tool name is enclosed in opening and closing tags, and each parameter is similarly enclosed within its own set of tags. Here's the structure: + + +value1 +value2 +... + + +For example: + + +src/main.js + + +Always adhere to this format for the tool use to ensure proper parsing and execution. + +# Tools + +## read_file +Description: Request to read the contents of a file at the specified path. Use this when you need to examine the contents of an existing file you do not know the contents of, for example to analyze code, review text files, or extract information from configuration files. Automatically extracts raw text from PDF and DOCX files. May not be suitable for other types of binary files, as it returns the raw content as a string. +Parameters: +- path: (required) The path of the file to read (relative to the current working directory ${cwd.toPosix()}) +Usage: + +File path here + + +## search_files +Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. +Parameters: +- path: (required) The path of the directory to search in (relative to the current working directory ${cwd.toPosix()}). This directory will be recursively searched. +- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax. +- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*). +Usage: + +Directory path here +Your regex pattern here +file pattern here (optional) + + +## list_files +Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not. +Parameters: +- path: (required) The path of the directory to list contents for (relative to the current working directory ${cwd.toPosix()}) +- recursive: (optional) Whether to list files recursively. Use true for recursive listing, false or omit for top-level only. +Usage: + +Directory path here +true or false (optional) + + +## list_code_definition_names +Description: Request to list definition names (classes, functions, methods, etc.) used in source code files at the top level of the specified directory. This tool provides insights into the codebase structure and important constructs, encapsulating high-level concepts and relationships that are crucial for understanding the overall architecture. +Parameters: +- path: (required) The path of the directory (relative to the current working directory ${cwd.toPosix()}) to list top level source code definitions for. +Usage: + +Directory path here +${ + supportsComputerUse + ? ` + +## browser_action +Description: Request to interact with a Puppeteer-controlled browser. Every action, except \`close\`, will be responded to with a screenshot of the browser's current state, along with any new console logs. You may only perform one browser action per message, and wait for the user's response including a screenshot and logs to determine the next action. +- The sequence of actions **must always start with** launching the browser at a URL, and **must always end with** closing the browser. If you need to visit a new URL that is not possible to navigate to from the current webpage, you must first close the browser, then launch again at the new URL. +- While the browser is active, only the \`browser_action\` tool can be used. No other tools should be called during this time. You may proceed to use other tools only after closing the browser. For example if you run into an error and need to fix a file, you must close the browser, then use other tools to make the necessary changes, then re-launch the browser to verify the result. +- The browser window has a resolution of **${browserSettings.viewport.width}x${browserSettings.viewport.height}** pixels. When performing any click actions, ensure the coordinates are within this resolution range. +- Before clicking on any elements such as icons, links, or buttons, you must consult the provided screenshot of the page to determine the coordinates of the element. The click should be targeted at the **center of the element**, not on its edges. +Parameters: +- action: (required) The action to perform. The available actions are: + * launch: Launch a new Puppeteer-controlled browser instance at the specified URL. This **must always be the first action**. + - Use with the \`url\` parameter to provide the URL. + - Ensure the URL is valid and includes the appropriate protocol (e.g. http://localhost:3000/page, file:///path/to/file.html, etc.) + * click: Click at a specific x,y coordinate. + - Use with the \`coordinate\` parameter to specify the location. + - Always click in the center of an element (icon, button, link, etc.) based on coordinates derived from a screenshot. + * type: Type a string of text on the keyboard. You might use this after clicking on a text field to input text. + - Use with the \`text\` parameter to provide the string to type. + * scroll_down: Scroll down the page by one page height. + * scroll_up: Scroll up the page by one page height. + * close: Close the Puppeteer-controlled browser instance. This **must always be the final browser action**. + - Example: \`close\` +- url: (optional) Use this for providing the URL for the \`launch\` action. + * Example: https://example.com +- coordinate: (optional) The X and Y coordinates for the \`click\` action. Coordinates should be within the **${browserSettings.viewport.width}x${browserSettings.viewport.height}** resolution. + * Example: 450,300 +- text: (optional) Use this for providing the text for the \`type\` action. + * Example: Hello, world! +Usage: + +Action to perform (e.g., launch, click, type, scroll_down, scroll_up, close) +URL to launch the browser at (optional) +x,y coordinates (optional) +Text to type (optional) +` + : "" +} + +## use_mcp_tool +Description: Request to use a tool provided by a connected MCP server. Each MCP server can provide multiple tools with different capabilities. Tools have defined input schemas that specify required and optional parameters. +Parameters: +- server_name: (required) The name of the MCP server providing the tool +- tool_name: (required) The name of the tool to execute +- arguments: (required) A JSON object containing the tool's input parameters, following the tool's input schema +Usage: + +server name here +tool name here + +{ + "param1": "value1", + "param2": "value2" +} + + + +## access_mcp_resource +Description: Request to access a resource provided by a connected MCP server. Resources represent data sources that can be used as context, such as files, API responses, or system information. +Parameters: +- server_name: (required) The name of the MCP server providing the resource +- uri: (required) The URI identifying the specific resource to access +Usage: + +server name here +resource URI here +${ + supportsConsultAdvisor + ? ` + +## consult_advisor +Description: Request to consult an advanced-reasoning AI model about a problem or question you are facing. This can be used to resolve errors you are stuck on, or get input from the model to work through a challenge you are facing. The relevant conversation history leading to the problem will also be provided to the advisor for additional context. +Parameters: +- problem: (required) A string describing the issue, question, or context you want the advisor to address. +Usage: + +Your problem or question here +` + : "" +} + +## respond_to_inquiry +Description: Respond to the user's inquiry with a clear answer. This tool should be used when you need to provide a response to a question or statement. It allows for direct communication with the user, ensuring they receive a clear answer that addresses their inquiry. It can also be used to ask the user for more information if needed. +Parameters: +- response: (required) The response to provide to the user. This should be a clear answer that addresses the user's inquiry. +Usage: + +Your response here + + +# Tool Use Examples + +## Example 1: Requesting to use an MCP tool + + +weather-server +get_forecast + +{ + "city": "San Francisco", + "days": 5 +} + + + +## Example 2: Requesting to access an MCP resource + + +weather-server +weather://san-francisco/current + + +# Tool Use Guidelines + +1. In tags, assess what information you already have and what information you need to proceed with the task. +2. Choose the most appropriate tool based on the task and the tool descriptions provided. Assess if you need additional information to proceed, and which of the available tools would be most effective for gathering this information. For example using the list_files tool is more effective than running a command like \`ls\` in the terminal. It's critical that you think about each available tool and use the one that best fits the current step in the task. +3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. +4. Formulate your tool use using the XML format specified for each tool. +5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: + - Information about whether the tool succeeded or failed, along with any reasons for failure. + - Linter errors that may have arisen due to the changes you made, which you'll need to address. + - New terminal output in reaction to the changes, which you may need to consider or act upon. + - Any other relevant feedback or information related to the tool use. +6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. + +It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: +1. Confirm the success of each step before proceeding. +2. Address any issues or errors that arise immediately. +3. Adapt your approach based on new information or unexpected results. +4. Ensure that each action builds correctly on the previous ones. + +By waiting for and carefully considering the user's response after each tool use, you can react accordingly and make informed decisions about how to proceed with the task. This iterative process helps ensure the overall success and accuracy of your work. + +==== + +MCP SERVERS + +The Model Context Protocol (MCP) enables communication between the system and locally running MCP servers that provide additional tools and resources to extend your capabilities. + +# Connected MCP Servers + +When a server is connected, you can use the server's tools via the \`use_mcp_tool\` tool, and access the server's resources via the \`access_mcp_resource\` tool. + +${ + mcpHub.getServers().length > 0 + ? `${mcpHub + .getServers() + .filter((server) => server.status === "connected") + .map((server) => { + const tools = server.tools + ?.map((tool) => { + const schemaStr = tool.inputSchema + ? ` Input Schema: + ${JSON.stringify(tool.inputSchema, null, 2).split("\n").join("\n ")}` + : "" + + return `- ${tool.name}: ${tool.description}\n${schemaStr}` + }) + .join("\n\n") + + const templates = server.resourceTemplates + ?.map((template) => `- ${template.uriTemplate} (${template.name}): ${template.description}`) + .join("\n") + + const resources = server.resources + ?.map((resource) => `- ${resource.uri} (${resource.name}): ${resource.description}`) + .join("\n") + + const config = JSON.parse(server.config) + + return ( + `## ${server.name} (\`${config.command}${config.args && Array.isArray(config.args) ? ` ${config.args.join(" ")}` : ""}\`)` + + (tools ? `\n\n### Available Tools\n${tools}` : "") + + (templates ? `\n\n### Resource Templates\n${templates}` : "") + + (resources ? `\n\n### Direct Resources\n${resources}` : "") + ) + }) + .join("\n\n")}` + : "(No MCP servers currently connected)" +}${ + supportsConsultAdvisor + ? ` + +==== + +CONSULTING THE ADVISOR MODEL + +You can use the consult_advisor tool to get suggestions from an advisor model, a powerful AI model that can provide strategic guidance and help solve complex problems. The conversation history that led to the current situation is automatically passed to the advisor, allowing it to provide contextually relevant guidance based on the full picture of the task at hand. + +# When to Use the Advisor + +- When stuck on persistent bugs that you cannot resolve +- If you've tried multiple approaches without success +- When facing complex type errors or package incompatibilities +- When debugging intricate interactions between multiple systems +- If you need deeper insight into system behavior that may not be apparent + +# How to Use Effectively + +## Provide Clear Context +- Explain the current situation and challenge +- Include relevant code snippets or error messages +- Describe what you've already tried +- Specify what kind of guidance you're seeking + +## Ask Specific Questions +- Instead of "Why isn't this working?" +- Better: "I'm encountering this specific type error when integrating these packages, here's what I've tried..." + +Example Usage: + + +I'm encountering persistent type errors while working with @types/react-query v4.0.0: + +Error: Type 'QueryClient' is not assignable to parameter of type 'never'. + The types of 'getQueryCache().notify' are incompatible between these types. + +I've tried: +- Checking package versions compatibility +- Explicitly typing the QueryClient instance +- Updating @types/react and @types/react-query + +Current package versions: +react-query: ^3.39.3 +@types/react-query: ^4.0.0 +react: ^18.2.0 +typescript: ^4.9.5 + +The error persists despite these attempts. Could this be due to version mismatches or breaking changes I'm not aware of? + + + +# Benefits of Using the Advisor + +- Break through debugging roadblocks +- Get fresh perspectives on complex issues +- Understand root causes of persistent bugs +- Solve challenging technical issues + +Remember: While you should attempt to solve problems with your own reasoning first, the advisor is a powerful resource available when you're stuck on a bug. Don't hesitate to consult it when you've hit a persistent roadblock that you cannot resolve.` + : "" +} + +==== + +CAPABILITIES + +- You have access to tools that let you list files, view source code definitions, regex search${ + supportsComputerUse ? ", use the browser" : "" +}, read files${ + supportsConsultAdvisor ? ", consult an advisor" : "" +}, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as understanding the current state of a project, and much more. +- When the user initially gives you a task, a recursive list of all filepaths in the current working directory ('${cwd.toPosix()}') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current working directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop. +- You can use search_files to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring. +- You can use the list_code_definition_names tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task.${ + supportsComputerUse + ? "\n- You can use the browser_action tool to interact with websites (including html files and locally running development servers) through a Puppeteer-controlled browser when you feel it is necessary in accomplishing the user's task. This tool is particularly useful for web development tasks as it allows you to launch a browser, navigate to pages, interact with elements through clicks and keyboard input, and capture the results through screenshots and console logs. This tool may be useful at key stages of web development tasks-such as after implementing new features, making substantial changes, when troubleshooting issues, or to verify the result of your work. You can analyze the provided screenshots to ensure correct rendering or identify errors, and review console logs for runtime issues.\n - For example, if asked to add a component to a react website, you might create the necessary files, use execute_command to run the site locally, then use browser_action to launch the browser, navigate to the local server, and verify the component renders & functions correctly before closing the browser." + : "" +} +- You have access to MCP servers that may provide additional tools and resources. Each server may provide different capabilities that you can use to accomplish tasks more effectively.${ + supportsConsultAdvisor + ? "\n- When you hit a roadblock, such as an error you've attempted to resolve several times without success, you can use the consult_advisor tool to get suggestions from an advanced-reasoning AI model. The conversation history that led to the current situation is automatically passed to the advisor, allowing it to provide contextually relevant guidance based on the full picture of the task at hand." + : "" +} + +==== + +RULES + +- Your current working directory is: ${cwd.toPosix()} +- You cannot \`cd\` into a different directory to complete a task. You are stuck operating from '${cwd.toPosix()}', so be sure to pass in the correct 'path' parameter when using tools that require a path. +- Do not use the ~ character or $HOME to refer to the home directory. +- When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using replace_in_file to make informed changes. +- The user may provide a file's contents directly in their message, in which case you shouldn't use the read_file tool to get the file contents again since you already have it.${ + supportsComputerUse + ? '\n- The user may ask generic non-development tasks, such as "what\'s the latest news" or "look up the weather in San Diego", in which case you might use the browser_action tool to complete the task if it makes sense to do so. However, if an available MCP server tool or resource can be used instead, you should prefer to use it over browser_action.' + : "" +} +- At the end of each user message, you will automatically receive environment_details. This information is not written by the user themselves, but is auto-generated to provide potentially relevant context about the project structure and environment. While this information can be valuable for understanding the project context, do not treat it as a direct part of the user's request or response. Use it to inform your actions and decisions, but don't assume the user is explicitly asking about or referring to this information unless they clearly do so in their message. When using environment_details, explain your actions clearly to ensure the user understands, as they may not be aware of these details. +- MCP operations should be used one at a time, similar to other tool usage. Wait for confirmation of success before proceeding with additional operations. +- It is critical you wait for the user's response after each tool use, in order to confirm the success of the tool use. For example, if asked to make a todo app, you would create a file, wait for the user's response it was created successfully, then create another file if needed, wait for the user's response it was created successfully, etc.${ + supportsComputerUse + ? " Then if you want to test your work, you might use browser_action to launch the site, wait for the user's response confirming the site was launched along with a screenshot, then perhaps e.g., click a button to test functionality if needed, wait for the user's response confirming the button was clicked along with a screenshot of the new state, before finally closing the browser." + : "" +} + +==== + +SYSTEM INFORMATION + +Operating System: ${osName()} +Default Shell: ${defaultShell} +Home Directory: ${os.homedir().toPosix()} +Current Working Directory: ${cwd.toPosix()} + +==== + +OBJECTIVE + +You respond to user inquiries by gathering relevant information through available tools and providing clear, informed responses. + +1. Analyze the user's inquiry to understand what information is needed to provide a complete and accurate response. +2. Use available tools one at a time to gather the necessary information. Each tool use should be purposeful in building your understanding to address the inquiry. +3. Remember, you have extensive capabilities with access to a wide range of tools that can be used in powerful and clever ways to gather relevant information. Before calling a tool, do some analysis within tags. First, analyze the file structure provided in environment_details to gain context and insights for proceeding effectively. Then, think about which of the provided tools is the most relevant tool to gather the information needed. Next, go through each of the required parameters of the relevant tool and determine if the user has directly provided or given enough information to infer a value. When deciding if the parameter can be inferred, carefully consider all the context to see if it supports a specific value. If all of the required parameters are present or can be reasonably inferred, close the thinking tag and proceed with the tool use. BUT, if one of the values for a required parameter is missing, DO NOT invoke the tool (not even with fillers for the missing params) and instead, ask the user to provide the missing parameters using the respond_to_inquiry tool. DO NOT ask for more information on optional parameters if it is not provided. +4. Once you've gathered the necessary information to address the inquiry, you must use the respond_to_inquiry tool to present a clear, well-informed response to the user. + +==== + +CHAT MODE + +You are now in chat mode, which means you will engage in conversational interactions rather than completing development tasks. In this mode: + +1. Your primary purpose is to respond helpfully to the user's questions and engage in natural dialogue +2. While you still have access to all tools, you will use them only to gather information to inform your responses +3. Instead of working towards task completion, you will work towards providing clear, informative responses +4. You must use the respond_to_inquiry tool to deliver your responses, not attempt_completion +5. Keep responses focused and relevant to the user's questions +6. You may use tools like: + - read_file to look up code context + - search_files to find relevant information + - list_files to understand project structure + - MCP tools/resources to get external data + But always with the goal of informing your response + +Your objective is to be a helpful conversational partner, not a task-completing agent. Every tool use should be in service of building a more complete and accurate response to the user's inquiry. However, if you have enough information to respond to the user's inquiry, you should use the respond_to_inquiry tool to immediately deliver a response. + +Important: In chat mode, you should immediately use the respond_to_inquiry tool to deliver your response, rather than using tags to analyze when to respond. Do not talk about using respond_to_inquiry - just use it directly to share your thoughts and provide helpful answers.` + +export function addUserInstructions(settingsCustomInstructions?: string, clineRulesFileInstructions?: string) { + let customInstructions = "" + if (settingsCustomInstructions) { + customInstructions += settingsCustomInstructions + "\n\n" + } + if (clineRulesFileInstructions) { + customInstructions += clineRulesFileInstructions + } + + return ` +==== + +USER'S CUSTOM INSTRUCTIONS + +The following additional instructions are provided by the user, and should be followed to the best of your ability without interfering with the TOOL USE guidelines. + +${customInstructions.trim()}` +} diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index e2865a4e25..a0fe39e7e1 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -3,14 +3,12 @@ import os from "os" import osName from "os-name" import { McpHub } from "../../services/mcp/McpHub" import { BrowserSettings } from "../../shared/BrowserSettings" -import { ChatSettings } from "../../shared/ChatSettings" export const SYSTEM_PROMPT = async ( cwd: string, supportsComputerUse: boolean, mcpHub: McpHub, browserSettings: BrowserSettings, - chatSettings: ChatSettings, supportsConsultAdvisor: boolean, ) => `You are Cline, a highly skilled software engineer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices. diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 903d1a4745..105f51091e 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -488,6 +488,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { this.cline.updateChatSettings(message.chatSettings) } await this.postStateToWebview() + this.cancelTask() } break // case "relaunchChromeDebugMode": diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index d5388d0467..3f6670b4f2 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -66,6 +66,7 @@ export interface ClineMessage { export type ClineAsk = | "followup" + | "respond_to_inquiry" | "command" | "command_output" | "completion_result" @@ -148,6 +149,7 @@ export interface ClineAskUseMcpServer { export interface ClineConsultAdvisor { problem: string advisorModelId?: string + estimatedCost?: number } export interface ClineApiReqInfo { diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 7375472c92..a063905da4 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -762,7 +762,19 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi padding: "8px 10px", marginTop: "8px", }}> - {consultAdvisor.problem} +
{consultAdvisor.problem}
+ {consultAdvisor.estimatedCost != null && ( +
+ Estimated cost: ${Number(consultAdvisor.estimatedCost).toFixed(4)} +
+ )}
) + case "respond_to_inquiry": + return ( +
+ +
+ ) default: return null } diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index 2d4833a350..a7c0742862 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -489,6 +489,10 @@ const ChatTextArea = forwardRef( mode: newMode, }, }) + // Focus the textarea after mode toggle with slight delay + setTimeout(() => { + textAreaRef.current?.focus() + }, 100) }, [chatSettings.mode, textAreaDisabled]) const handleContextButtonClick = useCallback(() => { diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index c1894ba03c..06ff6af2e0 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -103,6 +103,13 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie // setPrimaryButtonText(undefined) // setSecondaryButtonText(undefined) break + case "respond_to_inquiry": + setTextAreaDisabled(isPartial) + setClineAsk("respond_to_inquiry") + setEnableButtons(isPartial) + // setPrimaryButtonText(undefined) + // setSecondaryButtonText(undefined) + break case "tool": setTextAreaDisabled(isPartial) setClineAsk("tool") @@ -271,6 +278,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie } else if (clineAsk) { switch (clineAsk) { case "followup": + case "respond_to_inquiry": case "tool": case "browser_action_launch": case "command": // user can provide feedback to a tool or command use From c7c6c8f0d6c904f803fd0e0bf2aa1532faa2c2c4 Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Sun, 19 Jan 2025 11:35:51 -0800 Subject: [PATCH 13/49] Change to task mode --- src/core/Cline.ts | 6 +++--- src/shared/ChatSettings.ts | 4 ++-- webview-ui/src/components/chat/ChatTextArea.tsx | 4 ++-- webview-ui/src/components/chat/TaskHeader.tsx | 7 +++++-- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 345719d657..b49d7015a1 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -2741,7 +2741,7 @@ export class Cline { if (!response) { this.consecutiveMistakeCount++ pushToolResult(await this.sayAndCreateMissingParamError("respond_to_inquiry", "response")) - await this.saveCheckpoint() + // await this.saveCheckpoint() break } this.consecutiveMistakeCount = 0 @@ -2756,12 +2756,12 @@ export class Cline { const { text, images } = await this.ask("respond_to_inquiry", response, false) await this.say("user_feedback", text ?? "", images) pushToolResult(formatResponse.toolResult(`\n${text}\n`, images)) - await this.saveCheckpoint() + // await this.saveCheckpoint() break } } catch (error) { await handleError("responding to inquiry", error) - await this.saveCheckpoint() + // await this.saveCheckpoint() break } } diff --git a/src/shared/ChatSettings.ts b/src/shared/ChatSettings.ts index 5d0e48c264..18eab25312 100644 --- a/src/shared/ChatSettings.ts +++ b/src/shared/ChatSettings.ts @@ -1,7 +1,7 @@ export interface ChatSettings { - mode: "code" | "chat" + mode: "task" | "chat" } export const DEFAULT_CHAT_SETTINGS: ChatSettings = { - mode: "code", + mode: "task", } diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index a7c0742862..b0fe60f04a 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -482,7 +482,7 @@ const ChatTextArea = forwardRef( const onModeToggle = useCallback(() => { if (textAreaDisabled) return - const newMode = chatSettings.mode === "chat" ? "code" : "chat" + const newMode = chatSettings.mode === "chat" ? "task" : "chat" vscode.postMessage({ type: "chatSettings", chatSettings: { @@ -754,7 +754,7 @@ const ChatTextArea = forwardRef( - Code + Task Chat diff --git a/webview-ui/src/components/chat/TaskHeader.tsx b/webview-ui/src/components/chat/TaskHeader.tsx index ed2017d955..253ecef5df 100644 --- a/webview-ui/src/components/chat/TaskHeader.tsx +++ b/webview-ui/src/components/chat/TaskHeader.tsx @@ -30,7 +30,7 @@ const TaskHeader: React.FC = ({ totalCost, onClose, }) => { - const { apiConfiguration, currentTaskItem, checkpointTrackerErrorMessage } = useExtensionState() + const { apiConfiguration, currentTaskItem, checkpointTrackerErrorMessage, chatSettings } = useExtensionState() const [isTaskExpanded, setIsTaskExpanded] = useState(true) const [isTextExpanded, setIsTextExpanded] = useState(false) const [showSeeMore, setShowSeeMore] = useState(false) @@ -155,7 +155,10 @@ const TaskHeader: React.FC = ({ flexGrow: 1, minWidth: 0, // This allows the div to shrink below its content size }}> - Task{!isTaskExpanded && ":"} + + {chatSettings.mode === "task" ? "Task" : "Chat"} + {!isTaskExpanded && ":"} + {!isTaskExpanded && {highlightMentions(task.text, false)}}
From 8d7b70b1e5c65e294ada79c54efd2ad7ae486544 Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Sun, 19 Jan 2025 11:39:43 -0800 Subject: [PATCH 14/49] Only retry failed request if openrouter --- src/core/Cline.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index b49d7015a1..721e95799e 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -61,6 +61,7 @@ import { BrowserSettings } from "../shared/BrowserSettings" import { ADVISOR_SYSTEM_PROMPT } from "./prompts/advisor" import { ChatSettings } from "../shared/ChatSettings" import { CHAT_SYSTEM_PROMPT } from "./prompts/chat" +import { OpenRouterHandler } from "../api/providers/openrouter" const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution @@ -1387,7 +1388,8 @@ export class Cline { yield firstChunk.value this.isWaitingForFirstChunk = false } catch (error) { - if (!this.didAutomaticallyRetryFailedApiRequest) { + const isOpenRouter = this.api instanceof OpenRouterHandler + if (isOpenRouter && !this.didAutomaticallyRetryFailedApiRequest) { console.log("first chunk failed, waiting 1 second before retrying") await delay(1000) this.didAutomaticallyRetryFailedApiRequest = true From 8ec0b2cf0846fa9f82e5c25d61aa3c91e853e1a0 Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Sun, 19 Jan 2025 13:11:20 -0800 Subject: [PATCH 15/49] Add VS Code LM API --- package-lock.json | 12 +- package.json | 21 +- src/api/index.ts | 7 + src/api/providers/vscode-lm.ts | 547 ++++++++++++++++++ src/api/transform/vscode-lm-format.ts | 200 +++++++ src/core/webview/ClineProvider.ts | 22 + src/integrations/terminal/TerminalManager.ts | 16 +- src/shared/ExtensionMessage.ts | 3 + src/shared/WebviewMessage.ts | 1 + src/shared/api.ts | 2 + src/shared/vsCodeSelectorUtils.ts | 7 + webview-ui/src/components/chat/TaskHeader.tsx | 1 + .../src/components/settings/ApiOptions.tsx | 88 ++- .../src/context/ExtensionStateContext.tsx | 1 + webview-ui/src/utils/validate.ts | 5 + 15 files changed, 916 insertions(+), 17 deletions(-) create mode 100644 src/api/providers/vscode-lm.ts create mode 100644 src/api/transform/vscode-lm-format.ts create mode 100644 src/shared/vsCodeSelectorUtils.ts diff --git a/package-lock.json b/package-lock.json index b1de717b85..eb7141005b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "claude-dev", - "version": "3.1.8", + "version": "3.1.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "claude-dev", - "version": "3.1.8", + "version": "3.1.11", "license": "Apache-2.0", "dependencies": { "@anthropic-ai/bedrock-sdk": "^0.10.2", @@ -53,7 +53,7 @@ "@types/mocha": "^10.0.7", "@types/node": "20.x", "@types/should": "^11.2.0", - "@types/vscode": "^1.84.0", + "@types/vscode": "^1.96.0", "@typescript-eslint/eslint-plugin": "^7.14.1", "@typescript-eslint/parser": "^7.11.0", "@vscode/test-cli": "^0.0.9", @@ -4641,9 +4641,9 @@ "license": "MIT" }, "node_modules/@types/vscode": { - "version": "1.84.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.84.0.tgz", - "integrity": "sha512-lCGOSrhT3cL+foUEqc8G1PVZxoDbiMmxgnUZZTEnHF4mC47eKAUtBGAuMLY6o6Ua8PAuNCoKXbqPmJd1JYnQfg==", + "version": "1.96.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.96.0.tgz", + "integrity": "sha512-qvZbSZo+K4ZYmmDuaodMbAa67Pl6VDQzLKFka6rq+3WUTY4Kro7Bwoi0CuZLO/wema0ygcmpwow7zZfPJTs5jg==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index ce4278f676..8757be5c21 100644 --- a/package.json +++ b/package.json @@ -124,6 +124,25 @@ "when": "view == claude-dev.SidebarProvider" } ] + }, + "configuration": { + "title": "Cline", + "properties": { + "cline.vsCodeLmModelSelector": { + "type": "object", + "properties": { + "vendor": { + "type": "string", + "description": "The vendor of the language model (e.g. copilot)" + }, + "family": { + "type": "string", + "description": "The family of the language model (e.g. gpt-4)" + } + }, + "description": "Settings for VSCode Language Model API" + } + } } }, "scripts": { @@ -152,7 +171,7 @@ "@types/mocha": "^10.0.7", "@types/node": "20.x", "@types/should": "^11.2.0", - "@types/vscode": "^1.84.0", + "@types/vscode": "^1.96.0", "@typescript-eslint/eslint-plugin": "^7.14.1", "@typescript-eslint/parser": "^7.11.0", "@vscode/test-cli": "^0.0.9", diff --git a/src/api/index.ts b/src/api/index.ts index 061b61b8be..f200a91b21 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -12,6 +12,7 @@ import { OpenAiNativeHandler } from "./providers/openai-native" import { ApiStream } from "./transform/stream" import { DeepSeekHandler } from "./providers/deepseek" import { MistralHandler } from "./providers/mistral" +import { VsCodeLmHandler } from "./providers/vscode-lm" export interface ApiHandler { createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], modelType?: ModelType): ApiStream @@ -19,6 +20,10 @@ export interface ApiHandler { getAdvisorModel?(): { id: string; info: ModelInfo } } +export interface SingleCompletionHandler { + completePrompt(prompt: string): Promise +} + export function buildApiHandler(configuration: ApiConfiguration): ApiHandler { const { apiProvider, ...options } = configuration switch (apiProvider) { @@ -44,6 +49,8 @@ export function buildApiHandler(configuration: ApiConfiguration): ApiHandler { return new DeepSeekHandler(options) case "mistral": return new MistralHandler(options) + case "vscode-lm": + return new VsCodeLmHandler(options) default: return new AnthropicHandler(options) } diff --git a/src/api/providers/vscode-lm.ts b/src/api/providers/vscode-lm.ts new file mode 100644 index 0000000000..8c138a9102 --- /dev/null +++ b/src/api/providers/vscode-lm.ts @@ -0,0 +1,547 @@ +import { Anthropic } from "@anthropic-ai/sdk" +import * as vscode from "vscode" +import { ApiHandler, SingleCompletionHandler } from "../" +import { calculateApiCost } from "../../utils/cost" +import { ApiStream } from "../transform/stream" +import { convertToVsCodeLmMessages } from "../transform/vscode-lm-format" +import { SELECTOR_SEPARATOR, stringifyVsCodeLmModelSelector } from "../../shared/vsCodeSelectorUtils" +import { ApiHandlerOptions, ModelInfo, openAiModelInfoSaneDefaults } from "../../shared/api" + +/** + * Handles interaction with VS Code's Language Model API for chat-based operations. + * This handler implements the ApiHandler interface to provide VS Code LM specific functionality. + * + * @implements {ApiHandler} + * + * @remarks + * The handler manages a VS Code language model chat client and provides methods to: + * - Create and manage chat client instances + * - Stream messages using VS Code's Language Model API + * - Retrieve model information + * + * @example + * ```typescript + * const options = { + * vsCodeLmModelSelector: { vendor: "copilot", family: "gpt-4" } + * }; + * const handler = new VsCodeLmHandler(options); + * + * // Stream a conversation + * const systemPrompt = "You are a helpful assistant"; + * const messages = [{ role: "user", content: "Hello!" }]; + * for await (const chunk of handler.createMessage(systemPrompt, messages)) { + * console.log(chunk); + * } + * ``` + */ +export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler { + private options: ApiHandlerOptions + private client: vscode.LanguageModelChat | null + private disposable: vscode.Disposable | null + private currentRequestCancellation: vscode.CancellationTokenSource | null + + constructor(options: ApiHandlerOptions) { + this.options = options + this.client = null + this.disposable = null + this.currentRequestCancellation = null + + try { + // Listen for model changes and reset client + this.disposable = vscode.workspace.onDidChangeConfiguration((event) => { + if (event.affectsConfiguration("lm")) { + try { + this.client = null + this.ensureCleanState() + } catch (error) { + console.error("Error during configuration change cleanup:", error) + } + } + }) + } catch (error) { + // Ensure cleanup if constructor fails + this.dispose() + + throw new Error( + `Cline : Failed to initialize handler: ${error instanceof Error ? error.message : "Unknown error"}`, + ) + } + } + + /** + * Creates a language model chat client based on the provided selector. + * + * @param selector - Selector criteria to filter language model chat instances + * @returns Promise resolving to the first matching language model chat instance + * @throws Error when no matching models are found with the given selector + * + * @example + * const selector = { vendor: "copilot", family: "gpt-4o" }; + * const chatClient = await createClient(selector); + */ + async createClient(selector: vscode.LanguageModelChatSelector): Promise { + try { + const models = await vscode.lm.selectChatModels(selector) + + // Use first available model or create a minimal model object + if (models && Array.isArray(models) && models.length > 0) { + return models[0] + } + + // Create a minimal model if no models are available + return { + id: "default-lm", + name: "Default Language Model", + vendor: "vscode", + family: "lm", + version: "1.0", + maxInputTokens: 8192, + sendRequest: async (messages, options, token) => { + // Provide a minimal implementation + return { + stream: (async function* () { + yield new vscode.LanguageModelTextPart( + "Language model functionality is limited. Please check VS Code configuration.", + ) + })(), + text: (async function* () { + yield "Language model functionality is limited. Please check VS Code configuration." + })(), + } + }, + countTokens: async () => 0, + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : "Unknown error" + throw new Error(`Cline : Failed to select model: ${errorMessage}`) + } + } + + /** + * Creates and streams a message using the VS Code Language Model API. + * + * @param systemPrompt - The system prompt to initialize the conversation context + * @param messages - An array of message parameters following the Anthropic message format + * + * @yields {ApiStream} An async generator that yields either text chunks or tool calls from the model response + * + * @throws {Error} When vsCodeLmModelSelector option is not provided + * @throws {Error} When the response stream encounters an error + * + * @remarks + * This method handles the initialization of the VS Code LM client if not already created, + * converts the messages to VS Code LM format, and streams the response chunks. + * Tool calls handling is currently a work in progress. + */ + dispose(): void { + if (this.disposable) { + this.disposable.dispose() + } + + if (this.currentRequestCancellation) { + this.currentRequestCancellation.cancel() + this.currentRequestCancellation.dispose() + } + } + + private async countTokens(text: string | vscode.LanguageModelChatMessage): Promise { + // Check for required dependencies + if (!this.client) { + console.warn("Cline : No client available for token counting") + return 0 + } + + if (!this.currentRequestCancellation) { + console.warn("Cline : No cancellation token available for token counting") + return 0 + } + + // Validate input + if (!text) { + console.debug("Cline : Empty text provided for token counting") + return 0 + } + + try { + // Handle different input types + let tokenCount: number + + if (typeof text === "string") { + tokenCount = await this.client.countTokens(text, this.currentRequestCancellation.token) + } else if (text instanceof vscode.LanguageModelChatMessage) { + // For chat messages, ensure we have content + if (!text.content || (Array.isArray(text.content) && text.content.length === 0)) { + console.debug("Cline : Empty chat message content") + return 0 + } + tokenCount = await this.client.countTokens(text, this.currentRequestCancellation.token) + } else { + console.warn("Cline : Invalid input type for token counting") + return 0 + } + + // Validate the result + if (typeof tokenCount !== "number") { + console.warn("Cline : Non-numeric token count received:", tokenCount) + return 0 + } + + if (tokenCount < 0) { + console.warn("Cline : Negative token count received:", tokenCount) + return 0 + } + + return tokenCount + } catch (error) { + // Handle specific error types + if (error instanceof vscode.CancellationError) { + console.debug("Cline : Token counting cancelled by user") + return 0 + } + + const errorMessage = error instanceof Error ? error.message : "Unknown error" + console.warn("Cline : Token counting failed:", errorMessage) + + // Log additional error details if available + if (error instanceof Error && error.stack) { + console.debug("Token counting error stack:", error.stack) + } + + return 0 // Fallback to prevent stream interruption + } + } + + private async calculateTotalInputTokens( + systemPrompt: string, + vsCodeLmMessages: vscode.LanguageModelChatMessage[], + ): Promise { + const systemTokens: number = await this.countTokens(systemPrompt) + + const messageTokens: number[] = await Promise.all(vsCodeLmMessages.map((msg) => this.countTokens(msg))) + + return systemTokens + messageTokens.reduce((sum: number, tokens: number): number => sum + tokens, 0) + } + + private ensureCleanState(): void { + if (this.currentRequestCancellation) { + this.currentRequestCancellation.cancel() + this.currentRequestCancellation.dispose() + this.currentRequestCancellation = null + } + } + + private async getClient(): Promise { + if (!this.client) { + console.debug("Cline : Getting client with options:", { + vsCodeLmModelSelector: this.options.vsCodeLmModelSelector, + hasOptions: !!this.options, + selectorKeys: this.options.vsCodeLmModelSelector ? Object.keys(this.options.vsCodeLmModelSelector) : [], + }) + + try { + // Use default empty selector if none provided to get all available models + const selector = this.options?.vsCodeLmModelSelector || {} + console.debug("Cline : Creating client with selector:", selector) + this.client = await this.createClient(selector) + } catch (error) { + const message = error instanceof Error ? error.message : "Unknown error" + console.error("Cline : Client creation failed:", message) + throw new Error(`Cline : Failed to create client: ${message}`) + } + } + + return this.client + } + + private cleanTerminalOutput(text: string): string { + if (!text) { + return "" + } + + return ( + text + // Normalize line breaks + .replace(/\r\n/g, "\n") + .replace(/\r/g, "\n") + + // Remove ANSI escape sequences + .replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "") // Full set of ANSI sequences + .replace(/\x9B[0-?]*[ -/]*[@-~]/g, "") // CSI sequences + + // Remove terminal title setting sequences and other OSC sequences + .replace(/\x1B\][0-9;]*(?:\x07|\x1B\\)/g, "") + + // Remove control characters + .replace(/[\x00-\x09\x0B-\x0C\x0E-\x1F\x7F]/g, "") + + // Remove VS Code escape sequences + .replace(/\x1B[PD].*?\x1B\\/g, "") // DCS sequences + .replace(/\x1B_.*?\x1B\\/g, "") // APC sequences + .replace(/\x1B\^.*?\x1B\\/g, "") // PM sequences + .replace(/\x1B\[[\d;]*[HfABCDEFGJKST]/g, "") // Cursor movement and clear screen + + // Remove Windows paths and service information + .replace(/^(?:PS )?[A-Z]:\\[^\n]*$/gm, "") + .replace(/^;?Cwd=.*$/gm, "") + + // Clean escaped sequences + .replace(/\\x[0-9a-fA-F]{2}/g, "") + .replace(/\\u[0-9a-fA-F]{4}/g, "") + + // Final cleanup + .replace(/\n{3,}/g, "\n\n") // Remove multiple empty lines + .trim() + ) + } + + private cleanMessageContent(content: any): any { + if (!content) { + return content + } + + if (typeof content === "string") { + return this.cleanTerminalOutput(content) + } + + if (Array.isArray(content)) { + return content.map((item) => this.cleanMessageContent(item)) + } + + if (typeof content === "object") { + const cleaned: any = {} + for (const [key, value] of Object.entries(content)) { + cleaned[key] = this.cleanMessageContent(value) + } + return cleaned + } + + return content + } + + async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + // Ensure clean state before starting a new request + this.ensureCleanState() + const client: vscode.LanguageModelChat = await this.getClient() + + // Clean system prompt and messages + const cleanedSystemPrompt = this.cleanTerminalOutput(systemPrompt) + const cleanedMessages = messages.map((msg) => ({ + ...msg, + content: this.cleanMessageContent(msg.content), + })) + + // Convert Anthropic messages to VS Code LM messages + const vsCodeLmMessages: vscode.LanguageModelChatMessage[] = [ + vscode.LanguageModelChatMessage.Assistant(cleanedSystemPrompt), + ...convertToVsCodeLmMessages(cleanedMessages), + ] + + // Initialize cancellation token for the request + this.currentRequestCancellation = new vscode.CancellationTokenSource() + + // Calculate input tokens before starting the stream + const totalInputTokens: number = await this.calculateTotalInputTokens(systemPrompt, vsCodeLmMessages) + + // Accumulate the text and count at the end of the stream to reduce token counting overhead. + let accumulatedText: string = "" + + try { + // Create the response stream with minimal required options + const requestOptions: vscode.LanguageModelChatRequestOptions = { + justification: `Cline would like to use '${client.name}' from '${client.vendor}', Click 'Allow' to proceed.`, + } + + // Note: Tool support is currently provided by the VSCode Language Model API directly + // Extensions can register tools using vscode.lm.registerTool() + + const response: vscode.LanguageModelChatResponse = await client.sendRequest( + vsCodeLmMessages, + requestOptions, + this.currentRequestCancellation.token, + ) + + // Consume the stream and handle both text and tool call chunks + for await (const chunk of response.stream) { + if (chunk instanceof vscode.LanguageModelTextPart) { + // Validate text part value + if (typeof chunk.value !== "string") { + console.warn("Cline : Invalid text part value received:", chunk.value) + continue + } + + accumulatedText += chunk.value + yield { + type: "text", + text: chunk.value, + } + } else if (chunk instanceof vscode.LanguageModelToolCallPart) { + try { + // Validate tool call parameters + if (!chunk.name || typeof chunk.name !== "string") { + console.warn("Cline : Invalid tool name received:", chunk.name) + continue + } + + if (!chunk.callId || typeof chunk.callId !== "string") { + console.warn("Cline : Invalid tool callId received:", chunk.callId) + continue + } + + // Ensure input is a valid object + if (!chunk.input || typeof chunk.input !== "object") { + console.warn("Cline : Invalid tool input received:", chunk.input) + continue + } + + // Convert tool calls to text format with proper error handling + const toolCall = { + type: "tool_call", + name: chunk.name, + arguments: chunk.input, + callId: chunk.callId, + } + + const toolCallText = JSON.stringify(toolCall) + accumulatedText += toolCallText + + // Log tool call for debugging + console.debug("Cline : Processing tool call:", { + name: chunk.name, + callId: chunk.callId, + inputSize: JSON.stringify(chunk.input).length, + }) + + yield { + type: "text", + text: toolCallText, + } + } catch (error) { + console.error("Cline : Failed to process tool call:", error) + // Continue processing other chunks even if one fails + continue + } + } else { + console.warn("Cline : Unknown chunk type received:", chunk) + } + } + + // Count tokens in the accumulated text after stream completion + const totalOutputTokens: number = await this.countTokens(accumulatedText) + + // Report final usage after stream completion + yield { + type: "usage", + inputTokens: totalInputTokens, + outputTokens: totalOutputTokens, + totalCost: calculateApiCost(this.getModel().info, totalInputTokens, totalOutputTokens), + } + } catch (error: unknown) { + this.ensureCleanState() + + if (error instanceof vscode.CancellationError) { + throw new Error("Cline : Request cancelled by user") + } + + if (error instanceof Error) { + console.error("Cline : Stream error details:", { + message: error.message, + stack: error.stack, + name: error.name, + }) + + // Return original error if it's already an Error instance + throw error + } else if (typeof error === "object" && error !== null) { + // Handle error-like objects + const errorDetails = JSON.stringify(error, null, 2) + console.error("Cline : Stream error object:", errorDetails) + throw new Error(`Cline : Response stream error: ${errorDetails}`) + } else { + // Fallback for unknown error types + const errorMessage = String(error) + console.error("Cline : Unknown stream error:", errorMessage) + throw new Error(`Cline : Response stream error: ${errorMessage}`) + } + } + } + + // Return model information based on the current client state + getModel(): { id: string; info: ModelInfo } { + if (this.client) { + // Validate client properties + const requiredProps = { + id: this.client.id, + vendor: this.client.vendor, + family: this.client.family, + version: this.client.version, + maxInputTokens: this.client.maxInputTokens, + } + + // Log any missing properties for debugging + for (const [prop, value] of Object.entries(requiredProps)) { + if (!value && value !== 0) { + console.warn(`Cline : Client missing ${prop} property`) + } + } + + // Construct model ID using available information + const modelParts = [this.client.vendor, this.client.family, this.client.version].filter(Boolean) + + const modelId = this.client.id || modelParts.join(SELECTOR_SEPARATOR) + + // Build model info with conservative defaults for missing values + const modelInfo: ModelInfo = { + maxTokens: -1, // Unlimited tokens by default + contextWindow: + typeof this.client.maxInputTokens === "number" + ? Math.max(0, this.client.maxInputTokens) + : openAiModelInfoSaneDefaults.contextWindow, + supportsImages: false, // VSCode Language Model API currently doesn't support image inputs + supportsPromptCache: true, + inputPrice: 0, + outputPrice: 0, + description: `VSCode Language Model: ${modelId}`, + } + + return { id: modelId, info: modelInfo } + } + + // Fallback when no client is available + const fallbackId = this.options.vsCodeLmModelSelector + ? stringifyVsCodeLmModelSelector(this.options.vsCodeLmModelSelector) + : "vscode-lm" + + console.debug("Cline : No client available, using fallback model info") + + return { + id: fallbackId, + info: { + ...openAiModelInfoSaneDefaults, + description: `VSCode Language Model (Fallback): ${fallbackId}`, + }, + } + } + + async completePrompt(prompt: string): Promise { + try { + const client = await this.getClient() + const response = await client.sendRequest( + [vscode.LanguageModelChatMessage.User(prompt)], + {}, + new vscode.CancellationTokenSource().token, + ) + let result = "" + for await (const chunk of response.stream) { + if (chunk instanceof vscode.LanguageModelTextPart) { + result += chunk.value + } + } + return result + } catch (error) { + if (error instanceof Error) { + throw new Error(`VSCode LM completion error: ${error.message}`) + } + throw error + } + } +} diff --git a/src/api/transform/vscode-lm-format.ts b/src/api/transform/vscode-lm-format.ts new file mode 100644 index 0000000000..acec3656e1 --- /dev/null +++ b/src/api/transform/vscode-lm-format.ts @@ -0,0 +1,200 @@ +import { Anthropic } from "@anthropic-ai/sdk" +import * as vscode from "vscode" + +/** + * Safely converts a value into a plain object. + */ +function asObjectSafe(value: any): object { + // Handle null/undefined + if (!value) { + return {} + } + + try { + // Handle strings that might be JSON + if (typeof value === "string") { + return JSON.parse(value) + } + + // Handle pre-existing objects + if (typeof value === "object") { + return Object.assign({}, value) + } + + return {} + } catch (error) { + console.warn("Cline : Failed to parse object:", error) + return {} + } +} + +export function convertToVsCodeLmMessages( + anthropicMessages: Anthropic.Messages.MessageParam[], +): vscode.LanguageModelChatMessage[] { + const vsCodeLmMessages: vscode.LanguageModelChatMessage[] = [] + + for (const anthropicMessage of anthropicMessages) { + // Handle simple string messages + if (typeof anthropicMessage.content === "string") { + vsCodeLmMessages.push( + anthropicMessage.role === "assistant" + ? vscode.LanguageModelChatMessage.Assistant(anthropicMessage.content) + : vscode.LanguageModelChatMessage.User(anthropicMessage.content), + ) + continue + } + + // Handle complex message structures + switch (anthropicMessage.role) { + case "user": { + const { nonToolMessages, toolMessages } = anthropicMessage.content.reduce<{ + nonToolMessages: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[] + toolMessages: Anthropic.ToolResultBlockParam[] + }>( + (acc, part) => { + if (part.type === "tool_result") { + acc.toolMessages.push(part) + } else if (part.type === "text" || part.type === "image") { + acc.nonToolMessages.push(part) + } + return acc + }, + { nonToolMessages: [], toolMessages: [] }, + ) + + // Process tool messages first then non-tool messages + const contentParts = [ + // Convert tool messages to ToolResultParts + ...toolMessages.map((toolMessage) => { + // Process tool result content into TextParts + const toolContentParts: vscode.LanguageModelTextPart[] = + typeof toolMessage.content === "string" + ? [new vscode.LanguageModelTextPart(toolMessage.content)] + : (toolMessage.content?.map((part) => { + if (part.type === "image") { + return new vscode.LanguageModelTextPart( + `[Image (${part.source?.type || "Unknown source-type"}): ${part.source?.media_type || "unknown media-type"} not supported by VSCode LM API]`, + ) + } + return new vscode.LanguageModelTextPart(part.text) + }) ?? [new vscode.LanguageModelTextPart("")]) + + return new vscode.LanguageModelToolResultPart(toolMessage.tool_use_id, toolContentParts) + }), + + // Convert non-tool messages to TextParts after tool messages + ...nonToolMessages.map((part) => { + if (part.type === "image") { + return new vscode.LanguageModelTextPart( + `[Image (${part.source?.type || "Unknown source-type"}): ${part.source?.media_type || "unknown media-type"} not supported by VSCode LM API]`, + ) + } + return new vscode.LanguageModelTextPart(part.text) + }), + ] + + // Add single user message with all content parts + vsCodeLmMessages.push(vscode.LanguageModelChatMessage.User(contentParts)) + break + } + + case "assistant": { + const { nonToolMessages, toolMessages } = anthropicMessage.content.reduce<{ + nonToolMessages: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[] + toolMessages: Anthropic.ToolUseBlockParam[] + }>( + (acc, part) => { + if (part.type === "tool_use") { + acc.toolMessages.push(part) + } else if (part.type === "text" || part.type === "image") { + acc.nonToolMessages.push(part) + } + return acc + }, + { nonToolMessages: [], toolMessages: [] }, + ) + + // Process tool messages first then non-tool messages + const contentParts = [ + // Convert tool messages to ToolCallParts first + ...toolMessages.map( + (toolMessage) => + new vscode.LanguageModelToolCallPart( + toolMessage.id, + toolMessage.name, + asObjectSafe(toolMessage.input), + ), + ), + + // Convert non-tool messages to TextParts after tool messages + ...nonToolMessages.map((part) => { + if (part.type === "image") { + return new vscode.LanguageModelTextPart("[Image generation not supported by VSCode LM API]") + } + return new vscode.LanguageModelTextPart(part.text) + }), + ] + + // Add the assistant message to the list of messages + vsCodeLmMessages.push(vscode.LanguageModelChatMessage.Assistant(contentParts)) + break + } + } + } + + return vsCodeLmMessages +} + +export function convertToAnthropicRole(vsCodeLmMessageRole: vscode.LanguageModelChatMessageRole): string | null { + switch (vsCodeLmMessageRole) { + case vscode.LanguageModelChatMessageRole.Assistant: + return "assistant" + case vscode.LanguageModelChatMessageRole.User: + return "user" + default: + return null + } +} + +export async function convertToAnthropicMessage( + vsCodeLmMessage: vscode.LanguageModelChatMessage, +): Promise { + const anthropicRole: string | null = convertToAnthropicRole(vsCodeLmMessage.role) + if (anthropicRole !== "assistant") { + throw new Error("Cline : Only assistant messages are supported.") + } + + return { + id: crypto.randomUUID(), + type: "message", + model: "vscode-lm", + role: anthropicRole, + content: vsCodeLmMessage.content + .map((part): Anthropic.ContentBlock | null => { + if (part instanceof vscode.LanguageModelTextPart) { + return { + type: "text", + text: part.value, + } + } + + if (part instanceof vscode.LanguageModelToolCallPart) { + return { + type: "tool_use", + id: part.callId || crypto.randomUUID(), + name: part.name, + input: asObjectSafe(part.input), + } + } + + return null + }) + .filter((part): part is Anthropic.ContentBlock => part !== null), + stop_reason: null, + stop_sequence: null, + usage: { + input_tokens: 0, + output_tokens: 0, + }, + } +} diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 105f51091e..616c06e4de 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -69,6 +69,7 @@ type GlobalStateKey = | "autoApprovalSettings" | "browserSettings" | "chatSettings" + | "vsCodeLmModelSelector" export const GlobalFileNames = { apiConversationHistory: "api_conversation_history.json", @@ -424,6 +425,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { openRouterModelInfo, openRouterAdvisorModelId, openRouterAdvisorModelInfo, + vsCodeLmModelSelector, } = message.apiConfiguration await this.updateGlobalState("apiProvider", apiProvider) await this.updateGlobalState("apiModelId", apiModelId) @@ -454,6 +456,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { await this.updateGlobalState("openRouterModelInfo", openRouterModelInfo) await this.updateGlobalState("openRouterAdvisorModelId", openRouterAdvisorModelId) await this.updateGlobalState("openRouterAdvisorModelInfo", openRouterAdvisorModelInfo) + await this.updateGlobalState("vsCodeLmModelSelector", vsCodeLmModelSelector) if (this.cline) { this.cline.api = buildApiHandler(message.apiConfiguration) } @@ -547,6 +550,10 @@ export class ClineProvider implements vscode.WebviewViewProvider { lmStudioModels, }) break + case "requestVsCodeLmModels": + const vsCodeLmModels = await this.getVsCodeLmModels() + this.postMessageToWebview({ type: "vsCodeLmModels", vsCodeLmModels }) + break case "refreshOpenRouterModels": await this.refreshOpenRouterModels() break @@ -674,6 +681,18 @@ export class ClineProvider implements vscode.WebviewViewProvider { return settingsDir } + // VSCode LM API + + private async getVsCodeLmModels() { + try { + const models = await vscode.lm.selectChatModels({}) + return models || [] + } catch (error) { + console.error("Error fetching VS Code LM models:", error) + return [] + } + } + // Ollama async getOllamaModels(baseUrl?: string) { @@ -1090,6 +1109,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { autoApprovalSettings, browserSettings, chatSettings, + vsCodeLmModelSelector, ] = await Promise.all([ this.getGlobalState("apiProvider") as Promise, this.getGlobalState("apiModelId") as Promise, @@ -1126,6 +1146,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { this.getGlobalState("autoApprovalSettings") as Promise, this.getGlobalState("browserSettings") as Promise, this.getGlobalState("chatSettings") as Promise, + this.getGlobalState("vsCodeLmModelSelector") as Promise, ]) let apiProvider: ApiProvider @@ -1173,6 +1194,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { openRouterModelInfo, openRouterAdvisorModelId, openRouterAdvisorModelInfo, + vsCodeLmModelSelector, }, lastShownAnnouncementId, customInstructions, diff --git a/src/integrations/terminal/TerminalManager.ts b/src/integrations/terminal/TerminalManager.ts index 81e91ab6b8..2de5be3a6f 100644 --- a/src/integrations/terminal/TerminalManager.ts +++ b/src/integrations/terminal/TerminalManager.ts @@ -71,14 +71,14 @@ This approach allows us to leverage advanced features when available while ensur */ declare module "vscode" { // https://github.com/microsoft/vscode/blob/f0417069c62e20f3667506f4b7e53ca0004b4e3e/src/vscode-dts/vscode.d.ts#L7442 - interface Terminal { - shellIntegration?: { - cwd?: vscode.Uri - executeCommand?: (command: string) => { - read: () => AsyncIterable - } - } - } + // interface Terminal { + // shellIntegration?: { + // cwd?: vscode.Uri + // executeCommand?: (command: string) => { + // read: () => AsyncIterable + // } + // } + // } // https://github.com/microsoft/vscode/blob/f0417069c62e20f3667506f4b7e53ca0004b4e3e/src/vscode-dts/vscode.d.ts#L10794 interface Window { onDidStartTerminalShellExecution?: ( diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 3f6670b4f2..06b28e8823 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -23,6 +23,8 @@ export interface ExtensionMessage { | "mcpServers" | "relinquishControl" | "openAdvisorModelSettings" + | "vsCodeLmModels" + | "requestVsCodeLmModels" text?: string action?: "chatButtonClicked" | "mcpButtonClicked" | "settingsButtonClicked" | "historyButtonClicked" | "didBecomeVisible" invoke?: "sendMessage" | "primaryButtonClick" | "secondaryButtonClick" @@ -30,6 +32,7 @@ export interface ExtensionMessage { images?: string[] ollamaModels?: string[] lmStudioModels?: string[] + vsCodeLmModels?: { vendor?: string; family?: string; version?: string; id?: string }[] filePaths?: string[] partialMessage?: ClineMessage openRouterModels?: Record diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index b18738316b..897dabbb86 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -34,6 +34,7 @@ export interface WebviewMessage { | "checkpointRestore" | "taskCompletionViewChanges" | "openAdvisorModelSettings" + | "requestVsCodeLmModels" // | "relaunchChromeDebugMode" text?: string askResponse?: ClineAskResponse diff --git a/src/shared/api.ts b/src/shared/api.ts index 013a063777..139c5e0544 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -10,6 +10,7 @@ export type ApiProvider = | "openai-native" | "deepseek" | "mistral" + | "vscode-lm" export interface ApiHandlerOptions { apiModelId?: string @@ -40,6 +41,7 @@ export interface ApiHandlerOptions { deepSeekApiKey?: string mistralApiKey?: string azureApiVersion?: string + vsCodeLmModelSelector?: any } export type ApiConfiguration = ApiHandlerOptions & { diff --git a/src/shared/vsCodeSelectorUtils.ts b/src/shared/vsCodeSelectorUtils.ts new file mode 100644 index 0000000000..620fccccd8 --- /dev/null +++ b/src/shared/vsCodeSelectorUtils.ts @@ -0,0 +1,7 @@ +import { LanguageModelChatSelector } from "vscode" + +export const SELECTOR_SEPARATOR = "/" + +export function stringifyVsCodeLmModelSelector(selector: LanguageModelChatSelector): string { + return [selector.vendor, selector.family, selector.version, selector.id].filter(Boolean).join(SELECTOR_SEPARATOR) +} diff --git a/webview-ui/src/components/chat/TaskHeader.tsx b/webview-ui/src/components/chat/TaskHeader.tsx index 253ecef5df..cd7280737b 100644 --- a/webview-ui/src/components/chat/TaskHeader.tsx +++ b/webview-ui/src/components/chat/TaskHeader.tsx @@ -96,6 +96,7 @@ const TaskHeader: React.FC = ({ const isCostAvailable = useMemo(() => { return ( apiConfiguration?.apiProvider !== "openai" && + apiConfiguration?.apiProvider !== "vscode-lm" && apiConfiguration?.apiProvider !== "ollama" && apiConfiguration?.apiProvider !== "lmstudio" && apiConfiguration?.apiProvider !== "gemini" diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 2588bd10b9..0aace65eb4 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -42,6 +42,7 @@ import { vscode } from "../../utils/vscode" import VSCodeButtonLink from "../common/VSCodeButtonLink" import OpenRouterModelPicker, { ModelDescriptionMarkdown, OPENROUTER_MODEL_PICKER_Z_INDEX } from "./OpenRouterModelPicker" import styled from "styled-components" +import * as vscodemodels from "vscode" interface ApiOptionsProps { showModelOptions: boolean @@ -97,6 +98,7 @@ const ApiOptions = ({ const { apiConfiguration, setApiConfiguration, uriScheme } = useExtensionState() const [ollamaModels, setOllamaModels] = useState([]) const [lmStudioModels, setLmStudioModels] = useState([]) + const [vsCodeLmModels, setVsCodeLmModels] = useState([]) const [anthropicBaseUrlSelected, setAnthropicBaseUrlSelected] = useState(!!apiConfiguration?.anthropicBaseUrl) const [azureApiVersionSelected, setAzureApiVersionSelected] = useState(!!apiConfiguration?.azureApiVersion) const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false) @@ -125,14 +127,19 @@ const ApiOptions = ({ type: "requestLmStudioModels", text: apiConfiguration?.lmStudioBaseUrl, }) + } else if (selectedProvider === "vscode-lm") { + vscode.postMessage({ type: "requestVsCodeLmModels" }) } }, [selectedProvider, apiConfiguration?.ollamaBaseUrl, apiConfiguration?.lmStudioBaseUrl]) useEffect(() => { - if (selectedProvider === "ollama" || selectedProvider === "lmstudio") { + if (selectedProvider === "ollama" || selectedProvider === "lmstudio" || selectedProvider === "vscode-lm") { requestLocalModels() } }, [selectedProvider, requestLocalModels]) - useInterval(requestLocalModels, selectedProvider === "ollama" || selectedProvider === "lmstudio" ? 2000 : null) + useInterval( + requestLocalModels, + selectedProvider === "ollama" || selectedProvider === "lmstudio" || selectedProvider === "vscode-lm" ? 2000 : null, + ) const handleMessage = useCallback((event: MessageEvent) => { const message: ExtensionMessage = event.data @@ -140,6 +147,8 @@ const ApiOptions = ({ setOllamaModels(message.ollamaModels) } else if (message.type === "lmStudioModels" && message.lmStudioModels) { setLmStudioModels(message.lmStudioModels) + } else if (message.type === "vsCodeLmModels" && message.vsCodeLmModels) { + setVsCodeLmModels(message.vsCodeLmModels) } }, []) useEvent("message", handleMessage) @@ -204,6 +213,7 @@ const ApiOptions = ({ AWS Bedrock OpenAI OpenAI Compatible + VS Code LM API LM Studio Ollama @@ -630,6 +640,68 @@ const ApiOptions = ({
)} + {selectedProvider === "vscode-lm" && ( +
+
+ + {vsCodeLmModels.length > 0 ? ( + { + const value = (e.target as HTMLInputElement).value + if (!value) { + return + } + const [vendor, family] = value.split("/") + handleInputChange("vsCodeLmModelSelector")({ + target: { + value: { vendor, family }, + }, + }) + }} + style={{ width: "100%" }}> + Select a model... + {vsCodeLmModels.map((model) => ( + + {model.vendor} - {model.family} + + ))} + + ) : ( +

+ The VS Code Language Model API allows you to run models provided by other VS Code extensions + (including but not limited to GitHub Copilot). The easiest way to get started is to install the + Copilot extension from the VS Marketplace and enabling Claude 3.5 Sonnet. +

+ )} + +

+ Note: This is a very experimental integration and may not work as expected. +

+
+
+ )} + {selectedProvider === "lmstudio" && (
@@ -1089,6 +1162,17 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration): selectedModelId: apiConfiguration?.lmStudioModelId || "", selectedModelInfo: openAiModelInfoSaneDefaults, } + case "vscode-lm": + return { + selectedProvider: provider, + selectedModelId: apiConfiguration?.vsCodeLmModelSelector + ? `${apiConfiguration.vsCodeLmModelSelector.vendor}/${apiConfiguration.vsCodeLmModelSelector.family}` + : "", + selectedModelInfo: { + ...openAiModelInfoSaneDefaults, + supportsImages: false, // VSCode LM API currently doesn't support images + }, + } default: return getProviderData(anthropicModels, anthropicDefaultModelId) } diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index 425b35db88..69e67f1a3d 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -72,6 +72,7 @@ export const ExtensionStateContextProvider: React.FC<{ config.openAiNativeApiKey, config.deepSeekApiKey, config.mistralApiKey, + config.vsCodeLmModelSelector, ].some((key) => key !== undefined) : false setShowWelcome(!hasKey) diff --git a/webview-ui/src/utils/validate.ts b/webview-ui/src/utils/validate.ts index 302c45d6a2..e0b06429e1 100644 --- a/webview-ui/src/utils/validate.ts +++ b/webview-ui/src/utils/validate.ts @@ -58,6 +58,11 @@ export function validateApiConfiguration(apiConfiguration?: ApiConfiguration): s return "You must provide a valid model ID." } break + case "vscode-lm": + if (!apiConfiguration.vsCodeLmModelSelector) { + return "You must provide a valid model selector." + } + break } } return undefined From d9e1031f8597b7fc5268a672a178cfe63be817f2 Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Sun, 19 Jan 2025 13:43:58 -0800 Subject: [PATCH 16/49] Add auto approve settings for mcp tools --- src/core/Cline.ts | 9 +++- src/core/webview/ClineProvider.ts | 8 +++ src/services/mcp/McpHub.ts | 57 +++++++++++++++++++- src/shared/WebviewMessage.ts | 6 +++ src/shared/mcp.ts | 1 + webview-ui/src/components/chat/ChatRow.tsx | 20 ++++--- webview-ui/src/components/mcp/McpToolRow.tsx | 34 ++++++++++-- webview-ui/src/components/mcp/McpView.tsx | 2 +- 8 files changed, 123 insertions(+), 14 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 721e95799e..ff016906e3 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -2489,7 +2489,14 @@ export class Cline { arguments: mcp_arguments, } satisfies ClineAskUseMcpServer) - if (this.shouldAutoApproveTool(block.name)) { + const isToolAlwaysAllowed = this.providerRef + .deref() + ?.mcpHub?.connections?.find((conn) => conn.server.name === server_name) + ?.server.tools?.find((tool) => tool.name === tool_name)?.alwaysAllow + + // console.log("isToolAlwaysAllowed", server_name, tool_name, isToolAlwaysAllowed) + + if (this.shouldAutoApproveTool(block.name) && isToolAlwaysAllowed) { this.removeLastPartialMessageIfExistsWithType("ask", "use_mcp_server") await this.say("use_mcp_server", completeMessage, undefined, false) this.consecutiveAutoApprovedRequestsCount++ diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 616c06e4de..8828c3e6a4 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -608,6 +608,14 @@ export class ClineProvider implements vscode.WebviewViewProvider { } break } + case "toggleToolAlwaysAllow": { + try { + await this.mcpHub?.toggleToolAlwaysAllow(message.serverName!, message.toolName!, message.alwaysAllow!) + } catch (error) { + console.error(`Failed to toggle auto-approve for tool ${message.toolName}:`, error) + } + break + } case "restartMcpServer": { try { await this.mcpHub?.restartConnection(message.text!) diff --git a/src/services/mcp/McpHub.ts b/src/services/mcp/McpHub.ts index 59dd3bf38b..bca3bfa958 100644 --- a/src/services/mcp/McpHub.ts +++ b/src/services/mcp/McpHub.ts @@ -25,11 +25,14 @@ export type McpConnection = { transport: StdioClientTransport } +const AlwaysAllowSchema = z.array(z.string()).default([]) + // StdioServerParameters const StdioConfigSchema = z.object({ command: z.string(), args: z.array(z.string()).optional(), env: z.record(z.string()).optional(), + alwaysAllow: AlwaysAllowSchema.optional(), }) const McpSettingsSchema = z.object({ @@ -275,7 +278,21 @@ export class McpHub { const response = await this.connections .find((conn) => conn.server.name === serverName) ?.client.request({ method: "tools/list" }, ListToolsResultSchema) - return response?.tools || [] + + // Get always allow settings + const settingsPath = await this.getMcpSettingsFilePath() + const content = await fs.readFile(settingsPath, "utf-8") + const config = JSON.parse(content) + const alwaysAllowConfig = config.mcpServers[serverName]?.alwaysAllow || [] + + // Mark tools as always allowed based on settings + const tools = (response?.tools || []).map((tool) => ({ + ...tool, + alwaysAllow: alwaysAllowConfig.includes(tool.name), + })) + + // console.log(`[MCP] Fetched tools for ${serverName}:`, tools) + return tools } catch (error) { // console.error(`Failed to fetch tools for ${serverName}:`, error) return [] @@ -476,6 +493,44 @@ export class McpHub { ) } + async toggleToolAlwaysAllow(serverName: string, toolName: string, shouldAllow: boolean): Promise { + try { + const settingsPath = await this.getMcpSettingsFilePath() + const content = await fs.readFile(settingsPath, "utf-8") + const config = JSON.parse(content) + + // Initialize alwaysAllow if it doesn't exist + if (!config.mcpServers[serverName].alwaysAllow) { + config.mcpServers[serverName].alwaysAllow = [] + } + + const alwaysAllow = config.mcpServers[serverName].alwaysAllow + const toolIndex = alwaysAllow.indexOf(toolName) + + if (shouldAllow && toolIndex === -1) { + // Add tool to always allow list + alwaysAllow.push(toolName) + } else if (!shouldAllow && toolIndex !== -1) { + // Remove tool from always allow list + alwaysAllow.splice(toolIndex, 1) + } + + // Write updated config back to file + await fs.writeFile(settingsPath, JSON.stringify(config, null, 2)) + + // Update the tools list to reflect the change + const connection = this.connections.find((conn) => conn.server.name === serverName) + if (connection) { + connection.server.tools = await this.fetchToolsList(serverName) + await this.notifyWebviewOfServerChanges() + } + } catch (error) { + console.error("Failed to update always allow settings:", error) + vscode.window.showErrorMessage("Failed to update always allow settings") + throw error // Re-throw to ensure the error is properly handled + } + } + async dispose(): Promise { this.removeAllFileWatchers() for (const connection of this.connections) { diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 897dabbb86..a408f134a3 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -35,6 +35,7 @@ export interface WebviewMessage { | "taskCompletionViewChanges" | "openAdvisorModelSettings" | "requestVsCodeLmModels" + | "toggleToolAlwaysAllow" // | "relaunchChromeDebugMode" text?: string askResponse?: ClineAskResponse @@ -45,6 +46,11 @@ export interface WebviewMessage { autoApprovalSettings?: AutoApprovalSettings browserSettings?: BrowserSettings chatSettings?: ChatSettings + + // For toggleToolAutoApprove + serverName?: string + toolName?: string + alwaysAllow?: boolean } export type ClineAskResponse = "yesButtonClicked" | "noButtonClicked" | "messageResponse" diff --git a/src/shared/mcp.ts b/src/shared/mcp.ts index 82efae2f72..a00b34328b 100644 --- a/src/shared/mcp.ts +++ b/src/shared/mcp.ts @@ -12,6 +12,7 @@ export type McpTool = { name: string description?: string inputSchema?: object + alwaysAllow?: boolean } export type McpResource = { diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index a063905da4..8034142459 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -712,13 +712,19 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi {useMcpServer.type === "use_mcp_tool" && ( <> - tool.name === useMcpServer.toolName)?.description || "", - }} - /> +
e.stopPropagation()}> + tool.name === useMcpServer.toolName)?.description || "", + alwaysAllow: + server?.tools?.find((tool) => tool.name === useMcpServer.toolName)?.alwaysAllow || + false, + }} + serverName={useMcpServer.serverName} + /> +
{useMcpServer.arguments && useMcpServer.arguments !== "{}" && (
{ +const McpToolRow = ({ tool, serverName }: McpToolRowProps) => { + const { autoApprovalSettings } = useExtensionState() + + const handleAlwaysAllowChange = () => { + if (!serverName) return + + vscode.postMessage({ + type: "toggleToolAlwaysAllow", + serverName, + toolName: tool.name, + alwaysAllow: !tool.alwaysAllow, + }) + } return (
-
- - {tool.name} +
e.stopPropagation()}> +
+ + {tool.name} +
+ {serverName && autoApprovalSettings.enabled && autoApprovalSettings.actions.useMcp && ( + + Always allow + + )}
{tool.description && (
{ width: "100%", }}> {server.tools.map((tool) => ( - + ))}
) : ( From 89b9b56b7499f0c5e4f678ec605bde29662ae766 Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Sun, 19 Jan 2025 13:57:16 -0800 Subject: [PATCH 17/49] Fix followup/response button behavior --- webview-ui/src/components/chat/ChatView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 06ff6af2e0..ef51c676a5 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -99,14 +99,14 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie case "followup": setTextAreaDisabled(isPartial) setClineAsk("followup") - setEnableButtons(isPartial) + setEnableButtons(false) // setPrimaryButtonText(undefined) // setSecondaryButtonText(undefined) break case "respond_to_inquiry": setTextAreaDisabled(isPartial) setClineAsk("respond_to_inquiry") - setEnableButtons(isPartial) + setEnableButtons(false) // setPrimaryButtonText(undefined) // setSecondaryButtonText(undefined) break From fc82e95beb7cd7cba5e236d0cca041f651133eac Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Sun, 19 Jan 2025 14:19:27 -0800 Subject: [PATCH 18/49] Enable/disable MCP servers --- src/core/prompts/system.ts | 2 + src/core/webview/ClineProvider.ts | 8 ++ src/services/mcp/McpHub.ts | 91 ++++++++++++++++++++++- src/shared/WebviewMessage.ts | 2 + src/shared/mcp.ts | 1 + webview-ui/src/components/mcp/McpView.tsx | 50 +++++++++++++ 6 files changed, 153 insertions(+), 1 deletion(-) diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index a0fe39e7e1..cf2b3d1c62 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -717,6 +717,8 @@ npm run build 5. Install the MCP Server by adding the MCP server configuration to the settings file located at '${await mcpHub.getMcpSettingsFilePath()}'. The settings file may have other MCP servers already configured, so you would read it first and then add your new server to the existing \`mcpServers\` object. +IMPORTANT: Regardless of what else you see in the MCP settings file, you must default any new MCP servers you create to disabled=false and alwaysAllow=[]. + \`\`\`json { "mcpServers": { diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 8828c3e6a4..18fcd34ddf 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -608,6 +608,14 @@ export class ClineProvider implements vscode.WebviewViewProvider { } break } + case "toggleMcpServer": { + try { + await this.mcpHub?.toggleServerDisabled(message.serverName!, message.disabled!) + } catch (error) { + console.error(`Failed to toggle MCP server ${message.serverName}:`, error) + } + break + } case "toggleToolAlwaysAllow": { try { await this.mcpHub?.toggleToolAlwaysAllow(message.serverName!, message.toolName!, message.alwaysAllow!) diff --git a/src/services/mcp/McpHub.ts b/src/services/mcp/McpHub.ts index bca3bfa958..f3f5420192 100644 --- a/src/services/mcp/McpHub.ts +++ b/src/services/mcp/McpHub.ts @@ -33,6 +33,7 @@ const StdioConfigSchema = z.object({ args: z.array(z.string()).optional(), env: z.record(z.string()).optional(), alwaysAllow: AlwaysAllowSchema.optional(), + disabled: z.boolean().optional(), }) const McpSettingsSchema = z.object({ @@ -54,7 +55,8 @@ export class McpHub { } getServers(): McpServer[] { - return this.connections.map((conn) => conn.server) + // Only return enabled servers + return this.connections.filter((conn) => !conn.server.disabled).map((conn) => conn.server) } async getMcpServersPath(): Promise { @@ -192,11 +194,13 @@ export class McpHub { } // valid schema + const parsedConfig = StdioConfigSchema.parse(config) const connection: McpConnection = { server: { name, config: JSON.stringify(config), status: "connecting", + disabled: parsedConfig.disabled, }, client, transport, @@ -458,11 +462,91 @@ export class McpHub { // Using server + // Public methods for server management + + public async toggleServerDisabled(serverName: string, disabled: boolean): Promise { + let settingsPath: string + try { + settingsPath = await this.getMcpSettingsFilePath() + + // Ensure the settings file exists and is accessible + try { + await fs.access(settingsPath) + } catch (error) { + console.error("Settings file not accessible:", error) + throw new Error("Settings file not accessible") + } + const content = await fs.readFile(settingsPath, "utf-8") + const config = JSON.parse(content) + + // Validate the config structure + if (!config || typeof config !== "object") { + throw new Error("Invalid config structure") + } + + if (!config.mcpServers || typeof config.mcpServers !== "object") { + config.mcpServers = {} + } + + if (config.mcpServers[serverName]) { + // Create a new server config object to ensure clean structure + const serverConfig = { + ...config.mcpServers[serverName], + disabled, + } + + // Ensure required fields exist + if (!serverConfig.alwaysAllow) { + serverConfig.alwaysAllow = [] + } + + config.mcpServers[serverName] = serverConfig + + // Write the entire config back + const updatedConfig = { + mcpServers: config.mcpServers, + } + + await fs.writeFile(settingsPath, JSON.stringify(updatedConfig, null, 2)) + + const connection = this.connections.find((conn) => conn.server.name === serverName) + if (connection) { + try { + connection.server.disabled = disabled + + // Only refresh capabilities if connected + if (connection.server.status === "connected") { + connection.server.tools = await this.fetchToolsList(serverName) + connection.server.resources = await this.fetchResourcesList(serverName) + connection.server.resourceTemplates = await this.fetchResourceTemplatesList(serverName) + } + } catch (error) { + console.error(`Failed to refresh capabilities for ${serverName}:`, error) + } + } + + await this.notifyWebviewOfServerChanges() + } + } catch (error) { + console.error("Failed to update server disabled state:", error) + if (error instanceof Error) { + console.error("Error details:", error.message, error.stack) + } + vscode.window.showErrorMessage( + `Failed to update server state: ${error instanceof Error ? error.message : String(error)}`, + ) + throw error + } + } + async readResource(serverName: string, uri: string): Promise { const connection = this.connections.find((conn) => conn.server.name === serverName) if (!connection) { throw new Error(`No connection found for server: ${serverName}`) } + if (connection.server.disabled) { + throw new Error(`Server "${serverName}" is disabled`) + } return await connection.client.request( { method: "resources/read", @@ -481,6 +565,11 @@ export class McpHub { `No connection found for server: ${serverName}. Please make sure to use MCP servers available under 'Connected MCP Servers'.`, ) } + + if (connection.server.disabled) { + throw new Error(`Server "${serverName}" is disabled and cannot be used`) + } + return await connection.client.request( { method: "tools/call", diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index a408f134a3..5eb54169d5 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -36,8 +36,10 @@ export interface WebviewMessage { | "openAdvisorModelSettings" | "requestVsCodeLmModels" | "toggleToolAlwaysAllow" + | "toggleMcpServer" // | "relaunchChromeDebugMode" text?: string + disabled?: boolean askResponse?: ClineAskResponse apiConfiguration?: ApiConfiguration images?: string[] diff --git a/src/shared/mcp.ts b/src/shared/mcp.ts index a00b34328b..7df1415cf4 100644 --- a/src/shared/mcp.ts +++ b/src/shared/mcp.ts @@ -6,6 +6,7 @@ export type McpServer = { tools?: McpTool[] resources?: McpResource[] resourceTemplates?: McpResourceTemplate[] + disabled?: boolean } export type McpTool = { diff --git a/webview-ui/src/components/mcp/McpView.tsx b/webview-ui/src/components/mcp/McpView.tsx index 532282914c..993d060954 100644 --- a/webview-ui/src/components/mcp/McpView.tsx +++ b/webview-ui/src/components/mcp/McpView.tsx @@ -190,12 +190,62 @@ const ServerRow = ({ server }: { server: McpServer }) => { background: "var(--vscode-textCodeBlock-background)", cursor: server.error ? "default" : "pointer", borderRadius: isExpanded || server.error ? "4px 4px 0 0" : "4px", + opacity: server.disabled ? 0.6 : 1, }} onClick={handleRowClick}> {!server.error && ( )} {server.name} +
e.stopPropagation()}> +
{ + vscode.postMessage({ + type: "toggleMcpServer", + serverName: server.name, + disabled: !server.disabled, + }) + }} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault() + vscode.postMessage({ + type: "toggleMcpServer", + serverName: server.name, + disabled: !server.disabled, + }) + } + }}> +
+
+
Date: Sun, 19 Jan 2025 14:32:42 -0800 Subject: [PATCH 19/49] Rename alwaysAllow to autoApprove --- src/core/Cline.ts | 8 ++--- src/core/prompts/system.ts | 2 +- src/core/webview/ClineProvider.ts | 4 +-- src/services/mcp/McpHub.ts | 38 ++++++++++---------- src/shared/WebviewMessage.ts | 4 +-- src/shared/mcp.ts | 2 +- webview-ui/src/components/chat/ChatRow.tsx | 4 +-- webview-ui/src/components/mcp/McpToolRow.tsx | 10 +++--- 8 files changed, 35 insertions(+), 37 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index ff016906e3..e8d34262df 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -2489,14 +2489,12 @@ export class Cline { arguments: mcp_arguments, } satisfies ClineAskUseMcpServer) - const isToolAlwaysAllowed = this.providerRef + const isToolAutoApproved = this.providerRef .deref() ?.mcpHub?.connections?.find((conn) => conn.server.name === server_name) - ?.server.tools?.find((tool) => tool.name === tool_name)?.alwaysAllow + ?.server.tools?.find((tool) => tool.name === tool_name)?.autoApprove - // console.log("isToolAlwaysAllowed", server_name, tool_name, isToolAlwaysAllowed) - - if (this.shouldAutoApproveTool(block.name) && isToolAlwaysAllowed) { + if (this.shouldAutoApproveTool(block.name) && isToolAutoApproved) { this.removeLastPartialMessageIfExistsWithType("ask", "use_mcp_server") await this.say("use_mcp_server", completeMessage, undefined, false) this.consecutiveAutoApprovedRequestsCount++ diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index cf2b3d1c62..0a5b66b875 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -717,7 +717,7 @@ npm run build 5. Install the MCP Server by adding the MCP server configuration to the settings file located at '${await mcpHub.getMcpSettingsFilePath()}'. The settings file may have other MCP servers already configured, so you would read it first and then add your new server to the existing \`mcpServers\` object. -IMPORTANT: Regardless of what else you see in the MCP settings file, you must default any new MCP servers you create to disabled=false and alwaysAllow=[]. +IMPORTANT: Regardless of what else you see in the MCP settings file, you must default any new MCP servers you create to disabled=false and autoApprove=[]. \`\`\`json { diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 18fcd34ddf..111d71caab 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -616,9 +616,9 @@ export class ClineProvider implements vscode.WebviewViewProvider { } break } - case "toggleToolAlwaysAllow": { + case "toggleToolAutoApprove": { try { - await this.mcpHub?.toggleToolAlwaysAllow(message.serverName!, message.toolName!, message.alwaysAllow!) + await this.mcpHub?.toggleToolAutoApprove(message.serverName!, message.toolName!, message.autoApprove!) } catch (error) { console.error(`Failed to toggle auto-approve for tool ${message.toolName}:`, error) } diff --git a/src/services/mcp/McpHub.ts b/src/services/mcp/McpHub.ts index f3f5420192..2ea31b830d 100644 --- a/src/services/mcp/McpHub.ts +++ b/src/services/mcp/McpHub.ts @@ -25,14 +25,14 @@ export type McpConnection = { transport: StdioClientTransport } -const AlwaysAllowSchema = z.array(z.string()).default([]) +const AutoApproveSchema = z.array(z.string()).default([]) // StdioServerParameters const StdioConfigSchema = z.object({ command: z.string(), args: z.array(z.string()).optional(), env: z.record(z.string()).optional(), - alwaysAllow: AlwaysAllowSchema.optional(), + autoApprove: AutoApproveSchema.optional(), disabled: z.boolean().optional(), }) @@ -283,16 +283,16 @@ export class McpHub { .find((conn) => conn.server.name === serverName) ?.client.request({ method: "tools/list" }, ListToolsResultSchema) - // Get always allow settings + // Get autoApprove settings const settingsPath = await this.getMcpSettingsFilePath() const content = await fs.readFile(settingsPath, "utf-8") const config = JSON.parse(content) - const alwaysAllowConfig = config.mcpServers[serverName]?.alwaysAllow || [] + const autoApproveConfig = config.mcpServers[serverName]?.autoApprove || [] // Mark tools as always allowed based on settings const tools = (response?.tools || []).map((tool) => ({ ...tool, - alwaysAllow: alwaysAllowConfig.includes(tool.name), + autoApprove: autoApproveConfig.includes(tool.name), })) // console.log(`[MCP] Fetched tools for ${serverName}:`, tools) @@ -496,8 +496,8 @@ export class McpHub { } // Ensure required fields exist - if (!serverConfig.alwaysAllow) { - serverConfig.alwaysAllow = [] + if (!serverConfig.autoApprove) { + serverConfig.autoApprove = [] } config.mcpServers[serverName] = serverConfig @@ -582,26 +582,26 @@ export class McpHub { ) } - async toggleToolAlwaysAllow(serverName: string, toolName: string, shouldAllow: boolean): Promise { + async toggleToolAutoApprove(serverName: string, toolName: string, shouldAllow: boolean): Promise { try { const settingsPath = await this.getMcpSettingsFilePath() const content = await fs.readFile(settingsPath, "utf-8") const config = JSON.parse(content) - // Initialize alwaysAllow if it doesn't exist - if (!config.mcpServers[serverName].alwaysAllow) { - config.mcpServers[serverName].alwaysAllow = [] + // Initialize autoApprove if it doesn't exist + if (!config.mcpServers[serverName].autoApprove) { + config.mcpServers[serverName].autoApprove = [] } - const alwaysAllow = config.mcpServers[serverName].alwaysAllow - const toolIndex = alwaysAllow.indexOf(toolName) + const autoApprove = config.mcpServers[serverName].autoApprove + const toolIndex = autoApprove.indexOf(toolName) if (shouldAllow && toolIndex === -1) { - // Add tool to always allow list - alwaysAllow.push(toolName) + // Add tool to autoApprove list + autoApprove.push(toolName) } else if (!shouldAllow && toolIndex !== -1) { - // Remove tool from always allow list - alwaysAllow.splice(toolIndex, 1) + // Remove tool from autoApprove list + autoApprove.splice(toolIndex, 1) } // Write updated config back to file @@ -614,8 +614,8 @@ export class McpHub { await this.notifyWebviewOfServerChanges() } } catch (error) { - console.error("Failed to update always allow settings:", error) - vscode.window.showErrorMessage("Failed to update always allow settings") + console.error("Failed to update autoApprove settings:", error) + vscode.window.showErrorMessage("Failed to update autoApprove settings") throw error // Re-throw to ensure the error is properly handled } } diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 5eb54169d5..50ae6ad8fd 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -35,7 +35,7 @@ export interface WebviewMessage { | "taskCompletionViewChanges" | "openAdvisorModelSettings" | "requestVsCodeLmModels" - | "toggleToolAlwaysAllow" + | "toggleToolAutoApprove" | "toggleMcpServer" // | "relaunchChromeDebugMode" text?: string @@ -52,7 +52,7 @@ export interface WebviewMessage { // For toggleToolAutoApprove serverName?: string toolName?: string - alwaysAllow?: boolean + autoApprove?: boolean } export type ClineAskResponse = "yesButtonClicked" | "noButtonClicked" | "messageResponse" diff --git a/src/shared/mcp.ts b/src/shared/mcp.ts index 7df1415cf4..b84f33d21a 100644 --- a/src/shared/mcp.ts +++ b/src/shared/mcp.ts @@ -13,7 +13,7 @@ export type McpTool = { name: string description?: string inputSchema?: object - alwaysAllow?: boolean + autoApprove?: boolean } export type McpResource = { diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 8034142459..bc26bb152b 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -718,8 +718,8 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi name: useMcpServer.toolName || "", description: server?.tools?.find((tool) => tool.name === useMcpServer.toolName)?.description || "", - alwaysAllow: - server?.tools?.find((tool) => tool.name === useMcpServer.toolName)?.alwaysAllow || + autoApprove: + server?.tools?.find((tool) => tool.name === useMcpServer.toolName)?.autoApprove || false, }} serverName={useMcpServer.serverName} diff --git a/webview-ui/src/components/mcp/McpToolRow.tsx b/webview-ui/src/components/mcp/McpToolRow.tsx index def6a36160..18619fe07f 100644 --- a/webview-ui/src/components/mcp/McpToolRow.tsx +++ b/webview-ui/src/components/mcp/McpToolRow.tsx @@ -11,14 +11,14 @@ type McpToolRowProps = { const McpToolRow = ({ tool, serverName }: McpToolRowProps) => { const { autoApprovalSettings } = useExtensionState() - const handleAlwaysAllowChange = () => { + const handleAutoApproveChange = () => { if (!serverName) return vscode.postMessage({ - type: "toggleToolAlwaysAllow", + type: "toggleToolAutoApprove", serverName, toolName: tool.name, - alwaysAllow: !tool.alwaysAllow, + autoApprove: !tool.autoApprove, }) } return ( @@ -36,8 +36,8 @@ const McpToolRow = ({ tool, serverName }: McpToolRowProps) => { {tool.name}
{serverName && autoApprovalSettings.enabled && autoApprovalSettings.actions.useMcp && ( - - Always allow + + Auto-approve )}
From c50af7226ccda78a5141888b95a3bb1e8fcd6122 Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Sun, 19 Jan 2025 16:33:15 -0800 Subject: [PATCH 20/49] Show API provider as popup --- webview-ui/src/App.tsx | 15 +- webview-ui/src/components/chat/ChatRow.tsx | 2 +- .../src/components/chat/ChatTextArea.tsx | 262 ++++++++++++++++-- .../src/components/settings/ApiOptions.tsx | 34 ++- .../settings/OpenRouterModelPicker.tsx | 9 +- .../src/components/settings/SettingsView.tsx | 4 +- 6 files changed, 271 insertions(+), 55 deletions(-) diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx index 954ca8dd8d..f06453aca9 100644 --- a/webview-ui/src/App.tsx +++ b/webview-ui/src/App.tsx @@ -15,7 +15,6 @@ const AppContent = () => { const [showHistory, setShowHistory] = useState(false) const [showMcp, setShowMcp] = useState(false) const [showAnnouncement, setShowAnnouncement] = useState(false) - const [showAdvisorModelSettings, setShowAdvisorModelSettings] = useState(false) const handleMessage = useCallback((e: MessageEvent) => { const message: ExtensionMessage = e.data @@ -24,36 +23,26 @@ const AppContent = () => { switch (message.action!) { case "settingsButtonClicked": setShowSettings(true) - setShowAdvisorModelSettings(false) setShowHistory(false) setShowMcp(false) break case "historyButtonClicked": setShowSettings(false) - setShowAdvisorModelSettings(false) setShowHistory(true) setShowMcp(false) break case "mcpButtonClicked": setShowSettings(false) - setShowAdvisorModelSettings(false) setShowHistory(false) setShowMcp(true) break case "chatButtonClicked": setShowSettings(false) - setShowAdvisorModelSettings(false) setShowHistory(false) setShowMcp(false) break } break - case "openAdvisorModelSettings": - setShowSettings(true) - setShowAdvisorModelSettings(true) - setShowHistory(false) - setShowMcp(false) - break } }, []) @@ -76,9 +65,7 @@ const AppContent = () => { ) : ( <> - {showSettings && ( - setShowSettings(false)} showAdvisorModelSettings={showAdvisorModelSettings} /> - )} + {showSettings && setShowSettings(false)} />} {showHistory && setShowHistory(false)} />} {showMcp && setShowMcp(false)} />} {/* Do not conditionally load ChatView, it's expensive and there's state we don't want to lose (user input, disableInput, askResponse promise, etc.) */} diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index bc26bb152b..10b4505971 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -793,7 +793,7 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi vscode.postMessage({ type: "openAdvisorModelSettings" })}> - in Settings. + in API Settings.
diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index b0fe60f04a..735bc5c668 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -14,8 +14,12 @@ import ContextMenu from "./ContextMenu" import Thumbnails from "../common/Thumbnails" import { VSCodeButton } from "@vscode/webview-ui-toolkit/react" import styled from "styled-components" -import { useWindowSize } from "react-use" +import { useEvent, useWindowSize } from "react-use" import { vscode } from "../../utils/vscode" +import ApiOptions from "../settings/ApiOptions" +import { useClickAway } from "react-use" +import { CODE_BLOCK_BG_COLOR } from "../common/CodeBlock" +import { ExtensionMessage } from "../../../../src/shared/ExtensionMessage" interface ChatTextAreaProps { inputValue: string @@ -51,13 +55,11 @@ const SwitchContainer = styled.div<{ disabled: boolean }>` border: 1px solid var(--vscode-input-border); border-radius: 12px; overflow: hidden; - position: absolute; - right: 15px; cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")}; opacity: ${(props) => (props.disabled ? 0.5 : 1)}; transform: scale(0.85); transform-origin: right center; - flex-shrink: 0; + margin-left: -10px; // compensate for the transform so flex spacing works ` const Slider = styled.div<{ isChat: boolean }>` @@ -69,33 +71,117 @@ const Slider = styled.div<{ isChat: boolean }>` transform: translateX(${(props) => (props.isChat ? "100%" : "0%")}); ` +const ButtonGroup = styled.div` + display: flex; + align-items: center; + gap: 4px; + flex: 1; + min-width: 0; +` + const ButtonContainer = styled.div` display: flex; align-items: center; gap: 3px; font-size: 10px; white-space: nowrap; + min-width: 0; + width: 100%; ` -const ACTUAL_SWITCH_WIDTH = 90 -const SWITCH_WIDTH = ACTUAL_SWITCH_WIDTH * 0.85 // Account for the 0.85 scale transform -const CONTEXT_BUTTON_WIDTH = 60 -const IMAGES_BUTTON_WIDTH = 80 -const CONTAINER_PADDING = 30 // 15px left + 15px right -const TOTAL_WIDTH = SWITCH_WIDTH + 4 + CONTEXT_BUTTON_WIDTH + IMAGES_BUTTON_WIDTH + CONTAINER_PADDING - const ControlsContainer = styled.div` display: flex; align-items: center; - margin-top: -3px; - position: relative; + justify-content: space-between; + margin-top: -5px; padding: 0px 15px 5px 15px; ` -const ButtonGroup = styled.div` +const ModelSelectorTooltip = styled.div` + position: fixed; + bottom: calc(100% + 9px); + left: 15px; + right: 15px; + background: ${CODE_BLOCK_BG_COLOR}; + border: 1px solid var(--vscode-editorGroup-border); + padding: 12px; + border-radius: 3px; + z-index: 1000; + max-height: calc(100vh - 100px); + overflow-y: auto; + overscroll-behavior: contain; + + // Add invisible padding for hover zone + &::before { + content: ""; + position: fixed; + bottom: ${(props) => `calc(100vh - ${props.menuPosition}px - 2px)`}; + left: 0; + right: 0; + height: 8px; + } + + // Arrow pointing down + &::after { + content: ""; + position: fixed; + bottom: ${(props) => `calc(100vh - ${props.menuPosition}px)`}; + right: ${(props) => props.arrowPosition}px; + width: 10px; + height: 10px; + background: ${CODE_BLOCK_BG_COLOR}; + border-right: 1px solid var(--vscode-editorGroup-border); + border-bottom: 1px solid var(--vscode-editorGroup-border); + transform: rotate(45deg); + z-index: -1; + } +` + +const ModelContainer = styled.div` + position: relative; + display: flex; + flex: 1; + min-width: 0; +` + +const ModelDisplayButton = styled.a<{ isActive?: boolean }>` + padding: 0px 0px; + height: 20px; + width: 100%; + min-width: 0; + cursor: pointer; + text-decoration: ${(props) => (props.isActive ? "underline" : "none")}; + color: ${(props) => (props.isActive ? "var(--vscode-foreground)" : "var(--vscode-descriptionForeground)")}; display: flex; align-items: center; - gap: 4px; + font-size: 10px; + outline: none; + user-select: none; + + &:hover, + &:focus { + color: var(--vscode-foreground); + text-decoration: underline; + outline: none; + } + + &:active { + color: var(--vscode-foreground); + text-decoration: underline; + outline: none; + } + + &:focus-visible { + outline: none; + } +` + +const ModelButtonContent = styled.div` + width: 100%; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; ` const ChatTextArea = forwardRef( @@ -114,7 +200,7 @@ const ChatTextArea = forwardRef( }, ref, ) => { - const { filePaths, chatSettings } = useExtensionState() + const { filePaths, chatSettings, apiConfiguration } = useExtensionState() const [isTextAreaFocused, setIsTextAreaFocused] = useState(false) const [thumbnailsHeight, setThumbnailsHeight] = useState(0) const [textAreaBaseHeight, setTextAreaBaseHeight] = useState(undefined) @@ -129,8 +215,13 @@ const ChatTextArea = forwardRef( const [justDeletedSpaceAfterMention, setJustDeletedSpaceAfterMention] = useState(false) const [intendedCursorPosition, setIntendedCursorPosition] = useState(null) const contextMenuContainerRef = useRef(null) - const { width: windowWidth } = useWindowSize() - const showButtonText = windowWidth - CONTAINER_PADDING > TOTAL_WIDTH - CONTAINER_PADDING + const [showModelSelector, setShowModelSelector] = useState(false) + const [showModelSelectorWithAdvisor, setShowModelSelectorWithAdvisor] = useState(false) + const modelSelectorRef = useRef(null) + const { width: viewportWidth, height: viewportHeight } = useWindowSize() + const buttonRef = useRef(null) + const [arrowPosition, setArrowPosition] = useState(0) + const [menuPosition, setMenuPosition] = useState(0) const queryItems = useMemo(() => { return [ @@ -538,6 +629,83 @@ const ChatTextArea = forwardRef( updateHighlights() }, [inputValue, textAreaDisabled, handleInputChange, updateHighlights]) + // Add click away handler + useClickAway(modelSelectorRef, () => { + setShowModelSelector(false) + }) + + // Get model display name + const modelDisplayName = useMemo(() => { + const unknownModel = "unknown" + if (!apiConfiguration) return unknownModel + switch (apiConfiguration.apiProvider) { + case "anthropic": + return `anthropic:${apiConfiguration.apiModelId || unknownModel}` + case "openai": + return `openai:${apiConfiguration.openAiModelId || unknownModel}` + case "openrouter": + return `openrouter:${apiConfiguration.openRouterModelId || unknownModel}` + case "bedrock": + return `bedrock:${apiConfiguration.apiModelId || unknownModel}` + case "vertex": + return `vertex:${apiConfiguration.apiModelId || unknownModel}` + case "ollama": + return `ollama:${apiConfiguration.ollamaModelId || unknownModel}` + case "lmstudio": + return `lmstudio:${apiConfiguration.lmStudioModelId || unknownModel}` + case "gemini": + return `gemini:${apiConfiguration.apiModelId || unknownModel}` + case "openai-native": + return `openai-native:${apiConfiguration.apiModelId || unknownModel}` + case "deepseek": + return `deepseek:${apiConfiguration.apiModelId || unknownModel}` + case "mistral": + return `mistral:${apiConfiguration.apiModelId || unknownModel}` + case "vscode-lm": + return `vscode-lm:${apiConfiguration.vsCodeLmModelSelector ? `${apiConfiguration.vsCodeLmModelSelector.vendor ?? ""}/${apiConfiguration.vsCodeLmModelSelector.family ?? ""}` : unknownModel}` + default: + return unknownModel + } + }, [apiConfiguration]) + + // Calculate arrow position and menu position based on button location + useEffect(() => { + if (showModelSelector && buttonRef.current) { + const buttonRect = buttonRef.current.getBoundingClientRect() + const buttonCenter = buttonRect.left + buttonRect.width / 2 + + // Calculate distance from right edge of viewport using viewport coordinates + const rightPosition = document.documentElement.clientWidth - buttonCenter - 5 + + setArrowPosition(rightPosition) + setMenuPosition(buttonRect.top + 1) // Added +1 to move menu down by 1px + } + }, [showModelSelector, viewportWidth, viewportHeight]) + + // Reset advisor settings when model selector is closed + useEffect(() => { + if (!showModelSelector) { + setShowModelSelectorWithAdvisor(false) + // Reset any active styling by blurring the button + const button = buttonRef.current?.querySelector("a") + if (button) { + button.blur() + } + } + }, [showModelSelector]) + + const handleMessage = useCallback((e: MessageEvent) => { + const message: ExtensionMessage = e.data + switch (message.type) { + case "openAdvisorModelSettings": + setShowModelSelector(true) + setShowModelSelectorWithAdvisor(true) + break + } + }, []) + + useEvent("message", handleMessage) + return (
( aria-label="Add Context" disabled={textAreaDisabled} onClick={handleContextButtonClick} - style={{ padding: "0px 0px", height: "20px", marginTop: -1 }}> + style={{ padding: "0px 0px", height: "20px" }}> - @ - {showButtonText && Context} + @ + {/* {showButtonText && Context} */} @@ -739,17 +907,47 @@ const ChatTextArea = forwardRef( onSelectImages() } }} - style={{ - padding: "0px 0px", - height: "20px", - opacity: shouldDisableImages ? 0.5 : 1, - cursor: shouldDisableImages ? "not-allowed" : undefined, - }}> + style={{ padding: "0px 0px", height: "20px" }}> - - {showButtonText && Add images} + + {/* {showButtonText && Images} */} + + +
+ setShowModelSelector(!showModelSelector)} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault() + setShowModelSelector(!showModelSelector) + } + }} + tabIndex={0}> + {modelDisplayName} + +
+ {showModelSelector && ( + + + + )} +
@@ -763,4 +961,10 @@ const ChatTextArea = forwardRef( }, ) +// Update TypeScript interface for styled-component props +interface ModelSelectorTooltipProps { + arrowPosition: number + menuPosition: number +} + export default ChatTextArea diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 0aace65eb4..b2073a8df6 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -50,6 +50,7 @@ interface ApiOptionsProps { modelIdErrorMessage?: string advisorModelIdErrorMessage?: string showAdvisorModelSettings?: boolean + isPopup?: boolean } const TabPanel = ({ children, isSelected }: { children: React.ReactNode; isSelected: boolean }) => { @@ -88,12 +89,28 @@ const TabButton = ({ ) } +// This is necessary to ensure dropdown opens downward, important for when this is used in popup +const DROPDOWN_Z_INDEX = 1001 // Higher than the OpenRouterModelPicker's and ModelSelectorTooltip's z-index + +const DropdownContainer = styled.div` + position: relative; + z-index: ${DROPDOWN_Z_INDEX}; + + // Force dropdowns to open downward + & vscode-dropdown::part(listbox) { + position: absolute !important; + top: 100% !important; + bottom: auto !important; + } +` + const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, advisorModelIdErrorMessage, showAdvisorModelSettings, + isPopup, }: ApiOptionsProps) => { const { apiConfiguration, setApiConfiguration, uriScheme } = useExtensionState() const [ollamaModels, setOllamaModels] = useState([]) @@ -190,8 +207,8 @@ const ApiOptions = ({ } return ( -
-
+
+ @@ -202,7 +219,6 @@ const ApiOptions = ({ style={{ minWidth: 130, position: "relative", - zIndex: OPENROUTER_MODEL_PICKER_Z_INDEX + 1, }}> OpenRouter Anthropic @@ -217,7 +233,7 @@ const ApiOptions = ({ LM Studio Ollama -
+ {selectedProvider === "anthropic" && (
@@ -865,6 +881,7 @@ const ApiOptions = ({ modelInfo={selectedModelInfo} isDescriptionExpanded={isDescriptionExpanded} setIsDescriptionExpanded={setIsDescriptionExpanded} + isPopup={isPopup} /> )} @@ -906,7 +923,9 @@ const ApiOptions = ({ {createDropdown(anthropicModels, "base")}
)} - {selectedProvider === "openrouter" && } + {selectedProvider === "openrouter" && ( + + )} {modelIdErrorMessage && (

)} {selectedProvider === "openrouter" && ( - + )} {advisorModelIdErrorMessage && (

void + isPopup?: boolean }) => { const isGemini = Object.keys(geminiModels).includes(selectedModelId) @@ -987,6 +1008,7 @@ export const ModelInfoView = ({ markdown={modelInfo.description} isExpanded={isDescriptionExpanded} setIsExpanded={setIsDescriptionExpanded} + isPopup={isPopup} /> ), = ({ modelType }) => { +const OpenRouterModelPicker: React.FC = ({ modelType, isPopup }) => { const { apiConfiguration, setApiConfiguration, openRouterModels } = useExtensionState() const [searchTerm, setSearchTerm] = useState( modelType === "advisor" @@ -230,6 +232,7 @@ const OpenRouterModelPicker: React.FC = ({ modelType } isDescriptionExpanded={isDescriptionExpanded} setIsDescriptionExpanded={setIsDescriptionExpanded} + isPopup={isPopup} /> ) : (

void + isPopup?: boolean }) => { const [reactContent, setMarkdown] = useRemark() // const [isExpanded, setIsExpanded] = useState(false) @@ -434,7 +439,7 @@ export const ModelDescriptionMarkdown = memo( fontSize: "inherit", paddingRight: 0, paddingLeft: 3, - backgroundColor: "var(--vscode-sideBar-background)", + backgroundColor: isPopup ? CODE_BLOCK_BG_COLOR : "var(--vscode-sideBar-background)", }} onClick={() => setIsExpanded(true)}> See more diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index 921f311c56..91e9d136c8 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -8,11 +8,10 @@ import ApiOptions from "./ApiOptions" const IS_DEV = false // FIXME: use flags when packaging type SettingsViewProps = { - showAdvisorModelSettings: boolean onDone: () => void } -const SettingsView = ({ showAdvisorModelSettings, onDone }: SettingsViewProps) => { +const SettingsView = ({ onDone }: SettingsViewProps) => { const { apiConfiguration, version, customInstructions, setCustomInstructions, openRouterModels } = useExtensionState() const [apiErrorMessage, setApiErrorMessage] = useState(undefined) const [modelIdErrorMessage, setModelIdErrorMessage] = useState(undefined) @@ -94,7 +93,6 @@ const SettingsView = ({ showAdvisorModelSettings, onDone }: SettingsViewProps) =

Date: Sun, 19 Jan 2025 16:37:17 -0800 Subject: [PATCH 21/49] Fixes --- webview-ui/src/components/mcp/McpView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webview-ui/src/components/mcp/McpView.tsx b/webview-ui/src/components/mcp/McpView.tsx index 993d060954..4c47faa7bb 100644 --- a/webview-ui/src/components/mcp/McpView.tsx +++ b/webview-ui/src/components/mcp/McpView.tsx @@ -236,7 +236,7 @@ const ServerRow = ({ server }: { server: McpServer }) => { width: "6px", height: "6px", backgroundColor: "white", - border: "1px solid #666666", + border: "1px solid color-mix(in srgb, #666666 65%, transparent)", borderRadius: "50%", position: "absolute", top: "1px", From 05f85ecfeb7776459f84ee06d0bba9bf311a2fc6 Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Sun, 19 Jan 2025 17:22:14 -0800 Subject: [PATCH 22/49] Fix menu positioning --- webview-ui/src/components/chat/ChatTextArea.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index 735bc5c668..4c3f978ce9 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -144,6 +144,12 @@ const ModelContainer = styled.div` min-width: 0; ` +const ModelButtonWrapper = styled.div` + display: inline-flex; // Make it shrink to content + min-width: 0; // Allow shrinking + max-width: 100%; // Don't overflow parent +` + const ModelDisplayButton = styled.a<{ isActive?: boolean }>` padding: 0px 0px; height: 20px; @@ -915,7 +921,7 @@ const ChatTextArea = forwardRef( -
+ ( tabIndex={0}> {modelDisplayName} -
+ {showModelSelector && ( Date: Sun, 19 Jan 2025 17:24:30 -0800 Subject: [PATCH 23/49] Prepare for release --- CHANGELOG.md | 9 ++++ package.json | 2 +- src/core/webview/ClineProvider.ts | 2 +- .../src/components/chat/Announcement.tsx | 52 ++++++++----------- 4 files changed, 34 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fad3f4aae0..f3d8b2bea0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Change Log +## [3.2.0] + +- Add Advisor model tool to help when Cline hits a roadblock (available with OpenRouter and Anthropic) +- Add new Task/Chat mode toggle to turn Cline into a conversational partner, rather than a task-completing agent +- Easily switch between API providers and models using a new popup menu under the chat field +- Add VS Code LM API provider to run models provided by other VS Code extensions (e.g. GitHub Copilot). Shoutout to @julesmons, @RaySinner, and @MrUbens for putting this together! +- Add on/off toggle for MCP servers to disable them when not in use. Thanks @MrUbens! +- Add Auto-approve option for individual tools in MCP servers. Thanks @MrUbens! + ## [3.1.10] - New icon! diff --git a/package.json b/package.json index 8757be5c21..89e139fb0b 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "claude-dev", "displayName": "Cline", "description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.", - "version": "3.1.11", + "version": "3.2.0", "icon": "assets/icons/icon.png", "galleryBanner": { "color": "#617A91", diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 111d71caab..f010c4449f 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -88,7 +88,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { private cline?: Cline private workspaceTracker?: WorkspaceTracker mcpHub?: McpHub - private latestAnnouncementId = "jan-6-2025" // update to some unique identifier when we add a new announcement + private latestAnnouncementId = "jan-19-2025" // update to some unique identifier when we add a new announcement constructor( readonly context: vscode.ExtensionContext, diff --git a/webview-ui/src/components/chat/Announcement.tsx b/webview-ui/src/components/chat/Announcement.tsx index da4c002e98..6d35ba5811 100644 --- a/webview-ui/src/components/chat/Announcement.tsx +++ b/webview-ui/src/components/chat/Announcement.tsx @@ -31,39 +31,33 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
  • - Checkpoints are here! Cline now saves a snapshot of your workspace at each step of the task. Hover over - any message to see two new buttons: -
      -
    • - - Compare shows you a diff between the snapshot and your current workspace -
    • -
    • - - Restore lets you revert your project's files back to that point in the task -
    • -
    + New Consult Advisor tool lets Cline ask a powerful model like o1 or Opus for help when stuck on complex + problems. Benchmarks show a 23% improvement in Cline's ability to accomplish tasks!{" "} + + See a demo here. +
  • - 'See new changes' button when a task is completed, showing you an overview of all the changes Cline - made to your workspace throughout the task + Task/Chat mode toggle to turn Cline into a conversational partner, rather than a task-completing agent +
  • +
  • + Quick API/model switching with a new popup menu under the chat field +
  • +
  • + VS Code LM API lets you use models from other extensions like GitHub Copilot{" "} + (thanks @julesmons, @RaySinner, and @MrUbens!) +
  • +
  • + MCP server improvements: On/off toggle to disable servers when not in use, and Auto-approve option for + individual tools (thanks @MrUbens!) +
  • +
  • + In case you missed it, Cline now supports Checkpoints!{" "} + + See it in action here. +
-

- - See a demo of Checkpoints here! - -

{/*
  • OpenRouter now supports prompt caching! They also have much higher rate limits than other providers, From ef5bd56599262c38874e0eec85ef129604f26d66 Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Sun, 19 Jan 2025 17:37:58 -0800 Subject: [PATCH 24/49] Save api config when menu is closed; fix default display names --- src/core/webview/ClineProvider.ts | 3 ++ src/shared/WebviewMessage.ts | 1 + .../src/components/chat/ChatTextArea.tsx | 48 +++++++++++++++---- 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index f010c4449f..ed0722836b 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -601,6 +601,9 @@ export class ClineProvider implements vscode.WebviewViewProvider { type: "openAdvisorModelSettings", }) break + case "getLatestState": + await this.postStateToWebview() + break case "openMcpSettings": { const mcpSettingsFilePath = await this.mcpHub?.getMcpSettingsFilePath() if (mcpSettingsFilePath) { diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 50ae6ad8fd..ce405303a0 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -37,6 +37,7 @@ export interface WebviewMessage { | "requestVsCodeLmModels" | "toggleToolAutoApprove" | "toggleMcpServer" + | "getLatestState" // | "relaunchChromeDebugMode" text?: string disabled?: boolean diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index 4c3f978ce9..2740fbadae 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -20,6 +20,19 @@ import ApiOptions from "../settings/ApiOptions" import { useClickAway } from "react-use" import { CODE_BLOCK_BG_COLOR } from "../common/CodeBlock" import { ExtensionMessage } from "../../../../src/shared/ExtensionMessage" +import { validateAdvisorModelId } from "../../utils/validate" +import { validateModelId } from "../../utils/validate" +import { validateApiConfiguration } from "../../utils/validate" +import { + anthropicDefaultModelId, + bedrockDefaultModelId, + deepSeekDefaultModelId, + geminiDefaultModelId, + mistralDefaultModelId, + openAiNativeDefaultModelId, + openRouterDefaultModelId, + vertexDefaultModelId, +} from "../../../../src/shared/api" interface ChatTextAreaProps { inputValue: string @@ -206,7 +219,7 @@ const ChatTextArea = forwardRef( }, ref, ) => { - const { filePaths, chatSettings, apiConfiguration } = useExtensionState() + const { filePaths, chatSettings, apiConfiguration, openRouterModels } = useExtensionState() const [isTextAreaFocused, setIsTextAreaFocused] = useState(false) const [thumbnailsHeight, setThumbnailsHeight] = useState(0) const [textAreaBaseHeight, setTextAreaBaseHeight] = useState(undefined) @@ -229,6 +242,18 @@ const ChatTextArea = forwardRef( const [arrowPosition, setArrowPosition] = useState(0) const [menuPosition, setMenuPosition] = useState(0) + const handleApiConfigSubmit = useCallback(() => { + const apiValidationResult = validateApiConfiguration(apiConfiguration) + const modelIdValidationResult = validateModelId(apiConfiguration, openRouterModels) + const advisorModelIdValidationResult = validateAdvisorModelId(apiConfiguration, openRouterModels) + + if (!apiValidationResult && !modelIdValidationResult && !advisorModelIdValidationResult) { + vscode.postMessage({ type: "apiConfiguration", apiConfiguration }) + } else { + vscode.postMessage({ type: "getLatestState" }) + } + }, [apiConfiguration, openRouterModels]) + const queryItems = useMemo(() => { return [ { type: ContextMenuOptionType.Problems, value: "problems" }, @@ -646,27 +671,27 @@ const ChatTextArea = forwardRef( if (!apiConfiguration) return unknownModel switch (apiConfiguration.apiProvider) { case "anthropic": - return `anthropic:${apiConfiguration.apiModelId || unknownModel}` + return `anthropic:${apiConfiguration.apiModelId || anthropicDefaultModelId}` case "openai": return `openai:${apiConfiguration.openAiModelId || unknownModel}` case "openrouter": - return `openrouter:${apiConfiguration.openRouterModelId || unknownModel}` + return `openrouter:${apiConfiguration.openRouterModelId || openRouterDefaultModelId}` case "bedrock": - return `bedrock:${apiConfiguration.apiModelId || unknownModel}` + return `bedrock:${apiConfiguration.apiModelId || bedrockDefaultModelId}` case "vertex": - return `vertex:${apiConfiguration.apiModelId || unknownModel}` + return `vertex:${apiConfiguration.apiModelId || vertexDefaultModelId}` case "ollama": return `ollama:${apiConfiguration.ollamaModelId || unknownModel}` case "lmstudio": return `lmstudio:${apiConfiguration.lmStudioModelId || unknownModel}` case "gemini": - return `gemini:${apiConfiguration.apiModelId || unknownModel}` + return `gemini:${apiConfiguration.apiModelId || geminiDefaultModelId}` case "openai-native": - return `openai-native:${apiConfiguration.apiModelId || unknownModel}` + return `openai-native:${apiConfiguration.apiModelId || openAiNativeDefaultModelId}` case "deepseek": - return `deepseek:${apiConfiguration.apiModelId || unknownModel}` + return `deepseek:${apiConfiguration.apiModelId || deepSeekDefaultModelId}` case "mistral": - return `mistral:${apiConfiguration.apiModelId || unknownModel}` + return `mistral:${apiConfiguration.apiModelId || mistralDefaultModelId}` case "vscode-lm": return `vscode-lm:${apiConfiguration.vsCodeLmModelSelector ? `${apiConfiguration.vsCodeLmModelSelector.vendor ?? ""}/${apiConfiguration.vsCodeLmModelSelector.family ?? ""}` : unknownModel}` default: @@ -691,6 +716,9 @@ const ChatTextArea = forwardRef( // Reset advisor settings when model selector is closed useEffect(() => { if (!showModelSelector) { + // Attempt to save if possible + handleApiConfigSubmit() + setShowModelSelectorWithAdvisor(false) // Reset any active styling by blurring the button const button = buttonRef.current?.querySelector("a") @@ -698,7 +726,7 @@ const ChatTextArea = forwardRef( button.blur() } } - }, [showModelSelector]) + }, [showModelSelector, handleApiConfigSubmit]) const handleMessage = useCallback((e: MessageEvent) => { const message: ExtensionMessage = e.data From 86f3dc2fb55843b96247bf3a26424b3e92c45eee Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Sun, 19 Jan 2025 17:52:39 -0800 Subject: [PATCH 25/49] Fix saving API config when menu is closed --- .../src/components/chat/ChatTextArea.tsx | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index 2740fbadae..95a9742cb0 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -242,18 +242,6 @@ const ChatTextArea = forwardRef( const [arrowPosition, setArrowPosition] = useState(0) const [menuPosition, setMenuPosition] = useState(0) - const handleApiConfigSubmit = useCallback(() => { - const apiValidationResult = validateApiConfiguration(apiConfiguration) - const modelIdValidationResult = validateModelId(apiConfiguration, openRouterModels) - const advisorModelIdValidationResult = validateAdvisorModelId(apiConfiguration, openRouterModels) - - if (!apiValidationResult && !modelIdValidationResult && !advisorModelIdValidationResult) { - vscode.postMessage({ type: "apiConfiguration", apiConfiguration }) - } else { - vscode.postMessage({ type: "getLatestState" }) - } - }, [apiConfiguration, openRouterModels]) - const queryItems = useMemo(() => { return [ { type: ContextMenuOptionType.Problems, value: "problems" }, @@ -663,6 +651,7 @@ const ChatTextArea = forwardRef( // Add click away handler useClickAway(modelSelectorRef, () => { setShowModelSelector(false) + handleApiConfigSubmit() }) // Get model display name @@ -713,11 +702,25 @@ const ChatTextArea = forwardRef( } }, [showModelSelector, viewportWidth, viewportHeight]) + const handleApiConfigSubmit = useCallback(() => { + console.log("handleApiConfigSubmit") + const apiValidationResult = validateApiConfiguration(apiConfiguration) + const modelIdValidationResult = validateModelId(apiConfiguration, openRouterModels) + const advisorModelIdValidationResult = validateAdvisorModelId(apiConfiguration, openRouterModels) + + if (!apiValidationResult && !modelIdValidationResult && !advisorModelIdValidationResult) { + vscode.postMessage({ type: "apiConfiguration", apiConfiguration }) + } else { + vscode.postMessage({ type: "getLatestState" }) + } + }, [apiConfiguration, openRouterModels]) + // Reset advisor settings when model selector is closed useEffect(() => { if (!showModelSelector) { // Attempt to save if possible - handleApiConfigSubmit() + // NOTE: we cannot call this here since it will create an infinite loop between this effect and the callback since getLatestState will update state. Instead we should submitapiconfig when the menu is explicitly closed, rather than as an effect of showModelSelector changing. + // handleApiConfigSubmit() setShowModelSelectorWithAdvisor(false) // Reset any active styling by blurring the button @@ -726,7 +729,7 @@ const ChatTextArea = forwardRef( button.blur() } } - }, [showModelSelector, handleApiConfigSubmit]) + }, [showModelSelector]) const handleMessage = useCallback((e: MessageEvent) => { const message: ExtensionMessage = e.data @@ -953,10 +956,19 @@ const ChatTextArea = forwardRef( setShowModelSelector(!showModelSelector)} + onClick={() => { + if (showModelSelector) { + handleApiConfigSubmit() + } + setShowModelSelector(!showModelSelector) + }} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault() + + if (showModelSelector) { + handleApiConfigSubmit() + } setShowModelSelector(!showModelSelector) } }} From 88af56c06cf370680f5f3cbb3d8d9589b673e48a Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Sun, 19 Jan 2025 18:35:07 -0800 Subject: [PATCH 26/49] Update styles for advisor UI --- webview-ui/src/components/chat/ChatRow.tsx | 10 +++++++++- webview-ui/src/components/chat/ChatTextArea.tsx | 15 +++++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 10b4505971..d4c7109cdf 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -768,7 +768,15 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi padding: "8px 10px", marginTop: "8px", }}> -
    {consultAdvisor.problem}
    +
    + +
    {consultAdvisor.estimatedCost != null && (
    ` +const ModelDisplayButton = styled.a<{ isActive?: boolean; disabled?: boolean }>` padding: 0px 0px; height: 20px; width: 100%; min-width: 0; - cursor: pointer; + cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")}; text-decoration: ${(props) => (props.isActive ? "underline" : "none")}; color: ${(props) => (props.isActive ? "var(--vscode-foreground)" : "var(--vscode-descriptionForeground)")}; display: flex; @@ -176,17 +176,19 @@ const ModelDisplayButton = styled.a<{ isActive?: boolean }>` font-size: 10px; outline: none; user-select: none; + opacity: ${(props) => (props.disabled ? 0.5 : 1)}; + pointer-events: ${(props) => (props.disabled ? "none" : "auto")}; &:hover, &:focus { - color: var(--vscode-foreground); - text-decoration: underline; + color: ${(props) => (props.disabled ? "var(--vscode-descriptionForeground)" : "var(--vscode-foreground)")}; + text-decoration: ${(props) => (props.disabled ? "none" : "underline")}; outline: none; } &:active { - color: var(--vscode-foreground); - text-decoration: underline; + color: ${(props) => (props.disabled ? "var(--vscode-descriptionForeground)" : "var(--vscode-foreground)")}; + text-decoration: ${(props) => (props.disabled ? "none" : "underline")}; outline: none; } @@ -956,6 +958,7 @@ const ChatTextArea = forwardRef( { if (showModelSelector) { handleApiConfigSubmit() From d9ea1e606c538d9bedd61ef71bcdab92f16200ab Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Sun, 19 Jan 2025 18:56:40 -0800 Subject: [PATCH 27/49] Fix callback cycle --- .../src/components/chat/ChatTextArea.tsx | 60 +++++++++++-------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index 2e7bee4718..bed965af54 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -244,6 +244,9 @@ const ChatTextArea = forwardRef( const [arrowPosition, setArrowPosition] = useState(0) const [menuPosition, setMenuPosition] = useState(0) + // Add a ref to track previous menu state + const prevShowModelSelector = useRef(showModelSelector) + const queryItems = useMemo(() => { return [ { type: ContextMenuOptionType.Problems, value: "problems" }, @@ -650,10 +653,37 @@ const ChatTextArea = forwardRef( updateHighlights() }, [inputValue, textAreaDisabled, handleInputChange, updateHighlights]) - // Add click away handler + // Separate the API config submission logic + const submitApiConfig = useCallback(() => { + const apiValidationResult = validateApiConfiguration(apiConfiguration) + const modelIdValidationResult = validateModelId(apiConfiguration, openRouterModels) + const advisorModelIdValidationResult = validateAdvisorModelId(apiConfiguration, openRouterModels) + + if (!apiValidationResult && !modelIdValidationResult && !advisorModelIdValidationResult) { + vscode.postMessage({ type: "apiConfiguration", apiConfiguration }) + } else { + vscode.postMessage({ type: "getLatestState" }) + } + }, [apiConfiguration, openRouterModels]) + + // Use an effect to detect menu close + useEffect(() => { + if (prevShowModelSelector.current && !showModelSelector) { + // Menu was just closed + submitApiConfig() + } + prevShowModelSelector.current = showModelSelector + }, [showModelSelector, submitApiConfig]) + + // Remove the handleApiConfigSubmit callback + // Update click handler to just toggle the menu + const handleModelButtonClick = () => { + setShowModelSelector(!showModelSelector) + } + + // Update click away handler to just close menu useClickAway(modelSelectorRef, () => { setShowModelSelector(false) - handleApiConfigSubmit() }) // Get model display name @@ -704,19 +734,6 @@ const ChatTextArea = forwardRef( } }, [showModelSelector, viewportWidth, viewportHeight]) - const handleApiConfigSubmit = useCallback(() => { - console.log("handleApiConfigSubmit") - const apiValidationResult = validateApiConfiguration(apiConfiguration) - const modelIdValidationResult = validateModelId(apiConfiguration, openRouterModels) - const advisorModelIdValidationResult = validateAdvisorModelId(apiConfiguration, openRouterModels) - - if (!apiValidationResult && !modelIdValidationResult && !advisorModelIdValidationResult) { - vscode.postMessage({ type: "apiConfiguration", apiConfiguration }) - } else { - vscode.postMessage({ type: "getLatestState" }) - } - }, [apiConfiguration, openRouterModels]) - // Reset advisor settings when model selector is closed useEffect(() => { if (!showModelSelector) { @@ -959,20 +976,11 @@ const ChatTextArea = forwardRef( role="button" isActive={showModelSelector} disabled={textAreaDisabled} - onClick={() => { - if (showModelSelector) { - handleApiConfigSubmit() - } - setShowModelSelector(!showModelSelector) - }} + onClick={handleModelButtonClick} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault() - - if (showModelSelector) { - handleApiConfigSubmit() - } - setShowModelSelector(!showModelSelector) + handleModelButtonClick() } }} tabIndex={0}> From 806fe70c5153b18143b3d5439d065154cefa50ec Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Sun, 19 Jan 2025 19:00:21 -0800 Subject: [PATCH 28/49] Fixes --- webview-ui/src/components/chat/ChatTextArea.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index bed965af54..5c37147cf5 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -977,12 +977,12 @@ const ChatTextArea = forwardRef( isActive={showModelSelector} disabled={textAreaDisabled} onClick={handleModelButtonClick} - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === " ") { - e.preventDefault() - handleModelButtonClick() - } - }} + // onKeyDown={(e) => { + // if (e.key === "Enter" || e.key === " ") { + // e.preventDefault() + // handleModelButtonClick() + // } + // }} tabIndex={0}> {modelDisplayName} From 39b0389b9dcc076beab5715e115953ef8ff2acbe Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Sun, 19 Jan 2025 19:31:53 -0800 Subject: [PATCH 29/49] Add vscode LM API types --- package-lock.json | 12 +-- package.json | 2 +- src/api/providers/vscode-lm.ts | 92 +++++++++++++++++++ .../src/components/settings/ApiOptions.tsx | 11 ++- 4 files changed, 109 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index eb7141005b..b7415befb1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "claude-dev", - "version": "3.1.11", + "version": "3.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "claude-dev", - "version": "3.1.11", + "version": "3.2.0", "license": "Apache-2.0", "dependencies": { "@anthropic-ai/bedrock-sdk": "^0.10.2", @@ -53,7 +53,7 @@ "@types/mocha": "^10.0.7", "@types/node": "20.x", "@types/should": "^11.2.0", - "@types/vscode": "^1.96.0", + "@types/vscode": "^1.84.0", "@typescript-eslint/eslint-plugin": "^7.14.1", "@typescript-eslint/parser": "^7.11.0", "@vscode/test-cli": "^0.0.9", @@ -4641,9 +4641,9 @@ "license": "MIT" }, "node_modules/@types/vscode": { - "version": "1.96.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.96.0.tgz", - "integrity": "sha512-qvZbSZo+K4ZYmmDuaodMbAa67Pl6VDQzLKFka6rq+3WUTY4Kro7Bwoi0CuZLO/wema0ygcmpwow7zZfPJTs5jg==", + "version": "1.84.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.84.0.tgz", + "integrity": "sha512-lCGOSrhT3cL+foUEqc8G1PVZxoDbiMmxgnUZZTEnHF4mC47eKAUtBGAuMLY6o6Ua8PAuNCoKXbqPmJd1JYnQfg==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 89e139fb0b..075ee7e765 100644 --- a/package.json +++ b/package.json @@ -171,7 +171,7 @@ "@types/mocha": "^10.0.7", "@types/node": "20.x", "@types/should": "^11.2.0", - "@types/vscode": "^1.96.0", + "@types/vscode": "^1.84.0", "@typescript-eslint/eslint-plugin": "^7.14.1", "@typescript-eslint/parser": "^7.11.0", "@vscode/test-cli": "^0.0.9", diff --git a/src/api/providers/vscode-lm.ts b/src/api/providers/vscode-lm.ts index 8c138a9102..f28075f1da 100644 --- a/src/api/providers/vscode-lm.ts +++ b/src/api/providers/vscode-lm.ts @@ -7,6 +7,98 @@ import { convertToVsCodeLmMessages } from "../transform/vscode-lm-format" import { SELECTOR_SEPARATOR, stringifyVsCodeLmModelSelector } from "../../shared/vsCodeSelectorUtils" import { ApiHandlerOptions, ModelInfo, openAiModelInfoSaneDefaults } from "../../shared/api" +// Cline does not update VSCode type definitions or engine requirements to maintain compatibility. +// This declaration (as seen in src/integrations/TerminalManager.ts) provides types for the Language Model API in newer versions of VSCode. +// Extracted from https://github.com/microsoft/vscode/blob/131ee0ef660d600cd0a7e6058375b281553abe20/src/vscode-dts/vscode.d.ts +declare module "vscode" { + enum LanguageModelChatMessageRole { + User = 1, + Assistant = 2, + } + enum LanguageModelChatToolMode { + Auto = 1, + Required = 2, + } + interface LanguageModelChatSelector { + vendor?: string + family?: string + version?: string + id?: string + } + interface LanguageModelChatTool { + name: string + description: string + inputSchema?: object + } + interface LanguageModelChatRequestOptions { + justification?: string + modelOptions?: { [name: string]: any } + tools?: LanguageModelChatTool[] + toolMode?: LanguageModelChatToolMode + } + class LanguageModelTextPart { + value: string + constructor(value: string) + } + class LanguageModelToolCallPart { + callId: string + name: string + input: object + constructor(callId: string, name: string, input: object) + } + interface LanguageModelChatResponse { + stream: AsyncIterable + text: AsyncIterable + } + interface LanguageModelChat { + readonly name: string + readonly id: string + readonly vendor: string + readonly family: string + readonly version: string + readonly maxInputTokens: number + + sendRequest( + messages: LanguageModelChatMessage[], + options?: LanguageModelChatRequestOptions, + token?: CancellationToken, + ): Thenable + countTokens(text: string | LanguageModelChatMessage, token?: CancellationToken): Thenable + } + class LanguageModelPromptTsxPart { + value: unknown + constructor(value: unknown) + } + class LanguageModelToolResultPart { + callId: string + content: Array + constructor(callId: string, content: Array) + } + class LanguageModelChatMessage { + static User( + content: string | Array, + name?: string, + ): LanguageModelChatMessage + static Assistant( + content: string | Array, + name?: string, + ): LanguageModelChatMessage + + role: LanguageModelChatMessageRole + content: Array + name: string | undefined + + constructor( + role: LanguageModelChatMessageRole, + content: string | Array, + name?: string, + ) + } + namespace lm { + function selectChatModels(selector?: LanguageModelChatSelector): Thenable + } +} + /** * Handles interaction with VS Code's Language Model API for chat-based operations. * This handler implements the ApiHandler interface to provide VS Code LM specific functionality. diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index b2073a8df6..54b6b04621 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -40,7 +40,7 @@ import { ExtensionMessage } from "../../../../src/shared/ExtensionMessage" import { useExtensionState } from "../../context/ExtensionStateContext" import { vscode } from "../../utils/vscode" import VSCodeButtonLink from "../common/VSCodeButtonLink" -import OpenRouterModelPicker, { ModelDescriptionMarkdown, OPENROUTER_MODEL_PICKER_Z_INDEX } from "./OpenRouterModelPicker" +import OpenRouterModelPicker, { ModelDescriptionMarkdown } from "./OpenRouterModelPicker" import styled from "styled-components" import * as vscodemodels from "vscode" @@ -104,6 +104,15 @@ const DropdownContainer = styled.div` } ` +declare module "vscode" { + interface LanguageModelChatSelector { + vendor?: string + family?: string + version?: string + id?: string + } +} + const ApiOptions = ({ showModelOptions, apiErrorMessage, From 6bde6c6cbff92b99d61706fdd9b1c3a83dcaf3ce Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Sun, 19 Jan 2025 19:34:16 -0800 Subject: [PATCH 30/49] Fixes --- src/integrations/terminal/TerminalManager.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/integrations/terminal/TerminalManager.ts b/src/integrations/terminal/TerminalManager.ts index 2de5be3a6f..81e91ab6b8 100644 --- a/src/integrations/terminal/TerminalManager.ts +++ b/src/integrations/terminal/TerminalManager.ts @@ -71,14 +71,14 @@ This approach allows us to leverage advanced features when available while ensur */ declare module "vscode" { // https://github.com/microsoft/vscode/blob/f0417069c62e20f3667506f4b7e53ca0004b4e3e/src/vscode-dts/vscode.d.ts#L7442 - // interface Terminal { - // shellIntegration?: { - // cwd?: vscode.Uri - // executeCommand?: (command: string) => { - // read: () => AsyncIterable - // } - // } - // } + interface Terminal { + shellIntegration?: { + cwd?: vscode.Uri + executeCommand?: (command: string) => { + read: () => AsyncIterable + } + } + } // https://github.com/microsoft/vscode/blob/f0417069c62e20f3667506f4b7e53ca0004b4e3e/src/vscode-dts/vscode.d.ts#L10794 interface Window { onDidStartTerminalShellExecution?: ( From 44a331f6c155b9ab1674b3af90ef5e8e202378e0 Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Sun, 19 Jan 2025 20:42:06 -0800 Subject: [PATCH 31/49] Fixes --- webview-ui/src/components/chat/Announcement.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/webview-ui/src/components/chat/Announcement.tsx b/webview-ui/src/components/chat/Announcement.tsx index 6d35ba5811..0ae03b8b9e 100644 --- a/webview-ui/src/components/chat/Announcement.tsx +++ b/webview-ui/src/components/chat/Announcement.tsx @@ -31,10 +31,13 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
    • - New Consult Advisor tool lets Cline ask a powerful model like o1 or Opus for help when stuck on complex - problems. Benchmarks show a 23% improvement in Cline's ability to accomplish tasks!{" "} + + New Consult Advisor tool + {" "} + lets Cline ask a powerful model like o1 for help when stuck. Cline provides the full context of the problem, + and the Advisor model responds with a plan to fix it.{" "} - See a demo here. + See a demo here!
    • @@ -44,12 +47,11 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => { Quick API/model switching with a new popup menu under the chat field
    • - VS Code LM API lets you use models from other extensions like GitHub Copilot{" "} - (thanks @julesmons, @RaySinner, and @MrUbens!) + VS Code LM API lets you use models from other extensions like GitHub Copilot
    • MCP server improvements: On/off toggle to disable servers when not in use, and Auto-approve option for - individual tools (thanks @MrUbens!) + individual tools
    • In case you missed it, Cline now supports Checkpoints!{" "} From dca42c4891ead4373b12acd719f5c8cfc3111618 Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Sun, 19 Jan 2025 21:00:18 -0800 Subject: [PATCH 32/49] Copy --- webview-ui/src/components/chat/ChatView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index ef51c676a5..8dd275fb2a 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -174,7 +174,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie setTextAreaDisabled(false) setClineAsk("resume_task") setEnableButtons(true) - setPrimaryButtonText("Resume Task") + setPrimaryButtonText("Resume") setSecondaryButtonText(undefined) setDidClickCancel(false) // special case where we reset the cancel button state break From a88504e4fe165a707c827a61e6fde07749223526 Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Sun, 19 Jan 2025 22:35:50 -0800 Subject: [PATCH 33/49] Rename respond_to_inquiry to chat_mode_response --- src/core/Cline.ts | 85 ++-- src/core/assistant-message/index.ts | 2 +- src/core/prompts/chat.ts | 427 ------------------ src/core/prompts/system.ts | 22 + src/core/webview/ClineProvider.ts | 10 + src/shared/ExtensionMessage.ts | 2 +- webview-ui/src/components/chat/ChatRow.tsx | 2 +- webview-ui/src/components/chat/ChatView.tsx | 6 +- webview-ui/src/components/chat/TaskHeader.tsx | 2 +- 9 files changed, 83 insertions(+), 475 deletions(-) delete mode 100644 src/core/prompts/chat.ts diff --git a/src/core/Cline.ts b/src/core/Cline.ts index e8d34262df..4db84a989d 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -60,7 +60,6 @@ import getFolderSize from "get-folder-size" import { BrowserSettings } from "../shared/BrowserSettings" import { ADVISOR_SYSTEM_PROMPT } from "./prompts/advisor" import { ChatSettings } from "../shared/ChatSettings" -import { CHAT_SYSTEM_PROMPT } from "./prompts/chat" import { OpenRouterHandler } from "../api/providers/openrouter" const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution @@ -752,6 +751,8 @@ export class Cline { this.apiConversationHistory = [] await this.providerRef.deref()?.postStateToWebview() + await this.providerRef.deref()?.switchToTaskMode() + await this.say("text", task, images) this.isInitialized = true @@ -992,13 +993,17 @@ export class Cline { newUserContent.push({ type: "text", text: - `[TASK RESUMPTION] This task was interrupted ${agoText}. It may or may not be complete, so please reassess the task context. Be aware that the project state may have changed since then. The current working directory is now '${cwd.toPosix()}'. If the task has not been completed, retry the last step before interruption and proceed with completing the task.\n\nNote: If you previously attempted a tool use that the user did not provide a result for, you should assume the tool use was not successful and assess whether you should retry. If the last tool was a browser_action, the browser has been closed and you must launch a new browser if needed.${ + `[TASK RESUMPTION] ${ + this.chatSettings?.mode === "chat" + ? `This task was interrupted ${agoText}. The conversation may have been incomplete. Be aware that the project state may have changed since then. The current working directory is now '${cwd.toPosix()}'.\n\nNote: If you previously attempted a tool use that the user did not provide a result for, you should assume the tool use was not successful. However you are in CHAT MODE, so rather than continuing the task, you must respond to the user's message.` + : `This task was interrupted ${agoText}. It may or may not be complete, so please reassess the task context. Be aware that the project state may have changed since then. The current working directory is now '${cwd.toPosix()}'. If the task has not been completed, retry the last step before interruption and proceed with completing the task.\n\nNote: If you previously attempted a tool use that the user did not provide a result for, you should assume the tool use was not successful and assess whether you should retry. If the last tool was a browser_action, the browser has been closed and you must launch a new browser if needed.` + }${ wasRecent ? "\n\nIMPORTANT: If the last tool use was a replace_in_file or write_to_file that was interrupted, the file was reverted back to its original state before the interrupted edit, and you do NOT need to re-read the file as you already have its up-to-date contents." : "" }` + (responseText - ? `\n\nNew instructions for task continuation:\n\n${responseText}\n` + ? `\n\n${this.chatSettings?.mode === "chat" ? "New message to respond to with chat_mode_response tool (be sure to provide your response in the parameter)" : "New instructions for task continuation"}:\n\n${responseText}\n` : ""), }) @@ -1273,25 +1278,13 @@ export class Cline { const advisorModel = this.api.getAdvisorModel?.() const supportsConsultAdvisor = advisorModel !== undefined - let systemPrompt: string - - if (this.chatSettings.mode === "chat") { - systemPrompt = await CHAT_SYSTEM_PROMPT( - cwd, - this.api.getModel().info.supportsComputerUse ?? false, - mcpHub, - this.browserSettings, - supportsConsultAdvisor, - ) - } else { - systemPrompt = await SYSTEM_PROMPT( - cwd, - this.api.getModel().info.supportsComputerUse ?? false, - mcpHub, - this.browserSettings, - supportsConsultAdvisor, - ) - } + let systemPrompt = await SYSTEM_PROMPT( + cwd, + this.api.getModel().info.supportsComputerUse ?? false, + mcpHub, + this.browserSettings, + supportsConsultAdvisor, + ) let settingsCustomInstructions = this.customInstructions?.trim() const clineRulesFilePath = path.resolve(cwd, GlobalFileNames.clineRules) @@ -1532,7 +1525,7 @@ export class Cline { return `[${block.name} for '${block.params.problem}']` case "ask_followup_question": return `[${block.name} for '${block.params.question}']` - case "respond_to_inquiry": + case "chat_mode_response": return `[${block.name} for '${block.params.response}']` case "attempt_completion": return `[${block.name}]` @@ -2736,18 +2729,18 @@ export class Cline { break } } - case "respond_to_inquiry": { + case "chat_mode_response": { const response: string | undefined = block.params.response try { if (block.partial) { - await this.ask("respond_to_inquiry", removeClosingTag("response", response), block.partial).catch( + await this.ask("chat_mode_response", removeClosingTag("response", response), block.partial).catch( () => {}, ) break } else { if (!response) { this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("respond_to_inquiry", "response")) + pushToolResult(await this.sayAndCreateMissingParamError("chat_mode_response", "response")) // await this.saveCheckpoint() break } @@ -2760,7 +2753,7 @@ export class Cline { // }) // } - const { text, images } = await this.ask("respond_to_inquiry", response, false) + const { text, images } = await this.ask("chat_mode_response", response, false) await this.say("user_feedback", text ?? "", images) pushToolResult(formatResponse.toolResult(`\n${text}\n`, images)) // await this.saveCheckpoint() @@ -3449,20 +3442,20 @@ export class Cline { } // Add current time information with timezone - // const now = new Date() - // const formatter = new Intl.DateTimeFormat(undefined, { - // year: "numeric", - // month: "numeric", - // day: "numeric", - // hour: "numeric", - // minute: "numeric", - // second: "numeric", - // hour12: true, - // }) - // const timeZone = formatter.resolvedOptions().timeZone - // const timeZoneOffset = -now.getTimezoneOffset() / 60 // Convert to hours and invert sign to match conventional notation - // const timeZoneOffsetStr = `${timeZoneOffset >= 0 ? "+" : ""}${timeZoneOffset}:00` - // details += `\n\n# Current Time\n${formatter.format(now)} (${timeZone}, UTC${timeZoneOffsetStr})` + const now = new Date() + const formatter = new Intl.DateTimeFormat(undefined, { + year: "numeric", + month: "numeric", + day: "numeric", + hour: "numeric", + minute: "numeric", + second: "numeric", + hour12: true, + }) + const timeZone = formatter.resolvedOptions().timeZone + const timeZoneOffset = -now.getTimezoneOffset() / 60 // Convert to hours and invert sign to match conventional notation + const timeZoneOffsetStr = `${timeZoneOffset >= 0 ? "+" : ""}${timeZoneOffset}:00` + details += `\n\n# Current Time\n${formatter.format(now)} (${timeZone}, UTC${timeZoneOffsetStr})` if (includeFileDetails) { details += `\n\n# Current Working Directory (${cwd.toPosix()}) Files\n` @@ -3477,6 +3470,16 @@ export class Cline { } } + details += "\n\n# Current Mode" + if (this.chatSettings.mode === "chat") { + details += "\nCHAT MODE" + details += + '\n(Remember: You now only have access to the chat_mode_response tool. If it seems the user wants you to use tools only available in TASK MODE, you should ask the user to "toggle to Task mode" - they will have to manually do this themselves with the Task/Chat toggle button below.)' + } else { + details += "\nTASK MODE" + details += "\n(Remember: You cannot use the chat_mode_response tool.)" + } + return `\n${details.trim()}\n` } } diff --git a/src/core/assistant-message/index.ts b/src/core/assistant-message/index.ts index de2ade7a30..e4dd04e4a1 100644 --- a/src/core/assistant-message/index.ts +++ b/src/core/assistant-message/index.ts @@ -21,7 +21,7 @@ export const toolUseNames = [ "access_mcp_resource", "consult_advisor", "ask_followup_question", - "respond_to_inquiry", + "chat_mode_response", "attempt_completion", ] as const diff --git a/src/core/prompts/chat.ts b/src/core/prompts/chat.ts deleted file mode 100644 index 76e41d6f05..0000000000 --- a/src/core/prompts/chat.ts +++ /dev/null @@ -1,427 +0,0 @@ -import defaultShell from "default-shell" -import os from "os" -import osName from "os-name" -import { McpHub } from "../../services/mcp/McpHub" -import { BrowserSettings } from "../../shared/BrowserSettings" - -export const CHAT_SYSTEM_PROMPT = async ( - cwd: string, - supportsComputerUse: boolean, - mcpHub: McpHub, - browserSettings: BrowserSettings, - supportsConsultAdvisor: boolean, -) => `You are Cline, a highly skilled software engineer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices. - -==== - -TOOL USE - -You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to respond to the user's inquiry, with each tool use informed by the result of the previous tool use. - -# Tool Use Formatting - -Tool use is formatted using XML-style tags. The tool name is enclosed in opening and closing tags, and each parameter is similarly enclosed within its own set of tags. Here's the structure: - - -value1 -value2 -... - - -For example: - - -src/main.js - - -Always adhere to this format for the tool use to ensure proper parsing and execution. - -# Tools - -## read_file -Description: Request to read the contents of a file at the specified path. Use this when you need to examine the contents of an existing file you do not know the contents of, for example to analyze code, review text files, or extract information from configuration files. Automatically extracts raw text from PDF and DOCX files. May not be suitable for other types of binary files, as it returns the raw content as a string. -Parameters: -- path: (required) The path of the file to read (relative to the current working directory ${cwd.toPosix()}) -Usage: - -File path here - - -## search_files -Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. -Parameters: -- path: (required) The path of the directory to search in (relative to the current working directory ${cwd.toPosix()}). This directory will be recursively searched. -- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax. -- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*). -Usage: - -Directory path here -Your regex pattern here -file pattern here (optional) - - -## list_files -Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not. -Parameters: -- path: (required) The path of the directory to list contents for (relative to the current working directory ${cwd.toPosix()}) -- recursive: (optional) Whether to list files recursively. Use true for recursive listing, false or omit for top-level only. -Usage: - -Directory path here -true or false (optional) - - -## list_code_definition_names -Description: Request to list definition names (classes, functions, methods, etc.) used in source code files at the top level of the specified directory. This tool provides insights into the codebase structure and important constructs, encapsulating high-level concepts and relationships that are crucial for understanding the overall architecture. -Parameters: -- path: (required) The path of the directory (relative to the current working directory ${cwd.toPosix()}) to list top level source code definitions for. -Usage: - -Directory path here -${ - supportsComputerUse - ? ` - -## browser_action -Description: Request to interact with a Puppeteer-controlled browser. Every action, except \`close\`, will be responded to with a screenshot of the browser's current state, along with any new console logs. You may only perform one browser action per message, and wait for the user's response including a screenshot and logs to determine the next action. -- The sequence of actions **must always start with** launching the browser at a URL, and **must always end with** closing the browser. If you need to visit a new URL that is not possible to navigate to from the current webpage, you must first close the browser, then launch again at the new URL. -- While the browser is active, only the \`browser_action\` tool can be used. No other tools should be called during this time. You may proceed to use other tools only after closing the browser. For example if you run into an error and need to fix a file, you must close the browser, then use other tools to make the necessary changes, then re-launch the browser to verify the result. -- The browser window has a resolution of **${browserSettings.viewport.width}x${browserSettings.viewport.height}** pixels. When performing any click actions, ensure the coordinates are within this resolution range. -- Before clicking on any elements such as icons, links, or buttons, you must consult the provided screenshot of the page to determine the coordinates of the element. The click should be targeted at the **center of the element**, not on its edges. -Parameters: -- action: (required) The action to perform. The available actions are: - * launch: Launch a new Puppeteer-controlled browser instance at the specified URL. This **must always be the first action**. - - Use with the \`url\` parameter to provide the URL. - - Ensure the URL is valid and includes the appropriate protocol (e.g. http://localhost:3000/page, file:///path/to/file.html, etc.) - * click: Click at a specific x,y coordinate. - - Use with the \`coordinate\` parameter to specify the location. - - Always click in the center of an element (icon, button, link, etc.) based on coordinates derived from a screenshot. - * type: Type a string of text on the keyboard. You might use this after clicking on a text field to input text. - - Use with the \`text\` parameter to provide the string to type. - * scroll_down: Scroll down the page by one page height. - * scroll_up: Scroll up the page by one page height. - * close: Close the Puppeteer-controlled browser instance. This **must always be the final browser action**. - - Example: \`close\` -- url: (optional) Use this for providing the URL for the \`launch\` action. - * Example: https://example.com -- coordinate: (optional) The X and Y coordinates for the \`click\` action. Coordinates should be within the **${browserSettings.viewport.width}x${browserSettings.viewport.height}** resolution. - * Example: 450,300 -- text: (optional) Use this for providing the text for the \`type\` action. - * Example: Hello, world! -Usage: - -Action to perform (e.g., launch, click, type, scroll_down, scroll_up, close) -URL to launch the browser at (optional) -x,y coordinates (optional) -Text to type (optional) -` - : "" -} - -## use_mcp_tool -Description: Request to use a tool provided by a connected MCP server. Each MCP server can provide multiple tools with different capabilities. Tools have defined input schemas that specify required and optional parameters. -Parameters: -- server_name: (required) The name of the MCP server providing the tool -- tool_name: (required) The name of the tool to execute -- arguments: (required) A JSON object containing the tool's input parameters, following the tool's input schema -Usage: - -server name here -tool name here - -{ - "param1": "value1", - "param2": "value2" -} - - - -## access_mcp_resource -Description: Request to access a resource provided by a connected MCP server. Resources represent data sources that can be used as context, such as files, API responses, or system information. -Parameters: -- server_name: (required) The name of the MCP server providing the resource -- uri: (required) The URI identifying the specific resource to access -Usage: - -server name here -resource URI here -${ - supportsConsultAdvisor - ? ` - -## consult_advisor -Description: Request to consult an advanced-reasoning AI model about a problem or question you are facing. This can be used to resolve errors you are stuck on, or get input from the model to work through a challenge you are facing. The relevant conversation history leading to the problem will also be provided to the advisor for additional context. -Parameters: -- problem: (required) A string describing the issue, question, or context you want the advisor to address. -Usage: - -Your problem or question here -` - : "" -} - -## respond_to_inquiry -Description: Respond to the user's inquiry with a clear answer. This tool should be used when you need to provide a response to a question or statement. It allows for direct communication with the user, ensuring they receive a clear answer that addresses their inquiry. It can also be used to ask the user for more information if needed. -Parameters: -- response: (required) The response to provide to the user. This should be a clear answer that addresses the user's inquiry. -Usage: - -Your response here - - -# Tool Use Examples - -## Example 1: Requesting to use an MCP tool - - -weather-server -get_forecast - -{ - "city": "San Francisco", - "days": 5 -} - - - -## Example 2: Requesting to access an MCP resource - - -weather-server -weather://san-francisco/current - - -# Tool Use Guidelines - -1. In tags, assess what information you already have and what information you need to proceed with the task. -2. Choose the most appropriate tool based on the task and the tool descriptions provided. Assess if you need additional information to proceed, and which of the available tools would be most effective for gathering this information. For example using the list_files tool is more effective than running a command like \`ls\` in the terminal. It's critical that you think about each available tool and use the one that best fits the current step in the task. -3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. -4. Formulate your tool use using the XML format specified for each tool. -5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - - Information about whether the tool succeeded or failed, along with any reasons for failure. - - Linter errors that may have arisen due to the changes you made, which you'll need to address. - - New terminal output in reaction to the changes, which you may need to consider or act upon. - - Any other relevant feedback or information related to the tool use. -6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. - -It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: -1. Confirm the success of each step before proceeding. -2. Address any issues or errors that arise immediately. -3. Adapt your approach based on new information or unexpected results. -4. Ensure that each action builds correctly on the previous ones. - -By waiting for and carefully considering the user's response after each tool use, you can react accordingly and make informed decisions about how to proceed with the task. This iterative process helps ensure the overall success and accuracy of your work. - -==== - -MCP SERVERS - -The Model Context Protocol (MCP) enables communication between the system and locally running MCP servers that provide additional tools and resources to extend your capabilities. - -# Connected MCP Servers - -When a server is connected, you can use the server's tools via the \`use_mcp_tool\` tool, and access the server's resources via the \`access_mcp_resource\` tool. - -${ - mcpHub.getServers().length > 0 - ? `${mcpHub - .getServers() - .filter((server) => server.status === "connected") - .map((server) => { - const tools = server.tools - ?.map((tool) => { - const schemaStr = tool.inputSchema - ? ` Input Schema: - ${JSON.stringify(tool.inputSchema, null, 2).split("\n").join("\n ")}` - : "" - - return `- ${tool.name}: ${tool.description}\n${schemaStr}` - }) - .join("\n\n") - - const templates = server.resourceTemplates - ?.map((template) => `- ${template.uriTemplate} (${template.name}): ${template.description}`) - .join("\n") - - const resources = server.resources - ?.map((resource) => `- ${resource.uri} (${resource.name}): ${resource.description}`) - .join("\n") - - const config = JSON.parse(server.config) - - return ( - `## ${server.name} (\`${config.command}${config.args && Array.isArray(config.args) ? ` ${config.args.join(" ")}` : ""}\`)` + - (tools ? `\n\n### Available Tools\n${tools}` : "") + - (templates ? `\n\n### Resource Templates\n${templates}` : "") + - (resources ? `\n\n### Direct Resources\n${resources}` : "") - ) - }) - .join("\n\n")}` - : "(No MCP servers currently connected)" -}${ - supportsConsultAdvisor - ? ` - -==== - -CONSULTING THE ADVISOR MODEL - -You can use the consult_advisor tool to get suggestions from an advisor model, a powerful AI model that can provide strategic guidance and help solve complex problems. The conversation history that led to the current situation is automatically passed to the advisor, allowing it to provide contextually relevant guidance based on the full picture of the task at hand. - -# When to Use the Advisor - -- When stuck on persistent bugs that you cannot resolve -- If you've tried multiple approaches without success -- When facing complex type errors or package incompatibilities -- When debugging intricate interactions between multiple systems -- If you need deeper insight into system behavior that may not be apparent - -# How to Use Effectively - -## Provide Clear Context -- Explain the current situation and challenge -- Include relevant code snippets or error messages -- Describe what you've already tried -- Specify what kind of guidance you're seeking - -## Ask Specific Questions -- Instead of "Why isn't this working?" -- Better: "I'm encountering this specific type error when integrating these packages, here's what I've tried..." - -Example Usage: - - -I'm encountering persistent type errors while working with @types/react-query v4.0.0: - -Error: Type 'QueryClient' is not assignable to parameter of type 'never'. - The types of 'getQueryCache().notify' are incompatible between these types. - -I've tried: -- Checking package versions compatibility -- Explicitly typing the QueryClient instance -- Updating @types/react and @types/react-query - -Current package versions: -react-query: ^3.39.3 -@types/react-query: ^4.0.0 -react: ^18.2.0 -typescript: ^4.9.5 - -The error persists despite these attempts. Could this be due to version mismatches or breaking changes I'm not aware of? - - - -# Benefits of Using the Advisor - -- Break through debugging roadblocks -- Get fresh perspectives on complex issues -- Understand root causes of persistent bugs -- Solve challenging technical issues - -Remember: While you should attempt to solve problems with your own reasoning first, the advisor is a powerful resource available when you're stuck on a bug. Don't hesitate to consult it when you've hit a persistent roadblock that you cannot resolve.` - : "" -} - -==== - -CAPABILITIES - -- You have access to tools that let you list files, view source code definitions, regex search${ - supportsComputerUse ? ", use the browser" : "" -}, read files${ - supportsConsultAdvisor ? ", consult an advisor" : "" -}, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as understanding the current state of a project, and much more. -- When the user initially gives you a task, a recursive list of all filepaths in the current working directory ('${cwd.toPosix()}') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current working directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop. -- You can use search_files to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring. -- You can use the list_code_definition_names tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task.${ - supportsComputerUse - ? "\n- You can use the browser_action tool to interact with websites (including html files and locally running development servers) through a Puppeteer-controlled browser when you feel it is necessary in accomplishing the user's task. This tool is particularly useful for web development tasks as it allows you to launch a browser, navigate to pages, interact with elements through clicks and keyboard input, and capture the results through screenshots and console logs. This tool may be useful at key stages of web development tasks-such as after implementing new features, making substantial changes, when troubleshooting issues, or to verify the result of your work. You can analyze the provided screenshots to ensure correct rendering or identify errors, and review console logs for runtime issues.\n - For example, if asked to add a component to a react website, you might create the necessary files, use execute_command to run the site locally, then use browser_action to launch the browser, navigate to the local server, and verify the component renders & functions correctly before closing the browser." - : "" -} -- You have access to MCP servers that may provide additional tools and resources. Each server may provide different capabilities that you can use to accomplish tasks more effectively.${ - supportsConsultAdvisor - ? "\n- When you hit a roadblock, such as an error you've attempted to resolve several times without success, you can use the consult_advisor tool to get suggestions from an advanced-reasoning AI model. The conversation history that led to the current situation is automatically passed to the advisor, allowing it to provide contextually relevant guidance based on the full picture of the task at hand." - : "" -} - -==== - -RULES - -- Your current working directory is: ${cwd.toPosix()} -- You cannot \`cd\` into a different directory to complete a task. You are stuck operating from '${cwd.toPosix()}', so be sure to pass in the correct 'path' parameter when using tools that require a path. -- Do not use the ~ character or $HOME to refer to the home directory. -- When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using replace_in_file to make informed changes. -- The user may provide a file's contents directly in their message, in which case you shouldn't use the read_file tool to get the file contents again since you already have it.${ - supportsComputerUse - ? '\n- The user may ask generic non-development tasks, such as "what\'s the latest news" or "look up the weather in San Diego", in which case you might use the browser_action tool to complete the task if it makes sense to do so. However, if an available MCP server tool or resource can be used instead, you should prefer to use it over browser_action.' - : "" -} -- At the end of each user message, you will automatically receive environment_details. This information is not written by the user themselves, but is auto-generated to provide potentially relevant context about the project structure and environment. While this information can be valuable for understanding the project context, do not treat it as a direct part of the user's request or response. Use it to inform your actions and decisions, but don't assume the user is explicitly asking about or referring to this information unless they clearly do so in their message. When using environment_details, explain your actions clearly to ensure the user understands, as they may not be aware of these details. -- MCP operations should be used one at a time, similar to other tool usage. Wait for confirmation of success before proceeding with additional operations. -- It is critical you wait for the user's response after each tool use, in order to confirm the success of the tool use. For example, if asked to make a todo app, you would create a file, wait for the user's response it was created successfully, then create another file if needed, wait for the user's response it was created successfully, etc.${ - supportsComputerUse - ? " Then if you want to test your work, you might use browser_action to launch the site, wait for the user's response confirming the site was launched along with a screenshot, then perhaps e.g., click a button to test functionality if needed, wait for the user's response confirming the button was clicked along with a screenshot of the new state, before finally closing the browser." - : "" -} - -==== - -SYSTEM INFORMATION - -Operating System: ${osName()} -Default Shell: ${defaultShell} -Home Directory: ${os.homedir().toPosix()} -Current Working Directory: ${cwd.toPosix()} - -==== - -OBJECTIVE - -You respond to user inquiries by gathering relevant information through available tools and providing clear, informed responses. - -1. Analyze the user's inquiry to understand what information is needed to provide a complete and accurate response. -2. Use available tools one at a time to gather the necessary information. Each tool use should be purposeful in building your understanding to address the inquiry. -3. Remember, you have extensive capabilities with access to a wide range of tools that can be used in powerful and clever ways to gather relevant information. Before calling a tool, do some analysis within tags. First, analyze the file structure provided in environment_details to gain context and insights for proceeding effectively. Then, think about which of the provided tools is the most relevant tool to gather the information needed. Next, go through each of the required parameters of the relevant tool and determine if the user has directly provided or given enough information to infer a value. When deciding if the parameter can be inferred, carefully consider all the context to see if it supports a specific value. If all of the required parameters are present or can be reasonably inferred, close the thinking tag and proceed with the tool use. BUT, if one of the values for a required parameter is missing, DO NOT invoke the tool (not even with fillers for the missing params) and instead, ask the user to provide the missing parameters using the respond_to_inquiry tool. DO NOT ask for more information on optional parameters if it is not provided. -4. Once you've gathered the necessary information to address the inquiry, you must use the respond_to_inquiry tool to present a clear, well-informed response to the user. - -==== - -CHAT MODE - -You are now in chat mode, which means you will engage in conversational interactions rather than completing development tasks. In this mode: - -1. Your primary purpose is to respond helpfully to the user's questions and engage in natural dialogue -2. While you still have access to all tools, you will use them only to gather information to inform your responses -3. Instead of working towards task completion, you will work towards providing clear, informative responses -4. You must use the respond_to_inquiry tool to deliver your responses, not attempt_completion -5. Keep responses focused and relevant to the user's questions -6. You may use tools like: - - read_file to look up code context - - search_files to find relevant information - - list_files to understand project structure - - MCP tools/resources to get external data - But always with the goal of informing your response - -Your objective is to be a helpful conversational partner, not a task-completing agent. Every tool use should be in service of building a more complete and accurate response to the user's inquiry. However, if you have enough information to respond to the user's inquiry, you should use the respond_to_inquiry tool to immediately deliver a response. - -Important: In chat mode, you should immediately use the respond_to_inquiry tool to deliver your response, rather than using tags to analyze when to respond. Do not talk about using respond_to_inquiry - just use it directly to share your thoughts and provide helpful answers.` - -export function addUserInstructions(settingsCustomInstructions?: string, clineRulesFileInstructions?: string) { - let customInstructions = "" - if (settingsCustomInstructions) { - customInstructions += settingsCustomInstructions + "\n\n" - } - if (clineRulesFileInstructions) { - customInstructions += clineRulesFileInstructions - } - - return ` -==== - -USER'S CUSTOM INSTRUCTIONS - -The following additional instructions are provided by the user, and should be followed to the best of your ability without interfering with the TOOL USE guidelines. - -${customInstructions.trim()}` -} diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index 0a5b66b875..291748521d 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -243,6 +243,15 @@ Your final result description here Command to demonstrate result (optional) +## chat_mode_response +Description: Respond to the user's inquiry with a clear answer. This tool should be used when you need to provide a response to a question or statement. This tool is only available in CHAT MODE. The environment_details will specify the current mode, if it is not chat mode then you should not use this tool. +Parameters: +- response: (required) The response to provide to the user. This should be a clear answer that addresses the user's inquiry. +Usage: + +Your response here + + # Tool Use Examples ## Example 1: Requesting to execute a command @@ -896,6 +905,19 @@ Remember: While you should attempt to solve problems with your own reasoning fir ==== +TASK MODE V.S. CHAT MODE + +In each user message, the environment_details will specify the current mode. There are two modes: + +- TASK MODE: In this mode, you have access to all tools EXCEPT the chat_mode_response tool. + - In task mode, you use tools to accomplish the user's task. Once you've completed the user's task, you use the attempt_completion tool to present the result of the task to the user. +- CHAT MODE: In this mode, you ONLY have access to the chat_mode_response tool. + - In chat mode, you should immediately use the chat_mode_response tool to deliver your response, rather than using tags to analyze when to respond. Do not talk about using chat_mode_response - just use it directly to share your thoughts and provide helpful answers. + +You should only use tools that are available in the current mode. + +==== + CAPABILITIES - You have access to tools that let you execute CLI commands on the user's computer, list files, view source code definitions, regex search${ diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index ed0722836b..f8a5dfd362 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -673,6 +673,16 @@ export class ClineProvider implements vscode.WebviewViewProvider { } } + async switchToTaskMode() { + const { chatSettings } = await this.getState() + chatSettings.mode = "task" + await this.updateGlobalState("chatSettings", chatSettings) + if (this.cline) { + this.cline.updateChatSettings(chatSettings) + } + await this.postStateToWebview() + } + async updateCustomInstructions(instructions?: string) { // User may be clearing the field await this.updateGlobalState("customInstructions", instructions || undefined) diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 06b28e8823..0e0c60fff3 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -69,7 +69,7 @@ export interface ClineMessage { export type ClineAsk = | "followup" - | "respond_to_inquiry" + | "chat_mode_response" | "command" | "command_output" | "completion_result" diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index d4c7109cdf..d6fac41079 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -1270,7 +1270,7 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi
    ) - case "respond_to_inquiry": + case "chat_mode_response": return (
    diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 8dd275fb2a..400f42ae88 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -103,9 +103,9 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie // setPrimaryButtonText(undefined) // setSecondaryButtonText(undefined) break - case "respond_to_inquiry": + case "chat_mode_response": setTextAreaDisabled(isPartial) - setClineAsk("respond_to_inquiry") + setClineAsk("chat_mode_response") setEnableButtons(false) // setPrimaryButtonText(undefined) // setSecondaryButtonText(undefined) @@ -278,7 +278,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie } else if (clineAsk) { switch (clineAsk) { case "followup": - case "respond_to_inquiry": + case "chat_mode_response": case "tool": case "browser_action_launch": case "command": // user can provide feedback to a tool or command use diff --git a/webview-ui/src/components/chat/TaskHeader.tsx b/webview-ui/src/components/chat/TaskHeader.tsx index cd7280737b..1c04643e6e 100644 --- a/webview-ui/src/components/chat/TaskHeader.tsx +++ b/webview-ui/src/components/chat/TaskHeader.tsx @@ -157,7 +157,7 @@ const TaskHeader: React.FC = ({ minWidth: 0, // This allows the div to shrink below its content size }}> - {chatSettings.mode === "task" ? "Task" : "Chat"} + Task {!isTaskExpanded && ":"} {!isTaskExpanded && {highlightMentions(task.text, false)}} From df31f6bfebb36abda71f9a637380703bdde7c992 Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Mon, 20 Jan 2025 00:29:25 -0800 Subject: [PATCH 34/49] Use plan/act mode --- src/core/Cline.ts | 55 ++++++++++++------- src/core/assistant-message/index.ts | 2 +- src/core/prompts/system.ts | 27 ++++++--- src/core/webview/ClineProvider.ts | 25 +++++---- src/shared/ChatSettings.ts | 4 +- src/shared/ExtensionMessage.ts | 2 +- webview-ui/src/components/chat/ChatRow.tsx | 2 +- .../src/components/chat/ChatTextArea.tsx | 12 ++-- webview-ui/src/components/chat/ChatView.tsx | 8 +-- 9 files changed, 82 insertions(+), 55 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 4db84a989d..5ce6743cc5 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -98,6 +98,8 @@ export class Cline { conversationHistoryDeletedRange?: [number, number] isInitialized = false private advisorProblem?: string + isAwaitingPlanResponse = false + didRespondToPlanAskBySwitchingMode = false // streaming isWaitingForFirstChunk = false @@ -751,8 +753,6 @@ export class Cline { this.apiConversationHistory = [] await this.providerRef.deref()?.postStateToWebview() - await this.providerRef.deref()?.switchToTaskMode() - await this.say("text", task, images) this.isInitialized = true @@ -994,8 +994,8 @@ export class Cline { type: "text", text: `[TASK RESUMPTION] ${ - this.chatSettings?.mode === "chat" - ? `This task was interrupted ${agoText}. The conversation may have been incomplete. Be aware that the project state may have changed since then. The current working directory is now '${cwd.toPosix()}'.\n\nNote: If you previously attempted a tool use that the user did not provide a result for, you should assume the tool use was not successful. However you are in CHAT MODE, so rather than continuing the task, you must respond to the user's message.` + this.chatSettings?.mode === "plan" + ? `This task was interrupted ${agoText}. The conversation may have been incomplete. Be aware that the project state may have changed since then. The current working directory is now '${cwd.toPosix()}'.\n\nNote: If you previously attempted a tool use that the user did not provide a result for, you should assume the tool use was not successful. However you are in PLAN MODE, so rather than continuing the task, you must respond to the user's message.` : `This task was interrupted ${agoText}. It may or may not be complete, so please reassess the task context. Be aware that the project state may have changed since then. The current working directory is now '${cwd.toPosix()}'. If the task has not been completed, retry the last step before interruption and proceed with completing the task.\n\nNote: If you previously attempted a tool use that the user did not provide a result for, you should assume the tool use was not successful and assess whether you should retry. If the last tool was a browser_action, the browser has been closed and you must launch a new browser if needed.` }${ wasRecent @@ -1003,8 +1003,10 @@ export class Cline { : "" }` + (responseText - ? `\n\n${this.chatSettings?.mode === "chat" ? "New message to respond to with chat_mode_response tool (be sure to provide your response in the parameter)" : "New instructions for task continuation"}:\n\n${responseText}\n` - : ""), + ? `\n\n${this.chatSettings?.mode === "plan" ? "New message to respond to with plan_mode_response tool (be sure to provide your response in the parameter)" : "New instructions for task continuation"}:\n\n${responseText}\n` + : this.chatSettings.mode === "plan" + ? "(The user did not provide a new message. Consider asking them how they'd like you to proceed, or to switch to Act mode to continue with the task.)" + : ""), }) if (responseImages && responseImages.length > 0) { @@ -1525,8 +1527,8 @@ export class Cline { return `[${block.name} for '${block.params.problem}']` case "ask_followup_question": return `[${block.name} for '${block.params.question}']` - case "chat_mode_response": - return `[${block.name} for '${block.params.response}']` + case "plan_mode_response": + return `[${block.name}]` case "attempt_completion": return `[${block.name}]` } @@ -2729,18 +2731,18 @@ export class Cline { break } } - case "chat_mode_response": { + case "plan_mode_response": { const response: string | undefined = block.params.response try { if (block.partial) { - await this.ask("chat_mode_response", removeClosingTag("response", response), block.partial).catch( + await this.ask("plan_mode_response", removeClosingTag("response", response), block.partial).catch( () => {}, ) break } else { if (!response) { this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("chat_mode_response", "response")) + pushToolResult(await this.sayAndCreateMissingParamError("plan_mode_response", "response")) // await this.saveCheckpoint() break } @@ -2753,9 +2755,23 @@ export class Cline { // }) // } - const { text, images } = await this.ask("chat_mode_response", response, false) - await this.say("user_feedback", text ?? "", images) - pushToolResult(formatResponse.toolResult(`\n${text}\n`, images)) + this.isAwaitingPlanResponse = true + const { text, images } = await this.ask("plan_mode_response", response, false) + this.isAwaitingPlanResponse = false + + if (this.didRespondToPlanAskBySwitchingMode) { + // await this.say("user_feedback", text ?? "", images) + pushToolResult( + formatResponse.toolResult( + `[The user has switched to ACT MODE, so you may now proceed with the task.]`, + images, + ), + ) + } else { + await this.say("user_feedback", text ?? "", images) + pushToolResult(formatResponse.toolResult(`\n${text}\n`, images)) + } + // await this.saveCheckpoint() break } @@ -3471,13 +3487,14 @@ export class Cline { } details += "\n\n# Current Mode" - if (this.chatSettings.mode === "chat") { - details += "\nCHAT MODE" + if (this.chatSettings.mode === "plan") { + details += "\nPLAN MODE" + details += '\nSee "## What is PLAN MODE?" above for more information about what to do in this mode.' details += - '\n(Remember: You now only have access to the chat_mode_response tool. If it seems the user wants you to use tools only available in TASK MODE, you should ask the user to "toggle to Task mode" - they will have to manually do this themselves with the Task/Chat toggle button below.)' + '\n(Remember: You now only have access to the plan_mode_response tool. If it seems the user wants you to use tools only available in ACT MODE, you should ask the user to "toggle to Act mode" - they will have to manually do this themselves with the Plan/Act toggle button below. You do not have the ability to switch to ACT MODE yourself, and must wait for the user to do it themselves once they are satisfied with the plan.)' } else { - details += "\nTASK MODE" - details += "\n(Remember: You cannot use the chat_mode_response tool.)" + details += "\nACT MODE" + details += "\n(Remember: You cannot use the plan_mode_response tool.)" } return `\n${details.trim()}\n` diff --git a/src/core/assistant-message/index.ts b/src/core/assistant-message/index.ts index e4dd04e4a1..3ba167c527 100644 --- a/src/core/assistant-message/index.ts +++ b/src/core/assistant-message/index.ts @@ -21,7 +21,7 @@ export const toolUseNames = [ "access_mcp_resource", "consult_advisor", "ask_followup_question", - "chat_mode_response", + "plan_mode_response", "attempt_completion", ] as const diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index 291748521d..15648a2fa3 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -243,14 +243,14 @@ Your final result description here Command to demonstrate result (optional) -## chat_mode_response -Description: Respond to the user's inquiry with a clear answer. This tool should be used when you need to provide a response to a question or statement. This tool is only available in CHAT MODE. The environment_details will specify the current mode, if it is not chat mode then you should not use this tool. +## plan_mode_response +Description: Respond to the user's inquiry with a clear answer in an effort to plan a solution to the user's task. This tool should be used when you need to provide a response to a question or statement from the user about how you plan to accomplish the task. This tool is only available in PLAN MODE. The environment_details will specify the current mode, if it is not PLAN MODE then you should not use this tool. Depending on the user's message, you may ask questions to get clarification about the user's request, architect a solution to the task, and to brainstorm ideas with the user. For example, if the user's task is to create a website, you may start by asking some clarifying questions, then present a detailed plan for how you will accomplish the task given the context, and perhaps engage in a back and forth to finalize the details before the user switches you to ACT MODE to implement the solution. Parameters: -- response: (required) The response to provide to the user. This should be a clear answer that addresses the user's inquiry. +- response: (required) The response to provide to the user. Usage: - + Your response here - + # Tool Use Examples @@ -905,17 +905,26 @@ Remember: While you should attempt to solve problems with your own reasoning fir ==== -TASK MODE V.S. CHAT MODE +ACT MODE V.S. PLAN MODE In each user message, the environment_details will specify the current mode. There are two modes: -- TASK MODE: In this mode, you have access to all tools EXCEPT the chat_mode_response tool. +- ACT MODE: In this mode, you have access to all tools EXCEPT the plan_mode_response tool. - In task mode, you use tools to accomplish the user's task. Once you've completed the user's task, you use the attempt_completion tool to present the result of the task to the user. -- CHAT MODE: In this mode, you ONLY have access to the chat_mode_response tool. - - In chat mode, you should immediately use the chat_mode_response tool to deliver your response, rather than using tags to analyze when to respond. Do not talk about using chat_mode_response - just use it directly to share your thoughts and provide helpful answers. +- PLAN MODE: In this special mode, you ONLY have access to the plan_mode_response tool. + - In plan mode, you should immediately use the plan_mode_response tool to deliver your response, rather than using tags to analyze when to respond. Do not talk about using plan_mode_response - just use it directly to share your thoughts and provide helpful answers. You should only use tools that are available in the current mode. +## What is PLAN MODE? + +- While you are usually in ACT MODE, the user may switch to PLAN MODE in order to have a back and forth with you to plan how to best accomplish the task. +- When starting in PLAN MODE, ask the user some clarifying questions to get a better understanding of the task. (Generally three questions are enough to get the conversation started, but you may ask more questions if needed.) + - Make sure to wait for the user's response to your questions before moving on in creating a plan. +- Once you've gained more context about the user's request, you should architect and a detailed plan for how you will accomplish the task. +- Then you might ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and plan the best way to accomplish it. +- Finally once it seems like you've reached a good plan, ask the user to switch you back to ACT MODE to implement the solution. + ==== CAPABILITIES diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index f8a5dfd362..3c91deae3e 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -486,12 +486,23 @@ export class ClineProvider implements vscode.WebviewViewProvider { break case "chatSettings": if (message.chatSettings) { + const didSwitchToActMode = message.chatSettings.mode === "act" await this.updateGlobalState("chatSettings", message.chatSettings) + await this.postStateToWebview() if (this.cline) { this.cline.updateChatSettings(message.chatSettings) + if (this.cline.isAwaitingPlanResponse && didSwitchToActMode) { + this.cline.didRespondToPlanAskBySwitchingMode = true + // this is necessary for the webview to update accordingly, but Cline instance will not send text back as feedback message + await this.postMessageToWebview({ + type: "invoke", + invoke: "sendMessage", + text: "[Proceeding with the task...]", + }) + } else { + this.cancelTask() + } } - await this.postStateToWebview() - this.cancelTask() } break // case "relaunchChromeDebugMode": @@ -673,16 +684,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { } } - async switchToTaskMode() { - const { chatSettings } = await this.getState() - chatSettings.mode = "task" - await this.updateGlobalState("chatSettings", chatSettings) - if (this.cline) { - this.cline.updateChatSettings(chatSettings) - } - await this.postStateToWebview() - } - async updateCustomInstructions(instructions?: string) { // User may be clearing the field await this.updateGlobalState("customInstructions", instructions || undefined) diff --git a/src/shared/ChatSettings.ts b/src/shared/ChatSettings.ts index 18eab25312..1632082db1 100644 --- a/src/shared/ChatSettings.ts +++ b/src/shared/ChatSettings.ts @@ -1,7 +1,7 @@ export interface ChatSettings { - mode: "task" | "chat" + mode: "plan" | "act" } export const DEFAULT_CHAT_SETTINGS: ChatSettings = { - mode: "task", + mode: "act", } diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 0e0c60fff3..8b524240bf 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -69,7 +69,7 @@ export interface ClineMessage { export type ClineAsk = | "followup" - | "chat_mode_response" + | "plan_mode_response" | "command" | "command_output" | "completion_result" diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index d6fac41079..1d41949a7a 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -1270,7 +1270,7 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi
    ) - case "chat_mode_response": + case "plan_mode_response": return (
    diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index 5c37147cf5..55bcd93073 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -75,13 +75,13 @@ const SwitchContainer = styled.div<{ disabled: boolean }>` margin-left: -10px; // compensate for the transform so flex spacing works ` -const Slider = styled.div<{ isChat: boolean }>` +const Slider = styled.div<{ isAct: boolean }>` position: absolute; height: 100%; width: 50%; background-color: var(--vscode-badge-background); transition: transform 0.2s ease; - transform: translateX(${(props) => (props.isChat ? "100%" : "0%")}); + transform: translateX(${(props) => (props.isAct ? "100%" : "0%")}); ` const ButtonGroup = styled.div` @@ -597,7 +597,7 @@ const ChatTextArea = forwardRef( const onModeToggle = useCallback(() => { if (textAreaDisabled) return - const newMode = chatSettings.mode === "chat" ? "task" : "chat" + const newMode = chatSettings.mode === "plan" ? "act" : "plan" vscode.postMessage({ type: "chatSettings", chatSettings: { @@ -1008,9 +1008,9 @@ const ChatTextArea = forwardRef( - - Task - Chat + + Plan + Act
    diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 400f42ae88..2aa30d995f 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -103,9 +103,9 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie // setPrimaryButtonText(undefined) // setSecondaryButtonText(undefined) break - case "chat_mode_response": + case "plan_mode_response": setTextAreaDisabled(isPartial) - setClineAsk("chat_mode_response") + setClineAsk("plan_mode_response") setEnableButtons(false) // setPrimaryButtonText(undefined) // setSecondaryButtonText(undefined) @@ -174,7 +174,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie setTextAreaDisabled(false) setClineAsk("resume_task") setEnableButtons(true) - setPrimaryButtonText("Resume") + setPrimaryButtonText("Resume Task") setSecondaryButtonText(undefined) setDidClickCancel(false) // special case where we reset the cancel button state break @@ -278,7 +278,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie } else if (clineAsk) { switch (clineAsk) { case "followup": - case "chat_mode_response": + case "plan_mode_response": case "tool": case "browser_action_launch": case "command": // user can provide feedback to a tool or command use From 87772db80d463530b9c7eecb6f564ed8a53d476a Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Mon, 20 Jan 2025 00:57:36 -0800 Subject: [PATCH 35/49] Prepare for release --- CHANGELOG.md | 4 ++-- src/core/Cline.ts | 3 ++- src/core/webview/ClineProvider.ts | 2 +- webview-ui/src/components/chat/Announcement.tsx | 5 +++-- webview-ui/src/components/chat/TaskHeader.tsx | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3d8b2bea0..850007a49d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,8 @@ ## [3.2.0] -- Add Advisor model tool to help when Cline hits a roadblock (available with OpenRouter and Anthropic) -- Add new Task/Chat mode toggle to turn Cline into a conversational partner, rather than a task-completing agent +- Add 'Consult Advisor' tool to let Cline ask a powerful model like o1 for help when he hits a roadblock (available with OpenRouter and Anthropic) +- Add Plan/Act mode toggle to let you plan tasks with Cline before letting him get to work - Easily switch between API providers and models using a new popup menu under the chat field - Add VS Code LM API provider to run models provided by other VS Code extensions (e.g. GitHub Copilot). Shoutout to @julesmons, @RaySinner, and @MrUbens for putting this together! - Add on/off toggle for MCP servers to disable them when not in use. Thanks @MrUbens! diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 5ce6743cc5..c6abc1d612 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -3489,7 +3489,8 @@ export class Cline { details += "\n\n# Current Mode" if (this.chatSettings.mode === "plan") { details += "\nPLAN MODE" - details += '\nSee "## What is PLAN MODE?" above for more information about what to do in this mode.' + details += + "\nSee \"## What is PLAN MODE?\" above for more information about what to do in this mode. If you haven't done so already, it's a good idea to start by asking a question." details += '\n(Remember: You now only have access to the plan_mode_response tool. If it seems the user wants you to use tools only available in ACT MODE, you should ask the user to "toggle to Act mode" - they will have to manually do this themselves with the Plan/Act toggle button below. You do not have the ability to switch to ACT MODE yourself, and must wait for the user to do it themselves once they are satisfied with the plan.)' } else { diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 3c91deae3e..e5aeed0ea0 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -88,7 +88,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { private cline?: Cline private workspaceTracker?: WorkspaceTracker mcpHub?: McpHub - private latestAnnouncementId = "jan-19-2025" // update to some unique identifier when we add a new announcement + private latestAnnouncementId = "jan-20a-2025" // update to some unique identifier when we add a new announcement constructor( readonly context: vscode.ExtensionContext, diff --git a/webview-ui/src/components/chat/Announcement.tsx b/webview-ui/src/components/chat/Announcement.tsx index 0ae03b8b9e..6381abf5ee 100644 --- a/webview-ui/src/components/chat/Announcement.tsx +++ b/webview-ui/src/components/chat/Announcement.tsx @@ -35,13 +35,14 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => { New Consult Advisor tool {" "} lets Cline ask a powerful model like o1 for help when stuck. Cline provides the full context of the problem, - and the Advisor model responds with a plan to fix it.{" "} + and the Advisor model responds with a solution. (Available with OpenRouter and Anthropic.){" "} See a demo here!
  • - Task/Chat mode toggle to turn Cline into a conversational partner, rather than a task-completing agent + Plan/Act mode toggle: Plan mode lets Cline ask clarifying questions, brainstorm ideas, and architect a + solution. Switch back to Act mode to let him execute the plan!
  • Quick API/model switching with a new popup menu under the chat field diff --git a/webview-ui/src/components/chat/TaskHeader.tsx b/webview-ui/src/components/chat/TaskHeader.tsx index 1c04643e6e..d04f0aecf4 100644 --- a/webview-ui/src/components/chat/TaskHeader.tsx +++ b/webview-ui/src/components/chat/TaskHeader.tsx @@ -30,7 +30,7 @@ const TaskHeader: React.FC = ({ totalCost, onClose, }) => { - const { apiConfiguration, currentTaskItem, checkpointTrackerErrorMessage, chatSettings } = useExtensionState() + const { apiConfiguration, currentTaskItem, checkpointTrackerErrorMessage } = useExtensionState() const [isTaskExpanded, setIsTaskExpanded] = useState(true) const [isTextExpanded, setIsTextExpanded] = useState(false) const [showSeeMore, setShowSeeMore] = useState(false) From 1d7884d062cfc3a56d996b35e83c23db3de93f9d Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Mon, 20 Jan 2025 01:32:41 -0800 Subject: [PATCH 36/49] Fix switch color --- webview-ui/src/components/chat/ChatTextArea.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index 55bcd93073..3e8affcc55 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -49,7 +49,7 @@ interface ChatTextAreaProps { const SwitchOption = styled.div<{ isActive: boolean }>` padding: 2px 8px; - color: ${(props) => (props.isActive ? "var(--vscode-badge-foreground)" : "var(--vscode-input-foreground)")}; + color: ${(props) => (props.isActive ? "white" : "var(--vscode-input-foreground)")}; z-index: 1; transition: color 0.2s ease; font-size: 12px; @@ -79,7 +79,7 @@ const Slider = styled.div<{ isAct: boolean }>` position: absolute; height: 100%; width: 50%; - background-color: var(--vscode-badge-background); + background-color: var(--vscode-focusBorder); transition: transform 0.2s ease; transform: translateX(${(props) => (props.isAct ? "100%" : "0%")}); ` From f1a9f3fed76831751398a0ef87f830b8ba8e3ae2 Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Mon, 20 Jan 2025 13:46:19 -0800 Subject: [PATCH 37/49] Allow using tools in plan mode --- src/core/Cline.ts | 4 ++-- src/core/prompts/system.ts | 14 ++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index c6abc1d612..42107492fa 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -3490,9 +3490,9 @@ export class Cline { if (this.chatSettings.mode === "plan") { details += "\nPLAN MODE" details += - "\nSee \"## What is PLAN MODE?\" above for more information about what to do in this mode. If you haven't done so already, it's a good idea to start by asking a question." + "\nIn this mode you should focus on information gathering and architecting a solution. If you haven't done so already, it's a good idea to start by reading files to get context and then asking questions." details += - '\n(Remember: You now only have access to the plan_mode_response tool. If it seems the user wants you to use tools only available in ACT MODE, you should ask the user to "toggle to Act mode" - they will have to manually do this themselves with the Plan/Act toggle button below. You do not have the ability to switch to ACT MODE yourself, and must wait for the user to do it themselves once they are satisfied with the plan.)' + '\n(Remember: You now have access to the plan_mode_response tool, which allows you to engage in a more conversational back and forth with the user rather than jumping into executing the task. If it seems the user wants you to use tools only available in ACT MODE, you should ask the user to "toggle to Act mode" - they will have to manually do this themselves with the Plan/Act toggle button below. You do not have the ability to switch to ACT MODE yourself, and must wait for the user to do it themselves once they are satisfied with the plan.)' } else { details += "\nACT MODE" details += "\n(Remember: You cannot use the plan_mode_response tool.)" diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index 15648a2fa3..5ccf469eaf 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -910,18 +910,16 @@ ACT MODE V.S. PLAN MODE In each user message, the environment_details will specify the current mode. There are two modes: - ACT MODE: In this mode, you have access to all tools EXCEPT the plan_mode_response tool. - - In task mode, you use tools to accomplish the user's task. Once you've completed the user's task, you use the attempt_completion tool to present the result of the task to the user. -- PLAN MODE: In this special mode, you ONLY have access to the plan_mode_response tool. - - In plan mode, you should immediately use the plan_mode_response tool to deliver your response, rather than using tags to analyze when to respond. Do not talk about using plan_mode_response - just use it directly to share your thoughts and provide helpful answers. - -You should only use tools that are available in the current mode. + - In act mode, you use tools to accomplish the user's task. Once you've completed the user's task, you use the attempt_completion tool to present the result of the task to the user. +- PLAN MODE: In this special mode, you have access to the plan_mode_response tool. + - In plan mode, the goal is to gather information and get context to create a detailed plan for accomplishing the task, which the user will review and approve before you switch back to ACT MODE to implement the solution. + - In plan mode, you should use the plan_mode_response tool to deliver your response, rather than using tags to analyze when to respond. Do not talk about using plan_mode_response - just use it directly to share your thoughts and provide helpful answers. ## What is PLAN MODE? - While you are usually in ACT MODE, the user may switch to PLAN MODE in order to have a back and forth with you to plan how to best accomplish the task. -- When starting in PLAN MODE, ask the user some clarifying questions to get a better understanding of the task. (Generally three questions are enough to get the conversation started, but you may ask more questions if needed.) - - Make sure to wait for the user's response to your questions before moving on in creating a plan. -- Once you've gained more context about the user's request, you should architect and a detailed plan for how you will accomplish the task. +- When starting in PLAN MODE, depending on the user's request, you may need to do some information gathering e.g. using read_file or search_files to get more context about the task. You may also ask the user clarifying questions to get a better understanding of the task. +- Once you've gained more context about the user's request, you should architect a detailed plan for how you will accomplish the task. - Then you might ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and plan the best way to accomplish it. - Finally once it seems like you've reached a good plan, ask the user to switch you back to ACT MODE to implement the solution. From 6ceda01b245d102feb030a132f8fab788870385d Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Mon, 20 Jan 2025 15:00:42 -0800 Subject: [PATCH 38/49] Remove consult advisor tool --- CHANGELOG.md | 1 - src/api/index.ts | 5 +- src/api/providers/anthropic.ts | 40 +-- src/api/providers/openrouter.ts | 31 +-- src/core/Cline.ts | 240 ++---------------- src/core/assistant-message/index.ts | 2 - src/core/prompts/advisor.ts | 22 -- src/core/prompts/system.ts | 90 +------ src/core/webview/ClineProvider.ts | 30 --- src/shared/AutoApprovalSettings.ts | 2 - src/shared/ExtensionMessage.ts | 10 - src/shared/WebviewMessage.ts | 1 - src/shared/api.ts | 18 -- .../src/components/chat/Announcement.tsx | 14 +- .../src/components/chat/AutoApproveMenu.tsx | 15 +- webview-ui/src/components/chat/ChatRow.tsx | 118 +-------- .../src/components/chat/ChatTextArea.tsx | 60 ++--- webview-ui/src/components/chat/ChatView.tsx | 12 - .../src/components/settings/ApiOptions.tsx | 152 +---------- .../settings/OpenRouterModelPicker.tsx | 83 ++---- .../src/components/settings/SettingsView.tsx | 9 +- .../src/context/ExtensionStateContext.tsx | 11 +- webview-ui/src/utils/validate.ts | 22 +- 23 files changed, 99 insertions(+), 889 deletions(-) delete mode 100644 src/core/prompts/advisor.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 850007a49d..85989ce8e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,6 @@ ## [3.2.0] -- Add 'Consult Advisor' tool to let Cline ask a powerful model like o1 for help when he hits a roadblock (available with OpenRouter and Anthropic) - Add Plan/Act mode toggle to let you plan tasks with Cline before letting him get to work - Easily switch between API providers and models using a new popup menu under the chat field - Add VS Code LM API provider to run models provided by other VS Code extensions (e.g. GitHub Copilot). Shoutout to @julesmons, @RaySinner, and @MrUbens for putting this together! diff --git a/src/api/index.ts b/src/api/index.ts index f200a91b21..2ef82f8659 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,5 +1,5 @@ import { Anthropic } from "@anthropic-ai/sdk" -import { ApiConfiguration, ModelInfo, ModelType } from "../shared/api" +import { ApiConfiguration, ModelInfo } from "../shared/api" import { AnthropicHandler } from "./providers/anthropic" import { AwsBedrockHandler } from "./providers/bedrock" import { OpenRouterHandler } from "./providers/openrouter" @@ -15,9 +15,8 @@ import { MistralHandler } from "./providers/mistral" import { VsCodeLmHandler } from "./providers/vscode-lm" export interface ApiHandler { - createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], modelType?: ModelType): ApiStream + createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream getModel(): { id: string; info: ModelInfo } - getAdvisorModel?(): { id: string; info: ModelInfo } } export interface SingleCompletionHandler { diff --git a/src/api/providers/anthropic.ts b/src/api/providers/anthropic.ts index c84cbf0921..8c3fd1b87d 100644 --- a/src/api/providers/anthropic.ts +++ b/src/api/providers/anthropic.ts @@ -1,14 +1,6 @@ import { Anthropic } from "@anthropic-ai/sdk" import { Stream as AnthropicStream } from "@anthropic-ai/sdk/streaming" -import { - anthropicDefaultAdvisorModelId, - anthropicDefaultModelId, - AnthropicModelId, - anthropicModels, - ApiHandlerOptions, - ModelInfo, - ModelType, -} from "../../shared/api" +import { anthropicDefaultModelId, AnthropicModelId, anthropicModels, ApiHandlerOptions, ModelInfo } from "../../shared/api" import { ApiHandler } from "../index" import { ApiStream } from "../transform/stream" @@ -24,8 +16,8 @@ export class AnthropicHandler implements ApiHandler { }) } - async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], modelType: ModelType): ApiStream { - const model = modelType === "advisor" ? this.getAdvisorModel() : this.getModel() + async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + const model = this.getModel() let stream: AnthropicStream const modelId = model.id switch (modelId) { @@ -34,20 +26,6 @@ export class AnthropicHandler implements ApiHandler { case "claude-3-5-haiku-20241022": case "claude-3-opus-20240229": case "claude-3-haiku-20240307": { - // don't use prompt caching for advisor model requests - if (modelType === "advisor") { - stream = (await this.client.messages.create({ - model: modelId, - max_tokens: model.info.maxTokens || 8192, - temperature: 0, - system: [{ text: systemPrompt, type: "text" }], - messages, - // tools, - // tool_choice: { type: "auto" }, - stream: true, - })) as any - break - } /* The latest message will be the new user message, one before will be the assistant message from a previous request, and the user message before that will be a previously cached user message. So we need to mark the latest user message as ephemeral to cache it for the next request, and mark the second to last user message as ephemeral to let the server know the last message to retrieve from the cache for the current request.. */ @@ -208,16 +186,4 @@ export class AnthropicHandler implements ApiHandler { info: anthropicModels[anthropicDefaultModelId], } } - - getAdvisorModel(): { id: string; info: ModelInfo } { - const modelId = this.options.anthropicAdvisorModelId - if (modelId && modelId in anthropicModels) { - const id = modelId as AnthropicModelId - return { id, info: anthropicModels[id] } - } - return { - id: anthropicDefaultAdvisorModelId, - info: anthropicModels[anthropicDefaultAdvisorModelId], - } - } } diff --git a/src/api/providers/openrouter.ts b/src/api/providers/openrouter.ts index d044caad19..e0bec2cf1c 100644 --- a/src/api/providers/openrouter.ts +++ b/src/api/providers/openrouter.ts @@ -2,15 +2,7 @@ import { Anthropic } from "@anthropic-ai/sdk" import axios from "axios" import OpenAI from "openai" import { ApiHandler } from "../" -import { - ApiHandlerOptions, - ModelInfo, - ModelType, - openRouterDefaultAdvisorModelId, - openRouterDefaultAdvisorModelInfo, - openRouterDefaultModelId, - openRouterDefaultModelInfo, -} from "../../shared/api" +import { ApiHandlerOptions, ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "../../shared/api" import { convertToOpenAiMessages } from "../transform/openai-format" import { ApiStream } from "../transform/stream" import delay from "delay" @@ -31,8 +23,8 @@ export class OpenRouterHandler implements ApiHandler { }) } - async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], modelType?: ModelType): ApiStream { - const model = modelType === "advisor" ? this.getAdvisorModel() : this.getModel() + async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + const model = this.getModel() // Convert Anthropic messages to OpenAI format const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [ @@ -55,11 +47,6 @@ export class OpenRouterHandler implements ApiHandler { case "anthropic/claude-3-haiku:beta": case "anthropic/claude-3-opus": case "anthropic/claude-3-opus:beta": - // don't use prompt caching for advisor model requests - if (modelType === "advisor") { - break - } - openAiMessages[0] = { role: "system", content: [ @@ -196,16 +183,4 @@ export class OpenRouterHandler implements ApiHandler { info: openRouterDefaultModelInfo, } } - - getAdvisorModel(): { id: string; info: ModelInfo } { - const modelId = this.options.openRouterAdvisorModelId - const modelInfo = this.options.openRouterAdvisorModelInfo - if (modelId && modelInfo) { - return { id: modelId, info: modelInfo } - } - return { - id: openRouterDefaultAdvisorModelId, - info: openRouterDefaultAdvisorModelInfo, - } - } } diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 42107492fa..ef6d613c00 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -2,25 +2,29 @@ import { Anthropic } from "@anthropic-ai/sdk" import cloneDeep from "clone-deep" import delay from "delay" import fs from "fs/promises" +import getFolderSize from "get-folder-size" import os from "os" import pWaitFor from "p-wait-for" import * as path from "path" import { serializeError } from "serialize-error" import * as vscode from "vscode" import { ApiHandler, buildApiHandler } from "../api" -import { ApiStream } from "../api/transform/stream" +import CheckpointTracker from "../integrations/checkpoints/CheckpointTracker" import { DIFF_VIEW_URI_SCHEME, DiffViewProvider } from "../integrations/editor/DiffViewProvider" import { findToolName, formatContentBlockToMarkdown } from "../integrations/misc/export-markdown" import { extractTextFromFile } from "../integrations/misc/extract-text" +import { showSystemNotification } from "../integrations/notifications" import { TerminalManager } from "../integrations/terminal/TerminalManager" import { BrowserSession } from "../services/browser/BrowserSession" import { UrlContentFetcher } from "../services/browser/UrlContentFetcher" import { listFiles } from "../services/glob/list-files" import { regexSearchFiles } from "../services/ripgrep" import { parseSourceCodeForDefinitionsTopLevel } from "../services/tree-sitter" -import { ApiConfiguration, ModelInfo } from "../shared/api" +import { ApiConfiguration } from "../shared/api" import { findLast, findLastIndex } from "../shared/array" import { AutoApprovalSettings } from "../shared/AutoApprovalSettings" +import { BrowserSettings } from "../shared/BrowserSettings" +import { ChatSettings } from "../shared/ChatSettings" import { combineApiRequests } from "../shared/combineApiRequests" import { combineCommandSequences, COMMAND_REQ_APP_STRING } from "../shared/combineCommandSequences" import { @@ -31,7 +35,6 @@ import { ClineApiReqInfo, ClineAsk, ClineAskUseMcpServer, - ClineConsultAdvisor, ClineMessage, ClineSay, ClineSayBrowserAction, @@ -44,23 +47,18 @@ import { ClineAskResponse, ClineCheckpointRestore } from "../shared/WebviewMessa import { calculateApiCost } from "../utils/cost" import { fileExistsAtPath } from "../utils/fs" import { arePathsEqual, getReadablePath } from "../utils/path" +import { fixModelHtmlEscaping, removeInvalidChars } from "../utils/string" import { AssistantMessageContent, parseAssistantMessage, ToolParamName, ToolUseName } from "./assistant-message" import { constructNewFileContent } from "./assistant-message/diff" import { parseMentions } from "./mentions" import { formatResponse } from "./prompts/responses" -import { addUserInstructions, SYSTEM_PROMPT } from "./prompts/system" -import { getNextTruncationRange, getTruncatedMessages } from "./sliding-window" import { ClineProvider, GlobalFileNames } from "./webview/ClineProvider" -import { showSystemNotification } from "../integrations/notifications" -import { removeInvalidChars } from "../utils/string" -import { fixModelHtmlEscaping } from "../utils/string" -import { OpenAiHandler } from "../api/providers/openai" -import CheckpointTracker from "../integrations/checkpoints/CheckpointTracker" -import getFolderSize from "get-folder-size" -import { BrowserSettings } from "../shared/BrowserSettings" -import { ADVISOR_SYSTEM_PROMPT } from "./prompts/advisor" -import { ChatSettings } from "../shared/ChatSettings" import { OpenRouterHandler } from "../api/providers/openrouter" +import { getNextTruncationRange, getTruncatedMessages } from "./sliding-window" +import { SYSTEM_PROMPT } from "./prompts/system" +import { addUserInstructions } from "./prompts/system" +import { OpenAiHandler } from "../api/providers/openai" +import { ApiStream } from "../api/transform/stream" const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution @@ -97,7 +95,6 @@ export class Cline { checkpointTrackerErrorMessage?: string conversationHistoryDeletedRange?: [number, number] isInitialized = false - private advisorProblem?: string isAwaitingPlanResponse = false didRespondToPlanAskBySwitchingMode = false @@ -1082,8 +1079,6 @@ export class Cline { message.ask === "followup" || message.say === "use_mcp_server" || message.ask === "use_mcp_server" || - message.say === "consult_advisor" || - message.ask === "consult_advisor" || message.say === "browser_action" || message.say === "browser_action_launch" || message.ask === "browser_action_launch" @@ -1194,78 +1189,11 @@ export class Cline { case "access_mcp_resource": case "use_mcp_tool": return this.autoApprovalSettings.actions.useMcp - case "consult_advisor": - return this.autoApprovalSettings.actions.consultAdvisor ?? false } } return false } - estimateAdvisorModelCost(problem: string) { - const truncatedConversationHistory = getTruncatedMessages( - this.apiConversationHistory, - this.conversationHistoryDeletedRange, - ) - const advisorModel = this.api.getAdvisorModel?.() - if (!advisorModel) { - return 0 - } - const advisorMessage = this.createAdvisorMessage(truncatedConversationHistory, advisorModel, problem) - const prompt = ADVISOR_SYSTEM_PROMPT() + advisorMessage - // Estimate ~3 chars per token as a rough approximation - const estimatedInputTokens = Math.ceil(prompt.length / 3) - const estimatedOutputTokens = 300 // typical response size - // Note: we don't prompt cache since we only send up one request at a time - const inputCost = (estimatedInputTokens * (advisorModel.info.inputPrice ?? 0)) / 1_000_000 // Convert from per million tokens - const outputCost = (estimatedOutputTokens * (advisorModel.info.outputPrice ?? 0)) / 1_000_000 - return inputCost + outputCost - } - - createAdvisorMessage( - truncatedConversationHistory: Anthropic.Messages.MessageParam[], - advisorModel: { - id: string - info: ModelInfo - }, - advisorProblem: string, - ) { - // Generate markdown - const markdownContent = truncatedConversationHistory - .map((message) => { - const role = message.role === "user" ? "**User:**" : "**Coding Agent:**" - const content = Array.isArray(message.content) - ? message.content.map((block) => formatContentBlockToMarkdown(block)).join("\n") - : message.content - return `${role}\n\n${content}\n\n` - }) - .join("---\n\n") - - // Don't want to send the entire conv history, just the most recent context - // Get approximate char count from token limit - const advisorContextWindow = advisorModel.info.contextWindow || 128_000 - const tokensToKeep = Math.floor(advisorContextWindow / 2) - // Estimate ~3 chars per token as a rough approximation - const charsToKeep = tokensToKeep * 3 - // Get last n chars of markdown content - const isTruncated = markdownContent.length > charsToKeep - const firstMessage = truncatedConversationHistory.at(0) - const firstMessageContent = firstMessage - ? Array.isArray(firstMessage.content) - ? firstMessage.content.map((block) => (block.type === "text" ? block.text : "")).join("\n") - : firstMessage.content - : "" - const recentContext = - (isTruncated ? `**User:**:\n\n${firstMessageContent}\n\n... (older messages removed for brevity) ...\n\n` : "") + - markdownContent.slice(-charsToKeep) - const advisorMessage = - "\n\n# The conversation history leading up to this point:\n\n" + - recentContext + - "\n\n# The problem the coding agent needs advice on:\n\n" + - advisorProblem - - return advisorMessage - } - async *attemptApiRequest(previousApiReqIndex: number): ApiStream { // Wait for MCP servers to be connected before generating system prompt await pWaitFor(() => this.providerRef.deref()?.mcpHub?.isConnecting !== true, { timeout: 10_000 }).catch(() => { @@ -1277,15 +1205,11 @@ export class Cline { throw new Error("MCP hub not available") } - const advisorModel = this.api.getAdvisorModel?.() - const supportsConsultAdvisor = advisorModel !== undefined - let systemPrompt = await SYSTEM_PROMPT( cwd, this.api.getModel().info.supportsComputerUse ?? false, mcpHub, this.browserSettings, - supportsConsultAdvisor, ) let settingsCustomInstructions = this.customInstructions?.trim() @@ -1354,26 +1278,6 @@ export class Cline { let stream = this.api.createMessage(systemPrompt, truncatedConversationHistory) - // If we're consulting the advisor, override the request - if (this.advisorProblem && advisorModel) { - const advisorMessage = this.createAdvisorMessage(truncatedConversationHistory, advisorModel, this.advisorProblem) - stream = this.api.createMessage( - ADVISOR_SYSTEM_PROMPT(), - [ - { - role: "user", - content: [ - { - type: "text", - text: advisorMessage, - }, - ], - }, - ], - "advisor", - ) - } - const iterator = stream[Symbol.asyncIterator]() try { @@ -1438,11 +1342,6 @@ export class Cline { const block = cloneDeep(this.assistantMessageContent[this.currentStreamingContentIndex]) // need to create copy bc while stream is updating the array, it could be updating the reference block properties too switch (block.type) { case "text": { - if (this.advisorProblem) { - await this.say("advisor_response", block.content, undefined, block.partial) - break - } - if (this.didRejectTool || this.didAlreadyUseTool) { break } @@ -1523,8 +1422,6 @@ export class Cline { return `[${block.name} for '${block.params.server_name}']` case "access_mcp_resource": return `[${block.name} for '${block.params.server_name}']` - case "consult_advisor": - return `[${block.name} for '${block.params.problem}']` case "ask_followup_question": return `[${block.name} for '${block.params.question}']` case "plan_mode_response": @@ -2618,85 +2515,6 @@ export class Cline { break } } - case "consult_advisor": { - const problem: string | undefined = block.params.problem - try { - if (block.partial) { - const partialMessage = JSON.stringify({ - problem: removeClosingTag("problem", problem), - advisorModelId: this.api.getAdvisorModel?.().id, - } satisfies ClineConsultAdvisor) - - if (this.shouldAutoApproveTool(block.name)) { - this.removeLastPartialMessageIfExistsWithType("ask", "consult_advisor") - await this.say("consult_advisor", partialMessage, undefined, block.partial) - } else { - this.removeLastPartialMessageIfExistsWithType("say", "consult_advisor") - await this.ask("consult_advisor", partialMessage, block.partial).catch(() => {}) - } - - break - } else { - if (!problem) { - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("consult_advisor", "problem")) - await this.saveCheckpoint() - break - } - - this.consecutiveMistakeCount = 0 - - const estimatedCost = undefined //this.estimateAdvisorModelCost(problem) - const completeMessage = JSON.stringify({ - problem: removeClosingTag("problem", problem), - advisorModelId: this.api.getAdvisorModel?.().id, - estimatedCost, - } satisfies ClineConsultAdvisor) - - if (this.shouldAutoApproveTool(block.name)) { - this.removeLastPartialMessageIfExistsWithType("ask", "consult_advisor") - await this.say("consult_advisor", completeMessage, undefined, false) - this.consecutiveAutoApprovedRequestsCount++ - } else { - showNotificationForApprovalIfAutoApprovalEnabled( - `Cline wants to consult the Advisor model about: ${problem}`, - ) - this.removeLastPartialMessageIfExistsWithType("say", "consult_advisor") - const didApprove = await askApproval("consult_advisor", completeMessage) - if (!didApprove) { - await this.saveCheckpoint() - break - } - } - - // Update the last consult_advisor message in case the advisor model changed - const lastMessage = findLast( - this.clineMessages, - (m) => m.ask === "consult_advisor" || m.say === "consult_advisor", - ) - if (lastMessage) { - lastMessage.text = JSON.stringify({ - problem: removeClosingTag("problem", problem), - advisorModelId: this.api.getAdvisorModel?.().id, - estimatedCost, - } satisfies ClineConsultAdvisor) - } - - // now execute the tool - this.advisorProblem = problem - // await this.say("consult_advisor_request_started") - // const resourceResult = "Just try again bro." //await this.providerRef.deref()?.mcpHub?.readResource(server_name, uri) - // await this.say("consult_advisor_response", resourceResult) - pushToolResult(formatResponse.toolResult("Awaiting response from the Advisor model...")) - await this.saveCheckpoint() - break - } - } catch (error) { - await handleError("consulting advisor", error) - await this.saveCheckpoint() - break - } - } case "ask_followup_question": { const question: string | undefined = block.params.question try { @@ -3036,13 +2854,10 @@ export class Cline { // getting verbose details is an expensive operation, it uses globby to top-down build file structure of project which for large projects can take a few seconds // for the best UX we show a placeholder api_req_started message with a loading spinner as this happens - const advisorRequest = this.advisorProblem ? `(...conversation history)\n\n${this.advisorProblem}` : undefined await this.say( "api_req_started", JSON.stringify({ - request: - advisorRequest || - userContent.map((block) => formatContentBlockToMarkdown(block)).join("\n\n") + "\n\nLoading...", + request: userContent.map((block) => formatContentBlockToMarkdown(block)).join("\n\n") + "\n\nLoading...", }), ) @@ -3073,7 +2888,7 @@ export class Cline { // since we sent off a placeholder api_req_started message to update the webview while waiting to actually start the API request (to load potential details for example), we need to update the text of that message const lastApiReqIndex = findLastIndex(this.clineMessages, (m) => m.say === "api_req_started") this.clineMessages[lastApiReqIndex].text = JSON.stringify({ - request: advisorRequest || userContent.map((block) => formatContentBlockToMarkdown(block)).join("\n\n"), + request: userContent.map((block) => formatContentBlockToMarkdown(block)).join("\n\n"), } satisfies ClineApiReqInfo) await this.saveClineMessages() await this.providerRef.deref()?.postStateToWebview() @@ -3156,8 +2971,6 @@ export class Cline { this.didAutomaticallyRetryFailedApiRequest = false await this.diffViewProvider.reset() - const isCallingAdvisor = this.advisorProblem !== undefined - const stream = this.attemptApiRequest(previousApiReqIndex) // yields only if the first chunk is successful, otherwise will allow the user to retry the request (most likely due to rate limit error, which gets thrown on the first chunk) let assistantMessage = "" this.isStreaming = true @@ -3245,11 +3058,6 @@ export class Cline { await this.saveClineMessages() await this.providerRef.deref()?.postStateToWebview() - // If this last request was to the advisor model, then reset advisor problem to give control back to base model - if (isCallingAdvisor) { - this.advisorProblem = undefined - } - // now add to apiconversationhistory // need to save assistant responses to file before proceeding to tool use since user can exit at any moment and we wouldn't be able to save the assistant's response let didEndLoop = false @@ -3273,20 +3081,12 @@ export class Cline { const didToolUse = this.assistantMessageContent.some((block) => block.type === "tool_use") if (!didToolUse) { - if (isCallingAdvisor) { - // if the last request was a request to advisor then it wouldn't have used a tool - this.userMessageContent.push({ - type: "text", - text: "Please continue with the task, taking into account the advisor's response provided above.", - }) - } else { - // normal request where tool use is required - this.userMessageContent.push({ - type: "text", - text: formatResponse.noToolsUsed(), - }) - this.consecutiveMistakeCount++ - } + // normal request where tool use is required + this.userMessageContent.push({ + type: "text", + text: formatResponse.noToolsUsed(), + }) + this.consecutiveMistakeCount++ } const recDidEndLoop = await this.recursivelyMakeClineRequests(this.userMessageContent) diff --git a/src/core/assistant-message/index.ts b/src/core/assistant-message/index.ts index 3ba167c527..e3ba253e0e 100644 --- a/src/core/assistant-message/index.ts +++ b/src/core/assistant-message/index.ts @@ -19,7 +19,6 @@ export const toolUseNames = [ "browser_action", "use_mcp_tool", "access_mcp_resource", - "consult_advisor", "ask_followup_question", "plan_mode_response", "attempt_completion", @@ -45,7 +44,6 @@ export const toolParamNames = [ "tool_name", "arguments", "uri", - "problem", "question", "response", "result", diff --git a/src/core/prompts/advisor.ts b/src/core/prompts/advisor.ts deleted file mode 100644 index e0e3403e85..0000000000 --- a/src/core/prompts/advisor.ts +++ /dev/null @@ -1,22 +0,0 @@ -export const ADVISOR_SYSTEM_PROMPT = - () => `You are a senior AI advisor with deep expertise in software development, system architecture, and technical problem-solving. Your role is to assist another AI agent by providing strategic guidance and solutions to coding challenges. - -==== - -INPUT FORMAT - -You will receive: -1. The autonomous agent's conversation history thus far -2. A specific problem or question the agent needs help with - -==== - -HOW TO RESPOND - -After being given the necessary context, you may start by assessing the problem and key challenges, focusing on the most critical aspects that need to be addressed. - -You may then recommend a strategy or solution, broken down into clear, actionable steps. Include rationale for key decisions and potential trade-offs considered. Use specific technical guidance, including code snippets, architecture recommendations, or debugging strategies as needed. Focus on practical, implementable advice the agent can use to apply the solution. - -==== - -Remember: Your goal is to provide clear, actionable guidance that helps the agent make progress. Focus on practical solutions rather than theoretical discussions.` diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index 5ccf469eaf..302de8f96e 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -9,7 +9,6 @@ export const SYSTEM_PROMPT = async ( supportsComputerUse: boolean, mcpHub: McpHub, browserSettings: BrowserSettings, - supportsConsultAdvisor: boolean, ) => `You are Cline, a highly skilled software engineer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices. ==== @@ -205,20 +204,7 @@ Usage: server name here resource URI here -${ - supportsConsultAdvisor - ? ` - -## consult_advisor -Description: Request to consult an advanced-reasoning AI model about a problem or question you are facing. This can be used to resolve errors you are stuck on, or get input from the model to work through a challenge you are facing. The relevant conversation history leading to the problem will also be provided to the advisor for additional context. -Parameters: -- problem: (required) A string describing the issue, question, or context you want the advisor to address. -Usage: - -Your problem or question here -` - : "" -} + ## ask_followup_question Description: Ask the user a question to gather additional information needed to complete the task. This tool should be used when you encounter ambiguities, need clarification, or require more details to proceed effectively. It allows for interactive problem-solving by enabling direct communication with the user. Use this tool judiciously to maintain a balance between gathering necessary information and avoiding excessive back-and-forth. @@ -839,69 +825,7 @@ You have access to two tools for working with files: **write_to_file** and **rep 3. For major overhauls or initial file creation, rely on write_to_file. 4. Once the file has been edited with either write_to_file or replace_in_file, the system will provide you with the final state of the modified file. Use this updated content as the reference point for any subsequent SEARCH/REPLACE operations, since it reflects any auto-formatting or user-applied changes. -By thoughtfully selecting between write_to_file and replace_in_file, you can make your file editing process smoother, safer, and more efficient.${ - supportsConsultAdvisor - ? ` - -==== - -CONSULTING THE ADVISOR MODEL - -You can use the consult_advisor tool to get suggestions from an advisor model, a powerful AI model that can provide strategic guidance and help solve complex problems. The conversation history that led to the current situation is automatically passed to the advisor, allowing it to provide contextually relevant guidance based on the full picture of the task at hand. - -# When to Use the Advisor - -- When stuck on persistent bugs that you cannot resolve -- If you've tried multiple approaches without success -- When facing complex type errors or package incompatibilities -- When debugging intricate interactions between multiple systems -- If you need deeper insight into system behavior that may not be apparent - -# How to Use Effectively - -## Provide Clear Context -- Explain the current situation and challenge -- Include relevant code snippets or error messages -- Describe what you've already tried -- Specify what kind of guidance you're seeking - -## Ask Specific Questions -- Instead of "Why isn't this working?" -- Better: "I'm encountering this specific type error when integrating these packages, here's what I've tried..." - -Example Usage: - - -I'm encountering persistent type errors while working with @types/react-query v4.0.0: - -Error: Type 'QueryClient' is not assignable to parameter of type 'never'. - The types of 'getQueryCache().notify' are incompatible between these types. - -I've tried: -- Checking package versions compatibility -- Explicitly typing the QueryClient instance -- Updating @types/react and @types/react-query - -Current package versions: -react-query: ^3.39.3 -@types/react-query: ^4.0.0 -react: ^18.2.0 -typescript: ^4.9.5 - -The error persists despite these attempts. Could this be due to version mismatches or breaking changes I'm not aware of? - - - -# Benefits of Using the Advisor - -- Break through debugging roadblocks -- Get fresh perspectives on complex issues -- Understand root causes of persistent bugs -- Solve challenging technical issues - -Remember: While you should attempt to solve problems with your own reasoning first, the advisor is a powerful resource available when you're stuck on a bug. Don't hesitate to consult it when you've hit a persistent roadblock that you cannot resolve.` - : "" -} +By thoughtfully selecting between write_to_file and replace_in_file, you can make your file editing process smoother, safer, and more efficient. ==== @@ -929,9 +853,7 @@ CAPABILITIES - You have access to tools that let you execute CLI commands on the user's computer, list files, view source code definitions, regex search${ supportsComputerUse ? ", use the browser" : "" -}, read and edit files${ - supportsConsultAdvisor ? ", consult an advisor" : "" -}, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more. +}, read and edit files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more. - When the user initially gives you a task, a recursive list of all filepaths in the current working directory ('${cwd.toPosix()}') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current working directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop. - You can use search_files to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring. - You can use the list_code_definition_names tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task. @@ -941,11 +863,7 @@ CAPABILITIES ? "\n- You can use the browser_action tool to interact with websites (including html files and locally running development servers) through a Puppeteer-controlled browser when you feel it is necessary in accomplishing the user's task. This tool is particularly useful for web development tasks as it allows you to launch a browser, navigate to pages, interact with elements through clicks and keyboard input, and capture the results through screenshots and console logs. This tool may be useful at key stages of web development tasks-such as after implementing new features, making substantial changes, when troubleshooting issues, or to verify the result of your work. You can analyze the provided screenshots to ensure correct rendering or identify errors, and review console logs for runtime issues.\n - For example, if asked to add a component to a react website, you might create the necessary files, use execute_command to run the site locally, then use browser_action to launch the browser, navigate to the local server, and verify the component renders & functions correctly before closing the browser." : "" } -- You have access to MCP servers that may provide additional tools and resources. Each server may provide different capabilities that you can use to accomplish tasks more effectively.${ - supportsConsultAdvisor - ? "\n- When you hit a roadblock, such as an error you've attempted to resolve several times without success, you can use the consult_advisor tool to get suggestions from an advanced-reasoning AI model. The conversation history that led to the current situation is automatically passed to the advisor, allowing it to provide contextually relevant guidance based on the full picture of the task at hand." - : "" -} +- You have access to MCP servers that may provide additional tools and resources. Each server may provide different capabilities that you can use to accomplish tasks more effectively. ==== diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index e5aeed0ea0..f3d735cc19 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -46,7 +46,6 @@ type SecretKey = type GlobalStateKey = | "apiProvider" | "apiModelId" - | "anthropicAdvisorModelId" | "awsRegion" | "awsUseCrossRegionInference" | "vertexProjectId" @@ -63,9 +62,7 @@ type GlobalStateKey = | "anthropicBaseUrl" | "azureApiVersion" | "openRouterModelId" - | "openRouterAdvisorModelId" | "openRouterModelInfo" - | "openRouterAdvisorModelInfo" | "autoApprovalSettings" | "browserSettings" | "chatSettings" @@ -372,13 +369,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { ) await this.postStateToWebview() } - if (apiConfiguration.openRouterAdvisorModelId) { - await this.updateGlobalState( - "openRouterAdvisorModelInfo", - openRouterModels[apiConfiguration.openRouterAdvisorModelId], - ) - await this.postStateToWebview() - } } }) break @@ -398,7 +388,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { const { apiProvider, apiModelId, - anthropicAdvisorModelId, apiKey, openRouterApiKey, awsAccessKey, @@ -423,13 +412,10 @@ export class ClineProvider implements vscode.WebviewViewProvider { azureApiVersion, openRouterModelId, openRouterModelInfo, - openRouterAdvisorModelId, - openRouterAdvisorModelInfo, vsCodeLmModelSelector, } = message.apiConfiguration await this.updateGlobalState("apiProvider", apiProvider) await this.updateGlobalState("apiModelId", apiModelId) - await this.updateGlobalState("anthropicAdvisorModelId", anthropicAdvisorModelId) await this.storeSecret("apiKey", apiKey) await this.storeSecret("openRouterApiKey", openRouterApiKey) await this.storeSecret("awsAccessKey", awsAccessKey) @@ -454,8 +440,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { await this.updateGlobalState("azureApiVersion", azureApiVersion) await this.updateGlobalState("openRouterModelId", openRouterModelId) await this.updateGlobalState("openRouterModelInfo", openRouterModelInfo) - await this.updateGlobalState("openRouterAdvisorModelId", openRouterAdvisorModelId) - await this.updateGlobalState("openRouterAdvisorModelInfo", openRouterAdvisorModelInfo) await this.updateGlobalState("vsCodeLmModelSelector", vsCodeLmModelSelector) if (this.cline) { this.cline.api = buildApiHandler(message.apiConfiguration) @@ -607,11 +591,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { case "cancelTask": this.cancelTask() break - case "openAdvisorModelSettings": - this.postMessageToWebview({ - type: "openAdvisorModelSettings", - }) - break case "getLatestState": await this.postStateToWebview() break @@ -1106,7 +1085,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { const [ storedApiProvider, apiModelId, - anthropicAdvisorModelId, apiKey, openRouterApiKey, awsAccessKey, @@ -1131,8 +1109,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { azureApiVersion, openRouterModelId, openRouterModelInfo, - openRouterAdvisorModelId, - openRouterAdvisorModelInfo, lastShownAnnouncementId, customInstructions, taskHistory, @@ -1143,7 +1119,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { ] = await Promise.all([ this.getGlobalState("apiProvider") as Promise, this.getGlobalState("apiModelId") as Promise, - this.getGlobalState("anthropicAdvisorModelId") as Promise, this.getSecret("apiKey") as Promise, this.getSecret("openRouterApiKey") as Promise, this.getSecret("awsAccessKey") as Promise, @@ -1168,8 +1143,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { this.getGlobalState("azureApiVersion") as Promise, this.getGlobalState("openRouterModelId") as Promise, this.getGlobalState("openRouterModelInfo") as Promise, - this.getGlobalState("openRouterAdvisorModelId") as Promise, - this.getGlobalState("openRouterAdvisorModelInfo") as Promise, this.getGlobalState("lastShownAnnouncementId") as Promise, this.getGlobalState("customInstructions") as Promise, this.getGlobalState("taskHistory") as Promise, @@ -1197,7 +1170,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { apiConfiguration: { apiProvider, apiModelId, - anthropicAdvisorModelId, apiKey, openRouterApiKey, awsAccessKey, @@ -1222,8 +1194,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { azureApiVersion, openRouterModelId, openRouterModelInfo, - openRouterAdvisorModelId, - openRouterAdvisorModelInfo, vsCodeLmModelSelector, }, lastShownAnnouncementId, diff --git a/src/shared/AutoApprovalSettings.ts b/src/shared/AutoApprovalSettings.ts index 80f5f5a932..28376d4e06 100644 --- a/src/shared/AutoApprovalSettings.ts +++ b/src/shared/AutoApprovalSettings.ts @@ -8,7 +8,6 @@ export interface AutoApprovalSettings { executeCommands: boolean // Execute safe commands useBrowser: boolean // Use browser useMcp: boolean // Use MCP servers - consultAdvisor?: boolean // Consult the advisor model } // Global settings maxRequests: number // Maximum number of auto-approved requests @@ -23,7 +22,6 @@ export const DEFAULT_AUTO_APPROVAL_SETTINGS: AutoApprovalSettings = { executeCommands: false, useBrowser: false, useMcp: false, - consultAdvisor: false, }, maxRequests: 20, enableNotifications: false, diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 8b524240bf..ce6502774e 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -22,7 +22,6 @@ export interface ExtensionMessage { | "openRouterModels" | "mcpServers" | "relinquishControl" - | "openAdvisorModelSettings" | "vsCodeLmModels" | "requestVsCodeLmModels" text?: string @@ -81,7 +80,6 @@ export type ClineAsk = | "auto_approval_max_req_reached" | "browser_action_launch" | "use_mcp_server" - | "consult_advisor" export type ClineSay = | "task" @@ -103,10 +101,8 @@ export type ClineSay = | "mcp_server_request_started" | "mcp_server_response" | "use_mcp_server" - | "consult_advisor" | "diff_error" | "deleted_api_reqs" - | "advisor_response" export interface ClineSayTool { tool: @@ -149,12 +145,6 @@ export interface ClineAskUseMcpServer { uri?: string } -export interface ClineConsultAdvisor { - problem: string - advisorModelId?: string - estimatedCost?: number -} - export interface ClineApiReqInfo { request?: string tokensIn?: number diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index ce405303a0..6783bc0d79 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -33,7 +33,6 @@ export interface WebviewMessage { | "checkpointDiff" | "checkpointRestore" | "taskCompletionViewChanges" - | "openAdvisorModelSettings" | "requestVsCodeLmModels" | "toggleToolAutoApprove" | "toggleMcpServer" diff --git a/src/shared/api.ts b/src/shared/api.ts index 139c5e0544..f753525fc3 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -15,13 +15,10 @@ export type ApiProvider = export interface ApiHandlerOptions { apiModelId?: string apiKey?: string // anthropic - anthropicAdvisorModelId?: string anthropicBaseUrl?: string openRouterApiKey?: string openRouterModelId?: string - openRouterAdvisorModelId?: string openRouterModelInfo?: ModelInfo - openRouterAdvisorModelInfo?: ModelInfo awsAccessKey?: string awsSecretKey?: string awsSessionToken?: string @@ -63,13 +60,10 @@ export interface ModelInfo { description?: string } -export type ModelType = "base" | "advisor" - // Anthropic // https://docs.anthropic.com/en/docs/about-claude/models // prices updated 2025-01-02 export type AnthropicModelId = keyof typeof anthropicModels export const anthropicDefaultModelId: AnthropicModelId = "claude-3-5-sonnet-20241022" -export const anthropicDefaultAdvisorModelId: AnthropicModelId = "claude-3-opus-20240229" export const anthropicModels = { "claude-3-5-sonnet-20241022": { maxTokens: 8192, @@ -186,18 +180,6 @@ export const openRouterDefaultModelInfo: ModelInfo = { description: "The new Claude 3.5 Sonnet delivers better-than-Opus capabilities, faster-than-Sonnet speeds, at the same Sonnet prices. Sonnet is particularly good at:\n\n- Coding: New Sonnet scores ~49% on SWE-Bench Verified, higher than the last best score, and without any fancy prompt scaffolding\n- Data science: Augments human data science expertise; navigates unstructured data while using multiple tools for insights\n- Visual processing: excelling at interpreting charts, graphs, and images, accurately transcribing text to derive insights beyond just the text alone\n- Agentic tasks: exceptional tool use, making it great at agentic tasks (i.e. complex, multi-step problem solving tasks that require engaging with other systems)\n\n#multimodal\n\n_This is a faster endpoint, made available in collaboration with Anthropic, that is self-moderated: response moderation happens on the provider's side instead of OpenRouter's. For requests that pass moderation, it's identical to the [Standard](/anthropic/claude-3.5-sonnet) variant._", } -export const openRouterDefaultAdvisorModelId = "openai/o1-preview" // will always exist in openRouterModels -export const openRouterDefaultAdvisorModelInfo: ModelInfo = { - maxTokens: 33_000, - contextWindow: 128_000, - supportsImages: true, - supportsComputerUse: false, - supportsPromptCache: false, - inputPrice: 15, - outputPrice: 60, - description: - "The latest and strongest model family from OpenAI, o1 is designed to spend more time thinking before responding.\n\nThe o1 models are optimized for math, science, programming, and other STEM-related tasks. They consistently exhibit PhD-level accuracy on benchmarks in physics, chemistry, and biology. Learn more in the [launch announcement](https://openai.com/o1).\n\nNote: This model is currently experimental and not suitable for production use-cases, and may be heavily rate-limited.", -} // Vertex AI // https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-claude diff --git a/webview-ui/src/components/chat/Announcement.tsx b/webview-ui/src/components/chat/Announcement.tsx index 6381abf5ee..20ab2ee952 100644 --- a/webview-ui/src/components/chat/Announcement.tsx +++ b/webview-ui/src/components/chat/Announcement.tsx @@ -31,18 +31,8 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
    • - - New Consult Advisor tool - {" "} - lets Cline ask a powerful model like o1 for help when stuck. Cline provides the full context of the problem, - and the Advisor model responds with a solution. (Available with OpenRouter and Anthropic.){" "} - - See a demo here! - -
    • -
    • - Plan/Act mode toggle: Plan mode lets Cline ask clarifying questions, brainstorm ideas, and architect a - solution. Switch back to Act mode to let him execute the plan! + Plan/Act mode toggle: Plan mode lets Cline focus on gathering information, asking clarifying questions, + brainstorm ideas, and architect a solution. Switch back to Act mode to let him execute the plan!
    • Quick API/model switching with a new popup menu under the chat field diff --git a/webview-ui/src/components/chat/AutoApproveMenu.tsx b/webview-ui/src/components/chat/AutoApproveMenu.tsx index 4ede2742c1..0c2d9afc72 100644 --- a/webview-ui/src/components/chat/AutoApproveMenu.tsx +++ b/webview-ui/src/components/chat/AutoApproveMenu.tsx @@ -46,25 +46,16 @@ const ACTION_METADATA: { shortName: "MCP", description: "Allows use of configured MCP servers which may modify filesystem or interact with APIs.", }, - { - id: "consultAdvisor", - label: "Consult the Advisor model", - shortName: "Advisor", - description: "Allows Cline to consult the Advisor model to get advice on how to proceed.", - }, ] const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => { - const { autoApprovalSettings, apiConfiguration } = useExtensionState() + const { autoApprovalSettings } = useExtensionState() const [isExpanded, setIsExpanded] = useState(false) const [isHoveringCollapsibleSection, setIsHoveringCollapsibleSection] = useState(false) // Careful not to use partials to mutate since spread operator only does shallow copy - const supportsAdvisor = apiConfiguration?.apiProvider === "openrouter" || apiConfiguration?.apiProvider === "anthropic" - const actionMetadata = ACTION_METADATA.filter((action) => supportsAdvisor || action.id !== "consultAdvisor") - - const enabledActions = actionMetadata.filter((action) => autoApprovalSettings.actions[action.id]) + const enabledActions = ACTION_METADATA.filter((action) => autoApprovalSettings.actions[action.id]) const enabledActionsList = enabledActions.map((action) => action.shortName).join(", ") const hasEnabledActions = enabledActions.length > 0 @@ -228,7 +219,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => { Auto-approve allows Cline to perform the following actions without asking for permission. Please use with caution and only enable if you understand the risks.
- {actionMetadata.map((action) => ( + {ACTION_METADATA.map((action) => (
{ - const { mcpServers, apiConfiguration } = useExtensionState() + const { mcpServers } = useExtensionState() const [seeNewChangesDisabled, setSeeNewChangesDisabled] = useState(false) @@ -145,10 +141,6 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi useEvent("message", handleMessage) - const { selectedAdvisorModelId } = useMemo(() => { - return normalizeApiConfiguration(apiConfiguration) - }, [apiConfiguration]) - const [icon, title] = useMemo(() => { switch (type) { case "error": @@ -224,23 +216,6 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi )} , ] - case "consult_advisor": - // const consultAdvisor = JSON.parse(message.text || "{}") as ClineConsultAdvisor - const consultAdvisor = JSON.parse(message.text || "{}") as ClineConsultAdvisor - return [ - , - - <> - Cline wants to consult{" "} - {{isLast ? selectedAdvisorModelId : consultAdvisor.advisorModelId} || "Advisor model"}: - - , - ] case "completion_result": return [ server.name === useMcpServer.serverName) - return ( - <> -
- {icon} - {title} -
- -
-
- -
- {consultAdvisor.estimatedCost != null && ( -
- Estimated cost: ${Number(consultAdvisor.estimatedCost).toFixed(4)} -
- )} -
- -
- You can change the Advisor model Cline consults with{" "} - vscode.postMessage({ type: "openAdvisorModelSettings" })}> - in API Settings. - -
- - ) - } - switch (message.type) { case "say": switch (message.say) { @@ -926,32 +842,6 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi
) - case "advisor_response": - return ( -
-
- Advisor Response -
- -
- ) case "user_feedback": return (
( const [intendedCursorPosition, setIntendedCursorPosition] = useState(null) const contextMenuContainerRef = useRef(null) const [showModelSelector, setShowModelSelector] = useState(false) - const [showModelSelectorWithAdvisor, setShowModelSelectorWithAdvisor] = useState(false) const modelSelectorRef = useRef(null) const { width: viewportWidth, height: viewportHeight } = useWindowSize() const buttonRef = useRef(null) @@ -657,9 +652,8 @@ const ChatTextArea = forwardRef( const submitApiConfig = useCallback(() => { const apiValidationResult = validateApiConfiguration(apiConfiguration) const modelIdValidationResult = validateModelId(apiConfiguration, openRouterModels) - const advisorModelIdValidationResult = validateAdvisorModelId(apiConfiguration, openRouterModels) - if (!apiValidationResult && !modelIdValidationResult && !advisorModelIdValidationResult) { + if (!apiValidationResult && !modelIdValidationResult) { vscode.postMessage({ type: "apiConfiguration", apiConfiguration }) } else { vscode.postMessage({ type: "getLatestState" }) @@ -734,14 +728,12 @@ const ChatTextArea = forwardRef( } }, [showModelSelector, viewportWidth, viewportHeight]) - // Reset advisor settings when model selector is closed useEffect(() => { if (!showModelSelector) { // Attempt to save if possible // NOTE: we cannot call this here since it will create an infinite loop between this effect and the callback since getLatestState will update state. Instead we should submitapiconfig when the menu is explicitly closed, rather than as an effect of showModelSelector changing. // handleApiConfigSubmit() - setShowModelSelectorWithAdvisor(false) // Reset any active styling by blurring the button const button = buttonRef.current?.querySelector("a") if (button) { @@ -750,18 +742,6 @@ const ChatTextArea = forwardRef( } }, [showModelSelector]) - const handleMessage = useCallback((e: MessageEvent) => { - const message: ExtensionMessage = e.data - switch (message.type) { - case "openAdvisorModelSettings": - setShowModelSelector(true) - setShowModelSelectorWithAdvisor(true) - break - } - }, []) - - useEvent("message", handleMessage) - return (
( }}> diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 2aa30d995f..aec4e544a9 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -155,13 +155,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie setPrimaryButtonText("Approve") setSecondaryButtonText("Reject") break - case "consult_advisor": - setTextAreaDisabled(isPartial) - setClineAsk("consult_advisor") - setEnableButtons(!isPartial) - setPrimaryButtonText("Approve") - setSecondaryButtonText("Reject") - break case "completion_result": // extension waiting for feedback. but we can just present a new task button setTextAreaDisabled(isPartial) @@ -205,13 +198,11 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie case "error": case "api_req_finished": case "text": - case "advisor_response": case "browser_action": case "browser_action_result": case "browser_action_launch": case "command": case "use_mcp_server": - case "consult_advisor": case "command_output": case "mcp_server_request_started": case "mcp_server_response": @@ -284,7 +275,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie case "command": // user can provide feedback to a tool or command use case "command_output": // user can send input to command stdin case "use_mcp_server": - case "consult_advisor": case "completion_result": // if this happens then the user has feedback for the completion result case "resume_task": case "resume_completed_task": @@ -327,7 +317,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie case "tool": case "browser_action_launch": case "use_mcp_server": - case "consult_advisor": case "resume_task": case "mistake_limit_reached": case "auto_approval_max_req_reached": @@ -367,7 +356,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie case "tool": case "browser_action_launch": case "use_mcp_server": - case "consult_advisor": // responds to the API with a "This operation failed" and lets it try again vscode.postMessage({ type: "askResponse", diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 54b6b04621..07f0076dc3 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -13,8 +13,6 @@ import { ApiConfiguration, ApiProvider, ModelInfo, - ModelType, - anthropicDefaultAdvisorModelId, anthropicDefaultModelId, anthropicModels, azureOpenAiDefaultApiVersion, @@ -29,8 +27,6 @@ import { openAiModelInfoSaneDefaults, openAiNativeDefaultModelId, openAiNativeModels, - openRouterDefaultAdvisorModelId, - openRouterDefaultAdvisorModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo, vertexDefaultModelId, @@ -48,47 +44,9 @@ interface ApiOptionsProps { showModelOptions: boolean apiErrorMessage?: string modelIdErrorMessage?: string - advisorModelIdErrorMessage?: string - showAdvisorModelSettings?: boolean isPopup?: boolean } -const TabPanel = ({ children, isSelected }: { children: React.ReactNode; isSelected: boolean }) => { - if (!isSelected) return null - return
{children}
-} - -const StyledTabButton = styled.button<{ isSelected: boolean }>` - background: transparent; - border: none; - padding: 8px 16px; - color: ${(props) => (props.isSelected ? "var(--vscode-tab-activeForeground)" : "var(--vscode-tab-inactiveForeground)")}; - cursor: pointer; - border-bottom: 2px solid ${(props) => (props.isSelected ? "var(--vscode-foreground)" : "transparent")}; - font-size: 12px; - font-weight: 500; - - &:hover { - color: var(--vscode-tab-activeForeground); - } -` - -const TabButton = ({ - isSelected, - onClick, - children, -}: { - isSelected: boolean - onClick: () => void - children: React.ReactNode -}) => { - return ( - - {children} - - ) -} - // This is necessary to ensure dropdown opens downward, important for when this is used in popup const DROPDOWN_Z_INDEX = 1001 // Higher than the OpenRouterModelPicker's and ModelSelectorTooltip's z-index @@ -113,14 +71,7 @@ declare module "vscode" { } } -const ApiOptions = ({ - showModelOptions, - apiErrorMessage, - modelIdErrorMessage, - advisorModelIdErrorMessage, - showAdvisorModelSettings, - isPopup, -}: ApiOptionsProps) => { +const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, isPopup }: ApiOptionsProps) => { const { apiConfiguration, setApiConfiguration, uriScheme } = useExtensionState() const [ollamaModels, setOllamaModels] = useState([]) const [lmStudioModels, setLmStudioModels] = useState([]) @@ -128,7 +79,6 @@ const ApiOptions = ({ const [anthropicBaseUrlSelected, setAnthropicBaseUrlSelected] = useState(!!apiConfiguration?.anthropicBaseUrl) const [azureApiVersionSelected, setAzureApiVersionSelected] = useState(!!apiConfiguration?.azureApiVersion) const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false) - const [selectedTab, setSelectedTab] = useState(showAdvisorModelSettings ? "advisor" : "base") const handleInputChange = (field: keyof ApiConfiguration) => (event: any) => { setApiConfiguration({ @@ -137,7 +87,7 @@ const ApiOptions = ({ }) } - const { selectedProvider, selectedModelId, selectedModelInfo, selectedAdvisorModelId } = useMemo(() => { + const { selectedProvider, selectedModelId, selectedModelInfo } = useMemo(() => { return normalizeApiConfiguration(apiConfiguration) }, [apiConfiguration]) @@ -187,16 +137,12 @@ const ApiOptions = ({ As a workaround, we create separate instances of the dropdown for each provider, and then conditionally render the one that matches the current provider. */ - const createDropdown = (models: Record, modelType?: ModelType) => { + const createDropdown = (models: Record) => { return ( Select a model... {Object.keys(models).map((modelId) => ( @@ -866,7 +812,6 @@ const ApiOptions = ({ )} {selectedProvider !== "openrouter" && - selectedProvider !== "anthropic" && selectedProvider !== "openai" && selectedProvider !== "ollama" && selectedProvider !== "lmstudio" && @@ -877,6 +822,7 @@ const ApiOptions = ({ + {selectedProvider === "anthropic" && createDropdown(anthropicModels)} {selectedProvider === "bedrock" && createDropdown(bedrockModels)} {selectedProvider === "vertex" && createDropdown(vertexModels)} {selectedProvider === "gemini" && createDropdown(geminiModels)} @@ -895,7 +841,9 @@ const ApiOptions = ({ )} - {selectedProvider !== "openrouter" && selectedProvider !== "anthropic" && modelIdErrorMessage && ( + {selectedProvider === "openrouter" && showModelOptions && } + + {modelIdErrorMessage && (

)} - - {(selectedProvider === "openrouter" || selectedProvider === "anthropic") && showModelOptions && ( -

-
- setSelectedTab("base")}> - Cline Model - - setSelectedTab("advisor")}> - Advisor Model - -
- - -

- This model is the default driver for Cline. It will read and edit files, run commands, and more, with - your permission at each step. -

- {selectedProvider === "anthropic" && ( -
- {createDropdown(anthropicModels, "base")} -
- )} - {selectedProvider === "openrouter" && ( - - )} - {modelIdErrorMessage && ( -

- {modelIdErrorMessage} -

- )} -
- - -

- The Cline model can consult this more powerful model for advice when running into roadblocks, such as - an error it cannot resolve. -

- {selectedProvider === "anthropic" && ( -
- {createDropdown(anthropicModels, "advisor")} -
- )} - {selectedProvider === "openrouter" && ( - - )} - {advisorModelIdErrorMessage && ( -

- {advisorModelIdErrorMessage} -

- )} -
-
- )}
) } @@ -1127,8 +1002,6 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration): selectedProvider: ApiProvider selectedModelId: string selectedModelInfo: ModelInfo - selectedAdvisorModelId?: string - selectedAdvisorModelInfo?: ModelInfo } { const provider = apiConfiguration?.apiProvider || "anthropic" const modelId = apiConfiguration?.apiModelId @@ -1151,10 +1024,7 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration): } switch (provider) { case "anthropic": - return { - ...getProviderData(anthropicModels, anthropicDefaultModelId), - selectedAdvisorModelId: apiConfiguration?.anthropicAdvisorModelId || anthropicDefaultAdvisorModelId, - } + return getProviderData(anthropicModels, anthropicDefaultModelId) case "bedrock": return getProviderData(bedrockModels, bedrockDefaultModelId) case "vertex": @@ -1172,8 +1042,6 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration): selectedProvider: provider, selectedModelId: apiConfiguration?.openRouterModelId || openRouterDefaultModelId, selectedModelInfo: apiConfiguration?.openRouterModelInfo || openRouterDefaultModelInfo, - selectedAdvisorModelId: apiConfiguration?.openRouterAdvisorModelId || openRouterDefaultAdvisorModelId, - selectedAdvisorModelInfo: apiConfiguration?.openRouterAdvisorModelInfo || openRouterDefaultAdvisorModelInfo, } case "openai": return { diff --git a/webview-ui/src/components/settings/OpenRouterModelPicker.tsx b/webview-ui/src/components/settings/OpenRouterModelPicker.tsx index 5407a715ac..37b0bbfad3 100644 --- a/webview-ui/src/components/settings/OpenRouterModelPicker.tsx +++ b/webview-ui/src/components/settings/OpenRouterModelPicker.tsx @@ -4,12 +4,7 @@ import React, { KeyboardEvent, memo, useEffect, useMemo, useRef, useState } from import { useRemark } from "react-remark" import { useMount } from "react-use" import styled from "styled-components" -import { - ModelType, - openRouterDefaultAdvisorModelId, - openRouterDefaultAdvisorModelInfo, - openRouterDefaultModelId, -} from "../../../../src/shared/api" +import { openRouterDefaultModelId } from "../../../../src/shared/api" import { useExtensionState } from "../../context/ExtensionStateContext" import { vscode } from "../../utils/vscode" import { highlight } from "../history/HistoryView" @@ -17,17 +12,12 @@ import { ModelInfoView, normalizeApiConfiguration } from "./ApiOptions" import { CODE_BLOCK_BG_COLOR } from "../common/CodeBlock" export interface OpenRouterModelPickerProps { - modelType: ModelType isPopup?: boolean } -const OpenRouterModelPicker: React.FC = ({ modelType, isPopup }) => { +const OpenRouterModelPicker: React.FC = ({ isPopup }) => { const { apiConfiguration, setApiConfiguration, openRouterModels } = useExtensionState() - const [searchTerm, setSearchTerm] = useState( - modelType === "advisor" - ? apiConfiguration?.openRouterAdvisorModelId || openRouterDefaultAdvisorModelId - : apiConfiguration?.openRouterModelId || openRouterDefaultModelId, - ) + const [searchTerm, setSearchTerm] = useState(apiConfiguration?.openRouterModelId || openRouterDefaultModelId) const [isDropdownVisible, setIsDropdownVisible] = useState(false) const [selectedIndex, setSelectedIndex] = useState(-1) const dropdownRef = useRef(null) @@ -39,20 +29,15 @@ const OpenRouterModelPicker: React.FC = ({ modelType // could be setting invalid model id/undefined info but validation will catch it setApiConfiguration({ ...apiConfiguration, - ...(modelType === "advisor" - ? { - openRouterAdvisorModelId: newModelId, - openRouterAdvisorModelInfo: openRouterModels[newModelId], - } - : { - openRouterModelId: newModelId, - openRouterModelInfo: openRouterModels[newModelId], - }), + ...{ + openRouterModelId: newModelId, + openRouterModelInfo: openRouterModels[newModelId], + }, }) setSearchTerm(newModelId) } - const { selectedModelId, selectedModelInfo, selectedAdvisorModelId, selectedAdvisorModelInfo } = useMemo(() => { + const { selectedModelId, selectedModelInfo } = useMemo(() => { return normalizeApiConfiguration(apiConfiguration) }, [apiConfiguration]) @@ -161,9 +146,9 @@ const OpenRouterModelPicker: React.FC = ({ modelType `}
- {/* = ({ modelType {hasInfo ? ( = ({ modelType marginTop: 0, color: "var(--vscode-descriptionForeground)", }}> - {modelType === "base" ? ( - <> - The extension automatically fetches the latest list of models available on{" "} - - OpenRouter. - - If you're unsure which model to choose, Cline works best with{" "} - handleModelChange("anthropic/claude-3.5-sonnet:beta")}> - anthropic/claude-3.5-sonnet:beta. - - You can also try searching "free" for no-cost options currently available. - - ) : ( - <> - It's recommended using a higher-reasoning model such as{" "} - handleModelChange("openai/o1-preview")}> - openai/o1-preview - - for the best results. - - )} + <> + The extension automatically fetches the latest list of models available on{" "} + + OpenRouter. + + If you're unsure which model to choose, Cline works best with{" "} + handleModelChange("anthropic/claude-3.5-sonnet:beta")}> + anthropic/claude-3.5-sonnet:beta. + + You can also try searching "free" for no-cost options currently available. +

)}
diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index 91e9d136c8..8f13de7914 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -1,7 +1,7 @@ import { VSCodeButton, VSCodeLink, VSCodeTextArea } from "@vscode/webview-ui-toolkit/react" import { memo, useEffect, useState } from "react" import { useExtensionState } from "../../context/ExtensionStateContext" -import { validateAdvisorModelId, validateApiConfiguration, validateModelId } from "../../utils/validate" +import { validateApiConfiguration, validateModelId } from "../../utils/validate" import { vscode } from "../../utils/vscode" import ApiOptions from "./ApiOptions" @@ -15,18 +15,15 @@ const SettingsView = ({ onDone }: SettingsViewProps) => { const { apiConfiguration, version, customInstructions, setCustomInstructions, openRouterModels } = useExtensionState() const [apiErrorMessage, setApiErrorMessage] = useState(undefined) const [modelIdErrorMessage, setModelIdErrorMessage] = useState(undefined) - const [advisorModelIdErrorMessage, setAdvisorModelIdErrorMessage] = useState(undefined) const handleSubmit = () => { const apiValidationResult = validateApiConfiguration(apiConfiguration) const modelIdValidationResult = validateModelId(apiConfiguration, openRouterModels) - const advisorModelIdValidationResult = validateAdvisorModelId(apiConfiguration, openRouterModels) setApiErrorMessage(apiValidationResult) setModelIdErrorMessage(modelIdValidationResult) - setAdvisorModelIdErrorMessage(advisorModelIdValidationResult) - if (!apiValidationResult && !modelIdValidationResult && !advisorModelIdValidationResult) { + if (!apiValidationResult && !modelIdValidationResult) { vscode.postMessage({ type: "apiConfiguration", apiConfiguration }) vscode.postMessage({ type: "customInstructions", @@ -39,7 +36,6 @@ const SettingsView = ({ onDone }: SettingsViewProps) => { useEffect(() => { setApiErrorMessage(undefined) setModelIdErrorMessage(undefined) - setAdvisorModelIdErrorMessage(undefined) }, [apiConfiguration]) // validate as soon as the component is mounted @@ -95,7 +91,6 @@ const SettingsView = ({ onDone }: SettingsViewProps) => { showModelOptions={true} apiErrorMessage={apiErrorMessage} modelIdErrorMessage={modelIdErrorMessage} - advisorModelIdErrorMessage={advisorModelIdErrorMessage} />
diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index 69e67f1a3d..75db746f02 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -2,14 +2,7 @@ import React, { createContext, useCallback, useContext, useEffect, useState } fr import { useEvent } from "react-use" import { DEFAULT_AUTO_APPROVAL_SETTINGS } from "../../../src/shared/AutoApprovalSettings" import { ExtensionMessage, ExtensionState } from "../../../src/shared/ExtensionMessage" -import { - ApiConfiguration, - ModelInfo, - openRouterDefaultAdvisorModelId, - openRouterDefaultAdvisorModelInfo, - openRouterDefaultModelId, - openRouterDefaultModelInfo, -} from "../../../src/shared/api" +import { ApiConfiguration, ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "../../../src/shared/api" import { findLastIndex } from "../../../src/shared/array" import { McpServer } from "../../../src/shared/mcp" import { convertTextMateToHljs } from "../utils/textMateToHljs" @@ -49,7 +42,6 @@ export const ExtensionStateContextProvider: React.FC<{ const [filePaths, setFilePaths] = useState([]) const [openRouterModels, setOpenRouterModels] = useState>({ [openRouterDefaultModelId]: openRouterDefaultModelInfo, - [openRouterDefaultAdvisorModelId]: openRouterDefaultAdvisorModelInfo, }) const [mcpServers, setMcpServers] = useState([]) @@ -107,7 +99,6 @@ export const ExtensionStateContextProvider: React.FC<{ const updatedModels = message.openRouterModels ?? {} setOpenRouterModels({ [openRouterDefaultModelId]: openRouterDefaultModelInfo, // in case the extension sent a model list without the default model - [openRouterDefaultAdvisorModelId]: openRouterDefaultAdvisorModelInfo, ...updatedModels, }) break diff --git a/webview-ui/src/utils/validate.ts b/webview-ui/src/utils/validate.ts index e0b06429e1..beafc65572 100644 --- a/webview-ui/src/utils/validate.ts +++ b/webview-ui/src/utils/validate.ts @@ -1,4 +1,4 @@ -import { ApiConfiguration, openRouterDefaultAdvisorModelId, openRouterDefaultModelId } from "../../../src/shared/api" +import { ApiConfiguration, openRouterDefaultModelId } from "../../../src/shared/api" import { ModelInfo } from "../../../src/shared/api" export function validateApiConfiguration(apiConfiguration?: ApiConfiguration): string | undefined { if (apiConfiguration) { @@ -88,23 +88,3 @@ export function validateModelId( } return undefined } - -export function validateAdvisorModelId( - apiConfiguration?: ApiConfiguration, - openRouterModels?: Record, -): string | undefined { - if (apiConfiguration) { - switch (apiConfiguration.apiProvider) { - case "openrouter": - const advisorModelId = apiConfiguration.openRouterAdvisorModelId || openRouterDefaultAdvisorModelId // in case the user hasn't changed the model id, it will be undefined by default - if (!advisorModelId) { - return "You must provide a model ID." - } - if (openRouterModels && !Object.keys(openRouterModels).includes(advisorModelId)) { - return "The model ID you provided is not available. Please choose a different model." - } - break - } - } - return undefined -} From 1627f412157c81a2688dde3535173873cf872b24 Mon Sep 17 00:00:00 2001 From: Evan Date: Tue, 21 Jan 2025 12:49:20 +0800 Subject: [PATCH 39/49] filtering MCP servers based on connected status --- src/core/prompts/system.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index 239a390046..1ea58fee1a 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -740,9 +740,10 @@ npm run build ## Editing MCP Servers -The user may ask to add tools or resources that may make sense to add to an existing MCP server (listed under 'Connected MCP Servers' above: ${ +The user may ask to add tools or resources that may make sense to add to an existing MCP server (listed under 'Connected MCP Servers' below: ${ mcpHub .getServers() + .filter((server) => server.status === "connected") .map((server) => server.name) .join(", ") || "(None running currently)" }, e.g. if it would use the same API. This would be possible if you can locate the MCP server repository on the user's system by looking at the server arguments for a filepath. You might then use list_files and read_file to explore the files in the repository, and use replace_in_file to make changes to the files. From d25fa0b14e1a4af35059a35169a1f88d95508e5c Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Mon, 20 Jan 2025 20:55:09 -0800 Subject: [PATCH 40/49] Fixes --- src/core/Cline.ts | 4 +--- src/core/webview/ClineProvider.ts | 2 +- webview-ui/src/components/chat/Announcement.tsx | 5 ++++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index ef6d613c00..80e218ee68 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -3289,13 +3289,11 @@ export class Cline { details += "\n\n# Current Mode" if (this.chatSettings.mode === "plan") { details += "\nPLAN MODE" - details += - "\nIn this mode you should focus on information gathering and architecting a solution. If you haven't done so already, it's a good idea to start by reading files to get context and then asking questions." + details += "\nIn this mode you should focus on information gathering, asking questions, and architecting a solution." details += '\n(Remember: You now have access to the plan_mode_response tool, which allows you to engage in a more conversational back and forth with the user rather than jumping into executing the task. If it seems the user wants you to use tools only available in ACT MODE, you should ask the user to "toggle to Act mode" - they will have to manually do this themselves with the Plan/Act toggle button below. You do not have the ability to switch to ACT MODE yourself, and must wait for the user to do it themselves once they are satisfied with the plan.)' } else { details += "\nACT MODE" - details += "\n(Remember: You cannot use the plan_mode_response tool.)" } return `\n${details.trim()}\n` diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index f3d735cc19..81072c43b7 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -85,7 +85,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { private cline?: Cline private workspaceTracker?: WorkspaceTracker mcpHub?: McpHub - private latestAnnouncementId = "jan-20a-2025" // update to some unique identifier when we add a new announcement + private latestAnnouncementId = "jan-20-2025" // update to some unique identifier when we add a new announcement constructor( readonly context: vscode.ExtensionContext, diff --git a/webview-ui/src/components/chat/Announcement.tsx b/webview-ui/src/components/chat/Announcement.tsx index 20ab2ee952..a9c75ff02f 100644 --- a/webview-ui/src/components/chat/Announcement.tsx +++ b/webview-ui/src/components/chat/Announcement.tsx @@ -32,7 +32,10 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
  • Plan/Act mode toggle: Plan mode lets Cline focus on gathering information, asking clarifying questions, - brainstorm ideas, and architect a solution. Switch back to Act mode to let him execute the plan! + brainstorm ideas, and architect a solution. Switch back to Act mode to let him execute the plan!{" "} + + See a demo here. +
  • Quick API/model switching with a new popup menu under the chat field From 93e4f1cc6c35f37b327d039b9e100ea1b065fd96 Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Mon, 20 Jan 2025 23:33:09 -0800 Subject: [PATCH 41/49] Fixes --- src/core/Cline.ts | 5 +++-- src/core/prompts/system.ts | 9 ++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 80e218ee68..947a5fae6f 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -3289,9 +3289,10 @@ export class Cline { details += "\n\n# Current Mode" if (this.chatSettings.mode === "plan") { details += "\nPLAN MODE" - details += "\nIn this mode you should focus on information gathering, asking questions, and architecting a solution." details += - '\n(Remember: You now have access to the plan_mode_response tool, which allows you to engage in a more conversational back and forth with the user rather than jumping into executing the task. If it seems the user wants you to use tools only available in ACT MODE, you should ask the user to "toggle to Act mode" - they will have to manually do this themselves with the Plan/Act toggle button below. You do not have the ability to switch to ACT MODE yourself, and must wait for the user to do it themselves once they are satisfied with the plan.)' + "\nIn this mode you should focus on information gathering, asking questions, and architecting a solution. Once you have a plan, use the plan_mode_response tool to engage in a conversational back and forth with the user. Do not use the plan_mode_response tool until you've gathered all the information you need e.g. with read_file or ask_followup_question." + details += + '\n(Remember: If it seems the user wants you to use tools only available in Act Mode, you should ask the user to "toggle to Act mode" (use those words) - they will have to manually do this themselves with the Plan/Act toggle button below. You do not have the ability to switch to Act Mode yourself, and must wait for the user to do it themselves once they are satisfied with the plan.)' } else { details += "\nACT MODE" } diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index 302de8f96e..e6e3d0f29f 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -230,9 +230,9 @@ Your final result description here ## plan_mode_response -Description: Respond to the user's inquiry with a clear answer in an effort to plan a solution to the user's task. This tool should be used when you need to provide a response to a question or statement from the user about how you plan to accomplish the task. This tool is only available in PLAN MODE. The environment_details will specify the current mode, if it is not PLAN MODE then you should not use this tool. Depending on the user's message, you may ask questions to get clarification about the user's request, architect a solution to the task, and to brainstorm ideas with the user. For example, if the user's task is to create a website, you may start by asking some clarifying questions, then present a detailed plan for how you will accomplish the task given the context, and perhaps engage in a back and forth to finalize the details before the user switches you to ACT MODE to implement the solution. +Description: Respond to the user's inquiry in an effort to plan a solution to the user's task. This tool should be used when you need to provide a response to a question or statement from the user about how you plan to accomplish the task. This tool is only available in PLAN MODE. The environment_details will specify the current mode, if it is not PLAN MODE then you should not use this tool. Depending on the user's message, you may ask questions to get clarification about the user's request, architect a solution to the task, and to brainstorm ideas with the user. For example, if the user's task is to create a website, you may start by asking some clarifying questions, then present a detailed plan for how you will accomplish the task given the context, and perhaps engage in a back and forth to finalize the details before the user switches you to ACT MODE to implement the solution. Parameters: -- response: (required) The response to provide to the user. +- response: (required) The response to provide to the user. Do not try to use tools in this parameter, this is simply a chat response. Usage: Your response here @@ -834,10 +834,9 @@ ACT MODE V.S. PLAN MODE In each user message, the environment_details will specify the current mode. There are two modes: - ACT MODE: In this mode, you have access to all tools EXCEPT the plan_mode_response tool. - - In act mode, you use tools to accomplish the user's task. Once you've completed the user's task, you use the attempt_completion tool to present the result of the task to the user. + - In ACT MODE, you use tools to accomplish the user's task. Once you've completed the user's task, you use the attempt_completion tool to present the result of the task to the user. - PLAN MODE: In this special mode, you have access to the plan_mode_response tool. - - In plan mode, the goal is to gather information and get context to create a detailed plan for accomplishing the task, which the user will review and approve before you switch back to ACT MODE to implement the solution. - - In plan mode, you should use the plan_mode_response tool to deliver your response, rather than using tags to analyze when to respond. Do not talk about using plan_mode_response - just use it directly to share your thoughts and provide helpful answers. + - In PLAN MODE, the goal is to gather information and get context to create a detailed plan for accomplishing the task, which the user will review and approve before you switch back to ACT MODE to implement the solution. ## What is PLAN MODE? From 4f196f4a0efef2a9c6b6022ee0a5a23592b18964 Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Mon, 20 Jan 2025 23:40:07 -0800 Subject: [PATCH 42/49] Fixes --- src/core/prompts/system.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index e6e3d0f29f..670c74466e 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -836,7 +836,7 @@ In each user message, the environment_details will specify the current mode. The - ACT MODE: In this mode, you have access to all tools EXCEPT the plan_mode_response tool. - In ACT MODE, you use tools to accomplish the user's task. Once you've completed the user's task, you use the attempt_completion tool to present the result of the task to the user. - PLAN MODE: In this special mode, you have access to the plan_mode_response tool. - - In PLAN MODE, the goal is to gather information and get context to create a detailed plan for accomplishing the task, which the user will review and approve before you switch back to ACT MODE to implement the solution. + - In PLAN MODE, the goal is to gather information and get context to create a detailed plan for accomplishing the task, which the user will review and approve before they switch you to ACT MODE to implement the solution. ## What is PLAN MODE? From 10515035feacacb49a6d46c1e5525675410dec65 Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Mon, 20 Jan 2025 23:44:21 -0800 Subject: [PATCH 43/49] Fixes --- src/core/prompts/system.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index 670c74466e..be95de168e 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -837,6 +837,7 @@ In each user message, the environment_details will specify the current mode. The - In ACT MODE, you use tools to accomplish the user's task. Once you've completed the user's task, you use the attempt_completion tool to present the result of the task to the user. - PLAN MODE: In this special mode, you have access to the plan_mode_response tool. - In PLAN MODE, the goal is to gather information and get context to create a detailed plan for accomplishing the task, which the user will review and approve before they switch you to ACT MODE to implement the solution. + - In PLAN MODE, when you need to converse with the user or present a plan, you should use the plan_mode_response tool to deliver your response directly, rather than using tags to analyze when to respond. Do not talk about using plan_mode_response - just use it directly to share your thoughts and provide helpful answers. ## What is PLAN MODE? From 3f35aabbf9377f451b712ffb5eb6470b899b1b48 Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Tue, 21 Jan 2025 09:55:25 -0800 Subject: [PATCH 44/49] Fix announcement --- package.json | 2 +- webview-ui/src/components/chat/Announcement.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 075ee7e765..20ab2b6b92 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "claude-dev", "displayName": "Cline", "description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.", - "version": "3.2.0", + "version": "3.2.2", "icon": "assets/icons/icon.png", "galleryBanner": { "color": "#617A91", diff --git a/webview-ui/src/components/chat/Announcement.tsx b/webview-ui/src/components/chat/Announcement.tsx index a9c75ff02f..793a899296 100644 --- a/webview-ui/src/components/chat/Announcement.tsx +++ b/webview-ui/src/components/chat/Announcement.tsx @@ -31,9 +31,9 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
    • - Plan/Act mode toggle: Plan mode lets Cline focus on gathering information, asking clarifying questions, - brainstorm ideas, and architect a solution. Switch back to Act mode to let him execute the plan!{" "} - + Plan/Act mode toggle: Plan mode turns Cline into an architect that gathers information, asks clarifying + questions, and designs a solution. Switch back to Act mode to let him execute the plan!{" "} + See a demo here.
    • From f4df887fcd5378eec8af82e6a418672b0659caa2 Mon Sep 17 00:00:00 2001 From: Slava Kurilyak Date: Tue, 21 Jan 2025 16:37:34 -0500 Subject: [PATCH 45/49] feat: Add DeepSeek-R1 (deepseek-reasoner) support (#1355) * feat: Add DeepSeek-R1 (deepseek-reasoner) support - Add new deepseek-reasoner model with proper pricing info - Fix temperature parameter being sent to unsupported deepseek-reasoner model - Improve model selection logic in DeepSeekHandler - Update CHANGELOG with new features and fixes - Bump version to 3.1.11 * style: apply prettier formatting to deepseek provider and api definitions --- CHANGELOG.md | 6 ++++++ package-lock.json | 1 + src/api/providers/deepseek.ts | 20 ++++++++++++-------- src/shared/api.ts | 10 ++++++++++ 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85989ce8e2..46a688a808 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## [3.2.X] + +- Add DeepSeek-R1 (deepseek-reasoner) model support with proper parameter handling +- Fix temperature parameter being sent to unsupported deepseek-reasoner model +- Update DeepSeek pricing info with new reasoner model rates + ## [3.2.0] - Add Plan/Act mode toggle to let you plan tasks with Cline before letting him get to work diff --git a/package-lock.json b/package-lock.json index b7415befb1..c4f4ef03ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,6 +6,7 @@ "packages": { "": { "name": "claude-dev", + "version": "3.1.11", "version": "3.2.0", "license": "Apache-2.0", "dependencies": { diff --git a/src/api/providers/deepseek.ts b/src/api/providers/deepseek.ts index a903ce2dd9..97eca592d3 100644 --- a/src/api/providers/deepseek.ts +++ b/src/api/providers/deepseek.ts @@ -18,13 +18,15 @@ export class DeepSeekHandler implements ApiHandler { } async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + const model = this.getModel() const stream = await this.client.chat.completions.create({ - model: this.getModel().id, - max_completion_tokens: this.getModel().info.maxTokens, - temperature: 0, + model: model.id, + max_completion_tokens: model.info.maxTokens, messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)], stream: true, stream_options: { include_usage: true }, + // Only set temperature for non-reasoner models + ...(model.id === "deepseek-reasoner" ? {} : { temperature: 0 }), }) for await (const chunk of stream) { @@ -52,13 +54,15 @@ export class DeepSeekHandler implements ApiHandler { getModel(): { id: DeepSeekModelId; info: ModelInfo } { const modelId = this.options.apiModelId - if (modelId && modelId in deepSeekModels) { - const id = modelId as DeepSeekModelId - return { id, info: deepSeekModels[id] } + if (!modelId || !(modelId in deepSeekModels)) { + return { + id: deepSeekDefaultModelId, + info: deepSeekModels[deepSeekDefaultModelId], + } } return { - id: deepSeekDefaultModelId, - info: deepSeekModels[deepSeekDefaultModelId], + id: modelId as DeepSeekModelId, + info: deepSeekModels[modelId as DeepSeekModelId], } } } diff --git a/src/shared/api.ts b/src/shared/api.ts index f753525fc3..2eeb6387ed 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -377,6 +377,16 @@ export const deepSeekModels = { cacheWritesPrice: 0.14, cacheReadsPrice: 0.014, }, + "deepseek-reasoner": { + maxTokens: 8_000, + contextWindow: 64_000, + supportsImages: false, + supportsPromptCache: true, // supports context caching, but not in the way anthropic does it (deepseek reports input tokens and reads/writes in the same usage report) FIXME: we need to show users cache stats how deepseek does it + inputPrice: 0, // technically there is no input price, it's all either a cache hit or miss (ApiOptions will not show this) + outputPrice: 2.19, + cacheWritesPrice: 0.55, + cacheReadsPrice: 0.14, + }, } as const satisfies Record // Mistral From d9af2acf5b941f57422e036d3cc2bc0973cbf54f Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Tue, 21 Jan 2025 13:40:40 -0800 Subject: [PATCH 46/49] Fix deepseek --- CHANGELOG.md | 6 ++---- package.json | 2 +- src/api/providers/deepseek.ts | 12 +++++------- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46a688a808..de5566d05f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,8 @@ # Change Log -## [3.2.X] +## [3.2.3] -- Add DeepSeek-R1 (deepseek-reasoner) model support with proper parameter handling -- Fix temperature parameter being sent to unsupported deepseek-reasoner model -- Update DeepSeek pricing info with new reasoner model rates +- Add DeepSeek-R1 (deepseek-reasoner) model support with proper parameter handling (thanks @slavakurilyak!) ## [3.2.0] diff --git a/package.json b/package.json index 20ab2b6b92..63b162c186 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "claude-dev", "displayName": "Cline", "description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.", - "version": "3.2.2", + "version": "3.2.3", "icon": "assets/icons/icon.png", "galleryBanner": { "color": "#617A91", diff --git a/src/api/providers/deepseek.ts b/src/api/providers/deepseek.ts index 97eca592d3..d68dc49bed 100644 --- a/src/api/providers/deepseek.ts +++ b/src/api/providers/deepseek.ts @@ -54,15 +54,13 @@ export class DeepSeekHandler implements ApiHandler { getModel(): { id: DeepSeekModelId; info: ModelInfo } { const modelId = this.options.apiModelId - if (!modelId || !(modelId in deepSeekModels)) { - return { - id: deepSeekDefaultModelId, - info: deepSeekModels[deepSeekDefaultModelId], - } + if (modelId && modelId in deepSeekModels) { + const id = modelId as DeepSeekModelId + return { id, info: deepSeekModels[id] } } return { - id: modelId as DeepSeekModelId, - info: deepSeekModels[modelId as DeepSeekModelId], + id: deepSeekDefaultModelId, + info: deepSeekModels[deepSeekDefaultModelId], } } } From a88d45d8b43fd400589a0629ab6f1facd492f0e3 Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Tue, 21 Jan 2025 14:04:03 -0800 Subject: [PATCH 47/49] Fix provider dropdown z-index issues --- package.json | 2 +- .../src/components/settings/ApiOptions.tsx | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 63b162c186..915ffd67fb 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "claude-dev", "displayName": "Cline", "description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.", - "version": "3.2.3", + "version": "3.2.4", "icon": "assets/icons/icon.png", "galleryBanner": { "color": "#617A91", diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 07f0076dc3..ceb75a0ee4 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -50,9 +50,9 @@ interface ApiOptionsProps { // This is necessary to ensure dropdown opens downward, important for when this is used in popup const DROPDOWN_Z_INDEX = 1001 // Higher than the OpenRouterModelPicker's and ModelSelectorTooltip's z-index -const DropdownContainer = styled.div` +const DropdownContainer = styled.div<{ zIndex?: number }>` position: relative; - z-index: ${DROPDOWN_Z_INDEX}; + z-index: ${(props) => props.zIndex || DROPDOWN_Z_INDEX}; // Force dropdowns to open downward & vscode-dropdown::part(listbox) { @@ -406,7 +406,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is placeholder="Enter Session Token..."> AWS Session Token -
      + @@ -442,7 +442,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is us-gov-west-1 {/* us-gov-east-1 */} -
      + { @@ -481,7 +481,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is placeholder="Enter Project ID..."> Google Cloud Project ID -
      + @@ -497,7 +497,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is europe-west4 asia-southeast1 -
      +

      -

      + @@ -669,7 +669,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is }}> Note: This is a very experimental integration and may not work as expected.

      -
      +
)} @@ -818,7 +818,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is selectedProvider !== "vscode-lm" && showModelOptions && ( <> -
+ @@ -829,7 +829,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is {selectedProvider === "openai-native" && createDropdown(openAiNativeModels)} {selectedProvider === "deepseek" && createDropdown(deepSeekModels)} {selectedProvider === "mistral" && createDropdown(mistralModels)} -
+ Date: Wed, 22 Jan 2025 12:25:56 +0800 Subject: [PATCH 48/49] button styling changes; extracted to separate component --- .../src/components/common/SettingsButton.tsx | 36 +++++++++++++++++++ .../src/components/settings/SettingsView.tsx | 9 +++-- 2 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 webview-ui/src/components/common/SettingsButton.tsx diff --git a/webview-ui/src/components/common/SettingsButton.tsx b/webview-ui/src/components/common/SettingsButton.tsx new file mode 100644 index 0000000000..2f63240c71 --- /dev/null +++ b/webview-ui/src/components/common/SettingsButton.tsx @@ -0,0 +1,36 @@ +import { VSCodeButton } from "@vscode/webview-ui-toolkit/react" +import styled from "styled-components" + +const StyledButton = styled(VSCodeButton)` + --settings-button-bg: var(--vscode-button-secondaryBackground); + --settings-button-hover: var(--vscode-button-secondaryHoverBackground); + --settings-button-active: var(--vscode-button-secondaryBackground); + + background-color: var(--settings-button-bg) !important; + border-color: var(--settings-button-bg) !important; + width: 100% !important; + + &:hover { + background-color: var(--settings-button-hover) !important; + border-color: var(--settings-button-hover) !important; + } + + &:active { + background-color: var(--settings-button-active) !important; + border-color: var(--settings-button-active) !important; + } + + i.codicon { + margin-right: 6px; + flex-shrink: 0; + font-size: 16px !important; + } +` + +interface SettingsButtonProps extends React.ComponentProps {} + +const SettingsButton: React.FC = (props) => { + return +} + +export default SettingsButton diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index 7a74be34f3..6c1daf60e0 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -4,6 +4,7 @@ import { useExtensionState } from "../../context/ExtensionStateContext" import { validateApiConfiguration, validateModelId } from "../../utils/validate" import { vscode } from "../../utils/vscode" import ApiOptions from "./ApiOptions" +import SettingsButton from "../common/SettingsButton" const IS_DEV = false // FIXME: use flags when packaging @@ -137,16 +138,14 @@ const SettingsView = ({ onDone }: SettingsViewProps) => { display: "flex", justifyContent: "center", }}> - vscode.postMessage({ type: "openExtensionSettings" })} style={{ margin: "0 0 16px 0", - minWidth: "fit-content", - whiteSpace: "nowrap", }}> + Advanced Settings - +
Date: Wed, 22 Jan 2025 19:19:52 +0800 Subject: [PATCH 49/49] guard additional MCP sentence --- src/core/prompts/system.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index 1ea58fee1a..ddadd97bd4 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -876,7 +876,7 @@ RULES - The user may provide a file's contents directly in their message, in which case you shouldn't use the read_file tool to get the file contents again since you already have it. - Your goal is to try to accomplish the user's task, NOT engage in a back and forth conversation.${ supportsComputerUse - ? '\n- The user may ask generic non-development tasks, such as "what\'s the latest news" or "look up the weather in San Diego", in which case you might use the browser_action tool to complete the task if it makes sense to do so, rather than trying to create a website or using curl to answer the question. However, if an available MCP server tool or resource can be used instead, you should prefer to use it over browser_action.' + ? `\n- The user may ask generic non-development tasks, such as "what\'s the latest news" or "look up the weather in San Diego", in which case you might use the browser_action tool to complete the task if it makes sense to do so, rather than trying to create a website or using curl to answer the question.${mcpHub.shouldIncludeInPrompt() ? "However, if an available MCP server tool or resource can be used instead, you should prefer to use it over browser_action." : ""}` : "" } - NEVER end attempt_completion result with a question or request to engage in further conversation! Formulate the end of your result in a way that is final and does not require further input from the user.